""" ======================================================================== USER APP SETTINGS - SCREEN (part of Frigcal) Implement the settings screen, for editing configuration options that are stored in a JSON text file by settings.py. The screen's UI is defined in allscreens.kv. ======================================================================== """ from common import * # for folder-change verify from storage import CalendarsTable, CalendarsDirty # for folder-change verify # import main for ConfirmDialog fails: use Kivy Factory class SettingsGUI: allsettings = ['colortheme', 'globalfontsize', #'globalfontname', # CUT 'maxbackups', 'editbubbles', # new 'calendarsfolder'] def __init__(self, app, screen): self.app = app self.screen = screen def fix_widgets_for_theme(self): """ Ad-hoc theme fixes, called on theme changes Some already-built widgets do not auto-adjust """ ids = self.screen.ids for field in self.allsettings: widget = getattr(ids, field) widget.text_color_normal = self.app.manual_theme_color_fg() widget.text_color_focus = self.app.manual_theme_color_fg() # ---------- # FILL/FETCH # ---------- def fill_widgets_from_settings(self): """ Load GUI fields from all settings Run on startup, restores, and possibly more Settings are set explicitly in GUI callbacks """ for field in self.allsettings: value = getattr(self.app.settings, field) # like settings.field if field == 'calendarsfolder': # None till ask1 value = value or '' elif field in ['globalfontsize', 'maxbackups']: # integers value = str(value) # else str as is self.screen.ids[field].text = value # set in GUI, ids.field def fetch_settings_from_widgets(self): """ Load temp settings dict from all GUI fields Used before storing settings persistently Settings are saved explicitly on Settings Save """ values = {} for field in self.allsettings: value = self.screen.ids[field].text # like ids.field if field in ['globalfontsize', 'maxbackups']: # integers value = int(value) # else str as is values[field] = value # set in settings table return values # ---------- # COLORTHEME # ---------- def on_colortheme_touch(self, touch): trace('on_colortheme_touch') guifield = self.screen.ids.colortheme if not guifield.collide_point(*touch.pos): # or touch.x, touch.y return False allthemes = ['Dark', 'Light'] # future expansion? menu_items = [ # a list of dicts dict(viewclass='OneLineListItem', text=themename, on_release= lambda themename=themename: # else last loop-var value self.on_colortheme_menu_tap(themename), ) for themename in allthemes ] # bind nested MDLabels' font_size+name _after_ creation else kivmd overrides # PUNT/TBD: the OLLIs don't seem to be accessible here? self.colortheme_menu = MDDropdownMenu( items=menu_items, caller=guifield, # else default 4 may run into android edge-to-edge navbar border_margin=dp(60), ) self.colortheme_menu.open() return True # consume touch event def on_colortheme_menu_tap(self, themename): """ Set text field to menu pick, app theme auto changed on close. Text field need not be verified: it's readonly in .kv to prevent a pointless onscreen keyboard. App.settings settings table is not updated till Save to file. """ trace('on_colortheme_menu_tap') self.colortheme_menu.dismiss() self.screen.ids.colortheme.text = themename # settings screen if themename != self.app.theme_cls.theme_style: # whole app self.app.on_theme(self.screen, toggle=False) # don't open main menu # ------------------- # GLOBALFONTNAME: CUT # ------------------- '''CUT (see common.kv) def on_fontname_touch(self, touch): trace('on_fontname_touch') guifield = self.screen.ids.globalfontname if not guifield.collide_point(*touch.pos): # or touch.x, touch.y return False allnames = ['Roboto', 'DejaVuSans', 'RobotoMono-Regular'] # kivy builtins only menu_items = [ # a list of dicts dict(viewclass='OneLineListItem', text=fontname, on_release= lambda fontname=fontname: # else last loop-var value self.on_fontname_menu_tap(fontname), ) for fontname in allnames ] # bind nested MDLabels' font_size+name _after_ creation else kivmd overrides # PUNT/TBD: the OLLIs don't seem to be accessible here? self.fontname_menu = MDDropdownMenu( items=menu_items, caller=guifield, # else default 4 may run into android edge-to-edge navbar border_margin=dp(60), ) self.fontname_menu.open() return True # consume touch event CUT''' '''CUT (see common.kv) def on_fontname_menu_tap(self, fontname): """ Set text field to menu pick, app theme auto changed on close. changed on close. Text field need not be verified: it's readonly in .kv to prevent a pointless onscreen keyboard. App.settings settings table is not updated till Save to file. """ trace('on_fontname_menu_tap') self.fontname_menu.dismiss() self.screen.ids.globalfontname.text = fontname # settings screen self.app.set_font_name(fontname) # reset widgets now CUT''' # -------------- # GLOBALFONTSIZE # -------------- def on_fontsize_defocus(self): """ Unlike fontname, fontsize works globally and already-displayed text is auto-updated. Only caveats: with KivyMD, doesn't apply to text in drop-down menus and requires manual post-create rebinds for some widgets. NB: this triggers a later refill of Help/About text to rescale headers. """ trace('on_fontsize_defocus') guifield = self.screen.ids.globalfontsize try: intval = int(guifield.text) except: self.app.info_message('Invalid font size: try again', usetoast=True) guifield.text = '?' else: # self.app.settings.globalfontsize = intval # not to file till Settings Save self.app.set_font_size(intval) # set property to resize text # ---------- # MAXBACKUPS # ---------- def on_maxbackups_defocus(self): trace('on_maxbackups_defocus') guifield = self.screen.ids.maxbackups try: intval = int(guifield.text) except: self.app.info_message('Invalid max backups: try again', usetoast=True) guifield.text = '?' else: # self.app.settings.maxbackups = intval # not to file till Settings Save self.app.storage.maxbackups = intval # set in calendars-storage manager # ----------- # EDITBUBBLES # ----------- def on_editbubbles_touch(self, touch): # could be a toggle, but text for uniformity and descriptiveness trace('on_editbubbles_touch') guifield = self.screen.ids.editbubbles if not guifield.collide_point(*touch.pos): # or touch.x, touch.y return False allbubbles = ['Enabled', 'Disabled'] # future expansion? menu_items = [ # a list of dicts dict(viewclass='OneLineListItem', text=bubblename, on_release= lambda bubblename=bubblename: # else last loop-var value self.on_editbubbles_menu_tap(bubblename), ) for bubblename in allbubbles ] # bind nested MDLabels' font_size+name _after_ creation else kivmd overrides # PUNT/TBD: the OLLIs don't seem to be accessible here? self.editbubbles_menu = MDDropdownMenu( items=menu_items, caller=guifield, # else default 4 may run into android edge-to-edge navbar border_margin=dp(60), ) self.editbubbles_menu.open() return True # consume touch event def on_editbubbles_menu_tap(self, bubblename): """ Set text field to menu pick. Text field need not be verified: it's readonly in .kv to prevent a pointless onscreen keyboard. App.settings settings table is not updated till Save to file. """ trace('on_editbubbles_menu_tap') self.editbubbles_menu.dismiss() self.screen.ids.editbubbles.text = bubblename # settings screen # self.app.settings.editbubbles = bubblename # not to file till Settings Save self.app.editbubbles = bubblename # set in app for use in .kv code # --------------- # CALENDARSFOLDER # --------------- def on_calendarsfolder_touch(self, touch): trace('on_calendarsfolder_touch') guifield = self.screen.ids.calendarsfolder if not guifield.collide_point(*touch.pos): # or touch.x, touch.y return False def on_do_change_calendar(): # when no changes or changes discarded # skip if new path == current path? no: this # allows user to reload after adding ICS files # load_all_ics_files does most of the work here: # - resets calendar and event globals before load # - refills gui's events on load-thread exit # - checks for and informs user if no calendars # - updates folder in settings and Settings screen self.app.storage.run_with_calendars_folder( self.app.storage.load_all_ics_files, # iff selection forcedask=True) # warn user first if current folder's calendars have changes if not any(CalendarsDirty[icsfilename] for icsfilename in CalendarsTable): on_do_change_calendar() else: message = ( '[b]Unsaved calendar changes[/b]' '\n\n' '[u][i]Caution[/i][/u]: ' 'you have asked to change the calendars folder, but ' 'the currently loaded calendars have unsaved changes.' '\n\n' 'To save these changes, tap Cancel and then Save Calendars ' 'in the main menu. ' 'To discard changes and change folders now, tap Confirm.') confirmer = Factory.ConfirmDialog( message=message, onyes=lambda root: [self.app.dismiss_popup(root), # confirm dialog on_do_change_calendar()], # start change steps onno=self.app.dismiss_popup) # confirm dialog popup = Popup( title='Confirm Folder Change', content=confirmer, auto_dismiss=False, # ignore taps outside size_hint=(0.9, 0.8), # phones wide, e-2-e short **self.app.themeForPopup()) # fg/bg colors if 'light' self.app.popupStack.push(popup) self.app.popupStack.open() # show top Popup with Dialog return True # consume touch event def enable_calendardsfolder_hscroll(self, *args): """ UNUSED: an attempt to hscroll calandardsfolder TextField From PPUS: must calc TextField width manually this way for horizontal scroll in a ScrollView; just awful... """ scrollwidth = self.screen.ids.calendarsfolderscroll.width llabelwidth = self.screen.ids.calendarsfolder._lines_labels[0].width # not multiline widthrecalc = max(scrollwidth, llabelwidth + kivy.metrics.dp(25)) # padding self.screen.ids.calendarsfolder.width = widthrecalc # scroll, dammit trace('e_c_h', scrollwidth, llabelwidth, widthrecalc) # PUNT: calendar colors dropped, categories/color unchangeable """ def on_calendarcolors_touched(self, *args): trace('on_calendarcolors_touched') """ # ------------ # SAVE/RESTORE # ------------ def on_settings_save(self, screen): # update settings file from settings-screen widgets allvalues = self.fetch_settings_from_widgets() self.app.settings.update_and_save_settings(**allvalues) # not required: screen fields updated as changed # self.fill_widgets_from_settings() def on_settings_restore(self, screen): # restore settings and widgets from preset defaults self.app.settings.restore_settings() self.fill_widgets_from_settings() # apply Settings' dynamic configs now, else Restore == no-op, # even though the Settings screen has been reset to presets self.app.set_font_size(self.app.settings.globalfontsize) #self.app.set_font_name(self.app.settings.globalfontname) # CUT self.app.storage.maxbackups = self.app.settings.maxbackups self.app.editbubbles = self.app.settings.editbubbles if self.app.settings.colortheme != self.app.theme_cls.theme_style: self.app.on_theme(self.screen, toggle=False) # calendarsfolder is fetched when used, others not user changable