class Scene(quickstart.scenes.BaseScene): application_selection_dialog = None events = { "destroy" : ( "main", ), "toggled" : ( "enabled_checkbox", ), "clicked" : ( "add_button", "remove_button", ), "cursor-changed" : ( "enabled_treeview", ), "changed" : ( "position_combo", ) } def on_enabled_treeview_cursor_changed(self, treeview): """ Fired when the user changed something on the enabled_treeview. """ # Enable remove button GObject.idle_add(self.objects["remove_button"].set_sensitive, True) def on_add_button_clicked(self, button): """ Fired when the Add launcher button has been clicked. """ if not self.application_selection_dialog: self.application_selection_dialog = ApplicationSelectionDialog() self.application_selection_dialog.build_application_list() # Connect response signal self.application_selection_dialog.connect("response", self.on_application_selection_dialog_response) # Bind sensitiveness of the parent with the visibility of the new window self.application_selection_dialog.bind_property( "visible", self.objects["main"], "sensitive", GObject.BindingFlags.INVERT_BOOLEAN ) self.application_selection_dialog.show() def on_remove_button_clicked(self, button): """ Fired when the Remove launcher button has been clicked. """ # Get selection selection = self.objects["enabled_treeview"].get_selection() model, treeiter = selection.get_selected() del model[treeiter] # Disable remove button if we should if len(model) == 0: GObject.idle_add(self.objects["remove_button"].set_sensitive, False) def on_application_selection_dialog_response(self, dialog, response_id): """ Fired when the user triggered a response on the application_selection_dialog. """ # Hide dialog.hide() if response_id in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT): return # Get and add selection self.enabled_model.append(dialog.get_selection()) def on_scene_asked_to_close(self): """ Fired when the back button has been pressed """ settings = Gtk.Settings.get_default() icon_theme = settings.get_property("gtk-icon-theme-name") if not os.path.exists(os.path.dirname(CONFIG)): os.makedirs(os.path.dirname(CONFIG)) with open(CONFIG, "w") as f: if self.objects["position_combo"].get_active() != position_dict["bottom"]: # Panel position. If it is Bottom do not save it. # This avoids messing up with the panel position for those # who already modified it in the *primary* config. f.write("panel_position = %s left horizontal\n" % self.objects["combostore"][self.objects["position_combo"].get_active_iter()][1]) # Ensure that windows can be on top of the panel if the position is top if self.objects["position_combo"].get_active() == position_dict["top"]: f.write("panel_layer = bottom\n") if self.objects["Hide_checkbox"].get_active(): f.write("autohide = 1\n") if self.objects["ampm_enabled"].get_active(): f.write("time1_format = %I:%M %p\n") if self.objects["enabled_checkbox"].get_active(): f.write("panel_items = LTSC\n") else: f.write("panel_items = TSC\n") f.write("launcher_icon_theme = %s\n" % icon_theme) for treeiter in self.enabled_model: f.write("launcher_item_app = %s\n" % treeiter[1]) if self.objects["inverted_scroll_actions"].get_active(): f.write("mouse_scroll_down = toggle\nmouse_scroll_up = iconify\n") os.system("killall -SIGUSR1 tint2") # Destroy application_selection_dialog if self.application_selection_dialog: self.application_selection_dialog.destroy() self.application_selection_dialog = None return True def on_main_destroy(self, window): """ Fired when the main window should be destroyed. """ Gtk.main_quit() def on_enabled_checkbox_toggled(self, checkbox): """ Fired when the enabled checkbox has been toggled. """ if checkbox.get_active(): GObject.idle_add(self.objects["enabled_box"].set_sensitive, True) GObject.idle_add(self.objects["add_button"].set_sensitive, True) else: GObject.idle_add(self.objects["enabled_box"].set_sensitive, False) def on_position_combo_changed(self, combo): """ Fired when the position combobox has been changed. """ # Pre-set "Invert mouse scroll actions" if the newly selected # position is Top. if self.objects["position_combo"].get_active() == position_dict["top"]: self.objects["inverted_scroll_actions"].set_active(True) else: self.objects["inverted_scroll_actions"].set_active(False) @quickstart.threads.thread def initialize(self): """ Builds the enabled list. """ # Set-up the enabled_treeview self.enabled_model = Gtk.ListStore(str, str, Gio.Icon) # Link the treeview self.objects["enabled_treeview"].set_model(self.enabled_model) # Column column = Gtk.TreeViewColumn("Everything") # Icon cell_icon = Gtk.CellRendererPixbuf() column.pack_start(cell_icon, False) column.add_attribute(cell_icon, "gicon", 2) # Text cell_text = Gtk.CellRendererText() column.pack_start(cell_text, False) column.add_attribute(cell_text, "text", 0) # Append self.objects["enabled_treeview"].append_column(column) # Position combo: create renderer cellrenderer = Gtk.CellRendererText() self.objects["position_combo"].pack_start(cellrenderer, True) self.objects["position_combo"].add_attribute(cellrenderer, "text", 0) # Default position is "Bottom" (will be overriden later if we need to) self.objects["position_combo"].set_active(position_dict["bottom"]) # Open config if os.path.exists(CONFIG): up_inverted = False down_inverted = False with open(CONFIG) as f: for line in f.readlines(): try: line = line.split("=") if line[0].startswith("launcher_item_app"): # A launcher! path = line[1].strip("\n").replace(" /","/",1) desktopentry = xdg.DesktopEntry.DesktopEntry(path) iconpath = desktopentry.getIcon() if iconpath and iconpath.startswith("/"): icon = Gio.Icon.new_for_string(iconpath) elif iconpath: icon = Gio.ThemedIcon.new(iconpath.replace(".png","")) else: icon = None self.enabled_model.append((desktopentry.getName(), path, icon)) elif line[0].startswith("panel_items"): # Is the launcher enabled or not? if "L" in line[1]: # YES! self.objects["enabled_checkbox"].set_active(True) else: # No :/ GObject.idle_add(self.objects["enabled_box"].set_sensitive, False) elif line[0].startswith("time1_format"): # AM/PM? if "%p" in line[1]: # Yes! self.objects["ampm_enabled"].set_active(True) else: # No self.objects["ampm_enabled"].set_active(False) elif line[0].startswith("autohide"): # Autohide? if "1" in line[1]: # Yes self.objects["Hide_checkbox"].set_active(True) else: # No self.objects["Hide_checkbox"].set_active(False) elif line[0].startswith("panel_position"): # Position splt = line[1].strip("\n").split(" ") while splt.count(""): splt.remove("") self.objects["position_combo"].set_active(position_dict[splt[0]]) elif line[0].startswith("mouse_scroll_"): # Mouse scroll (up/down) actions. # Usually if they are on the secondary_config # it's because they're inverted, but let's check # anyways... if "down" in line[0] and "toggle" in line[1]: down_inverted = True elif "up" in line[0] and "iconify" in line[1]: up_inverted = True except Exception: print("Error while loading configuration.") self.objects["inverted_scroll_actions"].set_active(down_inverted and up_inverted) else: # Ensure the enabled_checkbox is not active. self.objects["enabled_checkbox"].set_active(False) self.objects["add_button"].set_sensitive(False) # First start, disable remove button. GObject.idle_add(self.objects["remove_button"].set_sensitive, False) def prepare_scene(self): """ Called when doing the scene setup. """ self.scene_container = self.objects.main self.initialize() self.objects["main"].show_all()
class Scene(quickstart.scenes.BaseScene): """ The main scene. """ events = { "response": ("add_new_custom_dialog", ), # the application selection dialog is handled manually "delete-event": ("add_new_custom_dialog", ), "changed": ( "custom_name", "custom_command", ), } application_selection_dialog = None desktop_list = [] current_edit_informations = {} def on_custom_entry_changed(self, entry): """ Fired when a custom entry has been changed. This method isn't directly connected to the custom GtkEntries, but it's directly linked to the on_custom_<id>_changed methods that quickstart expects. """ self.objects.add_new_custom_dialog.get_widget_for_response( Gtk.ResponseType.OK).set_sensitive(not ( (self.objects.custom_name.get_text_length() == 0) or (self.objects.custom_command.get_text_length() == 0))) on_custom_name_changed = on_custom_entry_changed on_custom_command_changed = on_custom_entry_changed def on_application_selection_dialog_response(self, dialog, response_id): """ Fired when the user triggered a response on the application_selection_dialog. """ # Hide dialog.hide() if response_id in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT): return # Get selection desktop_file = dialog.get_selection()[1] desktop_basename = os.path.basename(desktop_file) if os.path.basename(desktop_file) in self.desktop_list: # Already in list, bye return # Copy the desktop file and prepend a new row. # FIXME: currently applications with KDE in "OnlyShowIn" will get # regularly added, but they won't be autostarted by vera and they # will not show again on this module. # # This can be resolved once vera supports a "force-list", see # https://github.com/vera-desktop/vera/issues/2 # Copy the desktop file to ~/.config/autostart directory = os.path.expanduser("~/.config/autostart") target_file = os.path.join(directory, desktop_basename) if not os.path.exists(directory): os.makedirs(directory) shutil.copy2(desktop_file, target_file) entry = DesktopEntry(target_file) row = ApplicationRow(desktop_basename, entry) # Connect the changed signal row.connect("changed", self.on_row_changed) # Connect the requests_edit signal row.connect("requests_edit", self.on_row_requests_edit) # Prepend the row self.objects.list.prepend(row) self.desktop_list.append(desktop_basename) def on_add_new_application_activated(self, menuitem, parameter): """ Fired when the add new application item has been selected. """ if not self.application_selection_dialog: self.application_selection_dialog = ApplicationSelectionDialog() self.application_selection_dialog.build_application_list() # Connect response signal self.application_selection_dialog.connect( "response", self.on_application_selection_dialog_response) # Bind sensitiveness of the parent with the visibility of the new window self.application_selection_dialog.bind_property( "visible", self.objects.main, "sensitive", GObject.BindingFlags.INVERT_BOOLEAN) self.application_selection_dialog.show() def on_add_new_custom_activated(self, menuitem, parameter): """ Fired when the add new custom application item has been selected. """ # Set the proper title self.objects.add_new_custom_dialog.set_title( _("Add a new custom command")) # Hide the Remove button self.objects.add_new_custom_dialog.get_widget_for_response( Gtk.ResponseType.NO).hide() # Disable the sensitiveness of the Select button self.objects.add_new_custom_dialog.get_widget_for_response( Gtk.ResponseType.OK).set_sensitive(False) # Grab focus on the custom_name entry self.objects.custom_name.grab_focus() # Show the add_new_custom dialog self.objects.add_new_custom_dialog.show() def on_row_requests_edit(self, row, application_desktop): """ Fired when the add new custom application item has been selected. """ # Set the proper title self.objects.add_new_custom_dialog.set_title(_("Edit application")) # Show the Remove button self.objects.add_new_custom_dialog.get_widget_for_response( Gtk.ResponseType.NO).show() # Disable the sensitiveness of the Select button self.objects.add_new_custom_dialog.get_widget_for_response( Gtk.ResponseType.OK).set_sensitive(False) # Grab focus on the custom_name entry self.objects.custom_name.grab_focus() # Preload Name and Exec self.objects.custom_name.set_text(application_desktop.getName()) self.objects.custom_command.set_text(application_desktop.getExec()) # Populate self.current_edit_informations self.current_edit_informations["row"] = row self.current_edit_informations["desktop"] = application_desktop # Show the add_new_custom dialog self.objects.add_new_custom_dialog.show() def on_add_new_custom_dialog_response(self, dialog, response_id): """ Fired when the user triggered a response on the add_new_custom_dialog. """ # Clunky way to see if we have edit mode, but it works on_edit = dialog.get_widget_for_response( Gtk.ResponseType.NO).props.visible if not on_edit and response_id == Gtk.ResponseType.OK: # Obtain a working filename directory = os.path.expanduser("~/.config/autostart") if not os.path.exists(directory): os.makedirs(directory) filename = os.path.join( directory, self.objects.custom_name.get_text().lower().replace(" ", "-") + ".custom%s.desktop" % (random.randint(0, 1000), )) if os.path.exists(filename): return on_add_new_custom_dialog_response(dialog, response_id) desktop_basename = os.path.basename(filename) entry = DesktopEntry(filename) entry.set("Version", 1.0) entry.set("Name", self.objects.custom_name.get_text()) entry.set("Exec", self.objects.custom_command.get_text()) entry.set("X-Vera-Autostart-Phase", "Other") entry.write() row = ApplicationRow(desktop_basename, entry) # Connect the changed signal row.connect("changed", self.on_row_changed) # Connect the requests_edit signal row.connect("requests_edit", self.on_row_requests_edit) # Prepend the row self.objects.list.prepend(row) self.desktop_list.append(desktop_basename) elif on_edit and response_id == Gtk.ResponseType.OK: # Edit self.current_edit_informations["desktop"].set( "Name", self.objects.custom_name.get_text(), locale=True) self.current_edit_informations["desktop"].set( "Exec", self.objects.custom_command.get_text()) self.current_edit_informations["desktop"].write() self.current_edit_informations["row"].name.set_text( self.objects.custom_name.get_text()) elif on_edit and response_id == Gtk.ResponseType.NO: # Remove # Cleanup the entry from the ignore list by ensuring that # it's enabled in its last moments... self.on_row_changed( self.current_edit_informations["row"], os.path.basename( self.current_edit_informations["desktop"].filename), True) # Finally, remove os.remove(self.current_edit_informations["desktop"].filename) self.current_edit_informations["row"].destroy() # Hide dialog.hide() # Cleanup self.objects.custom_name.set_text("") self.objects.custom_command.set_text("") self.current_edit_informations = {} def on_add_new_custom_dialog_delete_event(self, dialog, event): """ Fired when the delete-event event of the add_new_custom_dialog is emitted. """ # Do not destroy return True def on_row_changed(self, row, application, enabled): """ Fired when the switch of a row has been modified. """ if enabled and application in BLACKLIST: # Remove from the blacklist BLACKLIST.remove(application) elif not enabled and application not in BLACKLIST: # Add to the blacklist BLACKLIST.append(application) # Set the new array SETTINGS.set_strv("autostart-ignore", BLACKLIST) #@quickstart.threads.on_idle @quickstart.threads.thread def add_applications(self): """ Populates the self.objects.list ListBox with the applications in SEARCH_PATH. """ for path in SEARCH_PATH: if not os.path.exists(path): continue for application in os.listdir(path): # Add the application, if we can try: entry = DesktopEntry(os.path.join(path, application)) if not "KDE" in entry.getOnlyShowIn( ) and not application in self.desktop_list: # While excluding only KDE is not ideal, we do so # to have consistency with vera's AutostartManager. # This check is obviously a FIXME. row = ApplicationRow(application, entry) # Connect the changed signal row.connect("changed", self.on_row_changed) # Connect the requests_edit signal row.connect("requests_edit", self.on_row_requests_edit) GObject.idle_add(self.objects.list.insert, row, -1) self.desktop_list.append(application) except: print("Unable to show informations for %s." % application) def prepare_scene(self): """ Scene setup. """ self.scene_container = self.objects.main # Create menu actiongroup = Gio.SimpleActionGroup.new() menu = Gio.Menu() menu.append(_("Select..."), "add-new.application") menu.append(_("Use a custom command"), "add-new.custom") add_new_application = Gio.SimpleAction.new("application", None) add_new_application.connect("activate", self.on_add_new_application_activated) add_new_custom = Gio.SimpleAction.new("custom", None) add_new_custom.connect("activate", self.on_add_new_custom_activated) actiongroup.add_action(add_new_application) actiongroup.add_action(add_new_custom) # Create popover self.popover = Gtk.Popover.new_from_model(self.objects.add_new, menu) self.popover.set_position(Gtk.PositionType.BOTTOM) self.popover.insert_action_group("add-new", actiongroup) self.objects.add_new.set_popover(self.popover) # Set-up the add_new_custom_dialog self.objects.add_new_custom_dialog.add_buttons(_("_Remove"), Gtk.ResponseType.NO, _("_Cancel"), Gtk.ResponseType.CANCEL, _("_Save"), Gtk.ResponseType.OK) # Bind sensitiveness of the parent with the visibility of the new window self.objects.add_new_custom_dialog.bind_property( "visible", self.objects.main, "sensitive", GObject.BindingFlags.INVERT_BOOLEAN) # Make the Select button the default self.objects.add_new_custom_dialog.set_default_response( Gtk.ResponseType.OK) # Set destructive-action to the Remove button self.objects.add_new_custom_dialog.get_widget_for_response( Gtk.ResponseType.NO).get_style_context().add_class( "destructive-action") self.add_applications() def on_scene_asked_to_close(self): """ Cleanup """ if self.application_selection_dialog: self.application_selection_dialog.destroy() self.application_selection_dialog = None return True
class Scene(quickstart.scenes.BaseScene): """ The main scene. """ events = { "response": ("add_new_custom_dialog",), # the application selection dialog is handled manually "delete-event" : ("add_new_custom_dialog",), "changed" : ("custom_name", "custom_command",), } application_selection_dialog = None desktop_list = [] current_edit_informations = {} def on_custom_entry_changed(self, entry): """ Fired when a custom entry has been changed. This method isn't directly connected to the custom GtkEntries, but it's directly linked to the on_custom_<id>_changed methods that quickstart expects. """ self.objects.add_new_custom_dialog.get_widget_for_response(Gtk.ResponseType.OK).set_sensitive( not ( ( self.objects.custom_name.get_text_length() == 0 ) or ( self.objects.custom_command.get_text_length() == 0 ) ) ) on_custom_name_changed = on_custom_entry_changed on_custom_command_changed = on_custom_entry_changed def on_application_selection_dialog_response(self, dialog, response_id): """ Fired when the user triggered a response on the application_selection_dialog. """ # Hide dialog.hide() if response_id in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT): return # Get selection desktop_file = dialog.get_selection()[1] desktop_basename = os.path.basename(desktop_file) if os.path.basename(desktop_file) in self.desktop_list: # Already in list, bye return # Copy the desktop file and prepend a new row. # FIXME: currently applications with KDE in "OnlyShowIn" will get # regularly added, but they won't be autostarted by vera and they # will not show again on this module. # # This can be resolved once vera supports a "force-list", see # https://github.com/vera-desktop/vera/issues/2 # Copy the desktop file to ~/.config/autostart directory = os.path.expanduser("~/.config/autostart") target_file = os.path.join(directory, desktop_basename) if not os.path.exists(directory): os.makedirs(directory) shutil.copy2( desktop_file, target_file ) entry = DesktopEntry(target_file) row = ApplicationRow(desktop_basename, entry) # Connect the changed signal row.connect("changed", self.on_row_changed) # Connect the requests_edit signal row.connect("requests_edit", self.on_row_requests_edit) # Prepend the row self.objects.list.prepend(row) self.desktop_list.append(desktop_basename) def on_add_new_application_activated(self, menuitem, parameter): """ Fired when the add new application item has been selected. """ if not self.application_selection_dialog: self.application_selection_dialog = ApplicationSelectionDialog() self.application_selection_dialog.build_application_list() # Connect response signal self.application_selection_dialog.connect("response", self.on_application_selection_dialog_response) # Bind sensitiveness of the parent with the visibility of the new window self.application_selection_dialog.bind_property( "visible", self.objects.main, "sensitive", GObject.BindingFlags.INVERT_BOOLEAN ) self.application_selection_dialog.show() def on_add_new_custom_activated(self, menuitem, parameter): """ Fired when the add new custom application item has been selected. """ # Set the proper title self.objects.add_new_custom_dialog.set_title(_("Add a new custom command")) # Hide the Remove button self.objects.add_new_custom_dialog.get_widget_for_response(Gtk.ResponseType.NO).hide() # Disable the sensitiveness of the Select button self.objects.add_new_custom_dialog.get_widget_for_response(Gtk.ResponseType.OK).set_sensitive(False) # Grab focus on the custom_name entry self.objects.custom_name.grab_focus() # Show the add_new_custom dialog self.objects.add_new_custom_dialog.show() def on_row_requests_edit(self, row, application_desktop): """ Fired when the add new custom application item has been selected. """ # Set the proper title self.objects.add_new_custom_dialog.set_title(_("Edit application")) # Show the Remove button self.objects.add_new_custom_dialog.get_widget_for_response(Gtk.ResponseType.NO).show() # Disable the sensitiveness of the Select button self.objects.add_new_custom_dialog.get_widget_for_response(Gtk.ResponseType.OK).set_sensitive(False) # Grab focus on the custom_name entry self.objects.custom_name.grab_focus() # Preload Name and Exec self.objects.custom_name.set_text(application_desktop.getName()) self.objects.custom_command.set_text(application_desktop.getExec()) # Populate self.current_edit_informations self.current_edit_informations["row"] = row self.current_edit_informations["desktop"] = application_desktop # Show the add_new_custom dialog self.objects.add_new_custom_dialog.show() def on_add_new_custom_dialog_response(self, dialog, response_id): """ Fired when the user triggered a response on the add_new_custom_dialog. """ # Clunky way to see if we have edit mode, but it works on_edit = dialog.get_widget_for_response(Gtk.ResponseType.NO).props.visible if not on_edit and response_id == Gtk.ResponseType.OK: # Obtain a working filename directory = os.path.expanduser("~/.config/autostart") if not os.path.exists(directory): os.makedirs(directory) filename = os.path.join( directory, self.objects.custom_name.get_text().lower().replace(" ","-") + ".custom%s.desktop" % (random.randint(0,1000),) ) if os.path.exists(filename): return on_add_new_custom_dialog_response(dialog, response_id) desktop_basename = os.path.basename(filename) entry = DesktopEntry(filename) entry.set("Version", 1.0) entry.set("Name", self.objects.custom_name.get_text()) entry.set("Exec", self.objects.custom_command.get_text()) entry.set("X-Vera-Autostart-Phase", "Other") entry.write() row = ApplicationRow(desktop_basename, entry) # Connect the changed signal row.connect("changed", self.on_row_changed) # Connect the requests_edit signal row.connect("requests_edit", self.on_row_requests_edit) # Prepend the row self.objects.list.prepend(row) self.desktop_list.append(desktop_basename) elif on_edit and response_id == Gtk.ResponseType.OK: # Edit self.current_edit_informations["desktop"].set("Name", self.objects.custom_name.get_text(), locale=True) self.current_edit_informations["desktop"].set("Exec", self.objects.custom_command.get_text()) self.current_edit_informations["desktop"].write() self.current_edit_informations["row"].name.set_text(self.objects.custom_name.get_text()) elif on_edit and response_id == Gtk.ResponseType.NO: # Remove # Cleanup the entry from the ignore list by ensuring that # it's enabled in its last moments... self.on_row_changed( self.current_edit_informations["row"], os.path.basename(self.current_edit_informations["desktop"].filename), True ) # Finally, remove os.remove(self.current_edit_informations["desktop"].filename) self.current_edit_informations["row"].destroy() # Hide dialog.hide() # Cleanup self.objects.custom_name.set_text("") self.objects.custom_command.set_text("") self.current_edit_informations = {} def on_add_new_custom_dialog_delete_event(self, dialog, event): """ Fired when the delete-event event of the add_new_custom_dialog is emitted. """ # Do not destroy return True def on_row_changed(self, row, application, enabled): """ Fired when the switch of a row has been modified. """ if enabled and application in BLACKLIST: # Remove from the blacklist BLACKLIST.remove(application) elif not enabled and application not in BLACKLIST: # Add to the blacklist BLACKLIST.append(application) # Set the new array SETTINGS.set_strv("autostart-ignore", BLACKLIST) #@quickstart.threads.on_idle @quickstart.threads.thread def add_applications(self): """ Populates the self.objects.list ListBox with the applications in SEARCH_PATH. """ for path in SEARCH_PATH: if not os.path.exists(path): continue for application in os.listdir(path): # Add the application, if we can try: entry = DesktopEntry(os.path.join(path, application)) if not "KDE" in entry.getOnlyShowIn() and not application in self.desktop_list: # While excluding only KDE is not ideal, we do so # to have consistency with vera's AutostartManager. # This check is obviously a FIXME. row = ApplicationRow(application, entry) # Connect the changed signal row.connect("changed", self.on_row_changed) # Connect the requests_edit signal row.connect("requests_edit", self.on_row_requests_edit) GObject.idle_add(self.objects.list.insert, row, -1 ) self.desktop_list.append(application) except: print("Unable to show informations for %s." % application) def prepare_scene(self): """ Scene setup. """ self.scene_container = self.objects.main # Create menu actiongroup = Gio.SimpleActionGroup.new() menu = Gio.Menu() menu.append( _("Select..."), "add-new.application" ) menu.append( _("Use a custom command"), "add-new.custom" ) add_new_application = Gio.SimpleAction.new("application", None) add_new_application.connect("activate", self.on_add_new_application_activated) add_new_custom = Gio.SimpleAction.new("custom", None) add_new_custom.connect("activate", self.on_add_new_custom_activated) actiongroup.add_action(add_new_application) actiongroup.add_action(add_new_custom) # Create popover self.popover = Gtk.Popover.new_from_model(self.objects.add_new, menu) self.popover.set_position(Gtk.PositionType.BOTTOM) self.popover.insert_action_group("add-new", actiongroup) self.objects.add_new.set_popover(self.popover) # Set-up the add_new_custom_dialog self.objects.add_new_custom_dialog.add_buttons( _("_Remove"), Gtk.ResponseType.NO, _("_Cancel"), Gtk.ResponseType.CANCEL, _("_Save"), Gtk.ResponseType.OK ) # Bind sensitiveness of the parent with the visibility of the new window self.objects.add_new_custom_dialog.bind_property( "visible", self.objects.main, "sensitive", GObject.BindingFlags.INVERT_BOOLEAN ) # Make the Select button the default self.objects.add_new_custom_dialog.set_default_response(Gtk.ResponseType.OK) # Set destructive-action to the Remove button self.objects.add_new_custom_dialog.get_widget_for_response(Gtk.ResponseType.NO).get_style_context().add_class("destructive-action") self.add_applications() def on_scene_asked_to_close(self): """ Cleanup """ if self.application_selection_dialog: self.application_selection_dialog.destroy() self.application_selection_dialog = None return True
class Scene(quickstart.scenes.BaseScene): application_selection_dialog = None events = { "destroy": ("main", ), "toggled": ("enabled_checkbox", ), "clicked": ( "add_button", "remove_button", ), "cursor-changed": ("enabled_treeview", ), "changed": ("position_combo", ) } def on_enabled_treeview_cursor_changed(self, treeview): """ Fired when the user changed something on the enabled_treeview. """ # Enable remove button GObject.idle_add(self.objects["remove_button"].set_sensitive, True) def on_add_button_clicked(self, button): """ Fired when the Add launcher button has been clicked. """ if not self.application_selection_dialog: self.application_selection_dialog = ApplicationSelectionDialog() self.application_selection_dialog.build_application_list() # Connect response signal self.application_selection_dialog.connect( "response", self.on_application_selection_dialog_response) # Bind sensitiveness of the parent with the visibility of the new window self.application_selection_dialog.bind_property( "visible", self.objects["main"], "sensitive", GObject.BindingFlags.INVERT_BOOLEAN) self.application_selection_dialog.show() def on_remove_button_clicked(self, button): """ Fired when the Remove launcher button has been clicked. """ # Get selection selection = self.objects["enabled_treeview"].get_selection() model, treeiter = selection.get_selected() del model[treeiter] # Disable remove button if we should if len(model) == 0: GObject.idle_add(self.objects["remove_button"].set_sensitive, False) def on_application_selection_dialog_response(self, dialog, response_id): """ Fired when the user triggered a response on the application_selection_dialog. """ # Hide dialog.hide() if response_id in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT): return # Get and add selection self.enabled_model.append(dialog.get_selection()) def on_scene_asked_to_close(self): """ Fired when the back button has been pressed """ settings = Gtk.Settings.get_default() icon_theme = settings.get_property("gtk-icon-theme-name") if not os.path.exists(os.path.dirname(CONFIG)): os.makedirs(os.path.dirname(CONFIG)) with open(CONFIG, "w") as f: if self.objects["position_combo"].get_active( ) != position_dict["bottom"]: # Panel position. If it is Bottom do not save it. # This avoids messing up with the panel position for those # who already modified it in the *primary* config. f.write("panel_position = %s left horizontal\n" % self.objects["combostore"] [self.objects["position_combo"].get_active_iter()][1]) # Ensure that windows can be on top of the panel if the position is top if self.objects["position_combo"].get_active( ) == position_dict["top"]: f.write("panel_layer = bottom\n") if self.objects["Hide_checkbox"].get_active(): f.write("autohide = 1\n") if self.objects["ampm_enabled"].get_active(): f.write("time1_format = %I:%M %p\n") if self.objects["enabled_checkbox"].get_active(): f.write("panel_items = LTSC\n") else: f.write("panel_items = TSC\n") f.write("launcher_icon_theme = %s\n" % icon_theme) for treeiter in self.enabled_model: f.write("launcher_item_app = %s\n" % treeiter[1]) if self.objects["inverted_scroll_actions"].get_active(): f.write( "mouse_scroll_down = toggle\nmouse_scroll_up = iconify\n") os.system("killall -SIGUSR1 tint2") # Destroy application_selection_dialog if self.application_selection_dialog: self.application_selection_dialog.destroy() self.application_selection_dialog = None return True def on_main_destroy(self, window): """ Fired when the main window should be destroyed. """ Gtk.main_quit() def on_enabled_checkbox_toggled(self, checkbox): """ Fired when the enabled checkbox has been toggled. """ if checkbox.get_active(): GObject.idle_add(self.objects["enabled_box"].set_sensitive, True) GObject.idle_add(self.objects["add_button"].set_sensitive, True) else: GObject.idle_add(self.objects["enabled_box"].set_sensitive, False) def on_position_combo_changed(self, combo): """ Fired when the position combobox has been changed. """ # Pre-set "Invert mouse scroll actions" if the newly selected # position is Top. if self.objects["position_combo"].get_active() == position_dict["top"]: self.objects["inverted_scroll_actions"].set_active(True) else: self.objects["inverted_scroll_actions"].set_active(False) @quickstart.threads.thread def initialize(self): """ Builds the enabled list. """ # Set-up the enabled_treeview self.enabled_model = Gtk.ListStore(str, str, Gio.Icon) # Link the treeview self.objects["enabled_treeview"].set_model(self.enabled_model) # Column column = Gtk.TreeViewColumn("Everything") # Icon cell_icon = Gtk.CellRendererPixbuf() column.pack_start(cell_icon, False) column.add_attribute(cell_icon, "gicon", 2) # Text cell_text = Gtk.CellRendererText() column.pack_start(cell_text, False) column.add_attribute(cell_text, "text", 0) # Append self.objects["enabled_treeview"].append_column(column) # Position combo: create renderer cellrenderer = Gtk.CellRendererText() self.objects["position_combo"].pack_start(cellrenderer, True) self.objects["position_combo"].add_attribute(cellrenderer, "text", 0) # Default position is "Bottom" (will be overriden later if we need to) self.objects["position_combo"].set_active(position_dict["bottom"]) # Open config if os.path.exists(CONFIG): up_inverted = False down_inverted = False with open(CONFIG) as f: for line in f.readlines(): try: line = line.split("=") if line[0].startswith("launcher_item_app"): # A launcher! path = line[1].strip("\n").replace(" /", "/", 1) desktopentry = xdg.DesktopEntry.DesktopEntry(path) iconpath = desktopentry.getIcon() if iconpath and iconpath.startswith("/"): icon = Gio.Icon.new_for_string(iconpath) elif iconpath: icon = Gio.ThemedIcon.new( iconpath.replace(".png", "")) else: icon = None self.enabled_model.append( (desktopentry.getName(), path, icon)) elif line[0].startswith("panel_items"): # Is the launcher enabled or not? if "L" in line[1]: # YES! self.objects["enabled_checkbox"].set_active( True) else: # No :/ GObject.idle_add( self.objects["enabled_box"].set_sensitive, False) elif line[0].startswith("time1_format"): # AM/PM? if "%p" in line[1]: # Yes! self.objects["ampm_enabled"].set_active(True) else: # No self.objects["ampm_enabled"].set_active(False) elif line[0].startswith("autohide"): # Autohide? if "1" in line[1]: # Yes self.objects["Hide_checkbox"].set_active(True) else: # No self.objects["Hide_checkbox"].set_active(False) elif line[0].startswith("panel_position"): # Position splt = line[1].strip("\n").split(" ") while splt.count(""): splt.remove("") self.objects["position_combo"].set_active( position_dict[splt[0]]) elif line[0].startswith("mouse_scroll_"): # Mouse scroll (up/down) actions. # Usually if they are on the secondary_config # it's because they're inverted, but let's check # anyways... if "down" in line[0] and "toggle" in line[1]: down_inverted = True elif "up" in line[0] and "iconify" in line[1]: up_inverted = True except Exception: print("Error while loading configuration.") self.objects["inverted_scroll_actions"].set_active(down_inverted and up_inverted) else: # Ensure the enabled_checkbox is not active. self.objects["enabled_checkbox"].set_active(False) self.objects["add_button"].set_sensitive(False) # First start, disable remove button. GObject.idle_add(self.objects["remove_button"].set_sensitive, False) def prepare_scene(self): """ Called when doing the scene setup. """ self.scene_container = self.objects.main self.initialize() self.objects["main"].show_all()