Example #1
0
    def __init__(self):
        Gtk.Overlay.__init__(self)

        self._box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        self._box.get_style_context().add_class(Gtk.STYLE_CLASS_LINKED)

        self._lock_button = Gtk.LockButton()
        self._lock_button.props.margin = 20
        self._lock_button.props.margin_end = 0
        self._lock_button.connect('clicked',
                                  lambda _: self.emit('unlock-clicked'))

        try:
            perm = Polkit.Permission.new_sync(
                'org.freedesktop.accounts.user-administration',
                Polkit.UnixProcess.new_for_owner(os.getpid(), 0, -1), None)
            self._lock_button.set_permission(perm)
        except GLib.Error as err:
            LOGGER.warning('Unable to get polkit permissions: ' + str(err))

        self._save_button = IconButton('folder-download-symbolic',
                                       'Save to file')
        self._save_button.props.margin = 20
        self._save_button.props.margin_start = 0
        self._save_button.connect('clicked',
                                  lambda _: self.emit('save-clicked'))

        self._box.pack_start(self._lock_button, False, True, 0)
        self._box.pack_start(self._save_button, False, True, 0)
        self._box.set_hexpand(False)
        self._box.set_vexpand(False)
        self._box.set_halign(Gtk.Align.END)
        self._box.set_valign(Gtk.Align.END)
        self.add_overlay(self._box)
Example #2
0
def _create_finished_screen(callback):
    """Give the user a nice, warm feeling."""
    control_grid = Gtk.Grid()
    control_grid.set_hexpand(False)
    control_grid.set_vexpand(False)
    control_grid.set_halign(Gtk.Align.CENTER)
    control_grid.set_valign(Gtk.Align.CENTER)

    # Lies make the user feel comfortable:
    label = Gtk.Label(
        use_markup=True, label='''<span font="65">✔</span>

<big><b>All went well!</b></big>
''',
        justify=Gtk.Justification.CENTER
    )
    label.get_style_context().add_class('dim-label')

    go_back = IconButton('go-jump-symbolic', 'Go back to Script')
    go_back.set_halign(Gtk.Align.CENTER)
    go_back.connect(
        'clicked', lambda _: callback()
    )

    control_grid.attach(label, 0, 0, 1, 1)
    control_grid.attach_next_to(
        go_back, label, Gtk.PositionType.BOTTOM, 1, 1
    )

    return control_grid
Example #3
0
class OverlaySaveButton(Gtk.Overlay):
    """Button box that contains two buttons in an overlay.
    The overlay is shown on top of the script editor.
    Buttons are: A unlock button for asking for root permissions
    and a save button to save the script somewhere.
    """

    __gsignals__ = {
        'save-clicked': (GObject.SIGNAL_RUN_FIRST, None, ()),
        'unlock-clicked': (GObject.SIGNAL_RUN_FIRST, None, ())
    }

    def __init__(self):
        Gtk.Overlay.__init__(self)

        self._box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        self._box.get_style_context().add_class(
            Gtk.STYLE_CLASS_LINKED
        )

        self._lock_button = Gtk.LockButton()
        self._lock_button.props.margin = 20
        self._lock_button.props.margin_end = 0
        self._lock_button.connect(
            'clicked', lambda _: self.emit('unlock-clicked')
        )

        if Polkit is not None:
            try:
                perm = Polkit.Permission.new_sync(
                    'org.freedesktop.accounts.user-administration',
                    Polkit.UnixProcess.new_for_owner(os.getpid(), 0, -1),
                    None
                )
                self._lock_button.set_permission(perm)
            except GLib.Error as err:
                LOGGER.warning('Unable to get polkit permissions: ' + str(err))


        self._save_button = IconButton(
            'folder-download-symbolic', 'Save to file'
        )
        self._save_button.props.margin = 20
        self._save_button.props.margin_start = 0
        self._save_button.connect(
            'clicked', lambda _: self.emit('save-clicked')
        )

        # Note: we're not showing the lock button yet,
        #       since we did not yet a policy for it.
        #
        # self._box.pack_start(self._lock_button, False, True, 0)
        self._box.pack_start(self._save_button, False, True, 0)
        self._box.set_hexpand(False)
        self._box.set_vexpand(False)
        self._box.set_halign(Gtk.Align.END)
        self._box.set_valign(Gtk.Align.END)
        self.add_overlay(self._box)
Example #4
0
    def set_sensitive(self, mode):
        btn_ctx = self.get_style_context()

        if mode:
            btn_ctx.add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
        else:
            btn_ctx.remove_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)

        IconButton.set_sensitive(self, mode)
        self.state.set_sensitive(mode)
Example #5
0
class RunButton(Gtk.Box):
    """Customized run button that can change color."""
    dry_run = GObject.Property(type=bool, default=True)

    def __init__(self, icon, label):
        Gtk.Box.__init__(self)
        self.get_style_context().add_class(
            Gtk.STYLE_CLASS_LINKED
        )

        self.button = IconButton(icon, label)
        self.state = Gtk.ToggleButton()
        self.state.add(
            Gtk.Label(use_markup=True, label='<small>Dry run?</small>')
        )

        self.state.connect('toggled', self._toggle_dry_run)

        self.pack_start(self.button, True, True, 0)
        self.pack_start(self.state, False, False, 0)
        self.bind_property(
            'dry_run', self.state, 'active',
            GObject.BindingFlags.BIDIRECTIONAL |
            GObject.BindingFlags.SYNC_CREATE
        )

        self.state.set_active(True)
        self._toggle_dry_run(self.state)

    def set_sensitive(self, mode):
        btn_ctx = self.button.get_style_context()
        dry_ctx = self.state.get_style_context()

        if mode:
            btn_ctx.add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
            dry_ctx.add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
        else:
            btn_ctx.remove_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
            dry_ctx.remove_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)

        self.button.set_sensitive(mode)
        self.state.set_sensitive(mode)

    def _toggle_dry_run(self, btn):
        """Change the color and severeness of the button."""
        for widget in [self.button, self.state]:
            ctx = widget.get_style_context()
            if not btn.get_active():
                ctx.remove_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
                ctx.add_class(Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION)
            else:
                ctx.remove_class(Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION)
                ctx.add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
Example #6
0
    def __init__(self, icon, label, state_btn):
        IconButton.__init__(self, icon, label)
        self.state = state_btn
        self.state.connect('notify::active', self._toggle_dry_run)

        self.bind_property(
            'dry_run', self.state, 'active',
            GObject.BindingFlags.BIDIRECTIONAL |
            GObject.BindingFlags.SYNC_CREATE
        )

        self.state.set_active(True)
        self._toggle_dry_run(self.state)
