From 1d8fefe7d328ad908bee8b84fee0842eee928221 Mon Sep 17 00:00:00 2001 From: Abdulqadir Ahmad <2004a3abuahmad@gmail.com> Date: Thu, 10 Aug 2023 17:19:58 +0100 Subject: [PATCH 1/4] created show user profile dialog --- src/controller/mainController.py | 9 ++ src/controller/mastodon/handler.py | 37 ++++- src/wxUI/dialogs/mastodon/dialogs.py | 9 +- src/wxUI/dialogs/mastodon/showUserProfile.py | 159 +++++++++++++++++++ 4 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 src/wxUI/dialogs/mastodon/showUserProfile.py diff --git a/src/controller/mainController.py b/src/controller/mainController.py index bf4acbb1..c7cc6736 100644 --- a/src/controller/mainController.py +++ b/src/controller/mainController.py @@ -139,6 +139,7 @@ class Controller(object): widgetUtils.connect_event(self.view, widgetUtils.MENU, self.delete, self.view.delete) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.follow, menuitem=self.view.follow) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.send_dm, self.view.dm) + widgetUtils.connect_event(self.view, widgetUtils.MENU, self.showUserProfile, self.view.details) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.get_more_items, menuitem=self.view.load_previous_items) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.clear_buffer, menuitem=self.view.clear) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.remove_buffer, self.view.deleteTl) @@ -1094,3 +1095,11 @@ class Controller(object): handler = self.get_handler(buffer.session.type) if handler: handler.update_profile(buffer.session) + + def showUserProfile(self, *args): + """Displays a user's profile.""" + log.debug("Showing user profile...") + buffer = self.get_best_buffer() + handler = self.get_handler(type=buffer.session.type) + if handler and hasattr(handler, 'showUserProfile'): + handler.showUserProfile(buffer=buffer) diff --git a/src/controller/mastodon/handler.py b/src/controller/mastodon/handler.py index b4876626..553c424a 100644 --- a/src/controller/mastodon/handler.py +++ b/src/controller/mastodon/handler.py @@ -10,6 +10,7 @@ from wxUI.dialogs.mastodon import dialogs from wxUI.dialogs import userAliasDialogs from wxUI import commonMessageDialogs from wxUI.dialogs.mastodon import updateProfile as update_profile_dialogs +from wxUI.dialogs.mastodon import showUserProfile from sessions.mastodon.utils import html_filter from . import userActions, settings @@ -44,7 +45,7 @@ class Handler(object): addAlias=_("Add a&lias"), addToList=None, removeFromList=None, - details=None, + details=_("Show user profile"), favs=None, # In buffer Menu. trends=None, @@ -287,4 +288,36 @@ class Handler(object): if data[key] == updated_data[key]: del updated_data[key] log.debug(f"Updating users profile with: {updated_data}") - call_threaded(session.api_call, "account_update_credentials", _("Update profile"), report_success=True, **updated_data) \ No newline at end of file + call_threaded(session.api_call, "account_update_credentials", _("Update profile"), report_success=True, **updated_data) + + def showUserProfile(self, buffer): + """Displays user profile in a dialog.""" + log.debug("Scraping for users in handler") + if not hasattr(buffer, 'get_item'): + return # Tell user? + item = buffer.get_item() + + if hasattr(item, 'username'): + # item is an account dict + users = [(item.display_name, item.username, item.id)] + elif hasattr(item, 'mentions'): + # statuse + if item.reblog: + item = item.reblog + users = [(user.display_name, user.username, user.id) for user in item.mentions] + users.insert(0, (item.account.display_name, item.account.username, item.account.id)) + elif hasattr(item, 'account'): + # Notifications + users = [(item.account.display_name, item.account.username, item.account.id)] + else: + dialogs.no_user() + return + + users = list(set(users)) + selectedUser = showUserProfile.selectUserDialog(users) + log.debug(f"Selected user = {selectedUser}") + user = buffer.session.api.account(selectedUser[2]) + dlg = showUserProfile.ShowUserProfile( + user.display_name, user.url, html_filter(user.note), user.header, user.avatar, + [(field.name, html_filter(field.value)) for field in user.fields], False, False, False) + dlg.ShowModal() diff --git a/src/wxUI/dialogs/mastodon/dialogs.py b/src/wxUI/dialogs/mastodon/dialogs.py index a42f6ffb..1f945d82 100644 --- a/src/wxUI/dialogs/mastodon/dialogs.py +++ b/src/wxUI/dialogs/mastodon/dialogs.py @@ -52,4 +52,11 @@ def no_followers(): def no_following(): dlg = wx.MessageDialog(None, _("This user is not following anyone. {0} can't create a timeline.").format(application.name), _(u"Error"), wx.ICON_ERROR) dlg.ShowModal() - dlg.Destroy() \ No newline at end of file + dlg.Destroy() + + dlg.Destroy() + +def no_user(): + dlg = wx.MessageDialog(None, _("The focused item has no user in it. {} ca't open a user profile").format(application.name), _(u"Error"), wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() diff --git a/src/wxUI/dialogs/mastodon/showUserProfile.py b/src/wxUI/dialogs/mastodon/showUserProfile.py new file mode 100644 index 00000000..71db5ebd --- /dev/null +++ b/src/wxUI/dialogs/mastodon/showUserProfile.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +def _(s): + return s +"""Wx dialogs for showing a user's profile.""" + +import wx +import os +import requests +from io import BytesIO +from typing import Tuple + + +def selectUserDialog(users: list) -> tuple: + """Choose a user from a possible list of users""" + if len(users) == 1: + return users[0] + dlg = wx.Dialog(None, title=_("Select user")) + label = wx.StaticText(dlg, label="Select a user: ") + choice = wx.Choice(dlg, choices=[f"{name}: @{username}" for name, username, id in users]) + ok = wx.Button(dlg, wx.ID_OK, _("OK")) + ok.SetDefault() + cancel = wx.Button(dlg, wx.ID_CANCEL, _("Cancel")) + dlg.SetEscapeId(cancel.GetId()) + + #sizers + sizer = wx.GridSizer(2, 2, 5, 5) + sizer.Add(label, wx.SizerFlags().Center()) + sizer.Add(choice, wx.SizerFlags().Center()) + sizer.Add(ok, wx.SizerFlags().Center()) + sizer.Add(cancel, wx.SizerFlags().Center()) + + if dlg.ShowModal() == wx.ID_CANCEL: + return + # return the selected user + return users[choice.GetSelection()] + + +def returnTrue(): + return True + + +class ShowUserProfile(wx.Dialog): + """ + A dialog for Showing user profile + layout is: + ``` + header + avatar + name + bio + meta data + ``` + """ + + def __init__(self, display_name: str, url: str, note: str, header: str, avatar: str, fields: list, locked: bool, bot: bool, discoverable: bool): + """Initialize update profile dialog + Parameters: + - display_name: The user's display name to show in the display name field + - username: The user's username + - note: The users bio to show in the bio field + - header: the users header pic link + - avatar: The users avatar pic link + """ + super().__init__(parent=None) + self.SetTitle(_("{}'s Profile").format(display_name)) + self.panel = wx.Panel(self) + wrapper = wx.BoxSizer(wx.VERTICAL) + sizer = wx.GridSizer(2, 11, 5, 5) + + # create widgets + nameLabel = wx.StaticText(self.panel, label=_("Name: ")) + name = self.createTextCtrl(display_name, size=(200, 30)) + sizer.Add(nameLabel, wx.SizerFlags().Center()) + sizer.Add(name, wx.SizerFlags().Center()) + + usernameLabel = wx.StaticText(self.panel, label=_("Username: ")) + username = self.createTextCtrl(username, size=(200, 30)) + sizer.Add(usernameLabel, wx.SizerFlags().Center()) + sizer.Add(username, wx.SizerFlags().Center()) + + bioLabel = wx.StaticText(self.panel, label=_("Bio: ")) + bio = self.createTextCtrl(note, (400, 60)) + sizer.Add(bioLabel, wx.SizerFlags().Center()) + sizer.Add(bio, wx.SizerFlags().Center()) + + # header + headerLabel = wx.StaticText(self.panel, label=_("Header: ")) + try: + response = requests.get(header) + except requests.exceptions.RequestException: + # Create empty image + headerImage = wx.StaticBitmap() + else: + image_bytes = BytesIO(response.content) + image = wx.Image(image_bytes, wx.BITMAP_TYPE_ANY) + image.Rescale(300, 100, wx.IMAGE_QUALITY_HIGH) + headerImage = wx.StaticBitmap(self.panel, bitmap=image.ConvertToBitmap()) + + headerImage.AcceptsFocusFromKeyboard = returnTrue + sizer.Add(headerLabel, wx.SizerFlags().Center()) + sizer.Add(headerImage, wx.SizerFlags().Center()) + + # avatar + avatarLabel = wx.StaticText(self.panel, label=_("Avatar")) + try: + response = requests.get(avatar) + except requests.exceptions.RequestException: + # Create empty image + avatarImage = wx.StaticBitmap() + else: + image_bytes = BytesIO(response.content) + image = wx.Image(image_bytes, wx.BITMAP_TYPE_ANY) + image.Rescale(150, 150, wx.IMAGE_QUALITY_HIGH) + avatarImage = wx.StaticBitmap(self.panel, bitmap=image.ConvertToBitmap()) + + avatarImage.AcceptsFocusFromKeyboard = returnTrue + sizer.Add(avatarLabel, wx.SizerFlags().Center()) + sizer.Add(avatarImage, wx.SizerFlags().Center()) + + self.fields = [] + for num, (label, content) in enumerate(fields): + labelSizer = wx.BoxSizer(wx.HORIZONTAL) + labelLabel = wx.StaticText(self.panel, label=_("Field {} - Label: ").format(num)) + labelSizer.Add(labelLabel, wx.SizerFlags().Center().Border(wx.ALL, 5)) + labelText = self.createTextCtrl(label, (230, 30), True) + labelSizer.Add(labelText, wx.SizerFlags().Expand().Border(wx.ALL, 5)) + sizer.Add(labelSizer, 0, wx.CENTER) + + contentSizer = wx.BoxSizer(wx.HORIZONTAL) + contentLabel = wx.StaticText(self.panel, label=_("Content: ")) + contentSizer.Add(contentLabel, wx.SizerFlags().Center()) + contentText = self.createTextCtrl(content, (400, 60), True) + contentSizer.Add(contentText, wx.SizerFlags().Center()) + sizer.Add(contentSizer, 0, wx.CENTER | wx.LEFT, 10) + + close = wx.Button(self.panel, wx.ID_CLOSE, _("Close")) + self.SetEscapeId(close.GetId()) + close.SetDefault() + sizer.Add(close, wx.SizerFlags().Center()) + wrapper.Add(sizer, 0, wx.CENTER) # For padding + self.panel.SetSizerAndFit(wrapper) + sizer.Fit(self) + self.Center() + + + def createTextCtrl(self, text: str, size: Tuple[int, int], multiline: bool = False) -> wx.TextCtrl: + """Creates a wx.TextCtrl and returns it + Parameters: + text: The value of the wx.TextCtrl + size: The size of the wx.TextCtrl + Returns: the created wx.TextCtrl object + """ + if not multiline: + style= wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB | wx.TE_READONLY + else: + style= wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB | wx.TE_READONLY | wx.TE_MULTILINE + textCtrl = wx.TextCtrl(self.panel, value=text, size=size, style=style) + textCtrl.AcceptsFocusFromKeyboard = returnTrue + return textCtrl From 35ba915be676d0dd386ee4f387a3b40d5354c644 Mon Sep 17 00:00:00 2001 From: Abdulqadir Ahmad <2004a3abuahmad@gmail.com> Date: Thu, 10 Aug 2023 19:15:34 +0100 Subject: [PATCH 2/4] fix show user profile not working when a post have mentions --- src/controller/mastodon/handler.py | 25 +++++++++++----- src/wxUI/dialogs/mastodon/showUserProfile.py | 31 ++++++++++++-------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/controller/mastodon/handler.py b/src/controller/mastodon/handler.py index 553c424a..f9bfad74 100644 --- a/src/controller/mastodon/handler.py +++ b/src/controller/mastodon/handler.py @@ -296,16 +296,20 @@ class Handler(object): if not hasattr(buffer, 'get_item'): return # Tell user? item = buffer.get_item() + if not item: + return # empty buffer - if hasattr(item, 'username'): + holdUser = item.get('account') + if item.get('username'): # item is an account dict users = [(item.display_name, item.username, item.id)] - elif hasattr(item, 'mentions'): + elif item.get('mentions'): # statuse if item.reblog: item = item.reblog - users = [(user.display_name, user.username, user.id) for user in item.mentions] + users = [(user.acct, user.id) for user in item.mentions] users.insert(0, (item.account.display_name, item.account.username, item.account.id)) + holdUser = item.account elif hasattr(item, 'account'): # Notifications users = [(item.account.display_name, item.account.username, item.account.id)] @@ -313,10 +317,17 @@ class Handler(object): dialogs.no_user() return - users = list(set(users)) - selectedUser = showUserProfile.selectUserDialog(users) - log.debug(f"Selected user = {selectedUser}") - user = buffer.session.api.account(selectedUser[2]) + if len(users) == 1: + user = holdUser + else: + users = list(set(users)) + selectedUser = showUserProfile.selectUserDialog(users) + if not selectedUser: + return # Canceled selection + elif selectedUser[-1] == holdUser.id: + user = holdUser + else: + user = buffer.session.api.account(selectedUser[-1]) dlg = showUserProfile.ShowUserProfile( user.display_name, user.url, html_filter(user.note), user.header, user.avatar, [(field.name, html_filter(field.value)) for field in user.fields], False, False, False) diff --git a/src/wxUI/dialogs/mastodon/showUserProfile.py b/src/wxUI/dialogs/mastodon/showUserProfile.py index 71db5ebd..6fb5667b 100644 --- a/src/wxUI/dialogs/mastodon/showUserProfile.py +++ b/src/wxUI/dialogs/mastodon/showUserProfile.py @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- -def _(s): - return s """Wx dialogs for showing a user's profile.""" -import wx -import os -import requests from io import BytesIO +import os from typing import Tuple +import requests +import wx + +def _(s): + return s def selectUserDialog(users: list) -> tuple: @@ -16,7 +17,13 @@ def selectUserDialog(users: list) -> tuple: return users[0] dlg = wx.Dialog(None, title=_("Select user")) label = wx.StaticText(dlg, label="Select a user: ") - choice = wx.Choice(dlg, choices=[f"{name}: @{username}" for name, username, id in users]) + choiceList = [] + for user in users: + if len(user) == 3: # (display_name, username, id) + choiceList.append(f"{user[0]}: @{user[1]}") + else: # (acct, id) + choiceList.append(f"{user[0]}") + choice = wx.Choice(dlg, choices=choiceList) ok = wx.Button(dlg, wx.ID_OK, _("OK")) ok.SetDefault() cancel = wx.Button(dlg, wx.ID_CANCEL, _("Cancel")) @@ -30,7 +37,7 @@ def selectUserDialog(users: list) -> tuple: sizer.Add(cancel, wx.SizerFlags().Center()) if dlg.ShowModal() == wx.ID_CANCEL: - return + return () # return the selected user return users[choice.GetSelection()] @@ -56,7 +63,7 @@ class ShowUserProfile(wx.Dialog): """Initialize update profile dialog Parameters: - display_name: The user's display name to show in the display name field - - username: The user's username + - url: The user's url - note: The users bio to show in the bio field - header: the users header pic link - avatar: The users avatar pic link @@ -73,10 +80,10 @@ class ShowUserProfile(wx.Dialog): sizer.Add(nameLabel, wx.SizerFlags().Center()) sizer.Add(name, wx.SizerFlags().Center()) - usernameLabel = wx.StaticText(self.panel, label=_("Username: ")) - username = self.createTextCtrl(username, size=(200, 30)) - sizer.Add(usernameLabel, wx.SizerFlags().Center()) - sizer.Add(username, wx.SizerFlags().Center()) + urlLabel = wx.StaticText(self.panel, label=_("URL: ")) + url = self.createTextCtrl(url, size=(200, 30)) + sizer.Add(urlLabel, wx.SizerFlags().Center()) + sizer.Add(url, wx.SizerFlags().Center()) bioLabel = wx.StaticText(self.panel, label=_("Bio: ")) bio = self.createTextCtrl(note, (400, 60)) From 15e2032afb9b12f2eb3fef214b8df7ec16104884 Mon Sep 17 00:00:00 2001 From: Abdulqadir Ahmad <2004a3abuahmad@gmail.com> Date: Sat, 12 Aug 2023 10:32:29 +0100 Subject: [PATCH 3/4] added fields for created_at, locked, bot and discoverable --- src/controller/mastodon/handler.py | 23 +++--- src/wxUI/dialogs/mastodon/showUserProfile.py | 75 ++++++++++++++------ 2 files changed, 63 insertions(+), 35 deletions(-) diff --git a/src/controller/mastodon/handler.py b/src/controller/mastodon/handler.py index f9bfad74..2b096a49 100644 --- a/src/controller/mastodon/handler.py +++ b/src/controller/mastodon/handler.py @@ -291,7 +291,8 @@ class Handler(object): call_threaded(session.api_call, "account_update_credentials", _("Update profile"), report_success=True, **updated_data) def showUserProfile(self, buffer): - """Displays user profile in a dialog.""" + """Displays user profile in a dialog. + This works as long as the focused item hass a 'account' key.""" log.debug("Scraping for users in handler") if not hasattr(buffer, 'get_item'): return # Tell user? @@ -300,20 +301,15 @@ class Handler(object): return # empty buffer holdUser = item.get('account') - if item.get('username'): - # item is an account dict - users = [(item.display_name, item.username, item.id)] - elif item.get('mentions'): - # statuse + users = [holdUser] + if item.get('mentions'): + # mentions in statuses if item.reblog: item = item.reblog users = [(user.acct, user.id) for user in item.mentions] users.insert(0, (item.account.display_name, item.account.username, item.account.id)) holdUser = item.account - elif hasattr(item, 'account'): - # Notifications - users = [(item.account.display_name, item.account.username, item.account.id)] - else: + elif not holdUser: dialogs.no_user() return @@ -326,9 +322,10 @@ class Handler(object): return # Canceled selection elif selectedUser[-1] == holdUser.id: user = holdUser - else: + else: # We don't have this user's dictionary, get it! user = buffer.session.api.account(selectedUser[-1]) dlg = showUserProfile.ShowUserProfile( - user.display_name, user.url, html_filter(user.note), user.header, user.avatar, - [(field.name, html_filter(field.value)) for field in user.fields], False, False, False) + user.display_name, user.url, user.created_at, html_filter(user.note), user.header, user.avatar, + [(field.name, html_filter(field.value)) for field in user.fields], user.locked, user.bot, user.discoverable + ) dlg.ShowModal() diff --git a/src/wxUI/dialogs/mastodon/showUserProfile.py b/src/wxUI/dialogs/mastodon/showUserProfile.py index 6fb5667b..142b4493 100644 --- a/src/wxUI/dialogs/mastodon/showUserProfile.py +++ b/src/wxUI/dialogs/mastodon/showUserProfile.py @@ -2,7 +2,6 @@ """Wx dialogs for showing a user's profile.""" from io import BytesIO -import os from typing import Tuple import requests import wx @@ -59,7 +58,7 @@ class ShowUserProfile(wx.Dialog): ``` """ - def __init__(self, display_name: str, url: str, note: str, header: str, avatar: str, fields: list, locked: bool, bot: bool, discoverable: bool): + def __init__(self, display_name: str, url: str, created_at, note: str, header: str, avatar: str, fields: list, locked: bool, bot: bool, discoverable: bool): """Initialize update profile dialog Parameters: - display_name: The user's display name to show in the display name field @@ -71,24 +70,29 @@ class ShowUserProfile(wx.Dialog): super().__init__(parent=None) self.SetTitle(_("{}'s Profile").format(display_name)) self.panel = wx.Panel(self) - wrapper = wx.BoxSizer(wx.VERTICAL) - sizer = wx.GridSizer(2, 11, 5, 5) + wrapperSizer = wx.BoxSizer(wx.VERTICAL) + topSizer = wx.GridSizer(2, 10, 5, 5) # create widgets nameLabel = wx.StaticText(self.panel, label=_("Name: ")) name = self.createTextCtrl(display_name, size=(200, 30)) - sizer.Add(nameLabel, wx.SizerFlags().Center()) - sizer.Add(name, wx.SizerFlags().Center()) + topSizer.Add(nameLabel, wx.SizerFlags().Center()) + topSizer.Add(name, wx.SizerFlags().Center()) urlLabel = wx.StaticText(self.panel, label=_("URL: ")) url = self.createTextCtrl(url, size=(200, 30)) - sizer.Add(urlLabel, wx.SizerFlags().Center()) - sizer.Add(url, wx.SizerFlags().Center()) + topSizer.Add(urlLabel, wx.SizerFlags().Center()) + topSizer.Add(url, wx.SizerFlags().Center()) + + joinLabel = wx.StaticText(self.panel, label=_("Joined at: ")) + joinText = self.createTextCtrl(created_at.strftime('%d %B, %Y'), (80, 30)) + topSizer.Add(joinLabel, wx.SizerFlags().Center()) + topSizer.Add(joinText, wx.SizerFlags().Center()) bioLabel = wx.StaticText(self.panel, label=_("Bio: ")) bio = self.createTextCtrl(note, (400, 60)) - sizer.Add(bioLabel, wx.SizerFlags().Center()) - sizer.Add(bio, wx.SizerFlags().Center()) + topSizer.Add(bioLabel, wx.SizerFlags().Center()) + topSizer.Add(bio, wx.SizerFlags().Center()) # header headerLabel = wx.StaticText(self.panel, label=_("Header: ")) @@ -104,11 +108,11 @@ class ShowUserProfile(wx.Dialog): headerImage = wx.StaticBitmap(self.panel, bitmap=image.ConvertToBitmap()) headerImage.AcceptsFocusFromKeyboard = returnTrue - sizer.Add(headerLabel, wx.SizerFlags().Center()) - sizer.Add(headerImage, wx.SizerFlags().Center()) + topSizer.Add(headerLabel, wx.SizerFlags().Center()) + topSizer.Add(headerImage, wx.SizerFlags().Center()) # avatar - avatarLabel = wx.StaticText(self.panel, label=_("Avatar")) + avatarLabel = wx.StaticText(self.panel, label=_("Avatar: ")) try: response = requests.get(avatar) except requests.exceptions.RequestException: @@ -121,32 +125,59 @@ class ShowUserProfile(wx.Dialog): avatarImage = wx.StaticBitmap(self.panel, bitmap=image.ConvertToBitmap()) avatarImage.AcceptsFocusFromKeyboard = returnTrue - sizer.Add(avatarLabel, wx.SizerFlags().Center()) - sizer.Add(avatarImage, wx.SizerFlags().Center()) + topSizer.Add(avatarLabel, wx.SizerFlags().Center()) + topSizer.Add(avatarImage, wx.SizerFlags().Center()) self.fields = [] for num, (label, content) in enumerate(fields): labelSizer = wx.BoxSizer(wx.HORIZONTAL) - labelLabel = wx.StaticText(self.panel, label=_("Field {} - Label: ").format(num)) + labelLabel = wx.StaticText(self.panel, label=_("Field {} - Label: ").format(num + 1)) labelSizer.Add(labelLabel, wx.SizerFlags().Center().Border(wx.ALL, 5)) labelText = self.createTextCtrl(label, (230, 30), True) labelSizer.Add(labelText, wx.SizerFlags().Expand().Border(wx.ALL, 5)) - sizer.Add(labelSizer, 0, wx.CENTER) + topSizer.Add(labelSizer, 0, wx.CENTER) contentSizer = wx.BoxSizer(wx.HORIZONTAL) contentLabel = wx.StaticText(self.panel, label=_("Content: ")) contentSizer.Add(contentLabel, wx.SizerFlags().Center()) contentText = self.createTextCtrl(content, (400, 60), True) contentSizer.Add(contentText, wx.SizerFlags().Center()) - sizer.Add(contentSizer, 0, wx.CENTER | wx.LEFT, 10) + topSizer.Add(contentSizer, 0, wx.CENTER | wx.LEFT, 10) + + # 3 X 2 grid sizer + bottomSizer = wx.GridSizer(3, 2, 10, 5) + bullSwitch = {True: _('Yes'), False: _('No'), None: _('No')} + privateSizer = wx.BoxSizer(wx.HORIZONTAL) + privateLabel = wx.StaticText(self.panel, label=_("Private account: ")) + private = self.createTextCtrl(bullSwitch[locked], (30, 30)) + privateSizer.Add(privateLabel, wx.SizerFlags().Center()) + privateSizer.Add(private, wx.SizerFlags().Center()) + bottomSizer.Add(privateSizer, 0, wx.ALL | wx.CENTER) + + + botSizer = wx.BoxSizer(wx.HORIZONTAL) + botLabel = wx.StaticText(self.panel, label=_("Bot account: ")) + botText = self.createTextCtrl(bullSwitch[bot], (30, 30)) + botSizer.Add(botLabel, wx.SizerFlags().Center()) + botSizer.Add(botText, wx.SizerFlags().Center()) + bottomSizer.Add(botSizer, 0, wx.ALL | wx.CENTER) + + discoverSizer = wx.BoxSizer(wx.HORIZONTAL) + discoverLabel = wx.StaticText(self.panel, label=_("Discoverable account: ")) + discoverText = self.createTextCtrl(bullSwitch[discoverable], (30, 30)) + discoverSizer.Add(discoverLabel, wx.SizerFlags().Center()) + discoverSizer.Add(discoverText, wx.SizerFlags().Center()) + bottomSizer.Add(discoverSizer, 0, wx.ALL | wx.CENTER) + close = wx.Button(self.panel, wx.ID_CLOSE, _("Close")) self.SetEscapeId(close.GetId()) close.SetDefault() - sizer.Add(close, wx.SizerFlags().Center()) - wrapper.Add(sizer, 0, wx.CENTER) # For padding - self.panel.SetSizerAndFit(wrapper) - sizer.Fit(self) + wrapperSizer.Add(topSizer, 0, wx.CENTER) + wrapperSizer.Add(bottomSizer, 0, wx.CENTER) + wrapperSizer.Add(close, wx.SizerFlags().Center()) + self.panel.SetSizerAndFit(wrapperSizer) + topSizer.Fit(self) self.Center() From f4ec03099aeab47bdca14eedb270dd6ba1960aad Mon Sep 17 00:00:00 2001 From: Abdulqadir Ahmad <2004a3abuahmad@gmail.com> Date: Sun, 27 Aug 2023 17:56:39 +0100 Subject: [PATCH 4/4] added actions, following, followers and posts button to show user profile dialog --- src/controller/mainController.py | 39 +++++- src/controller/mastodon/handler.py | 101 ++++++++------- src/wxUI/dialogs/mastodon/showUserProfile.py | 123 ++++++++++++------- 3 files changed, 170 insertions(+), 93 deletions(-) diff --git a/src/controller/mainController.py b/src/controller/mainController.py index c7cc6736..1b3e3b9f 100644 --- a/src/controller/mainController.py +++ b/src/controller/mainController.py @@ -986,9 +986,9 @@ class Controller(object): def repeat_item(self, *args, **kwargs): output.speak(self.get_current_buffer().get_message()) - def execute_action(self, action): + def execute_action(self, action, kwargs={}): if hasattr(self, action): - getattr(self, action)() + getattr(self, action)(**kwargs) def update_buffers(self): for i in self.buffers[:]: @@ -1102,4 +1102,37 @@ class Controller(object): buffer = self.get_best_buffer() handler = self.get_handler(type=buffer.session.type) if handler and hasattr(handler, 'showUserProfile'): - handler.showUserProfile(buffer=buffer) + handler.showUserProfile(buffer) + + def openPostTimeline(self, *args, user=None): + """Opens selected user's posts timeline + Parameters: + args: Other argument. Useful when binding to widgets. + user: if specified, open this user timeline. It is currently mandatory, but could be optional when user selection is implemented in handler + """ + buffer = self.get_best_buffer() + handler = self.get_handler(type=buffer.session.type) + if handler and hasattr(handler, 'openPostTimeline'): + handler.openPostTimeline(self, buffer, user) + + def openFollowersTimeline(self, *args, user=None): + """Opens selected user's followers timeline + Parameters: + args: Other argument. Useful when binding to widgets. + user: if specified, open this user timeline. It is currently mandatory, but could be optional when user selection is implemented in handler + """ + buffer = self.get_best_buffer() + handler = self.get_handler(type=buffer.session.type) + if handler and hasattr(handler, 'openFollowersTimeline'): + handler.openFollowersTimeline(self, buffer, user) + + def openFollowingTimeline(self, *args, user=None): + """Opens selected user's following timeline + Parameters: + args: Other argument. Useful when binding to widgets. + user: if specified, open this user timeline. It is currently mandatory, but could be optional when user selection is implemented in handler + """ + buffer = self.get_best_buffer() + handler = self.get_handler(type=buffer.session.type) + if handler and hasattr(handler, 'openFollowingTimeline'): + handler.openFollowingTimeline(self, buffer, user) diff --git a/src/controller/mastodon/handler.py b/src/controller/mastodon/handler.py index 2b096a49..6da135e2 100644 --- a/src/controller/mastodon/handler.py +++ b/src/controller/mastodon/handler.py @@ -186,38 +186,52 @@ class Handler(object): return user = u.user if action == "posts": - if user.statuses_count == 0: - dialogs.no_posts() - return - if user.id in buffer.session.settings["other_buffers"]["timelines"]: - commonMessageDialogs.timeline_exist() - return - timelines_position =controller.view.search("timelines", buffer.session.get_name()) - pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=buffer.session.type, buffer_title=_("Timeline for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="%s-timeline" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", id=user.id)) - buffer.session.settings["other_buffers"]["timelines"].append(user.id) - buffer.session.sound.play("create_timeline.ogg") + self.openPostTimeline(controller, buffer, user) elif action == "followers": - if user.followers_count == 0: - dialogs.no_followers() - return - if user.id in buffer.session.settings["other_buffers"]["followers_timelines"]: - commonMessageDialogs.timeline_exist() - return - timelines_position =controller.view.search("timelines", buffer.session.get_name()) - pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Followers for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id)) - buffer.session.settings["other_buffers"]["followers_timelines"].append(user.id) - buffer.session.sound.play("create_timeline.ogg") + self.openFollowersTimeline(controller, buffer, user) elif action == "following": - if user.following_count == 0: - dialogs.no_following() - return - if user.id in buffer.session.settings["other_buffers"]["following_timelines"]: - commonMessageDialogs.timeline_exist() - return - timelines_position =controller.view.search("timelines", buffer.session.get_name()) - pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Following for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id)) - buffer.session.settings["other_buffers"]["following_timelines"].append(user.id) - buffer.session.sound.play("create_timeline.ogg") + self.openFollowingTimeline(controller, buffer, user) + + def openPostTimeline(self, controller, buffer, user): + """Opens post timeline for user""" + if user.statuses_count == 0: + dialogs.no_posts() + return + if user.id in buffer.session.settings["other_buffers"]["timelines"]: + commonMessageDialogs.timeline_exist() + return + timelines_position =controller.view.search("timelines", buffer.session.get_name()) + pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=buffer.session.type, buffer_title=_("Timeline for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="%s-timeline" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", id=user.id)) + buffer.session.settings["other_buffers"]["timelines"].append(user.id) + buffer.session.sound.play("create_timeline.ogg") + buffer.session.settings.write() + + def openFollowersTimeline(self, controller, buffer, user): + """Open followers timeline for user""" + if user.followers_count == 0: + dialogs.no_followers() + return + if user.id in buffer.session.settings["other_buffers"]["followers_timelines"]: + commonMessageDialogs.timeline_exist() + return + timelines_position =controller.view.search("timelines", buffer.session.get_name()) + pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Followers for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id)) + buffer.session.settings["other_buffers"]["followers_timelines"].append(user.id) + buffer.session.sound.play("create_timeline.ogg") + buffer.session.settings.write() + + def openFollowingTimeline(self, controller, buffer, user): + """Open following timeline for user""" + if user.following_count == 0: + dialogs.no_following() + return + if user.id in buffer.session.settings["other_buffers"]["following_timelines"]: + commonMessageDialogs.timeline_exist() + return + timelines_position =controller.view.search("timelines", buffer.session.get_name()) + pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Following for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id)) + buffer.session.settings["other_buffers"]["following_timelines"].append(user.id) + buffer.session.sound.play("create_timeline.ogg") buffer.session.settings.write() def account_settings(self, buffer, controller): @@ -293,39 +307,38 @@ class Handler(object): def showUserProfile(self, buffer): """Displays user profile in a dialog. This works as long as the focused item hass a 'account' key.""" - log.debug("Scraping for users in handler") if not hasattr(buffer, 'get_item'): return # Tell user? item = buffer.get_item() if not item: return # empty buffer - holdUser = item.get('account') - users = [holdUser] - if item.get('mentions'): + log.debug(f"Opening user profile. dictionary: {item}") + mentionedUsers = list() + holdUser = item.account if item.get('account') else None + if item.get('username'): # account dict + holdUser = item + elif isinstance(item.get('mentions'), list): # mentions in statuses if item.reblog: item = item.reblog - users = [(user.acct, user.id) for user in item.mentions] - users.insert(0, (item.account.display_name, item.account.username, item.account.id)) + mentionedUsers = [(user.acct, user.id) for user in item.mentions] holdUser = item.account - elif not holdUser: + if not holdUser: dialogs.no_user() return - if len(users) == 1: + if len(mentionedUsers) == 0: user = holdUser else: - users = list(set(users)) - selectedUser = showUserProfile.selectUserDialog(users) + mentionedUsers.insert(0, (holdUser.display_name, holdUser.username, holdUser.id)) + mentionedUsers = list(set(mentionedUsers)) + selectedUser = showUserProfile.selectUserDialog(mentionedUsers) if not selectedUser: return # Canceled selection elif selectedUser[-1] == holdUser.id: user = holdUser else: # We don't have this user's dictionary, get it! user = buffer.session.api.account(selectedUser[-1]) - dlg = showUserProfile.ShowUserProfile( - user.display_name, user.url, user.created_at, html_filter(user.note), user.header, user.avatar, - [(field.name, html_filter(field.value)) for field in user.fields], user.locked, user.bot, user.discoverable - ) + dlg = showUserProfile.ShowUserProfile(user) dlg.ShowModal() diff --git a/src/wxUI/dialogs/mastodon/showUserProfile.py b/src/wxUI/dialogs/mastodon/showUserProfile.py index 142b4493..f4a55280 100644 --- a/src/wxUI/dialogs/mastodon/showUserProfile.py +++ b/src/wxUI/dialogs/mastodon/showUserProfile.py @@ -2,10 +2,14 @@ """Wx dialogs for showing a user's profile.""" from io import BytesIO +from pubsub import pub from typing import Tuple import requests import wx +from sessions.mastodon.utils import html_filter + + def _(s): return s @@ -58,46 +62,47 @@ class ShowUserProfile(wx.Dialog): ``` """ - def __init__(self, display_name: str, url: str, created_at, note: str, header: str, avatar: str, fields: list, locked: bool, bot: bool, discoverable: bool): + def __init__(self, user): """Initialize update profile dialog Parameters: - - display_name: The user's display name to show in the display name field - - url: The user's url - - note: The users bio to show in the bio field - - header: the users header pic link - - avatar: The users avatar pic link + - user: user dictionary """ super().__init__(parent=None) - self.SetTitle(_("{}'s Profile").format(display_name)) + self.user = user + self.SetTitle(_("{}'s Profile").format(user.display_name)) self.panel = wx.Panel(self) wrapperSizer = wx.BoxSizer(wx.VERTICAL) - topSizer = wx.GridSizer(2, 10, 5, 5) + mainSizer = wx.GridSizer(12, 2, 5, 5) # create widgets nameLabel = wx.StaticText(self.panel, label=_("Name: ")) - name = self.createTextCtrl(display_name, size=(200, 30)) - topSizer.Add(nameLabel, wx.SizerFlags().Center()) - topSizer.Add(name, wx.SizerFlags().Center()) + name = self.createTextCtrl(user.display_name, size=(200, 30)) + mainSizer.Add(nameLabel, wx.SizerFlags().Center()) + mainSizer.Add(name, wx.SizerFlags().Center()) urlLabel = wx.StaticText(self.panel, label=_("URL: ")) - url = self.createTextCtrl(url, size=(200, 30)) - topSizer.Add(urlLabel, wx.SizerFlags().Center()) - topSizer.Add(url, wx.SizerFlags().Center()) - - joinLabel = wx.StaticText(self.panel, label=_("Joined at: ")) - joinText = self.createTextCtrl(created_at.strftime('%d %B, %Y'), (80, 30)) - topSizer.Add(joinLabel, wx.SizerFlags().Center()) - topSizer.Add(joinText, wx.SizerFlags().Center()) + url = self.createTextCtrl(user.url, size=(200, 30)) + mainSizer.Add(urlLabel, wx.SizerFlags().Center()) + mainSizer.Add(url, wx.SizerFlags().Center()) bioLabel = wx.StaticText(self.panel, label=_("Bio: ")) - bio = self.createTextCtrl(note, (400, 60)) - topSizer.Add(bioLabel, wx.SizerFlags().Center()) - topSizer.Add(bio, wx.SizerFlags().Center()) + bio = self.createTextCtrl(html_filter(user.note), (400, 60)) + mainSizer.Add(bioLabel, wx.SizerFlags().Center()) + mainSizer.Add(bio, wx.SizerFlags().Center()) + + joinLabel = wx.StaticText(self.panel, label=_("Joined at: ")) + joinText = self.createTextCtrl(user.created_at.strftime('%d %B, %Y'), (80, 30)) + mainSizer.Add(joinLabel, wx.SizerFlags().Center()) + mainSizer.Add(joinText, wx.SizerFlags().Center()) + + actions = wx.Button(self.panel, label=_("Actions")) + actions.Bind(wx.EVT_BUTTON, self.onAction) + mainSizer.Add(actions, wx.SizerFlags().Center()) # header headerLabel = wx.StaticText(self.panel, label=_("Header: ")) try: - response = requests.get(header) + response = requests.get(user.header) except requests.exceptions.RequestException: # Create empty image headerImage = wx.StaticBitmap() @@ -108,13 +113,13 @@ class ShowUserProfile(wx.Dialog): headerImage = wx.StaticBitmap(self.panel, bitmap=image.ConvertToBitmap()) headerImage.AcceptsFocusFromKeyboard = returnTrue - topSizer.Add(headerLabel, wx.SizerFlags().Center()) - topSizer.Add(headerImage, wx.SizerFlags().Center()) + mainSizer.Add(headerLabel, wx.SizerFlags().Center()) + mainSizer.Add(headerImage, wx.SizerFlags().Center()) # avatar avatarLabel = wx.StaticText(self.panel, label=_("Avatar: ")) try: - response = requests.get(avatar) + response = requests.get(user.avatar) except requests.exceptions.RequestException: # Create empty image avatarImage = wx.StaticBitmap() @@ -125,59 +130,69 @@ class ShowUserProfile(wx.Dialog): avatarImage = wx.StaticBitmap(self.panel, bitmap=image.ConvertToBitmap()) avatarImage.AcceptsFocusFromKeyboard = returnTrue - topSizer.Add(avatarLabel, wx.SizerFlags().Center()) - topSizer.Add(avatarImage, wx.SizerFlags().Center()) + mainSizer.Add(avatarLabel, wx.SizerFlags().Center()) + mainSizer.Add(avatarImage, wx.SizerFlags().Center()) self.fields = [] - for num, (label, content) in enumerate(fields): + for num, field in enumerate(user.fields): labelSizer = wx.BoxSizer(wx.HORIZONTAL) labelLabel = wx.StaticText(self.panel, label=_("Field {} - Label: ").format(num + 1)) labelSizer.Add(labelLabel, wx.SizerFlags().Center().Border(wx.ALL, 5)) - labelText = self.createTextCtrl(label, (230, 30), True) + labelText = self.createTextCtrl(html_filter(field.name), (230, 30), True) labelSizer.Add(labelText, wx.SizerFlags().Expand().Border(wx.ALL, 5)) - topSizer.Add(labelSizer, 0, wx.CENTER) + mainSizer.Add(labelSizer, 0, wx.CENTER) contentSizer = wx.BoxSizer(wx.HORIZONTAL) contentLabel = wx.StaticText(self.panel, label=_("Content: ")) contentSizer.Add(contentLabel, wx.SizerFlags().Center()) - contentText = self.createTextCtrl(content, (400, 60), True) + contentText = self.createTextCtrl(html_filter(field.value), (400, 60), True) contentSizer.Add(contentText, wx.SizerFlags().Center()) - topSizer.Add(contentSizer, 0, wx.CENTER | wx.LEFT, 10) + mainSizer.Add(contentSizer, 0, wx.CENTER | wx.LEFT, 10) - # 3 X 2 grid sizer - bottomSizer = wx.GridSizer(3, 2, 10, 5) bullSwitch = {True: _('Yes'), False: _('No'), None: _('No')} privateSizer = wx.BoxSizer(wx.HORIZONTAL) privateLabel = wx.StaticText(self.panel, label=_("Private account: ")) - private = self.createTextCtrl(bullSwitch[locked], (30, 30)) + private = self.createTextCtrl(bullSwitch[user.locked], (30, 30)) privateSizer.Add(privateLabel, wx.SizerFlags().Center()) privateSizer.Add(private, wx.SizerFlags().Center()) - bottomSizer.Add(privateSizer, 0, wx.ALL | wx.CENTER) - + mainSizer.Add(privateSizer, 0, wx.ALL | wx.CENTER) botSizer = wx.BoxSizer(wx.HORIZONTAL) botLabel = wx.StaticText(self.panel, label=_("Bot account: ")) - botText = self.createTextCtrl(bullSwitch[bot], (30, 30)) + botText = self.createTextCtrl(bullSwitch[user.bot], (30, 30)) botSizer.Add(botLabel, wx.SizerFlags().Center()) botSizer.Add(botText, wx.SizerFlags().Center()) - bottomSizer.Add(botSizer, 0, wx.ALL | wx.CENTER) + mainSizer.Add(botSizer, 0, wx.ALL | wx.CENTER) discoverSizer = wx.BoxSizer(wx.HORIZONTAL) discoverLabel = wx.StaticText(self.panel, label=_("Discoverable account: ")) - discoverText = self.createTextCtrl(bullSwitch[discoverable], (30, 30)) + discoverText = self.createTextCtrl(bullSwitch[user.discoverable], (30, 30)) discoverSizer.Add(discoverLabel, wx.SizerFlags().Center()) discoverSizer.Add(discoverText, wx.SizerFlags().Center()) - bottomSizer.Add(discoverSizer, 0, wx.ALL | wx.CENTER) + mainSizer.Add(discoverSizer, 0, wx.ALL | wx.CENTER) + posts = wx.Button(self.panel, label=_("{} posts. Click to open posts timeline").format(user.statuses_count)) + # posts.SetToolTip(_("Click to open {}'s posts").format(user.display_name)) + posts.Bind(wx.EVT_BUTTON, self.onPost) + mainSizer.Add(posts, wx.SizerFlags().Center()) + + following = wx.Button(self.panel, label=_("{} following. Click to open Following timeline").format(user.following_count)) + mainSizer.Add(following, wx.SizerFlags().Center()) + following.Bind(wx.EVT_BUTTON, self.onFollowing) + + followers = wx.Button(self.panel, label=_("{} followers. Click to open followers timeline").format(user.followers_count)) + mainSizer.Add(followers, wx.SizerFlags().Center()) + followers.Bind(wx.EVT_BUTTON, self.onFollowers) close = wx.Button(self.panel, wx.ID_CLOSE, _("Close")) self.SetEscapeId(close.GetId()) close.SetDefault() - wrapperSizer.Add(topSizer, 0, wx.CENTER) - wrapperSizer.Add(bottomSizer, 0, wx.CENTER) + wrapperSizer.Add(mainSizer, 0, wx.CENTER) wrapperSizer.Add(close, wx.SizerFlags().Center()) - self.panel.SetSizerAndFit(wrapperSizer) - topSizer.Fit(self) + self.panel.SetSizer(wrapperSizer) + wrapperSizer.Fit(self.panel) + self.panel.Center() + mainSizer.Fit(self) self.Center() @@ -195,3 +210,19 @@ class ShowUserProfile(wx.Dialog): textCtrl = wx.TextCtrl(self.panel, value=text, size=size, style=style) textCtrl.AcceptsFocusFromKeyboard = returnTrue return textCtrl + + def onAction(self, *args): + """Opens the Open timeline dialog""" + pub.sendMessage('execute-action', action='follow') + + def onPost(self, *args): + """Open this user's timeline""" + pub.sendMessage('execute-action', action='openPostTimeline', kwargs=dict(user=self.user)) + + def onFollowing(self, *args): + """Open following timeline for this user""" + pub.sendMessage('execute-action', action='openFollowingTimeline', kwargs=dict(user=self.user)) + + def onFollowers(self, *args): + """Open followers timeline for this user""" + pub.sendMessage('execute-action', action='openFollowersTimeline', kwargs=dict(user=self.user))