def __init__(self, app): View.__init__(self, app, sub_title='Tweak how files are synchronized') self._grid = Gtk.Grid() self._grid.set_margin_start(30) self._grid.set_margin_end(40) self._grid.set_margin_top(5) self._grid.set_margin_bottom(15) self._grid.set_hexpand(True) self._grid.set_vexpand(True) self._grid.set_halign(Gtk.Align.FILL) self._grid.set_valign(Gtk.Align.FILL) self.add(self._grid) self.save_settings = False self.sections = {} self.metadata = {} self.appy_btn = SuggestedButton() self.deny_btn = DestructiveButton('Reset to defaults') self.appy_btn.connect('clicked', self.on_apply_settings) self.deny_btn.connect('clicked', self.on_reset_to_defaults) self.search_entry.connect('search-changed', self.on_search_changed) # Initialize from current settings: self.build()
def __init__(self, app): View.__init__( self, app, sub_title='Configure how duplicates are searched' ) self._grid = Gtk.Grid() self._grid.set_margin_start(30) self._grid.set_margin_end(40) self._grid.set_margin_top(5) self._grid.set_margin_bottom(15) self._grid.set_hexpand(True) self._grid.set_vexpand(True) self._grid.set_halign(Gtk.Align.FILL) self._grid.set_valign(Gtk.Align.FILL) self.add(self._grid) self.save_settings = False self.sections = {} self.metadata = {} self.appy_btn = SuggestedButton() self.deny_btn = DestructiveButton('Reset to defaults') self.appy_btn.connect('clicked', self.on_apply_settings) self.deny_btn.connect('clicked', self.on_reset_to_defaults) self.search_entry.connect( 'search-changed', self.on_search_changed ) # Initialize from current settings: self.build()
class SettingsView(View): """Generic GSettingsView in a modern Gnome like appearance.""" def __init__(self, app): View.__init__( self, app, sub_title='Configure how duplicates are searched' ) self._grid = Gtk.Grid() self._grid.set_margin_start(30) self._grid.set_margin_end(40) self._grid.set_margin_top(5) self._grid.set_margin_bottom(15) self._grid.set_hexpand(True) self._grid.set_vexpand(True) self._grid.set_halign(Gtk.Align.FILL) self._grid.set_valign(Gtk.Align.FILL) self.add(self._grid) self.save_settings = False self.sections = {} self.metadata = {} self.appy_btn = SuggestedButton() self.deny_btn = DestructiveButton('Reset to defaults') self.appy_btn.connect('clicked', self.on_apply_settings) self.deny_btn.connect('clicked', self.on_reset_to_defaults) self.search_entry.connect( 'search-changed', self.on_search_changed ) # Initialize from current settings: self.build() def append_section(self, heading): """Append a new named section for multiple entries with `heading`""" box = Gtk.ListBox() box.set_selection_mode(Gtk.SelectionMode.NONE) box.set_size_request(350, -1) box.set_hexpand(True) frame = Gtk.Frame() frame.set_halign(Gtk.Align.FILL) frame.add(box) label = Gtk.Label() label.set_margin_top(30) label.set_markup( '<b>{}:</b>'.format(GLib.markup_escape_text(heading, -1)) ) label.set_halign(Gtk.Align.START) label.set_margin_bottom(2) self.sections[heading.lower()] = box self.metadata[heading.lower()] = { 'label': label, 'frame': frame } self._grid.attach(label, 0, len(self._grid), 1, 1) self._grid.attach(frame, 0, len(self._grid), 1, 1) def append_entry(self, section, val_widget, key_name, summary, desc=None): """Append an entry to a named section. section: A previously inserted section name. val_widget: The widget to show the key in. summary: A short summary to show. desc: A longer description. """ desc_label = Gtk.Label(desc or '') summ_label = Gtk.Label(summary or '') desc_label.get_style_context().add_class( Gtk.STYLE_CLASS_DIM_LABEL ) for label in desc_label, summ_label: label.set_use_markup(True) label.set_halign(Gtk.Align.FILL) label.set_hexpand(True) label.set_halign(Gtk.Align.START) val_widget.set_halign(Gtk.Align.END) sub_grid = Gtk.Grid() sub_grid.attach(summ_label, 0, 0, 1, 1) sub_grid.attach(desc_label, 0, 1, 1, 1) sub_grid.attach(val_widget, 1, 0, 1, 2) sub_grid.set_border_width(3) listbox = self.sections[section.lower()] if len(listbox): sep_row = Gtk.ListBoxRow() sep_row.set_activatable(False) sep_row.add(Gtk.Separator()) listbox.insert(sep_row, -1) row = Gtk.ListBoxRow() row.add(sub_grid) row.set_can_focus(False) listbox.insert(row, -1) self.metadata[section.lower()][key_name] = { 'summary': summary.lower() or '', 'description': desc.lower() or '', 'widget': row } def reset_to_defaults(self): """Reset whole view and keys to their defaults""" for key_name in self.app.settings.list_keys(): self.app.settings.reset(key_name) def build(self): """Built all entries and sections""" gst = self.app.settings entry_rows = [] for key_name in gst.list_keys(): key = gst.get_property('settings-schema').get_key(key_name) variant_key = gst.get_value(key_name) # Try to find a way to render this option: constructor = VARIANT_TO_WIDGET.get(variant_key.get_type_string()) if constructor is None: continue # Get the key summary and description: summary = '{}'.format(key.get_summary()) # This is an extension of this code: if summary.startswith('[hidden]'): continue order, order_grep = 0, re.match(r'\[(\d+)]\s(.*)', summary) if order_grep is not None: order, summary = int(order_grep.group(1)), order_grep.group(2) description = key.get_description() if description: description = '<small>{desc}</small>'.format( desc=key.get_description() ) # Get a fitting, readily prepared configure widget val_widget = constructor(gst, key_name, summary, description) # Try to find the section name by the keyname. # This is an extension made by this code and not part of GSettings. section = '' if '-' in key_name: section, _ = key_name.split('-', maxsplit=1) entry_rows.append( (order, section, val_widget, key_name, summary, description) ) for section in sorted(set([entry[1] for entry in entry_rows])): self.append_section(section.capitalize()) for entry in sorted(entry_rows, key=itemgetter(0, 2)): self.append_entry(*entry[1:]) #################### # SIGNAL CALLBACKS # #################### def on_search_changed(self, _): """Called once the user enteres a new search query.""" query = self.search_entry.get_text().lower() def _set_vis(widget, state, lower): """Set opacity and sensitivity in one.""" widget.set_sensitive(state) widget.set_opacity(1.0 if state else lower) for _, metadata in self.metadata.items(): section_visible = 0 for key_name, info in metadata.items(): if key_name in ['label', 'frame']: continue row = info['widget'] if query in info['summary'] or query in info['description']: section_visible += 1 _set_vis(row, True, 0.5) else: _set_vis(row, False, 0.5) section_frame = metadata['frame'] section_label = metadata['label'] _set_vis(section_frame, section_visible > 0, 0.2) _set_vis(section_label, section_visible > 0, 0.2) def on_view_enter(self): """Called once the view is visible. Delay save of settings.""" self.save_settings = False self.app.settings.delay() # Give the buttons a specific context meaning: self.on_key_changed(self.app.settings, None) self.app.settings.connect('changed', self.on_key_changed) self.add_header_widget(self.appy_btn) self.add_header_widget(self.deny_btn, Gtk.Align.START) # It's usually more useful when switching back to the latest view, # not to the view to the right since user might just have wanted # to have a quick settings change from anywhere in the application. self.app_window.views.switch_to_previous_next() def on_view_leave(self): """Called once the view gets out of sight. Revert or apply.""" if self.save_settings: self.app.settings.apply() else: self.app.settings.revert() self.clear_header_widgets() def on_apply_settings(self, *_): """Callback for the apply button.""" self.save_settings = True self.app_window.views.switch_to_previous() def on_reset_to_defaults(self, *_): """Callback for the reset button.""" self.app.settings.revert() GLib.timeout_add( 100, lambda: self.reset_to_defaults() or self.app.settings.delay() ) self.save_settings = False self.app_window.views.switch_to_previous() def on_key_changed(self, settings, _): """Called when a key in GSettings changes.""" is_sensitive = settings.get_has_unapplied() self.appy_btn.set_sensitive(is_sensitive) self.deny_btn.set_sensitive(is_sensitive) def on_default_action(self): """Called on Ctrl-Enter""" if self.appy_btn.is_sensitive(): self.on_apply_settings() else: self.app_window.views.switch_to_previous()
class SettingsView(View): """Generic GSettingsView in a modern Gnome like appearance.""" def __init__(self, app): View.__init__(self, app, sub_title='Tweak how files are synchronized') self._grid = Gtk.Grid() self._grid.set_margin_start(30) self._grid.set_margin_end(40) self._grid.set_margin_top(5) self._grid.set_margin_bottom(15) self._grid.set_hexpand(True) self._grid.set_vexpand(True) self._grid.set_halign(Gtk.Align.FILL) self._grid.set_valign(Gtk.Align.FILL) self.add(self._grid) self.save_settings = False self.sections = {} self.metadata = {} self.appy_btn = SuggestedButton() self.deny_btn = DestructiveButton('Reset to defaults') self.appy_btn.connect('clicked', self.on_apply_settings) self.deny_btn.connect('clicked', self.on_reset_to_defaults) self.search_entry.connect('search-changed', self.on_search_changed) # Initialize from current settings: self.build() def append_section(self, heading): """Append a new named section for multiple entries with `heading`""" box = Gtk.ListBox() box.set_selection_mode(Gtk.SelectionMode.NONE) box.set_size_request(350, -1) box.set_hexpand(True) frame = Gtk.Frame() frame.set_halign(Gtk.Align.FILL) frame.add(box) label = Gtk.Label() label.set_margin_top(30) label.set_markup('<b>{}:</b>'.format( GLib.markup_escape_text(heading, -1))) label.set_halign(Gtk.Align.START) label.set_margin_bottom(2) self.sections[heading.lower()] = box self.metadata[heading.lower()] = {'label': label, 'frame': frame} self._grid.attach(label, 0, len(self._grid), 1, 1) self._grid.attach(frame, 0, len(self._grid), 1, 1) def append_entry(self, section, val_widget, key_name, summary, desc=None): """Append an entry to a named section. section: A previously inserted section name. val_widget: The widget to show the key in. summary: A short summary to show. desc: A longer description. """ desc_label = Gtk.Label(desc or '') summ_label = Gtk.Label(summary or '') desc_label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL) for label in desc_label, summ_label: label.set_use_markup(True) label.set_halign(Gtk.Align.FILL) label.set_hexpand(True) label.set_halign(Gtk.Align.START) val_widget.set_halign(Gtk.Align.END) sub_grid = Gtk.Grid() sub_grid.attach(summ_label, 0, 0, 1, 1) sub_grid.attach(desc_label, 0, 1, 1, 1) sub_grid.attach(val_widget, 1, 0, 1, 2) sub_grid.set_border_width(3) listbox = self.sections[section.lower()] if len(listbox): sep_row = Gtk.ListBoxRow() # Needs gtk >= 3.14 if hasattr(sep_row, 'set_activatable'): sep_row.set_activatable(False) sep_row.add(Gtk.Separator()) listbox.insert(sep_row, -1) row = Gtk.ListBoxRow() row.add(sub_grid) row.set_can_focus(False) listbox.insert(row, -1) self.metadata[section.lower()][key_name] = { 'summary': summary.lower() or '', 'description': desc.lower() or '', 'widget': row } def reset_to_defaults(self): """Reset whole view and keys to their defaults""" for key_name in self.app.settings.list_keys(): self.app.settings.reset(key_name) def build(self): """Built all entries and sections""" gst = self.app.settings entry_rows = [] for key_name in gst.list_keys(): key = gst.get_property('settings-schema').get_key(key_name) variant_key = gst.get_value(key_name) # Try to find a way to render this option: constructor = VARIANT_TO_WIDGET.get(variant_key.get_type_string()) if constructor is None: continue # Get the key summary and description: summary = '{}'.format(key.get_summary()) # This is an extension of this code: if summary.startswith('[hidden]'): continue order, order_grep = 0, re.match(r'\[(\d+)]\s(.*)', summary) if order_grep is not None: order, summary = int(order_grep.group(1)), order_grep.group(2) description = key.get_description() if description: description = '<small>{desc}</small>'.format( desc=key.get_description()) # Get a fitting, readily prepared configure widget val_widget = constructor(gst, key_name, summary, description) # Try to find the section name by the keyname. # This is an extension made by this code and not part of GSettings. section = '' if '-' in key_name: section, _ = key_name.split('-', maxsplit=1) entry_rows.append( (order, section, val_widget, key_name, summary, description)) for section in sorted(set([entry[1] for entry in entry_rows])): self.append_section(section.capitalize()) for entry in sorted(entry_rows, key=itemgetter(0, 2)): self.append_entry(*entry[1:]) #################### # SIGNAL CALLBACKS # #################### def on_search_changed(self, _): """Called once the user enteres a new search query.""" query = self.search_entry.get_text().lower() def _set_vis(widget, state, lower): """Set opacity and sensitivity in one.""" widget.set_sensitive(state) widget.set_opacity(1.0 if state else lower) for _, metadata in self.metadata.items(): section_visible = 0 for key_name, info in metadata.items(): if key_name in ['label', 'frame']: continue row = info['widget'] if query in info['summary'] or query in info['description']: section_visible += 1 _set_vis(row, True, 0.5) else: _set_vis(row, False, 0.5) section_frame = metadata['frame'] section_label = metadata['label'] _set_vis(section_frame, section_visible > 0, 0.2) _set_vis(section_label, section_visible > 0, 0.2) def on_view_enter(self): """Called once the view is visible. Delay save of settings.""" self.save_settings = False self.app.settings.delay() # Give the buttons a specific context meaning: self.on_key_changed(self.app.settings, None) self.app.settings.connect('changed', self.on_key_changed) self.add_header_widget(self.appy_btn) self.add_header_widget(self.deny_btn, Gtk.Align.START) # It's usually more useful when switching back to the latest view, # not to the view to the right since user might just have wanted # to have a quick settings change from anywhere in the application. self.app_window.views.switch_to_previous_next() def on_view_leave(self): """Called once the view gets out of sight. Revert or apply.""" if self.save_settings: self.app.settings.apply() else: self.app.settings.revert() self.clear_header_widgets() def on_apply_settings(self, *_): """Callback for the apply button.""" self.save_settings = True self.app_window.views.switch_to_previous() def on_reset_to_defaults(self, *_): """Callback for the reset button.""" self.app.settings.revert() GLib.timeout_add( 100, lambda: self.reset_to_defaults() or self.app.settings.delay()) self.save_settings = False self.app_window.views.switch_to_previous() def on_key_changed(self, settings, _): """Called when a key in GSettings changes.""" is_sensitive = settings.get_has_unapplied() self.appy_btn.set_sensitive(is_sensitive) self.deny_btn.set_sensitive(is_sensitive) def on_default_action(self): """Called on Ctrl-Enter""" if self.appy_btn.is_sensitive(): self.on_apply_settings() else: self.app_window.views.switch_to_previous()