Example #7
0
    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)
Example #8
0
class RunButton(Gtk.Box):
    """Customized run button that can change color."""
    dry_run = GObject.Property(type=bool, default=True)

    def __init__(self, icon, label):
        Gtk.Box.__init__(self)
        self.get_style_context().add_class(Gtk.STYLE_CLASS_LINKED)

        self.button = IconButton(icon, label)
        self.state = Gtk.ToggleButton()
        self.state.add(
            Gtk.Label(use_markup=True, label='<small>Dry run?</small>'))

        self.state.connect('toggled', self._toggle_dry_run)

        self.pack_start(self.button, True, True, 0)
        self.pack_start(self.state, False, False, 0)
        self.bind_property(
            'dry_run', self.state, 'active', GObject.BindingFlags.BIDIRECTIONAL
            | GObject.BindingFlags.SYNC_CREATE)

        self.state.set_active(True)
        self._toggle_dry_run(self.state)

    def set_sensitive(self, mode):
        btn_ctx = self.button.get_style_context()
        dry_ctx = self.state.get_style_context()

        if mode:
            btn_ctx.add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
            dry_ctx.add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
        else:
            btn_ctx.remove_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
            dry_ctx.remove_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)

        self.button.set_sensitive(mode)
        self.state.set_sensitive(mode)

    def _toggle_dry_run(self, btn):
        """Change the color and severeness of the button."""
        for widget in [self.button, self.state]:
            ctx = widget.get_style_context()
            if not btn.get_active():
                ctx.remove_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
                ctx.add_class(Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION)
            else:
                ctx.remove_class(Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION)
                ctx.add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
Example #9
0
    def __init__(self, icon, label):
        Gtk.Box.__init__(self)
        self.get_style_context().add_class(Gtk.STYLE_CLASS_LINKED)

        self.button = IconButton(icon, label)
        self.state = Gtk.ToggleButton()
        self.state.add(
            Gtk.Label(use_markup=True, label='<small>Dry run?</small>'))

        self.state.connect('toggled', self._toggle_dry_run)

        self.pack_start(self.button, True, True, 0)
        self.pack_start(self.state, False, False, 0)
        self.bind_property(
            'dry_run', self.state, 'active', GObject.BindingFlags.BIDIRECTIONAL
            | GObject.BindingFlags.SYNC_CREATE)

        self.state.set_active(True)
        self._toggle_dry_run(self.state)
Example #10
0
    def __init__(self, view):
        Gtk.ActionBar.__init__(self)

        left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        left_box.get_style_context().add_class("linked")
        self.pack_start(left_box)

        self.refresh_button = IconButton('view-refresh-symbolic')
        self.settings_button = IconButton('system-run-symbolic')

        self.refresh_button.connect(
            'clicked', lambda _: view.app_window.views['runner'].rerun()
        )
        self.settings_button.connect(
            'clicked', lambda _: view.app_window.views.switch('settings')
        )

        left_box.pack_start(self.refresh_button, False, False, 0)
        left_box.pack_start(self.settings_button, False, False, 0)

        right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        right_box.get_style_context().add_class(
            Gtk.STYLE_CLASS_LINKED
        )

        self.script_btn = IconButton(
            'emblem-documents-symbolic', 'Render script from'
        )
        self.script_btn.connect(
            'clicked', self.on_generate_script
        )

        self.script_type_btn = MultipleChoiceButton(
            ['All', 'Filtered', 'Selected'], 'All', 'All'
        )
        self.script_type_btn.set_relief(Gtk.ReliefStyle.NORMAL)

        right_box.pack_start(self.script_btn, True, True, 0)
        right_box.pack_start(self.script_type_btn, True, False, 0)

        self.pack_end(right_box)
        self.set_sensitive(False)
Example #11
0
def _create_finished_screen(callback):
    """Give the user a nice, warm feeling."""
    control_grid = Gtk.Grid()
    control_grid.set_hexpand(False)
    control_grid.set_vexpand(False)
    control_grid.set_halign(Gtk.Align.CENTER)
    control_grid.set_valign(Gtk.Align.CENTER)

    # Lies make the user feel comfortable:
    label = Gtk.Label(use_markup=True,
                      label='''<span font="65">✔</span>


<big><b>All went well!</b></big>




''',
                      justify=Gtk.Justification.CENTER)
    label.get_style_context().add_class('dim-label')

    go_back = IconButton('go-jump-symbolic', 'Go back to Script')
    go_back.set_halign(Gtk.Align.CENTER)
    go_back.connect('clicked', lambda _: callback())

    control_grid.attach(label, 0, 0, 1, 1)
    control_grid.attach_next_to(go_back, label, Gtk.PositionType.BOTTOM, 1, 1)

    return control_grid
Example #12
0
    def __init__(self, view):
        Gtk.ActionBar.__init__(self)

        left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        left_box.get_style_context().add_class("linked")
        self.pack_start(left_box)

        self.refresh_button = IconButton('view-refresh-symbolic')
        self.settings_button = IconButton('system-run-symbolic')

        self.refresh_button.connect(
            'clicked', lambda _: view.app_window.views['runner'].rerun())
        self.settings_button.connect(
            'clicked', lambda _: view.app_window.views.switch('settings'))

        left_box.pack_start(self.refresh_button, False, False, 0)
        left_box.pack_start(self.settings_button, False, False, 0)

        right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        right_box.get_style_context().add_class(Gtk.STYLE_CLASS_LINKED)

        self.script_btn = IconButton('emblem-documents-symbolic',
                                     'Render script from')
        self.script_btn.connect('clicked', self.on_generate_script)

        self.script_type_btn = MultipleChoiceButton(RENDER_CHOICES, 'All',
                                                    'All')
        self.script_type_btn.set_relief(Gtk.ReliefStyle.NORMAL)

        right_box.pack_start(self.script_btn, True, True, 0)
        right_box.pack_start(self.script_type_btn, True, False, 0)

        self.pack_end(right_box)
        self.set_sensitive(False)
Example #13
0
    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)
