diff --git a/src/controller/mastodon/handler.py b/src/controller/mastodon/handler.py index 68cd74a8..d281d448 100644 --- a/src/controller/mastodon/handler.py +++ b/src/controller/mastodon/handler.py @@ -2,12 +2,13 @@ import wx import logging from pubsub import pub +from mysc import restart from wxUI.dialogs.mastodon import dialogs from wxUI.dialogs.mastodon import search as search_dialogs from wxUI.dialogs.mastodon import dialogs from wxUI import commonMessageDialogs from sessions.twitter import utils -from . import userActions +from . import userActions, settings log = logging.getLogger("controller.mastodon.handler") @@ -177,3 +178,13 @@ class Handler(object): 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): + d = settings.accountSettingsController(buffer, controller) + if d.response == wx.ID_OK: + d.save_configuration() + if d.needs_restart == True: + commonMessageDialogs.needs_restart() + buffer.session.settings.write() + buffer.session.save_persistent_data() + restart.restart_program() diff --git a/src/controller/mastodon/settings.py b/src/controller/mastodon/settings.py new file mode 100644 index 00000000..104a2631 --- /dev/null +++ b/src/controller/mastodon/settings.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +import os +import threading +import logging +import sound_lib +import paths +import widgetUtils +import output +from collections import OrderedDict +from wxUI import commonMessageDialogs +from wxUI.dialogs.mastodon import configuration +from extra.autocompletionUsers import scan, manage +from extra.ocr import OCRSpace +from controller.settings import globalSettingsController +from . templateEditor import EditTemplate + +log = logging.getLogger("Settings") + +class accountSettingsController(globalSettingsController): + def __init__(self, buffer, window): + self.user = buffer.session.db["user_name"] + self.buffer = buffer + self.window = window + self.config = buffer.session.settings + self.dialog = configuration.configurationDialog() + self.create_config() + self.needs_restart = False + self.is_started = True + + def create_config(self): + self.dialog.create_general_account() +# widgetUtils.connect_event(self.dialog.general.userAutocompletionScan, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_scan) +# widgetUtils.connect_event(self.dialog.general.userAutocompletionManage, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_manage) + self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"]) + self.dialog.set_value("general", "show_screen_names", self.config["general"]["show_screen_names"]) + self.dialog.set_value("general", "hide_emojis", self.config["general"]["hide_emojis"]) + self.dialog.set_value("general", "itemsPerApiCall", self.config["general"]["max_posts_per_call"]) + self.dialog.set_value("general", "reverse_timelines", self.config["general"]["reverse_timelines"]) + boost_mode = self.config["general"]["boost_mode"] + if boost_mode == "ask": + self.dialog.set_value("general", "ask_before_boost", True) + else: + self.dialog.set_value("general", "ask_before_boost", False) + self.dialog.set_value("general", "persist_size", str(self.config["general"]["persist_size"])) + self.dialog.set_value("general", "load_cache_in_memory", self.config["general"]["load_cache_in_memory"]) + self.dialog.create_reporting() + self.dialog.set_value("reporting", "speech_reporting", self.config["reporting"]["speech_reporting"]) + self.dialog.set_value("reporting", "braille_reporting", self.config["reporting"]["braille_reporting"]) + post_template = self.config["templates"]["post"] + conversation_template = self.config["templates"]["conversation"] + person_template = self.config["templates"]["person"] + self.dialog.create_templates(post_template=post_template, conversation_template=conversation_template, person_template=person_template) + widgetUtils.connect_event(self.dialog.templates.post, widgetUtils.BUTTON_PRESSED, self.edit_post_template) + widgetUtils.connect_event(self.dialog.templates.conversation, widgetUtils.BUTTON_PRESSED, self.edit_conversation_template) + widgetUtils.connect_event(self.dialog.templates.person, widgetUtils.BUTTON_PRESSED, self.edit_person_template) + self.dialog.create_other_buffers() + buffer_values = self.get_buffers_list() + self.dialog.buffers.insert_buffers(buffer_values) + self.dialog.buffers.connect_hook_func(self.toggle_buffer_active) + widgetUtils.connect_event(self.dialog.buffers.toggle_state, widgetUtils.BUTTON_PRESSED, self.toggle_state) + widgetUtils.connect_event(self.dialog.buffers.up, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_up) + widgetUtils.connect_event(self.dialog.buffers.down, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_down) + self.input_devices = sound_lib.input.Input.get_device_names() + self.output_devices = sound_lib.output.Output.get_device_names() + self.soundpacks = [] + [self.soundpacks.append(i) for i in os.listdir(paths.sound_path()) if os.path.isdir(os.path.join(paths.sound_path(), i)) == True ] + self.dialog.create_sound(self.input_devices, self.output_devices, self.soundpacks) + self.dialog.set_value("sound", "volumeCtrl", int(self.config["sound"]["volume"]*100)) + self.dialog.set_value("sound", "input", self.config["sound"]["input_device"]) + self.dialog.set_value("sound", "output", self.config["sound"]["output_device"]) + self.dialog.set_value("sound", "session_mute", self.config["sound"]["session_mute"]) + self.dialog.set_value("sound", "soundpack", self.config["sound"]["current_soundpack"]) + self.dialog.set_value("sound", "indicate_audio", self.config["sound"]["indicate_audio"]) + self.dialog.set_value("sound", "indicate_img", self.config["sound"]["indicate_img"]) + self.dialog.create_extras(OCRSpace.translatable_langs) + language_index = OCRSpace.OcrLangs.index(self.config["mysc"]["ocr_language"]) + self.dialog.extras.ocr_lang.SetSelection(language_index) + self.dialog.realize() + self.dialog.set_title(_("Account settings for %s") % (self.user,)) + self.response = self.dialog.get_response() + + def edit_post_template(self, *args, **kwargs): + template = self.config["templates"]["post"] + control = EditTemplate(template=template, type="post") + result = control.run_dialog() + if result != "": # Template has been saved. + self.config["templates"]["post"] = result + self.config.write() + self.dialog.templates.post.SetLabel(_("Edit template for posts. Current template: {}").format(result)) + + def edit_conversation_template(self, *args, **kwargs): + template = self.config["templates"]["conversation"] + control = EditTemplate(template=template, type="conversation") + result = control.run_dialog() + if result != "": # Template has been saved. + self.config["templates"]["conversation"] = result + self.config.write() + self.dialog.templates.conversation.SetLabel(_("Edit template for conversations. Current template: {}").format(result)) + + def edit_person_template(self, *args, **kwargs): + template = self.config["templates"]["person"] + control = EditTemplate(template=template, type="person") + result = control.run_dialog() + if result != "": # Template has been saved. + self.config["templates"]["person"] = result + self.config.write() + self.dialog.templates.person.SetLabel(_("Edit template for persons. Current template: {}").format(result)) + + def save_configuration(self): + if self.config["general"]["relative_times"] != self.dialog.get_value("general", "relative_time"): + self.needs_restart = True + log.debug("Triggered app restart due to change in relative times.") + self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time") + self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names") + self.config["general"]["hide_emojis"] = self.dialog.get_value("general", "hide_emojis") + self.config["general"]["max_posts_per_call"] = self.dialog.get_value("general", "itemsPerApiCall") + if self.config["general"]["load_cache_in_memory"] != self.dialog.get_value("general", "load_cache_in_memory"): + self.config["general"]["load_cache_in_memory"] = self.dialog.get_value("general", "load_cache_in_memory") + self.needs_restart = True + log.debug("Triggered app restart due to change in database strategy management.") + if self.config["general"]["persist_size"] != self.dialog.get_value("general", "persist_size"): + if self.dialog.get_value("general", "persist_size") == '': + self.config["general"]["persist_size"] =-1 + else: + try: + self.config["general"]["persist_size"] = int(self.dialog.get_value("general", "persist_size")) + except ValueError: + output.speak("Invalid cache size, setting to default.",True) + self.config["general"]["persist_size"] =1764 + + if self.config["general"]["reverse_timelines"] != self.dialog.get_value("general", "reverse_timelines"): + self.needs_restart = True + log.debug("Triggered app restart due to change in timeline order.") + self.config["general"]["reverse_timelines"] = self.dialog.get_value("general", "reverse_timelines") + ask_before_boost = self.dialog.get_value("general", "ask_before_boost") + if ask_before_boost == True: + self.config["general"]["boost_mode"] = "ask" + else: + self.config["general"]["boost_mode"] = "direct" + buffers_list = self.dialog.buffers.get_list() + if buffers_list != self.config["general"]["buffer_order"]: + self.needs_restart = True + log.debug("Triggered app restart due to change in buffer ordering.") + self.config["general"]["buffer_order"] = buffers_list + self.config["reporting"]["speech_reporting"] = self.dialog.get_value("reporting", "speech_reporting") + self.config["reporting"]["braille_reporting"] = self.dialog.get_value("reporting", "braille_reporting") + self.config["mysc"]["ocr_language"] = OCRSpace.OcrLangs[self.dialog.extras.ocr_lang.GetSelection()] + if self.config["sound"]["input_device"] != self.dialog.sound.get("input"): + self.config["sound"]["input_device"] = self.dialog.sound.get("input") + try: + self.buffer.session.sound.input.set_device(self.buffer.session.sound.input.find_device_by_name(self.config["sound"]["input_device"])) + except: + self.config["sound"]["input_device"] = "default" + if self.config["sound"]["output_device"] != self.dialog.sound.get("output"): + self.config["sound"]["output_device"] = self.dialog.sound.get("output") + try: + self.buffer.session.sound.output.set_device(self.buffer.session.sound.output.find_device_by_name(self.config["sound"]["output_device"])) + except: + self.config["sound"]["output_device"] = "default" + self.config["sound"]["volume"] = self.dialog.get_value("sound", "volumeCtrl")/100.0 + self.config["sound"]["session_mute"] = self.dialog.get_value("sound", "session_mute") + self.config["sound"]["current_soundpack"] = self.dialog.sound.get("soundpack") + self.config["sound"]["indicate_audio"] = self.dialog.get_value("sound", "indicate_audio") + self.config["sound"]["indicate_img"] = self.dialog.get_value("sound", "indicate_img") + self.buffer.session.sound.config = self.config["sound"] + self.buffer.session.sound.check_soundpack() + self.config.write() + + def toggle_state(self,*args,**kwargs): + return self.dialog.buffers.change_selected_item() + + def on_autocompletion_scan(self, *args, **kwargs): + configuration = scan.autocompletionScan(self.buffer.session.settings, self.buffer, self.window) + to_scan = configuration.show_dialog() + if to_scan == True: + configuration.prepare_progress_dialog() + t = threading.Thread(target=configuration.scan) + t.start() + + def on_autocompletion_manage(self, *args, **kwargs): + configuration = manage.autocompletionManage(self.buffer.session) + configuration.show_settings() + + def get_buffers_list(self): + all_buffers=OrderedDict() + all_buffers['home']=_("Home") + all_buffers['local'] = _("Local") + all_buffers['federated'] = _("Federated") + all_buffers['mentions']=_("Mentions") + all_buffers['direct_messages']=_("Direct Messages") + all_buffers['sent']=_("Sent") + all_buffers['favorites']=_("Favorites") + all_buffers['bookmarks']=_("Bookmarks") + all_buffers['followers']=_("Followers") + all_buffers['following']=_("Following") + all_buffers['blocked']=_("Blocked users") + all_buffers['muted']=_("Muted users") + all_buffers['notifications']=_("Notifications") + list_buffers = [] + hidden_buffers=[] + all_buffers_keys = list(all_buffers.keys()) + # Check buffers shown first. + for i in self.config["general"]["buffer_order"]: + if i in all_buffers_keys: + list_buffers.append((i, all_buffers[i], True)) + # This second pass will retrieve all hidden buffers. + for i in all_buffers_keys: + if i not in self.config["general"]["buffer_order"]: + hidden_buffers.append((i, all_buffers[i], False)) + list_buffers.extend(hidden_buffers) + return list_buffers + + def toggle_buffer_active(self, ev): + change = self.dialog.buffers.get_event(ev) + if change == True: + self.dialog.buffers.change_selected_item() diff --git a/src/wxUI/dialogs/mastodon/configuration.py b/src/wxUI/dialogs/mastodon/configuration.py new file mode 100644 index 00000000..9b0af7c5 --- /dev/null +++ b/src/wxUI/dialogs/mastodon/configuration.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +import wx +import widgetUtils +from wxUI.dialogs import baseDialog +# As some panels are the same than those used in Twitter sessions, let's import them directly. +from wxUI.dialogs.configuration import reporting, other_buffers +from multiplatform_widgets import widgets + +class generalAccount(wx.Panel, baseDialog.BaseWXDialog): + + def __init__(self, parent): + super(generalAccount, self).__init__(parent) + sizer = wx.BoxSizer(wx.VERTICAL) + userAutocompletionBox = wx.StaticBox(self, label=_("User autocompletion settings")) + self.userAutocompletionScan = wx.Button(self, wx.ID_ANY, _("Scan account and add followers and following users to the user autocompletion database")) + self.userAutocompletionScan.Enable(False) + self.userAutocompletionManage = wx.Button(self, wx.ID_ANY, _("Manage autocompletion database")) + self.userAutocompletionManage.Enable(False) + autocompletionSizer = wx.StaticBoxSizer(userAutocompletionBox, wx.HORIZONTAL) + autocompletionSizer.Add(self.userAutocompletionScan, 0, wx.ALL, 5) + autocompletionSizer.Add(self.userAutocompletionManage, 0, wx.ALL, 5) + sizer.Add(autocompletionSizer, 0, wx.ALL, 5) + self.relative_time = wx.CheckBox(self, wx.ID_ANY, _("Relative timestamps")) + sizer.Add(self.relative_time, 0, wx.ALL, 5) + itemsPerCallBox = wx.BoxSizer(wx.HORIZONTAL) + itemsPerCallBox.Add(wx.StaticText(self, -1, _("Items on each API call")), 0, wx.ALL, 5) + self.itemsPerApiCall = wx.SpinCtrl(self, wx.ID_ANY) + self.itemsPerApiCall.SetRange(0, 40) + self.itemsPerApiCall.SetSize(self.itemsPerApiCall.GetBestSize()) + itemsPerCallBox.Add(self.itemsPerApiCall, 0, wx.ALL, 5) + sizer.Add(itemsPerCallBox, 0, wx.ALL, 5) + self.reverse_timelines = wx.CheckBox(self, wx.ID_ANY, _("Inverted buffers: The newest items will be shown at the beginning while the oldest at the end")) + sizer.Add(self.reverse_timelines, 0, wx.ALL, 5) + self.ask_before_boost = wx.CheckBox(self, wx.ID_ANY, _("Ask confirmation before boosting a post")) + sizer.Add(self.ask_before_boost, 0, wx.ALL, 5) + self.show_screen_names = wx.CheckBox(self, wx.ID_ANY, _("Show screen names instead of full names")) + sizer.Add(self.show_screen_names, 0, wx.ALL, 5) + self.hide_emojis = wx.CheckBox(self, wx.ID_ANY, _("hide emojis in usernames")) + sizer.Add(self.hide_emojis, 0, wx.ALL, 5) + PersistSizeLabel = wx.StaticText(self, -1, _("Number of items per buffer to cache in database (0 to disable caching, blank for unlimited)")) + self.persist_size = wx.TextCtrl(self, -1) + sizer.Add(PersistSizeLabel, 0, wx.ALL, 5) + sizer.Add(self.persist_size, 0, wx.ALL, 5) + self.load_cache_in_memory = wx.CheckBox(self, wx.NewId(), _("Load cache for items in memory (much faster in big datasets but requires more RAM)")) + self.SetSizer(sizer) + +class templates(wx.Panel, baseDialog.BaseWXDialog): + def __init__(self, parent, post_template, conversation_template, person_template): + super(templates, self).__init__(parent) + sizer = wx.BoxSizer(wx.VERTICAL) + self.post = wx.Button(self, wx.ID_ANY, _("Edit template for posts. Current template: {}").format(post_template)) + sizer.Add(self.post, 0, wx.ALL, 5) + self.conversation = wx.Button(self, wx.ID_ANY, _("Edit template for conversations. Current template: {}").format(conversation_template)) + sizer.Add(self.conversation, 0, wx.ALL, 5) + self.person = wx.Button(self, wx.ID_ANY, _("Edit template for persons. Current template: {}").format(person_template)) + sizer.Add(self.person, 0, wx.ALL, 5) + self.SetSizer(sizer) + +class sound(wx.Panel): + def __init__(self, parent, input_devices, output_devices, soundpacks): + wx.Panel.__init__(self, parent) + sizer = wx.BoxSizer(wx.VERTICAL) + volume = wx.StaticText(self, -1, _(u"Volume")) + self.volumeCtrl = wx.Slider(self) + # Connect a key handler here to handle volume slider being inverted when moving with up and down arrows. + # see https://github.com/manuelcortez/TWBlue/issues/261 + widgetUtils.connect_event(self.volumeCtrl, widgetUtils.KEYPRESS, self.on_keypress) + self.volumeCtrl.SetRange(0, 100) + self.volumeCtrl.SetSize(self.volumeCtrl.GetBestSize()) + volumeBox = wx.BoxSizer(wx.HORIZONTAL) + volumeBox.Add(volume, 0, wx.ALL, 5) + volumeBox.Add(self.volumeCtrl, 0, wx.ALL, 5) + sizer.Add(volumeBox, 0, wx.ALL, 5) + self.session_mute = wx.CheckBox(self, -1, _(u"Session mute")) + sizer.Add(self.session_mute, 0, wx.ALL, 5) + output_label = wx.StaticText(self, -1, _(u"Output device")) + self.output = wx.ComboBox(self, -1, choices=output_devices, style=wx.CB_READONLY) + self.output.SetSize(self.output.GetBestSize()) + outputBox = wx.BoxSizer(wx.HORIZONTAL) + outputBox.Add(output_label, 0, wx.ALL, 5) + outputBox.Add(self.output, 0, wx.ALL, 5) + sizer.Add(outputBox, 0, wx.ALL, 5) + input_label = wx.StaticText(self, -1, _(u"Input device")) + self.input = wx.ComboBox(self, -1, choices=input_devices, style=wx.CB_READONLY) + self.input.SetSize(self.input.GetBestSize()) + inputBox = wx.BoxSizer(wx.HORIZONTAL) + inputBox.Add(input_label, 0, wx.ALL, 5) + inputBox.Add(self.input, 0, wx.ALL, 5) + sizer.Add(inputBox, 0, wx.ALL, 5) + soundBox = wx.BoxSizer(wx.VERTICAL) + soundpack_label = wx.StaticText(self, -1, _(u"Sound pack")) + self.soundpack = wx.ComboBox(self, -1, choices=soundpacks, style=wx.CB_READONLY) + self.soundpack.SetSize(self.soundpack.GetBestSize()) + soundBox.Add(soundpack_label, 0, wx.ALL, 5) + soundBox.Add(self.soundpack, 0, wx.ALL, 5) + sizer.Add(soundBox, 0, wx.ALL, 5) + self.indicate_audio = wx.CheckBox(self, -1, _("Indicate audio or video in posts with sound")) + sizer.Add(self.indicate_audio, 0, wx.ALL, 5) + self.indicate_img = wx.CheckBox(self, -1, _("Indicate posts containing images with sound")) + sizer.Add(self.indicate_img, 0, wx.ALL, 5) + self.SetSizer(sizer) + + def on_keypress(self, event, *args, **kwargs): + """ Invert movement of up and down arrow keys when dealing with a wX Slider. + See https://github.com/manuelcortez/TWBlue/issues/261 + and http://trac.wxwidgets.org/ticket/2068 + """ + keycode = event.GetKeyCode() + if keycode == wx.WXK_UP: + return self.volumeCtrl.SetValue(self.volumeCtrl.GetValue()+1) + elif keycode == wx.WXK_DOWN: + return self.volumeCtrl.SetValue(self.volumeCtrl.GetValue()-1) + event.Skip() + + def get(self, control): + return getattr(self, control).GetStringSelection() + +class extrasPanel(wx.Panel): + def __init__(self, parent, ocr_languages=[], translation_languages=[]): + super(extrasPanel, self).__init__(parent) + mainSizer = wx.BoxSizer(wx.VERTICAL) + OCRBox = wx.StaticBox(self, label=_(u"Language for OCR")) + self.ocr_lang = wx.ListBox(self, -1, choices=ocr_languages) + self.ocr_lang.SetSize(self.ocr_lang.GetBestSize()) + ocrLanguageSizer = wx.StaticBoxSizer(OCRBox, wx.HORIZONTAL) + ocrLanguageSizer.Add(self.ocr_lang, 0, wx.ALL, 5) + mainSizer.Add(ocrLanguageSizer, 0, wx.ALL, 5) + self.SetSizer(mainSizer) + +class configurationDialog(baseDialog.BaseWXDialog): + def set_title(self, title): + self.SetTitle(title) + + def __init__(self): + super(configurationDialog, self).__init__(None, -1) + self.panel = wx.Panel(self) + self.sizer = wx.BoxSizer(wx.VERTICAL) + self.notebook = wx.Notebook(self.panel) + + def create_general_account(self): + self.general = generalAccount(self.notebook) + self.notebook.AddPage(self.general, _(u"General")) + self.general.SetFocus() + + def create_reporting(self): + self.reporting = reporting(self.notebook) + self.notebook.AddPage(self.reporting, _(u"Feedback")) + + def create_other_buffers(self): + self.buffers = other_buffers(self.notebook) + self.notebook.AddPage(self.buffers, _(u"Buffers")) + + def create_templates(self, post_template, conversation_template, person_template): + self.templates = templates(self.notebook, post_template=post_template, conversation_template=conversation_template, person_template=person_template) + self.notebook.AddPage(self.templates, _("Templates")) + + def create_sound(self, output_devices, input_devices, soundpacks): + self.sound = sound(self.notebook, output_devices, input_devices, soundpacks) + self.notebook.AddPage(self.sound, _(u"Sound")) + + def create_extras(self, ocr_languages=[], translator_languages=[]): + self.extras = extrasPanel(self.notebook, ocr_languages, translator_languages) + self.notebook.AddPage(self.extras, _(u"Extras")) + + def realize(self): + self.sizer.Add(self.notebook, 0, wx.ALL, 5) + ok_cancel_box = wx.BoxSizer(wx.HORIZONTAL) + ok = wx.Button(self.panel, wx.ID_OK, _(u"Save")) + ok.SetDefault() + cancel = wx.Button(self.panel, wx.ID_CANCEL, _(u"Close")) + self.SetEscapeId(cancel.GetId()) + ok_cancel_box.Add(ok, 0, wx.ALL, 5) + ok_cancel_box.Add(cancel, 0, wx.ALL, 5) + self.sizer.Add(ok_cancel_box, 0, wx.ALL, 5) + self.panel.SetSizer(self.sizer) + self.SetClientSize(self.sizer.CalcMin()) + + def get_value(self, panel, key): + p = getattr(self, panel) + return getattr(p, key).GetValue() + + def set_value(self, panel, key, value): + p = getattr(self, panel) + control = getattr(p, key) + getattr(control, "SetValue")(value) +