示例#1
0
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()
示例#2
0
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()
示例#3
0
class ScriptSaverDialog(Gtk.FileChooserWidget):
    """GtkFileChooserWidget tailored for saving a `Script` instance."""

    __gsignals__ = {
        'saved': (GObject.SIGNAL_RUN_FIRST, None, ()),
    }

    def __init__(self, editor_view):
        Gtk.FileChooserWidget.__init__(self)

        self.editor_view = editor_view
        self.set_select_multiple(False)
        self.set_create_folders(False)
        self.set_action(Gtk.FileChooserAction.SAVE)
        self.set_do_overwrite_confirmation(True)

        self.file_type = MultipleChoiceButton(['sh', 'json', 'csv'], 'sh',
                                              'sh')
        self.file_type.set_halign(Gtk.Align.START)
        self.file_type.set_hexpand(True)
        self.file_type.connect('row-selected', self.on_file_type_changed)
        self.file_type.props.margin_end = 10

        self.confirm = SuggestedButton('Save')
        self.confirm.connect('clicked', self.on_save_clicked)
        self.confirm.set_halign(Gtk.Align.END)
        self.confirm.set_hexpand(False)
        self.confirm.set_sensitive(False)
        self.confirm.props.margin_end = 10

        self.cancel_button = IconButton('window-close-symbolic', 'Cancel')
        self.cancel_button.connect('clicked', self.on_cancel_clicked)
        self.cancel_button.set_halign(Gtk.Align.END)
        self.cancel_button.set_hexpand(False)

        self.connect('selection-changed', self.on_selection_changed)

        file_type_label = Gtk.Label('<b>Filetype</b>')
        file_type_label.set_use_markup(True)
        file_type_label.props.margin_end = 5
        file_type_label.get_style_context().add_class(
            Gtk.STYLE_CLASS_DIM_LABEL)

        self.extra_box = Gtk.Grid()
        self.extra_box.attach(self.file_type, 0, 0, 1, 1)
        self.extra_box.attach(self.confirm, 1, 0, 1, 1)
        self.extra_box.attach_next_to(file_type_label, self.file_type,
                                      Gtk.PositionType.LEFT, 1, 1)

        self.extra_box.set_hexpand(True)
        self.extra_box.set_halign(Gtk.Align.FILL)

    def show_controls(self):
        """Show cancel, save and file type chooser buttons."""
        self.editor_view.add_header_widget(self.extra_box)
        self.editor_view.add_header_widget(self.cancel_button,
                                           align=Gtk.Align.START)

        self.update_file_suggestion()

    def update_file_suggestion(self):
        """Suggest a name for the script to save."""
        file_type = self.file_type.get_selected_choice() or 'sh'
        self.set_current_name(time.strftime('rmlint-%FT%T%z.' + file_type))

    def on_file_type_changed(self, _):
        """Called once the user chose a different format"""
        current_path = self.get_filename()
        if not current_path:
            self.update_file_suggestion()
        else:
            try:
                path, _ = current_path.rsplit('.', 1)
                self.set_current_name(
                    path + '.' + self.file_type.get_selected_choice() or '')
            except ValueError:
                # No extension. Leave it.
                pass

    def _exit_from_save(self):
        """Preparation to go back to script view."""
        self.emit('saved')
        self.editor_view.clear_header_widgets()

    def on_cancel_clicked(self, _):
        """Signal handler for the cancel button."""
        self._exit_from_save()

    def on_save_clicked(self, _):
        """Called once the user clicked the `Save` button"""
        file_type = self.file_type.get_selected_choice()
        abs_path = self.get_filename()

        runner = self.editor_view.app_window.views['runner'].runner
        LOGGER.info('Saving script as `%s` to: %s', file_type, abs_path)
        runner.save(abs_path, file_type)
        self._exit_from_save()

    def on_selection_changed(self, _):
        """Called once a file or directory was clicked"""
        filename = self.get_filename()
        self.confirm.set_sensitive(bool(filename))

        # Make sure the user-typed extension gets set in teh type chooser also.
        name = self.get_current_name()
        *_, extension = name.rsplit('.', 1)
        self.file_type.set_selected_choice(extension)