Example #14
0
    def __init__(self):
        Gtk.Overlay.__init__(self)

        self._box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        self._box.get_style_context().add_class(
            Gtk.STYLE_CLASS_LINKED
        )

        self._lock_button = Gtk.LockButton()
        self._lock_button.props.margin = 20
        self._lock_button.props.margin_end = 0
        self._lock_button.connect(
            'clicked', lambda _: self.emit('unlock-clicked')
        )

        if Polkit is not None:
            try:
                perm = Polkit.Permission.new_sync(
                    'org.freedesktop.accounts.user-administration',
                    Polkit.UnixProcess.new_for_owner(os.getpid(), 0, -1),
                    None
                )
                self._lock_button.set_permission(perm)
            except GLib.Error as err:
                LOGGER.warning('Unable to get polkit permissions: ' + str(err))


        self._save_button = IconButton(
            'folder-download-symbolic', 'Save to file'
        )
        self._save_button.props.margin = 20
        self._save_button.props.margin_start = 0
        self._save_button.connect(
            'clicked', lambda _: self.emit('save-clicked')
        )

        # Note: we're not showing the lock button yet,
        #       since we did not yet a policy for it.
        #
        # self._box.pack_start(self._lock_button, False, True, 0)
        self._box.pack_start(self._save_button, False, True, 0)
        self._box.set_hexpand(False)
        self._box.set_vexpand(False)
        self._box.set_halign(Gtk.Align.END)
        self._box.set_valign(Gtk.Align.END)
        self.add_overlay(self._box)
Example #15
0
    def __init__(self, icon, label):
        Gtk.Box.__init__(self)
        self.get_style_context().add_class(
            Gtk.STYLE_CLASS_LINKED
        )

        self.button = IconButton(icon, label)
        self.state = Gtk.ToggleButton()
        self.state.add(
            Gtk.Label(use_markup=True, label='<small>Dry run?</small>')
        )

        self.state.connect('toggled', self._toggle_dry_run)

        self.pack_start(self.button, True, True, 0)
        self.pack_start(self.state, False, False, 0)
        self.bind_property(
            'dry_run', self.state, 'active',
            GObject.BindingFlags.BIDIRECTIONAL |
            GObject.BindingFlags.SYNC_CREATE
        )

        self.state.set_active(True)
        self._toggle_dry_run(self.state)
Example #16
0
class ResultActionBar(Gtk.ActionBar):
    """Down right bar with the controls"""
    __gsignals__ = {
        'generate-all-script': (GObject.SIGNAL_RUN_FIRST, None, ()),
        'generate-filtered-script': (GObject.SIGNAL_RUN_FIRST, None, ()),
        'generate-selection-script': (GObject.SIGNAL_RUN_FIRST, None, ())
    }

    def __init__(self, view):
        Gtk.ActionBar.__init__(self)

        left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        left_box.get_style_context().add_class("linked")
        self.pack_start(left_box)

        self.refresh_button = IconButton('view-refresh-symbolic')
        self.settings_button = IconButton('system-run-symbolic')

        self.refresh_button.connect(
            'clicked', lambda _: view.app_window.views['runner'].rerun())
        self.settings_button.connect(
            'clicked', lambda _: view.app_window.views.switch('settings'))

        left_box.pack_start(self.refresh_button, False, False, 0)
        left_box.pack_start(self.settings_button, False, False, 0)

        right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        right_box.get_style_context().add_class(Gtk.STYLE_CLASS_LINKED)

        self.script_btn = IconButton('emblem-documents-symbolic',
                                     'Render script from')
        self.script_btn.connect('clicked', self.on_generate_script)

        self.script_type_btn = MultipleChoiceButton(RENDER_CHOICES, 'All',
                                                    'All')
        self.script_type_btn.set_relief(Gtk.ReliefStyle.NORMAL)

        right_box.pack_start(self.script_btn, True, True, 0)
        right_box.pack_start(self.script_type_btn, True, False, 0)

        self.pack_end(right_box)
        self.set_sensitive(False)

    def set_choice(self, choice):
        """Set the current choice. Might be one of RENDER_CHOICES"""
        if choice not in RENDER_CHOICES:
            raise ValueError("Bad choice for button: " + choice)

        self.script_type_btn.set_selected_choice(choice)

    def on_generate_script(self, _):
        """Called when the left side of the compound button was clicked."""
        choice = self.script_type_btn.get_selected_choice().lower()

        if choice == 'all':
            self.emit('generate-all-script')
        elif choice == 'filtered':
            self.emit('generate-filtered-script')
        elif choice == 'selected':
            self.emit('generate-selection-script')
        else:
            LOGGER.error('Bug: bad choice selection: %s', choice)

    def set_sensitive(self, mode):
        """Set the gen-script button (non)-sensitive and (non)-suggested"""
        btn_ctx = self.script_btn.get_style_context()
        type_ctx = self.script_type_btn.get_style_context()

        if mode:
            btn_ctx.add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
            type_ctx.add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
        else:
            btn_ctx.remove_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
            type_ctx.remove_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)

        self.script_btn.set_sensitive(mode)
        self.script_type_btn.set_sensitive(mode)

    def is_sensitive(self):
        return self.script_btn.is_sensitive()
Example #17
0
 def on_view_enter(self):
     """Called when the view gets visible."""
     create_button = IconButton('emblem-ok-symbolic', 'Create repository')
     create_button.get_style_context().add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
     self.add_header_widget(create_button)
Example #18
0
    def __init__(self, app):
        View.__init__(self, app)
        self.selected_locations = []
        self.known_paths = set()
        self._set_title()

        self.box = Gtk.ListBox()
        self.box.set_selection_mode(Gtk.SelectionMode.NONE)
        self.box.set_hexpand(True)
        self.box.set_placeholder(Gtk.Label('No locations mounted.'))
        self.box.set_valign(Gtk.Align.FILL)
        self.box.set_vexpand(True)

        self.chooser_button = IconButton('list-add-symbolic', 'Add Location')
        self.chooser_button.connect('clicked', self.on_chooser_button_clicked)

        self.file_chooser = Gtk.FileChooserWidget()
        self.file_chooser.set_select_multiple(True)
        self.file_chooser.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
        self.file_chooser.set_create_folders(False)

        self.stack = Gtk.Stack()
        self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP)

        scw = Gtk.ScrolledWindow()
        scw.add(self.box)

        self.stack.add_named(scw, 'list')
        self.stack.add_named(self.file_chooser, 'chooser')

        self.box.set_activate_on_single_click(True)
        self.box.set_filter_func(self._filter_func)
        self.box.connect('row-activated', self.on_row_clicked)

        self.search_entry.connect('search-changed', self.on_search_changed)

        self.volume_monitor = Gio.VolumeMonitor.get()
        self.recent_mgr = Gtk.RecentManager.get_default()
        self.recent_mgr.connect('changed', self.refill_entries)
        self.volume_monitor.connect('volume-changed', self.refill_entries)
        self.volume_monitor.connect('drive-changed', self.refill_entries)
        self.volume_monitor.connect('mount-changed', self.refill_entries)
        self.refill_entries()

        run_button = IconButton('edit-find-symbolic', 'Scan folders')
        run_button.connect('clicked', self._run_clicked)
        run_button.get_style_context().add_class(
            Gtk.STYLE_CLASS_SUGGESTED_ACTION)

        del_button = IconButton('user-trash-symbolic', 'Remove from list')
        del_button.connect('clicked', self._del_clicked)

        self.selected_label = Gtk.Label()
        self.selected_label.get_style_context().add_class(
            Gtk.STYLE_CLASS_DIM_LABEL)

        action_bar = Gtk.ActionBar()
        action_bar.pack_start(del_button)
        action_bar.set_center_widget(self.selected_label)
        action_bar.pack_end(run_button)

        self.revealer = Gtk.Revealer()
        self.revealer.add(action_bar)
        self.revealer.set_hexpand(True)
        self.revealer.set_halign(Gtk.Align.FILL)

        grid = Gtk.Grid()
        grid.attach(self.stack, 0, 0, 1, 1)
        grid.attach(self.revealer, 0, 1, 1, 1)

        self.add(grid)
