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)
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
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)
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)
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)
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)
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)
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)
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 __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 _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
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 __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)
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)
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()
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)
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 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()
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)
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)
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)
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 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()
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()
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)