示例#4
0
文件: editor.py 项目: FihlaTV/rmlint
class ScriptSaverDialog(Gtk.FileChooserWidget):
    """GtkFileChooserWidget tailored for saving a `Script` instance."""

    __gsignals__ = {
        'saved': (GObject.SIGNAL_RUN_FIRST, None, ()),
    }

    def __init__(self, editor_view):
        Gtk.FileChooserWidget.__init__(self)

        self.editor_view = editor_view
        self.set_select_multiple(False)
        self.set_create_folders(False)
        self.set_action(Gtk.FileChooserAction.SAVE)
        self.set_do_overwrite_confirmation(True)

        self.file_type = MultipleChoiceButton(
            ['sh', 'json', 'csv'], 'sh', 'sh'
        )
        self.file_type.set_halign(Gtk.Align.START)
        self.file_type.set_hexpand(True)
        self.file_type.connect('row-selected', self.on_file_type_changed)
        self.file_type.props.margin_end = 10

        self.confirm = SuggestedButton('Save')
        self.confirm.connect('clicked', self.on_save_clicked)
        self.confirm.set_halign(Gtk.Align.END)
        self.confirm.set_hexpand(False)
        self.confirm.set_sensitive(False)
        self.confirm.props.margin_end = 10

        self.cancel_button = IconButton('window-close-symbolic', 'Cancel')
        self.cancel_button.connect('clicked', self.on_cancel_clicked)
        self.cancel_button.set_halign(Gtk.Align.END)
        self.cancel_button.set_hexpand(False)

        self.connect('selection-changed', self.on_selection_changed)

        file_type_label = Gtk.Label('<b>Filetype</b>')
        file_type_label.set_use_markup(True)
        file_type_label.props.margin_end = 5
        file_type_label.get_style_context().add_class(
            Gtk.STYLE_CLASS_DIM_LABEL
        )

        self.extra_box = Gtk.Grid()
        self.extra_box.attach(self.file_type, 0, 0, 1, 1)
        self.extra_box.attach(self.confirm, 1, 0, 1, 1)
        self.extra_box.attach_next_to(
            file_type_label,
            self.file_type,
            Gtk.PositionType.LEFT,
            1,
            1
        )

        self.extra_box.set_hexpand(True)
        self.extra_box.set_halign(Gtk.Align.FILL)

    def show_controls(self):
        """Show cancel, save and file type chooser buttons."""
        self.editor_view.add_header_widget(self.extra_box)
        self.editor_view.add_header_widget(
            self.cancel_button, align=Gtk.Align.START
        )

        self.update_file_suggestion()

    def update_file_suggestion(self):
        """Suggest a name for the script to save."""
        file_type = self.file_type.get_selected_choice() or 'sh'
        self.set_current_name(time.strftime('rmlint-%FT%T%z.' + file_type))

    def on_file_type_changed(self, _):
        """Called once the user chose a different format"""
        current_path = self.get_filename()
        if not current_path:
            self.update_file_suggestion()
        else:
            try:
                path, _ = current_path.rsplit('.', 1)
                self.set_current_name(
                    path + '.' + self.file_type.get_selected_choice() or ''
                )
            except ValueError:
                # No extension. Leave it.
                pass

    def _exit_from_save(self):
        """Preparation to go back to script view."""
        self.emit('saved')
        self.editor_view.clear_header_widgets()

    def on_cancel_clicked(self, _):
        """Signal handler for the cancel button."""
        self._exit_from_save()

    def on_save_clicked(self, _):
        """Called once the user clicked the `Save` button"""
        file_type = self.file_type.get_selected_choice()
        abs_path = self.get_filename()

        runner = self.editor_view.app_window.views['runner'].runner
        LOGGER.info('Saving script as `%s` to: %s', file_type, abs_path)
        runner.save(abs_path, file_type)
        self._exit_from_save()

    def on_selection_changed(self, _):
        """Called once a file or directory was clicked"""
        filename = self.get_filename()
        self.confirm.set_sensitive(bool(filename))

        # Make sure the user-typed extension gets set in teh type chooser also.
        name = self.get_current_name()
        *_, extension = name.rsplit('.', 1)
        self.file_type.set_selected_choice(extension)