Example #19
0
    def on_chooser_button_clicked(self, _):
        """Button click on the location chooser."""
        self.stack.set_visible_child_name('chooser')
        self.app_window.remove_header_widget(self.chooser_button)
        self.app_window.views.go_right.set_sensitive(False)
        self.app_window.views.go_left.set_sensitive(False)
        self.revealer.set_reveal_child(False)
        self.sub_title = 'Choose a new location'

        open_button = IconButton('emblem-ok-symbolic', 'Add selected')
        open_button.get_style_context().add_class(
            Gtk.STYLE_CLASS_SUGGESTED_ACTION)

        close_button = IconButton('window-close-symbolic', 'Cancel')

        self.add_header_widget(open_button)
        self.add_header_widget(close_button, align=Gtk.Align.START)

        def _go_back():
            """Switch back to the LocationEntry list."""
            self.app_window.remove_header_widget(open_button)
            self.app_window.remove_header_widget(close_button)
            self.add_header_widget(self.chooser_button)
            self.stack.set_visible_child_name('list')
            self.app_window.views.go_right.set_sensitive(True)
            self.app_window.views.go_left.set_sensitive(True)
            self.revealer.set_reveal_child(True)
            self.selected_locations = []
            self._update_selected_label()
            self._set_title()

        def _open_clicked(_):
            """The open file button was clicked. Add paths."""
            for path in self.file_chooser.get_filenames():
                name = os.path.basename(path)
                # self.recent_mgr.add_item(path)
                self.add_recent_item(path)
                entry = self.add_entry(name,
                                       path,
                                       Gio.ThemedIcon(name='folder-new'),
                                       idx=0)
                self.box.select_row(entry)
            self.box.show_all()

            _go_back()

        def _close_clicked(_):
            """Abort choosing."""
            _go_back()

        def _selection_changed(_):
            """Make the open button sensitive when something is selected."""
            is_sensitive = bool(self.file_chooser.get_filenames())
            open_button.set_sensitive(is_sensitive)

        open_button.connect('clicked', _open_clicked)
        close_button.connect('clicked', _close_clicked)
        self.file_chooser.connect('selection-changed', _selection_changed)
        open_button.show_all()
        close_button.show_all()
