diff --git a/src/controller/mainController.py b/src/controller/mainController.py index 91900a4b..a4264cf6 100644 --- a/src/controller/mainController.py +++ b/src/controller/mainController.py @@ -166,6 +166,7 @@ class Controller(object): widgetUtils.connect_event(self.view, widgetUtils.MENU, self.manage_aliases, self.view.manageAliases) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.report_error, self.view.reportError) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.create_filter, self.view.filter) + widgetUtils.connect_event(self.view, widgetUtils.MENU, self.manage_filters, self.view.manage_filters) def set_systray_icon(self): self.systrayIcon = sysTrayIcon.SysTrayIcon() @@ -1162,3 +1163,9 @@ class Controller(object): handler = self.get_handler(type=buffer.session.type) if handler and hasattr(handler, 'create_filter'): handler.create_filter(self, buffer) + + def manage_filters(self, *args, **kwargs): + buffer = self.get_best_buffer() + handler = self.get_handler(type=buffer.session.type) + if handler and hasattr(handler, 'manage_filters'): + handler.manage_filters(self, buffer) \ No newline at end of file diff --git a/src/controller/mastodon/filters/__init__.py b/src/controller/mastodon/filters/__init__.py new file mode 100644 index 00000000..40a96afc --- /dev/null +++ b/src/controller/mastodon/filters/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/src/controller/mastodon/filters.py b/src/controller/mastodon/filters/create_filter.py similarity index 88% rename from src/controller/mastodon/filters.py rename to src/controller/mastodon/filters/create_filter.py index e47a5163..98edbe9a 100644 --- a/src/controller/mastodon/filters.py +++ b/src/controller/mastodon/filters/create_filter.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- import widgetUtils -from wxUI.dialogs.mastodon import filters as dialogs +from wxUI.dialogs.mastodon.filters import create_filter as dialog from mastodon import MastodonAPIError -class FilterController(object): +class CreateFilterController(object): def __init__(self, session, filter_data=None): - super(FilterController, self).__init__() + super(CreateFilterController, self).__init__() self.session = session self.filter_data = filter_data - self.dialog = dialogs.MastodonFilterDialog(parent=None) + self.dialog = dialog.CreateFilterDialog(parent=None) if self.filter_data is not None: self.keywords = self.filter_data.get("keywords") self.load_filter_data() @@ -72,6 +72,7 @@ class FilterController(object): def load_filter_data(self): if 'title' in self.filter_data: self.dialog.name_ctrl.SetValue(self.filter_data['title']) + self.dialog.SetTitle(_("Update Filter: {}").format(self.filter_data['title'])) if 'context' in self.filter_data: for context in self.filter_data['context']: if context in self.dialog.context_checkboxes: @@ -81,6 +82,7 @@ class FilterController(object): self.dialog.action_choice.SetSelection(action_index) if 'expires_in' in self.filter_data: self.set_expires_in(self.filter_data['expires_in']) + print(self.filter_data) if 'keywords' in self.filter_data: self.keywords = self.filter_data['keywords'] self.dialog.keyword_panel.set_keywords(self.filter_data['keywords']) @@ -102,6 +104,9 @@ class FilterController(object): response = self.dialog.ShowModal() if response == widgetUtils.OK: filter_data = self.get_filter_data() - result = self.session.api.create_filter_v2(**filter_data) + if self.filter_data == None: + result = self.session.api.create_filter_v2(**filter_data) + else: + result = self.session.api.update_filter_v2(filter_id=self.filter_data['id'], **filter_data) return result return None \ No newline at end of file diff --git a/src/controller/mastodon/filters/manage_filters.py b/src/controller/mastodon/filters/manage_filters.py new file mode 100644 index 00000000..27fd3320 --- /dev/null +++ b/src/controller/mastodon/filters/manage_filters.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +import datetime +import wx +import widgetUtils +from wxUI import commonMessageDialogs +from wxUI.dialogs.mastodon.filters import manage_filters as dialog +from . import create_filter +from mastodon import MastodonError + +class ManageFiltersController(object): + def __init__(self, session): + super(ManageFiltersController, self).__init__() + self.session = session + self.selected_filter_idx = -1 + self.error_loading = False + self.dialog = dialog.ManageFiltersDialog(parent=None) + self.dialog.filter_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_filter_selected) + self.dialog.filter_list.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.on_filter_deselected) + widgetUtils.connect_event(self.dialog.add_button, wx.EVT_BUTTON, self.on_add_filter) + widgetUtils.connect_event(self.dialog.edit_button, wx.EVT_BUTTON, self.on_edit_filter) + widgetUtils.connect_event(self.dialog.remove_button, wx.EVT_BUTTON, self.on_remove_filter) + self.load_filter_data() + + def on_filter_selected(self, event): + """Handle filter selection event.""" + self.selected_filter_idx = event.GetIndex() + self.dialog.edit_button.Enable() + self.dialog.remove_button.Enable() + + def on_filter_deselected(self, event): + """Handle filter deselection event.""" + self.selected_filter_idx = -1 + self.dialog.edit_button.Disable() + self.dialog.remove_button.Disable() + + def get_selected_filter_id(self): + """Get the ID of the currently selected filter.""" + if self.selected_filter_idx != -1: + return self.dialog.filter_list.GetItemData(self.selected_filter_idx) + return None + + def load_filter_data(self): + try: + filters = self.session.api.filters_v2() + self.dialog.filter_list.DeleteAllItems() + for i, filter_obj in enumerate(filters): + index = self.dialog.filter_list.InsertItem(i, filter_obj.title) + keyword_count = len(filter_obj.keywords) + self.dialog.filter_list.SetItem(index, 1, str(keyword_count)) + contexts = ", ".join(filter_obj.context) + self.dialog.filter_list.SetItem(index, 2, contexts) + self.dialog.filter_list.SetItem(index, 3, filter_obj.filter_action) + if filter_obj.expires_at: + expiry_str = filter_obj.expires_at.strftime("%Y-%m-%d %H:%M") + else: + expiry_str = _("Never") + self.dialog.filter_list.SetItem(index, 4, expiry_str) + self.dialog.filter_list.SetItemData(index, int(filter_obj.id) if isinstance(filter_obj.id, (int, str)) else 0) + except MastodonError as e: + commonMessageDialogs.error_loading_filters() + self.error_loading = True + + def on_add_filter(self, *args, **kwargs): + filterController = create_filter.CreateFilterController(self.session) + try: + filter = filterController.get_response() + self.load_filter_data() + except MastodonError as error: + commonMessageDialogs.error_adding_filter() + return self.on_add_filter() + + def on_edit_filter(self, *args, **kwargs): + filter_id = self.get_selected_filter_id() + if filter_id == None: + return + try: + filter_data = self.session.api.filter_v2(filter_id) + filterController = create_filter.CreateFilterController(self.session, filter_data=filter_data) + filterController.get_response() + self.load_filter_data() + except MastodonError as error: + commonMessageDialogs.error_adding_filter() + + def on_remove_filter(self, *args, **kwargs): + filter_id = self.get_selected_filter_id() + if filter_id == None: + return + dlg = commonMessageDialogs.remove_filter() + if dlg == widgetUtils.NO: + return + try: + self.session.api.delete_filter_v2(filter_id) + self.load_filter_data() + except MastodonError as error: + commonMessageDialogs.error_removing_filter() + + def get_response(self): + return self.dialog.ShowModal() == wx.ID_OK \ No newline at end of file diff --git a/src/controller/mastodon/handler.py b/src/controller/mastodon/handler.py index 8f461584..d6335fab 100644 --- a/src/controller/mastodon/handler.py +++ b/src/controller/mastodon/handler.py @@ -14,7 +14,8 @@ from wxUI import commonMessageDialogs from wxUI.dialogs.mastodon import updateProfile as update_profile_dialogs from wxUI.dialogs.mastodon import showUserProfile, communityTimeline from sessions.mastodon.utils import html_filter -from . import userActions, settings, filters +from . import userActions, settings +from .filters import create_filter, manage_filters log = logging.getLogger("controller.mastodon.handler") @@ -52,7 +53,7 @@ class Handler(object): # In buffer Menu. community_timeline =_("Create c&ommunity timeline"), filter=_("Create a &filter"), - manage_filters=None + manage_filters=_("&Manage filters") ) # Name for the "tweet" menu in the menu bar. self.item_menu = _("&Post") @@ -408,10 +409,14 @@ class Handler(object): pub.sendMessage("createBuffer", buffer_type="CommunityBuffer", session_type=buffer.session.type, buffer_title=title, parent_tab=communities_position, start=True, kwargs=dict(parent=controller.view.nb, function="timeline", name=tl_info, sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", community_url=url, timeline=bufftype)) def create_filter(self, controller, buffer): - filterController = filters.FilterController(buffer.session) + filterController = create_filter.CreateFilterController(buffer.session) try: filter = filterController.get_response() except MastodonError as error: log.exception("Error adding filter.") commonMessageDialogs.error_adding_filter() - return self.create_filter(controller=controller, buffer=buffer) \ No newline at end of file + return self.create_filter(controller=controller, buffer=buffer) + + def manage_filters(self, controller, buffer): + manageFiltersController = manage_filters.ManageFiltersController(buffer.session) + manageFiltersController.get_response() \ No newline at end of file diff --git a/src/wxUI/commonMessageDialogs.py b/src/wxUI/commonMessageDialogs.py index 7545ddba..401ba8a4 100644 --- a/src/wxUI/commonMessageDialogs.py +++ b/src/wxUI/commonMessageDialogs.py @@ -49,4 +49,13 @@ def invalid_instance(): return wx.MessageDialog(None, _("the provided instance is invalid. Please try again."), _("Invalid instance"), wx.ICON_ERROR).ShowModal() def error_adding_filter(): - return wx.MessageDialog(None, _("TWBlue was unable to add or update the filter with the specified settings. Please try again."), _("Error"), wx.ICON_ERROR).ShowModal() \ No newline at end of file + return wx.MessageDialog(None, _("TWBlue was unable to add or update the filter with the specified settings. Please try again."), _("Error"), wx.ICON_ERROR).ShowModal() + +def error_loading_filters(): + return wx.MessageDialog(None, _("TWBlue was unable to load your filters from the instance. Please try again."), _("Error"), wx.ICON_ERROR).ShowModal() + +def remove_filter(): + dlg = wx.MessageDialog(None, _("Do you really want to delete this filter ?"), _("Delete filter"), wx.ICON_QUESTION|wx.YES_NO) + return dlg.ShowModal() +def error_removing_filters(): + return wx.MessageDialog(None, _("TWBlue was unable to remove the filter you specified. Please try again."), _("Error"), wx.ICON_ERROR).ShowModal() diff --git a/src/wxUI/dialogs/mastodon/filters/__init__.py b/src/wxUI/dialogs/mastodon/filters/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/wxUI/dialogs/mastodon/filters.py b/src/wxUI/dialogs/mastodon/filters/create_filter.py similarity index 82% rename from src/wxUI/dialogs/mastodon/filters.py rename to src/wxUI/dialogs/mastodon/filters/create_filter.py index 4ad0b89c..8329b16c 100644 --- a/src/wxUI/dialogs/mastodon/filters.py +++ b/src/wxUI/dialogs/mastodon/filters/create_filter.py @@ -8,7 +8,7 @@ class FilterKeywordPanel(wx.Panel): self.keywords = [] # Add widgets list_panel = wx.Panel(self) - self.keyword_list = wx.ListCtrl(list_panel, style=wx.LC_REPORT | wx.LC_SINGLE_SEL) + self.keyword_list = wx.ListCtrl(list_panel, wx.ID_ANY, style=wx.LC_REPORT | wx.LC_SINGLE_SEL) self.keyword_list.InsertColumn(0, _("Keyword")) self.keyword_list.InsertColumn(1, _("Whole word")) self.keyword_list.SetColumnWidth(0, wx.LIST_AUTOSIZE_USEHEADER) @@ -16,14 +16,14 @@ class FilterKeywordPanel(wx.Panel): list_sizer = wx.BoxSizer(wx.VERTICAL) list_sizer.Add(self.keyword_list, 1, wx.EXPAND) list_panel.SetSizer(list_sizer) - keyword_label = wx.StaticText(self, label=_("Keyword:")) - self.keyword_text = wx.TextCtrl(self) + keyword_label = wx.StaticText(self, wx.ID_ANY, label=_("Keyword:")) + self.keyword_text = wx.TextCtrl(self, wx.ID_ANY) keyword_sizer = wx.BoxSizer(wx.VERTICAL) keyword_sizer.Add(keyword_label, 0, wx.RIGHT, 5) keyword_sizer.Add(self.keyword_text, 0, wx.EXPAND) - self.whole_word_checkbox = wx.CheckBox(self, label=_("Whole word")) - self.add_button = wx.Button(self, label=_("Add")) - self.remove_button = wx.Button(self, label=_("Remove")) + self.whole_word_checkbox = wx.CheckBox(self, wx.ID_ANY, label=_("Whole word")) + self.add_button = wx.Button(self, wx.ID_ANY, label=_("Add")) + self.remove_button = wx.Button(self, wx.ID_ANY, label=_("Remove")) input_sizer = wx.BoxSizer(wx.HORIZONTAL) input_sizer.Add(keyword_sizer, 1, wx.RIGHT, 5) input_sizer.Add(self.whole_word_checkbox, 0) @@ -70,9 +70,9 @@ class FilterKeywordPanel(wx.Panel): self.keyword_list.SetColumnWidth(0, wx.LIST_AUTOSIZE) self.keyword_list.SetColumnWidth(1, wx.LIST_AUTOSIZE_USEHEADER) -class MastodonFilterDialog(wx.Dialog): +class CreateFilterDialog(wx.Dialog): def __init__(self, parent, title=_("New filter")): - super(MastodonFilterDialog, self).__init__(parent, title=title, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) + super(CreateFilterDialog, self).__init__(parent, title=title, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) self.contexts = ["home", "public", "notifications", "thread", "account"] self.context_labels = { "home": _("Home timeline"), @@ -94,35 +94,36 @@ class MastodonFilterDialog(wx.Dialog): ("months", _("months")) ] main_sizer = wx.BoxSizer(wx.VERTICAL) - name_label = wx.StaticText(self, label=_("Title:")) - self.name_ctrl = wx.TextCtrl(self) + name_label = wx.StaticText(self, wx.ID_ANY, label=_("Title:")) + self.name_ctrl = wx.TextCtrl(self, wx.ID_ANY) + name_sizer = wx.BoxSizer(wx.HORIZONTAL) name_sizer.Add(name_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) name_sizer.Add(self.name_ctrl, 1, wx.EXPAND) main_sizer.Add(name_sizer, 0, wx.EXPAND | wx.ALL, 10) - static_box = wx.StaticBox(self, label=_("Apply to:")) + static_box = wx.StaticBox(self, wx.ID_ANY, label=_("Apply to:")) context_sizer = wx.StaticBoxSizer(static_box, wx.VERTICAL) self.context_checkboxes = {} context_grid = wx.FlexGridSizer(rows=3, cols=2, vgap=5, hgap=10) for context in self.contexts: - checkbox = wx.CheckBox(static_box, label=self.context_labels[context]) + checkbox = wx.CheckBox(static_box, wx.ID_ANY, label=self.context_labels[context]) self.context_checkboxes[context] = checkbox context_grid.Add(checkbox) context_sizer.Add(context_grid, 0, wx.ALL, 10) main_sizer.Add(context_sizer, 0, wx.EXPAND | wx.ALL, 10) - action_label = wx.StaticText(self, label=_("Action:")) - self.action_choice = wx.Choice(self) + action_label = wx.StaticText(self, wx.ID_ANY, label=_("Action:")) + self.action_choice = wx.Choice(self, wx.ID_ANY) for action in self.actions: self.action_choice.Append(self.action_labels[action]) action_sizer = wx.BoxSizer(wx.HORIZONTAL) action_sizer.Add(action_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) action_sizer.Add(self.action_choice, 1) main_sizer.Add(action_sizer, 0, wx.EXPAND | wx.ALL, 10) - expiration_label = wx.StaticText(self, label=_("Expires in:")) - self.expiration_choice = wx.Choice(self) + expiration_label = wx.StaticText(self, wx.ID_ANY, label=_("Expires in:")) + self.expiration_choice = wx.Choice(self, wx.ID_ANY) for e, label in self.expiration_options: self.expiration_choice.Append(label) - self.expiration_value = wx.SpinCtrl(self, min=1, max=9999, initial=1) + self.expiration_value = wx.SpinCtrl(self, wx.ID_ANY, min=1, max=9999, initial=1) self.expiration_value.Enable(False) self.expiration_choice.Bind(wx.EVT_CHOICE, self.on_expiration_changed) expiration_sizer = wx.BoxSizer(wx.HORIZONTAL) @@ -138,8 +139,8 @@ class MastodonFilterDialog(wx.Dialog): self.SetSize((450, 550)) self.action_choice.SetSelection(0) self.expiration_choice.SetSelection(0) + wx.CallAfter(self.name_ctrl.SetFocus) def on_expiration_changed(self, event): selection = self.expiration_choice.GetSelection() self.expiration_value.Enable(selection != 0) - diff --git a/src/wxUI/dialogs/mastodon/filters/manage_filters.py b/src/wxUI/dialogs/mastodon/filters/manage_filters.py new file mode 100644 index 00000000..1710afab --- /dev/null +++ b/src/wxUI/dialogs/mastodon/filters/manage_filters.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +import wx + +class ManageFiltersDialog(wx.Dialog): + """ + A dialog that displays a list of Mastodon filters and provides controls + to add, edit and remove them. + """ + + def __init__(self, parent, title=_("Filters"), *args, **kwargs): + """Initialize the filters view dialog. """ + super(ManageFiltersDialog, self).__init__(parent, title=title, *args, **kwargs) + main_sizer = wx.BoxSizer(wx.VERTICAL) + self.filter_list = wx.ListCtrl(self, style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.BORDER_SUNKEN) + self.filter_list.InsertColumn(0, _("Title"), width=150) + self.filter_list.InsertColumn(1, _("Keywords"), width=80) + self.filter_list.InsertColumn(2, _("Contexts"), width=150) + self.filter_list.InsertColumn(3, _("Action"), width=100) + self.filter_list.InsertColumn(4, _("Expires"), width=150) + main_sizer.Add(self.filter_list, 1, wx.EXPAND | wx.ALL, 10) + button_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.add_button = wx.Button(self, label=_("Add")) + self.edit_button = wx.Button(self, label=_("Edit")) + self.remove_button = wx.Button(self, label=_("Remove")) + close_button = wx.Button(self, wx.ID_CLOSE) + self.edit_button.Disable() + self.remove_button.Disable() + button_sizer.Add(self.add_button, 0, wx.RIGHT, 5) + button_sizer.Add(self.edit_button, 0, wx.RIGHT, 5) + button_sizer.Add(self.remove_button, 0, wx.RIGHT, 5) + button_sizer.Add((0, 0), 1, wx.EXPAND) # Spacer to push close button to right + button_sizer.Add(close_button, 0) + self.SetEscapeId(close_button.GetId()) + main_sizer.Add(button_sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10) + self.SetSizer(main_sizer)