Example #20
0
class LocationView(View):
    """The actual view instance."""
    def __init__(self, app):
        View.__init__(self, app)
        self.selected_locations = []
        self.known_paths = set()
        self._set_title()

        self.box = Gtk.ListBox()
        self.box.set_selection_mode(Gtk.SelectionMode.NONE)
        self.box.set_hexpand(True)
        self.box.set_placeholder(Gtk.Label('No locations mounted.'))
        self.box.set_valign(Gtk.Align.FILL)
        self.box.set_vexpand(True)

        self.chooser_button = IconButton(
            'list-add-symbolic', 'Add Location'
        )
        self.chooser_button.connect(
            'clicked', self.on_chooser_button_clicked
        )

        self.file_chooser = Gtk.FileChooserWidget()
        self.file_chooser.set_select_multiple(True)
        self.file_chooser.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
        self.file_chooser.set_create_folders(False)

        self.stack = Gtk.Stack()
        self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP)

        scw = Gtk.ScrolledWindow()
        scw.add(self.box)

        self.stack.add_named(scw, 'list')
        self.stack.add_named(self.file_chooser, 'chooser')

        self.box.set_activate_on_single_click(True)
        self.box.set_filter_func(self._filter_func)
        self.box.connect('row-activated', self.on_row_clicked)

        self.search_entry.connect(
            'search-changed', self.on_search_changed
        )

        entries = load_saved_entries()
        if entries:
            self.load_entries_from_disk(entries)
        else:
            self.load_entries_initially()

        run_button = IconButton('edit-find-symbolic', 'Scan folders')
        run_button.connect('clicked', self._run_clicked)
        run_button.get_style_context().add_class(
            Gtk.STYLE_CLASS_SUGGESTED_ACTION
        )

        del_button = IconButton('user-trash-symbolic', 'Remove from list')
        del_button.connect('clicked', self._del_clicked)

        self.selected_label = Gtk.Label()
        self.selected_label.get_style_context().add_class(
            Gtk.STYLE_CLASS_DIM_LABEL
        )

        action_bar = Gtk.ActionBar()
        action_bar.pack_start(del_button)
        action_bar.set_center_widget(self.selected_label)
        action_bar.pack_end(run_button)

        self.revealer = Gtk.Revealer()
        self.revealer.add(action_bar)
        self.revealer.set_hexpand(True)
        self.revealer.set_halign(Gtk.Align.FILL)

        grid = Gtk.Grid()
        grid.attach(self.stack, 0, 0, 1, 1)
        grid.attach(self.revealer, 0, 1, 1, 1)

        self.add(grid)

    def _set_title(self):
        """Make it an own method, so we don't need to retype it."""
        self.sub_title = 'Click locations to scan'

    def add_recent_item(self, path):
        """Add item to GtkRecentManager"""
        data = Gtk.RecentData()
        data.is_private = False
        data.app_exec = 'gedit %s'
        data.app_name = 'gedit'
        data.description = path
        data.display_name = path
        data.mime_type = 'inode/directory'

        recent_mgr = Gtk.RecentManager.get_default()
        if not recent_mgr.add_full(path, data):
            LOGGER.warning('Could not add to recently used: ' + path)

    def load_entries_from_disk(self, entries):
        LOGGER.info('Loading entries initially')

        for entry in entries:
            self.add_entry(
                entry["name"],
                entry["path"],
                Gio.ThemedIcon(name=entry["icon"])
            )

        self.show_all()


    def load_entries_initially(self, *_):
        """Re-read all LocationEntries from every possible source."""
        LOGGER.info('Refilling location entries')
        recent_mgr = Gtk.RecentManager.get_default()
        for child in list(self.box):
            self.box.remove(child)

        self.known_paths = set()

        # Static entries:
        self.add_entry(
            'Personal directory',
            os.path.expanduser('~'),
            Gio.ThemedIcon(name='user-home')
        )
        self.add_entry(
            'Cache and logs',
            '/var',
            Gio.ThemedIcon(name='folder-templates')
        )

        # Mounted volumes:
        volume_monitor = Gio.VolumeMonitor.get()
        for mount in volume_monitor.get_mounts():
            if mount.get_root().get_path() is None:
                continue

            try:
                info = mount.get_root().query_filesystem_info(
                    ','.join([
                        Gio.FILE_ATTRIBUTE_FILESYSTEM_SIZE,
                        Gio.FILE_ATTRIBUTE_FILESYSTEM_USED
                    ])
                )
            except Exception as exc:
                LOGGER.warning("Failed to get fs info for {}: {}".format(
                    mount.get_name(),
                    exc,
                ))
                continue

            self.add_entry(
                mount.get_name(),
                mount.get_root().get_path(),
                mount.get_icon(),
                fill_level=(
                    info.get_attribute_uint64(
                        Gio.FILE_ATTRIBUTE_FILESYSTEM_USED),
                    info.get_attribute_uint64(
                        Gio.FILE_ATTRIBUTE_FILESYSTEM_SIZE)))

        # Recently used items:
        for item in recent_mgr.get_items():
            # Note: item.get_exists() tells us bullshit sometimes.
            if item.get_mime_type() != 'inode/directory':
                continue

            path = item.get_uri()
            if path.startswith('file://'):
                path = path[7:]

            self.add_entry(
                os.path.basename(path),
                path,
                item.get_gicon()
            )

        self.show_all()

    def add_entry(self, name, path, icon, fill_level=None, idx=-1):
        """Add a new LocationEntry to the list"""
        path = path.strip()

        if path == '/':
            return

        if path in self.known_paths:
            LOGGER.info('In known paths: ' + path)
            return

        entry = LocationEntry(name, path, icon, fill_level)
        self.known_paths.add(path)
        self.box.insert(entry, idx)

        entry.connect(
            'notify::preferred',
            lambda *_: self._update_selected_label()
        )
        entry.connect(
            'shortcut', self._shortcut_clicked
        )

        self.cache_saved_entries()
        return entry

    def cache_saved_entries(self):
        entries = []
        for child in self.box.get_children():
            entries.append(child.to_dict())

        store_saved_entries(entries)

    def on_row_clicked(self, _, row):
        """Highlight an entry when a row was clicked."""
        style_ctx = row.get_style_context()
        if style_ctx.has_class('selected'):
            style_ctx.remove_class('selected')
            self.selected_locations.remove(row)
        else:
            style_ctx.add_class('selected')
            self.selected_locations.append(row)

        self._update_selected_label()

    def _update_selected_label(self):
        """Update the lower count of selected LocationEntries."""
        prefd_paths = sum(rw.props.preferred for rw in self.selected_locations)
        count = len(self.selected_locations)

        self.selected_label.set_markup(
            '{sel} {dirs} - {pref} of them preferred'.format(
                sel=count,
                dirs='directory' if count is 1 else 'directories',
                pref=prefd_paths
            )
        )
        self.revealer.set_reveal_child(bool(self.selected_locations))

    def on_search_changed(self, _):
        """Called once the user enteres a new search query."""
        if self.is_visible:
            self.box.invalidate_filter()

    def _filter_func(self, row):
        """Decide if a row should be visible depending on the search term."""
        query = self.search_entry.get_text().lower()
        if query in row.path.lower():
            return True

        return query in row.name.lower()

    def on_view_enter(self):
        """Called when the view gets visible."""
        self.add_header_widget(self.chooser_button)
        self.chooser_button.show_all()

        # If no process is currently running it should not be
        # possible to go right from locations view.
        main_view = self.app_window.views['runner']
        if not main_view.is_running:
            GLib.idle_add(
                lambda: self.app_window.views.go_right.set_sensitive(False)
            )

    def on_chooser_button_clicked(self, _):
        """Button click on the location chooser."""
        self.stack.set_visible_child_name('chooser')
        self.app_window.remove_header_widget(self.chooser_button)
        self.app_window.views.go_right.set_sensitive(False)
        self.app_window.views.go_left.set_sensitive(False)
        self.revealer.set_reveal_child(False)
        self.sub_title = 'Choose a new location'

        open_button = IconButton('emblem-ok-symbolic', 'Add selected')
        open_button.get_style_context().add_class(
            Gtk.STYLE_CLASS_SUGGESTED_ACTION
        )

        close_button = IconButton('window-close-symbolic', 'Cancel')

        self.add_header_widget(open_button)
        self.add_header_widget(close_button, align=Gtk.Align.START)

        def _go_back():
            """Switch back to the LocationEntry list."""
            self.app_window.remove_header_widget(open_button)
            self.app_window.remove_header_widget(close_button)
            self.add_header_widget(self.chooser_button)
            self.stack.set_visible_child_name('list')
            self.app_window.views.go_right.set_sensitive(True)
            self.app_window.views.go_left.set_sensitive(True)
            self.revealer.set_reveal_child(True)
            self.selected_locations = []
            self._update_selected_label()
            self._set_title()

        def _open_clicked(_):
            """The open file button was clicked. Add paths."""
            for path in self.file_chooser.get_filenames():
                name = os.path.basename(path)
                entry = self.add_entry(
                    name, path, Gio.ThemedIcon(
                        name='folder-new'
                    ),
                    idx=0
                )
                self.box.select_row(entry)
            self.box.show_all()

            _go_back()

        def _close_clicked(_):
            """Abort choosing."""
            _go_back()

        def _selection_changed(_):
            """Make the open button sensitive when something is selected."""
            is_sensitive = bool(self.file_chooser.get_filenames())
            open_button.set_sensitive(is_sensitive)

        open_button.connect('clicked', _open_clicked)
        close_button.connect('clicked', _close_clicked)
        self.file_chooser.connect('selection-changed', _selection_changed)
        open_button.show_all()
        close_button.show_all()

    def _run_clicked(self, _):
        """Switch one view further to the runner view."""
        tagged, untagged = [], []
        for row in self.selected_locations:
            if row.props.preferred:
                tagged.append(row.path)
            else:
                untagged.append(row.path)

        self.scan_paths(untagged, tagged)

    def _shortcut_clicked(self, row):
        """User clicked on one of the row side arrows."""
        # It's only one path. Do not worry about tagged/untagged.
        self.scan_paths([row.path], [])

    def scan_paths(self, untagged, tagged):
        """Actually go to the main view and trigger scan."""
        main_view = self.app_window.views['runner']
        if tagged or untagged:
            main_view.trigger_run(untagged, tagged)
            self.app_window.views.switch('runner')

    def _del_clicked(self, _):
        """Delete all selected LocationEntries."""
        for row in self.selected_locations:
            LOGGER.debug('Removing location entry:' + row.path)
            self.box.remove(row)

            try:
                Gtk.RecentManager.get_default().remove_item(row.path)
            except GLib.Error:
                LOGGER.warning('Could not remove recent item: %s', row.path)

            if row.path in self.known_paths:
                self.known_paths.remove(row.path)

        self.selected_locations = []
        self._update_selected_label()
        self.cache_saved_entries()

    def on_default_action(self):
        """Executed on Ctrl-Enter"""
        self._run_clicked(None)
Example #21
0
class LocationView(View):
    """The actual view instance."""
    def __init__(self, app):
        View.__init__(self, app)
        self.selected_locations = []
        self.known_paths = set()
        self._set_title()

        self.box = Gtk.ListBox()
        self.box.set_selection_mode(Gtk.SelectionMode.NONE)
        self.box.set_hexpand(True)
        self.box.set_placeholder(Gtk.Label('No locations mounted.'))
        self.box.set_valign(Gtk.Align.FILL)
        self.box.set_vexpand(True)

        self.chooser_button = IconButton('list-add-symbolic', 'Add Location')
        self.chooser_button.connect('clicked', self.on_chooser_button_clicked)

        self.file_chooser = Gtk.FileChooserWidget()
        self.file_chooser.set_select_multiple(True)
        self.file_chooser.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
        self.file_chooser.set_create_folders(False)

        self.stack = Gtk.Stack()
        self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP)

        scw = Gtk.ScrolledWindow()
        scw.add(self.box)

        self.stack.add_named(scw, 'list')
        self.stack.add_named(self.file_chooser, 'chooser')

        self.box.set_activate_on_single_click(True)
        self.box.set_filter_func(self._filter_func)
        self.box.connect('row-activated', self.on_row_clicked)

        self.search_entry.connect('search-changed', self.on_search_changed)

        self.volume_monitor = Gio.VolumeMonitor.get()
        self.recent_mgr = Gtk.RecentManager.get_default()
        self.recent_mgr.connect('changed', self.refill_entries)
        self.volume_monitor.connect('volume-changed', self.refill_entries)
        self.volume_monitor.connect('drive-changed', self.refill_entries)
        self.volume_monitor.connect('mount-changed', self.refill_entries)
        self.refill_entries()

        run_button = IconButton('edit-find-symbolic', 'Scan folders')
        run_button.connect('clicked', self._run_clicked)
        run_button.get_style_context().add_class(
            Gtk.STYLE_CLASS_SUGGESTED_ACTION)

        del_button = IconButton('user-trash-symbolic', 'Remove from list')
        del_button.connect('clicked', self._del_clicked)

        self.selected_label = Gtk.Label()
        self.selected_label.get_style_context().add_class(
            Gtk.STYLE_CLASS_DIM_LABEL)

        action_bar = Gtk.ActionBar()
        action_bar.pack_start(del_button)
        action_bar.set_center_widget(self.selected_label)
        action_bar.pack_end(run_button)

        self.revealer = Gtk.Revealer()
        self.revealer.add(action_bar)
        self.revealer.set_hexpand(True)
        self.revealer.set_halign(Gtk.Align.FILL)

        grid = Gtk.Grid()
        grid.attach(self.stack, 0, 0, 1, 1)
        grid.attach(self.revealer, 0, 1, 1, 1)

        self.add(grid)

    def _set_title(self):
        """Make it an own method, so we don't need to retype it."""
        self.sub_title = 'Click locations to scan'

    def add_recent_item(self, path):
        """Add item to GtkRecentManager"""
        data = Gtk.RecentData()
        data.is_private = False
        data.app_exec = 'gedit %s'
        data.app_name = 'gedit'
        data.description = path
        data.display_name = path
        data.mime_type = 'inode/directory'

        if not self.recent_mgr.add_full(path, data):
            LOGGER.warning('Could not add to recently used: ' + path)

    def refill_entries(self, *_):
        """Re-read all LocationEntries from every possible source."""
        LOGGER.info('Refilling location entries')
        for child in list(self.box):
            self.box.remove(child)

        self.known_paths = set()

        # Static entries:
        self.add_entry('Personal directory', os.path.expanduser('~'),
                       Gio.ThemedIcon(name='user-home'))
        self.add_entry('Cache and logs', '/var',
                       Gio.ThemedIcon(name='folder-templates'))

        # Mounted volumes:
        for mount in self.volume_monitor.get_mounts():
            if mount.get_root().get_path() is None:
                continue

            info = mount.get_root().query_filesystem_info(','.join([
                Gio.FILE_ATTRIBUTE_FILESYSTEM_SIZE,
                Gio.FILE_ATTRIBUTE_FILESYSTEM_USED
            ]))

            self.add_entry(
                mount.get_name(),
                mount.get_root().get_path(),
                mount.get_icon(),
                fill_level=(info.get_attribute_uint64(
                    Gio.FILE_ATTRIBUTE_FILESYSTEM_USED),
                            info.get_attribute_uint64(
                                Gio.FILE_ATTRIBUTE_FILESYSTEM_SIZE)))

        # Recently used items:
        for item in self.recent_mgr.get_items():
            # Note: item.get_exists() tells us bullshit sometimes.
            if item.get_mime_type() != 'inode/directory':
                continue

            path = item.get_uri()
            if path.startswith('file://'):
                path = path[7:]

            self.add_entry(os.path.basename(path), path, item.get_gicon())

        self.show_all()

    def add_entry(self, name, path, icon, fill_level=None, idx=-1):
        """Add a new LocationEntry to the list"""
        path = path.strip()

        if path == '/':
            return

        if path in self.known_paths:
            LOGGER.info('In known paths: ' + path)
            return

        entry = LocationEntry(name, path, icon, fill_level)
        self.known_paths.add(path)
        self.box.insert(entry, idx)

        entry.connect('notify::preferred',
                      lambda *_: self._update_selected_label())
        entry.connect('shortcut', self._shortcut_clicked)
        return entry

    def on_row_clicked(self, _, row):
        """Highlight an entry when a row was clicked."""
        style_ctx = row.get_style_context()
        if style_ctx.has_class('selected'):
            style_ctx.remove_class('selected')
            self.selected_locations.remove(row)
        else:
            style_ctx.add_class('selected')
            self.selected_locations.append(row)

        self._update_selected_label()

    def _update_selected_label(self):
        """Update the lower count of selected LocationEntries."""
        prefd_paths = sum(rw.props.preferred for rw in self.selected_locations)
        count = len(self.selected_locations)

        self.selected_label.set_markup(
            '{sel} {dirs} - {pref} of them preferred'.format(
                sel=count,
                dirs='directory' if count is 1 else 'directories',
                pref=prefd_paths))
        self.revealer.set_reveal_child(bool(self.selected_locations))

    def on_search_changed(self, _):
        """Called once the user enteres a new search query."""
        if self.is_visible:
            self.box.invalidate_filter()

    def _filter_func(self, row):
        """Decide if a row shoul be visible depending on the search term."""
        query = self.search_entry.get_text().lower()
        if query in row.path.lower():
            return True

        return query in row.name.lower()

    def on_view_enter(self):
        """Called when the view gets visible."""
        self.add_header_widget(self.chooser_button)
        self.chooser_button.show_all()

        # If no process is currently running it should not be
        # possible to go right from locations view.
        main_view = self.app_window.views['runner']
        if not main_view.is_running:
            GLib.idle_add(
                lambda: self.app_window.views.go_right.set_sensitive(False))

    def on_chooser_button_clicked(self, _):
        """Button click on the location chooser."""
        self.stack.set_visible_child_name('chooser')
        self.app_window.remove_header_widget(self.chooser_button)
        self.app_window.views.go_right.set_sensitive(False)
        self.app_window.views.go_left.set_sensitive(False)
        self.revealer.set_reveal_child(False)
        self.sub_title = 'Choose a new location'

        open_button = IconButton('emblem-ok-symbolic', 'Add selected')
        open_button.get_style_context().add_class(
            Gtk.STYLE_CLASS_SUGGESTED_ACTION)

        close_button = IconButton('window-close-symbolic', 'Cancel')

        self.add_header_widget(open_button)
        self.add_header_widget(close_button, align=Gtk.Align.START)

        def _go_back():
            """Switch back to the LocationEntry list."""
            self.app_window.remove_header_widget(open_button)
            self.app_window.remove_header_widget(close_button)
            self.add_header_widget(self.chooser_button)
            self.stack.set_visible_child_name('list')
            self.app_window.views.go_right.set_sensitive(True)
            self.app_window.views.go_left.set_sensitive(True)
            self.revealer.set_reveal_child(True)
            self.selected_locations = []
            self._update_selected_label()
            self._set_title()

        def _open_clicked(_):
            """The open file button was clicked. Add paths."""
            for path in self.file_chooser.get_filenames():
                name = os.path.basename(path)
                # self.recent_mgr.add_item(path)
                self.add_recent_item(path)
                entry = self.add_entry(name,
                                       path,
                                       Gio.ThemedIcon(name='folder-new'),
                                       idx=0)
                self.box.select_row(entry)
            self.box.show_all()

            _go_back()

        def _close_clicked(_):
            """Abort choosing."""
            _go_back()

        def _selection_changed(_):
            """Make the open button sensitive when something is selected."""
            is_sensitive = bool(self.file_chooser.get_filenames())
            open_button.set_sensitive(is_sensitive)

        open_button.connect('clicked', _open_clicked)
        close_button.connect('clicked', _close_clicked)
        self.file_chooser.connect('selection-changed', _selection_changed)
        open_button.show_all()
        close_button.show_all()

    def _run_clicked(self, _):
        """Switch one view further to the runner view."""
        tagged, untagged = [], []
        for row in self.selected_locations:
            if row.props.preferred:
                tagged.append(row.path)
            else:
                untagged.append(row.path)

        self.scan_paths(untagged, tagged)

    def _shortcut_clicked(self, row):
        """User clicked on one of the row side arrows."""
        # It's only one path. Do not worry about tagged/untagged.
        self.scan_paths([row.path], [])

    def scan_paths(self, untagged, tagged):
        """Actually go to the main view and trigger scan."""
        main_view = self.app_window.views['runner']
        if tagged or untagged:
            main_view.trigger_run(untagged, tagged)
            self.app_window.views.switch('runner')

    def _del_clicked(self, _):
        """Delete all selected LocationEntries."""
        for row in self.selected_locations:
            LOGGER.debug('Removing location entry:' + row.path)
            self.box.remove(row)

            try:
                Gtk.RecentManager.get_default().remove_item(row.path)
            except GLib.Error:
                LOGGER.warning('Could not remove recent item: %s', row.path)

            if row.path in self.known_paths:
                self.known_paths.remove(row.path)

        self.selected_locations = []
        self._update_selected_label()

    def on_default_action(self):
        """Executed on Ctrl-Enter"""
        self._run_clicked(None)
Example #22
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)
Example #23
0
    def __init__(self, app):
        View.__init__(self, app)
        self.selected_locations = []
        self.known_paths = set()
        self._set_title()

        self.box = Gtk.ListBox()
        self.box.set_selection_mode(Gtk.SelectionMode.NONE)
        self.box.set_hexpand(True)
        self.box.set_placeholder(Gtk.Label('No locations mounted.'))
        self.box.set_valign(Gtk.Align.FILL)
        self.box.set_vexpand(True)

        self.chooser_button = IconButton(
            'list-add-symbolic', 'Add Location'
        )
        self.chooser_button.connect(
            'clicked', self.on_chooser_button_clicked
        )

        self.file_chooser = Gtk.FileChooserWidget()
        self.file_chooser.set_select_multiple(True)
        self.file_chooser.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
        self.file_chooser.set_create_folders(False)

        self.stack = Gtk.Stack()
        self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP)

        scw = Gtk.ScrolledWindow()
        scw.add(self.box)

        self.stack.add_named(scw, 'list')
        self.stack.add_named(self.file_chooser, 'chooser')

        self.box.set_activate_on_single_click(True)
        self.box.set_filter_func(self._filter_func)
        self.box.connect('row-activated', self.on_row_clicked)

        self.search_entry.connect(
            'search-changed', self.on_search_changed
        )

        entries = load_saved_entries()
        if entries:
            self.load_entries_from_disk(entries)
        else:
            self.load_entries_initially()

        run_button = IconButton('edit-find-symbolic', 'Scan folders')
        run_button.connect('clicked', self._run_clicked)
        run_button.get_style_context().add_class(
            Gtk.STYLE_CLASS_SUGGESTED_ACTION
        )

        del_button = IconButton('user-trash-symbolic', 'Remove from list')
        del_button.connect('clicked', self._del_clicked)

        self.selected_label = Gtk.Label()
        self.selected_label.get_style_context().add_class(
            Gtk.STYLE_CLASS_DIM_LABEL
        )

        action_bar = Gtk.ActionBar()
        action_bar.pack_start(del_button)
        action_bar.set_center_widget(self.selected_label)
        action_bar.pack_end(run_button)

        self.revealer = Gtk.Revealer()
        self.revealer.add(action_bar)
        self.revealer.set_hexpand(True)
        self.revealer.set_halign(Gtk.Align.FILL)

        grid = Gtk.Grid()
        grid.attach(self.stack, 0, 0, 1, 1)
        grid.attach(self.revealer, 0, 1, 1, 1)

        self.add(grid)
Example #24
0
    def on_chooser_button_clicked(self, _):
        """Button click on the location chooser."""
        self.stack.set_visible_child_name('chooser')
        self.app_window.remove_header_widget(self.chooser_button)
        self.app_window.views.go_right.set_sensitive(False)
        self.app_window.views.go_left.set_sensitive(False)
        self.revealer.set_reveal_child(False)
        self.sub_title = 'Choose a new location'

        open_button = IconButton('emblem-ok-symbolic', 'Add selected')
        open_button.get_style_context().add_class(
            Gtk.STYLE_CLASS_SUGGESTED_ACTION
        )

        close_button = IconButton('window-close-symbolic', 'Cancel')

        self.add_header_widget(open_button)
        self.add_header_widget(close_button, align=Gtk.Align.START)

        def _go_back():
            """Switch back to the LocationEntry list."""
            self.app_window.remove_header_widget(open_button)
            self.app_window.remove_header_widget(close_button)
            self.add_header_widget(self.chooser_button)
            self.stack.set_visible_child_name('list')
            self.app_window.views.go_right.set_sensitive(True)
            self.app_window.views.go_left.set_sensitive(True)
            self.revealer.set_reveal_child(True)
            self.selected_locations = []
            self._update_selected_label()
            self._set_title()

        def _open_clicked(_):
            """The open file button was clicked. Add paths."""
            for path in self.file_chooser.get_filenames():
                name = os.path.basename(path)
                entry = self.add_entry(
                    name, path, Gio.ThemedIcon(
                        name='folder-new'
                    ),
                    idx=0
                )
                self.box.select_row(entry)
            self.box.show_all()

            _go_back()

        def _close_clicked(_):
            """Abort choosing."""
            _go_back()

        def _selection_changed(_):
            """Make the open button sensitive when something is selected."""
            is_sensitive = bool(self.file_chooser.get_filenames())
            open_button.set_sensitive(is_sensitive)

        open_button.connect('clicked', _open_clicked)
        close_button.connect('clicked', _close_clicked)
        self.file_chooser.connect('selection-changed', _selection_changed)
        open_button.show_all()
        close_button.show_all()
Example #25
0
class ResultActionBar(Gtk.ActionBar):
    """Down right bar with the controls"""
    __gsignals__ = {
        'generate-all-script': (GObject.SIGNAL_RUN_FIRST, None, ()),
        'generate-filtered-script': (GObject.SIGNAL_RUN_FIRST, None, ()),
        'generate-selection-script': (GObject.SIGNAL_RUN_FIRST, None, ())
    }

    def __init__(self, view):
        Gtk.ActionBar.__init__(self)

        left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        left_box.get_style_context().add_class("linked")
        self.pack_start(left_box)

        self.refresh_button = IconButton('view-refresh-symbolic')
        self.settings_button = IconButton('system-run-symbolic')

        self.refresh_button.connect(
            'clicked', lambda _: view.app_window.views['runner'].rerun()
        )
        self.settings_button.connect(
            'clicked', lambda _: view.app_window.views.switch('settings')
        )

        left_box.pack_start(self.refresh_button, False, False, 0)
        left_box.pack_start(self.settings_button, False, False, 0)

        right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        right_box.get_style_context().add_class(
            Gtk.STYLE_CLASS_LINKED
        )

        self.script_btn = IconButton(
            'emblem-documents-symbolic', 'Render script from'
        )
        self.script_btn.connect(
            'clicked', self.on_generate_script
        )

        self.script_type_btn = MultipleChoiceButton(
            ['All', 'Filtered', 'Selected'], 'All', 'All'
        )
        self.script_type_btn.set_relief(Gtk.ReliefStyle.NORMAL)

        right_box.pack_start(self.script_btn, True, True, 0)
        right_box.pack_start(self.script_type_btn, True, False, 0)

        self.pack_end(right_box)
        self.set_sensitive(False)

    def on_generate_script(self, _):
        """Called when the left side of the compound button was clicked."""
        choice = self.script_type_btn.get_selected_choice().lower()

        if choice == 'all':
            self.emit('generate-all-script')
        elif choice == 'filtered':
            self.emit('generate-filtered-script')
        elif choice == 'selected':
            self.emit('generate-selection-script')
        else:
            LOGGER.error('Bug: bad choice selection: %s', choice)

    def set_sensitive(self, mode):
        """Set the gen-script button (non)-sensitive and (non)-suggested"""
        btn_ctx = self.script_btn.get_style_context()
        type_ctx = self.script_type_btn.get_style_context()

        if mode:
            btn_ctx.add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
            type_ctx.add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
        else:
            btn_ctx.remove_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
            type_ctx.remove_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)

        self.script_btn.set_sensitive(mode)
        self.script_type_btn.set_sensitive(mode)

    def is_sensitive(self):
        return self.script_btn.is_sensitive()
Example #26
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)