class UIMain(object):
    def __init__(self, application):
        self.application = application
        # Load settings
        settings.settings = settings.Settings(FILE_SETTINGS, False)
        settings.positions = settings.Settings(FILE_WINDOWS_POSITION, False)
        self.folders = {}
        preferences.preferences = preferences.Preferences()
        self.loadUI()
        # Prepares the models for folders and applications
        self.model_folders = ModelAppFolders(self.ui.store_folders)
        self.model_applications = ModelApplications(self.ui.store_applications)
        self.model_applications.model.set_sort_column_id(
            self.ui.treeview_column_applications.get_sort_column_id(),
            Gtk.SortType.ASCENDING)
        # Detect the AppFolders and select the first one automatically
        self.reload_folders()
        if self.model_folders.count() > 0:
            self.ui.treeview_folders.set_cursor(0)
        self.ui.treeview_folders.grab_focus()
        # Restore the saved size and position
        settings.positions.restore_window_position(
            self.ui.win_main, SECTION_WINDOW_NAME)

    def loadUI(self):
        """Load the interface UI"""
        self.ui = GtkBuilderLoader(get_ui_file('main.ui'))
        self.ui.win_main.set_application(self.application)
        self.ui.win_main.set_title(APP_NAME)
        # Initialize actions
        for widget in self.ui.get_objects_by_type(Gtk.Action):
            # Connect the actions accelerators
            widget.connect_accelerator()
            # Set labels
            label = widget.get_label()
            if not label:
                label = widget.get_short_label()
            widget.set_label(text(label))
            widget.set_short_label(label)
        # Initialize labels
        for widget in self.ui.get_objects_by_type(Gtk.Label):
            widget.set_label(text(widget.get_label()))
        # Initialize tooltips
        for widget in self.ui.get_objects_by_type(Gtk.Button):
            action = widget.get_related_action()
            if action:
                widget.set_tooltip_text(action.get_label().replace('_', ''))
        # Initialize Gtk.HeaderBar
        self.ui.header_bar.props.title = self.ui.win_main.get_title()
        self.ui.win_main.set_titlebar(self.ui.header_bar)
        for button in (self.ui.button_folder_new, self.ui.button_folder_remove,
                       self.ui.button_folder_properties,
                       self.ui.button_files_add, self.ui.button_files_remove,
                       self.ui.button_files_save, self.ui.button_about, ):
            action = button.get_related_action()
            icon_name = action.get_icon_name()
            if preferences.get(preferences.HEADERBARS_SYMBOLIC_ICONS):
                icon_name += '-symbolic'
            # Get desired icon size
            icon_size = (Gtk.IconSize.BUTTON
                         if preferences.get(preferences.HEADERBARS_SMALL_ICONS)
                         else Gtk.IconSize.LARGE_TOOLBAR)
            button.set_image(Gtk.Image.new_from_icon_name(icon_name,
                                                          icon_size))
            # Remove the button label
            button.props.label = None
            # Set the tooltip from the action label
            button.set_tooltip_text(action.get_label().replace('_', ''))
        # Set preferences button icon
        icon_name = self.ui.image_preferences.get_icon_name()[0]
        if preferences.get(preferences.HEADERBARS_SYMBOLIC_ICONS):
            icon_name += '-symbolic'
        self.ui.image_preferences.set_from_icon_name(icon_name, icon_size)
        # Load settings
        self.dict_settings_map = {
            preferences.HEADERBARS_SMALL_ICONS:
                self.ui.action_preferences_small_icons,
            preferences.HEADERBARS_SYMBOLIC_ICONS:
                self.ui.action_preferences_symbolic_icons,
            preferences.PREFERENCES_SHOW_MISSING:
                self.ui.action_preferences_show_missing_files
        }
        for setting_name, action in self.dict_settings_map.items():
            action.set_active(preferences.get(setting_name))
        # Connect signals from the glade file to the module functions
        self.ui.connect_signals(self)

    def run(self):
        """Show the UI"""
        self.ui.win_main.show_all()

    def on_win_main_delete_event(self, widget, event):
        """Save the settings and close the application"""
        settings.positions.save_window_position(
            self.ui.win_main, SECTION_WINDOW_NAME)
        settings.positions.save()
        settings.settings.save()
        self.application.quit()

    def on_action_about_activate(self, action):
        """Show the about dialog"""
        dialog = UIAbout(self.ui.win_main)
        dialog.show()
        dialog.destroy()

    def on_action_shortcuts_activate(self, action):
        """Show the shortcuts dialog"""
        dialog = UIShortcuts(self.ui.win_main)
        dialog.show()

    def on_action_quit_activate(self, action):
        """Close the application by closing the main window"""
        event = Gdk.Event()
        event.key.type = Gdk.EventType.DELETE
        self.ui.win_main.event(event)

    def reload_folders(self):
        """Reload the Application Folders"""
        self.folders = {}
        self.model_folders.clear()
        settings_folders = Gio.Settings.new(SCHEMA_FOLDERS)
        list_folders = settings_folders.get_strv('folder-children')
        for folder_name in list_folders:
            folder_info = FolderInfo(folder_name)
            appfolder = AppFolderInfo(folder_info)
            self.model_folders.add_data(appfolder)
            self.folders[folder_info.folder] = folder_info

    def on_treeview_folders_cursor_changed(self, widget):
        selected_row = get_treeview_selected_row(self.ui.treeview_folders)
        if selected_row:
            folder_name = self.model_folders.get_key(selected_row)
            # Check if the folder still exists
            # (model erased while the cursor moves through the Gtk.TreeView)
            if folder_name in self.folders:
                folder_info = self.folders[folder_name]
                # Clear any previous application icon
                self.model_applications.clear()
                # Add new application icons
                applications = folder_info.get_applications()
                for application in applications:
                    desktop_file = applications[application]
                    if desktop_file or preferences.get(
                            preferences.PREFERENCES_SHOW_MISSING):
                        application_file = applications[application]
                        application_info = ApplicationInfo(
                            application,
                            application_file.getName()
                            if desktop_file else 'Missing desktop file',
                            application_file.getComment()
                            if desktop_file else application,
                            application_file.getIcon()
                            if desktop_file else None,
                            # Always show any application, also if hidden
                            True)
                        self.model_applications.add_data(application_info)
            # Disable folder content saving
            self.ui.action_files_save.set_sensitive(False)

    def on_treeview_selection_folders_changed(self, widget):
        """Set action sensitiveness on selection change"""
        selected_row = get_treeview_selected_row(self.ui.treeview_folders)
        for widget in (self.ui.action_folders_remove,
                       self.ui.action_folders_properties,
                       self.ui.action_files_new):
            widget.set_sensitive(bool(selected_row))
        if not selected_row:
            self.ui.action_files_new.set_sensitive(False)
            self.ui.action_files_remove.set_sensitive(False)
            self.ui.action_files_save.set_sensitive(False)
            self.model_applications.clear()

    def on_action_files_new_activate(self, action):
        """Show an application picker to add to the current AppFolder"""
        dialog = UIApplicationPicker(self.ui.win_main,
                                     self.model_applications.rows.keys())
        if dialog.show() == Gtk.ResponseType.OK:
            if dialog.selected_applications:
                for application in dialog.selected_applications:
                    # Get the selected application in the application picker
                    # and add it to the current AppFolder
                    application_info = dialog.model_applications.items[
                        application]
                    self.model_applications.add_data(application_info)
                    # Automatically select the newly added application
                    filename = application_info.filename
                    treeiter = self.model_applications.rows[filename]
                if treeiter:
                    # Automatically select the last added application
                    self.ui.treeview_applications.set_cursor(
                        self.model_applications.get_path(treeiter))
                # Enable folder content saving
                self.ui.action_files_save.set_sensitive(True)
        dialog.destroy()

    def on_treeview_selection_applications_changed(self, widget):
        """Set action sensitiveness on selection change"""
        selected_row = get_treeview_selected_row(self.ui.treeview_applications)
        self.ui.action_files_remove.set_sensitive(bool(selected_row))

    def on_action_files_remove_activate(self, action):
        """Remove the selected application from the current AppFolder"""
        selected_row = get_treeview_selected_row(self.ui.treeview_applications)
        if selected_row:
            self.model_applications.remove(selected_row)
            # Enable folder content saving
            self.ui.action_files_save.set_sensitive(True)

    def on_action_files_save_activate(self, action):
        """Save the current AppFolder"""
        selected_row = get_treeview_selected_row(self.ui.treeview_folders)
        if selected_row:
            folder_name = self.model_folders.get_key(selected_row)
            folder_info = self.folders[folder_name]
            folder_info.set_applications(self.model_applications.rows.keys())
        # Disable folder content saving
        self.ui.action_files_save.set_sensitive(False)

    def on_action_folders_remove_activate(self, action):
        """Remove the current AppFolder"""
        selected_row = get_treeview_selected_row(self.ui.treeview_folders)
        if selected_row:
            folder_name = self.model_folders.get_key(selected_row)
            if show_message_dialog(class_=UIMessageDialogNoYes,
                                   parent=self.ui.win_main,
                                   message_type=Gtk.MessageType.QUESTION,
                                   title=None,
                                   msg1=_('Remove the selected folder?'),
                                   msg2=_('Are you sure you want to remove '
                                          'the folder %s?') % folder_name,
                                   is_response_id=Gtk.ResponseType.YES):
                # Remove the AppFolder from settings
                folder_info = self.folders[folder_name]
                folder_info.remove()
                self.folders.pop(folder_name)
                # Remove the folder name from the folders list
                settings_folders = Gio.Settings.new(SCHEMA_FOLDERS)
                list_folders = settings_folders.get_strv('folder-children')
                list_folders.remove(folder_name)
                settings_folders.set_strv('folder-children', list_folders)
                # Clear the applications model
                self.model_applications.clear()
                # Remove the folder from the folders model
                self.model_folders.remove(selected_row)

    def on_action_folders_new_activate(self, action):
        """Creat a new AppFolder"""
        dialog = UICreateAppFolder(self.ui.win_main,
                                   self.model_folders.rows.keys())
        if dialog.show(name='', title='') == Gtk.ResponseType.OK:
            # Create a new FolderInfo object and set its title
            folder_info = FolderInfo(dialog.folder_name)
            folder_info.set_title(dialog.folder_title)
            # Add the folder to the folders list
            settings_folders = Gio.Settings.new(SCHEMA_FOLDERS)
            list_folders = settings_folders.get_strv('folder-children')
            list_folders.append(dialog.folder_name)
            settings_folders.set_strv('folder-children', list_folders)
            # Reload folders list
            self.reload_folders()
        dialog.destroy()

    def on_action_folders_properties_activate(self, action):
        """Set the AppFolder properties"""
        selected_row = get_treeview_selected_row(self.ui.treeview_folders)
        if selected_row:
            name = self.model_folders.get_key(selected_row)
            title = self.model_folders.get_title(selected_row)
            dialog = UICreateAppFolder(self.ui.win_main,
                                       self.model_folders.rows.keys())
            if dialog.show(name=name, title=title) == Gtk.ResponseType.OK:
                folder_name = dialog.folder_name
                folder_title = dialog.folder_title
                # Update the folder title
                folder_info = self.folders[folder_name]
                folder_info.name = folder_title
                folder_info.set_title(folder_title)
                # Reload the folders list and select the folder again
                self.reload_folders()
                self.ui.treeview_folders.set_cursor(
                    self.model_folders.get_path_by_name(folder_name))
            dialog.destroy()

    def on_action_preferences_toggled(self, widget):
        """Change a preference value"""
        for setting_name, action in self.dict_settings_map.items():
            if action is widget:
                preferences.set(setting_name, widget.get_active())

    def on_action_preferences_need_restart_toggled(self, widget):
        """Show the infobar to require application restart"""
        self.ui.label_infobar.set_label(
            _('The application must be restarted to apply the settings'))
        self.ui.infobar.show()

    def on_action_preferences_show_missing_files_toggled(self, widget):
        """Show and hide the missing desktop files"""
        self.on_treeview_folders_cursor_changed(
            self.ui.treeview_folders)

    def on_treeview_folders_row_activated(self, widget, path, column):
        """Show folder properties on activation"""
        self.ui.action_folders_properties.activate()
Beispiel #2
0
class UIApplicationPicker(object):
    def __init__(self, parent, existing_files):
        """Prepare the application picker dialog"""
        # Load the user interface
        self.ui = GtkBuilderLoader(get_ui_file('application_picker.ui'))
        self.ui.dialog_application_picker.set_titlebar(self.ui.header_bar)
        # Prepares the models for the applications
        self.model_applications = ModelApplications(self.ui.store_applications)
        self.model_applications.model.set_sort_column_id(
            self.ui.treeview_column_applications.get_sort_column_id(),
            Gtk.SortType.ASCENDING)
        self.ui.filter_applications.set_visible_column(
            ModelApplications.COL_VISIBLE)
        # Initialize actions
        for widget in self.ui.get_objects_by_type(Gtk.Action):
            # Connect the actions accelerators
            widget.connect_accelerator()
            # Set labels
            label = widget.get_label()
            if not label:
                label = widget.get_short_label()
            widget.set_short_label(text(label))
            widget.set_label(text(label))
        # Initialize tooltips
        for widget in self.ui.get_objects_by_type(Gtk.Button):
            action = widget.get_related_action()
            if action:
                widget.set_tooltip_text(action.get_label().replace('_', ''))
        # Set various properties
        self.ui.dialog_application_picker.set_transient_for(parent)
        set_style_suggested_action(self.ui.button_add)
        self.selected_applications = None
        # Set preferences button icon
        icon_name = self.ui.image_preferences.get_icon_name()[0]
        if preferences.get(preferences.HEADERBARS_SYMBOLIC_ICONS):
            icon_name += '-symbolic'
        # Get desired icon size
        icon_size = (Gtk.IconSize.BUTTON
                     if preferences.get(preferences.HEADERBARS_SMALL_ICONS)
                     else Gtk.IconSize.LARGE_TOOLBAR)
        self.ui.image_preferences.set_from_icon_name(icon_name, icon_size)
        # Load settings
        self.dict_settings_map = {
            preferences.APP_PICKER_SHOW_HIDDEN: self.ui.action_show_hidden
        }
        for setting_name, action in self.dict_settings_map.items():
            action.set_active(preferences.get(setting_name))
        # Prepares the applications list
        for desktop_entry in Gio.app_info_get_all():
            try:
                icon_name = None
                icon = desktop_entry.get_icon()
                if isinstance(icon, Gio.ThemedIcon):
                    # From Gio.ThemedIcon get the icon name only
                    icons = desktop_entry.get_icon().get_names()
                    icon_name = icons[0] if icons else None
                elif isinstance(icon, Gio.FileIcon):
                    # From Gio.FileIcon get the full file name
                    icon_name = icon.get_file().get_parse_name()
                description = (desktop_entry.get_description()
                               if desktop_entry.get_description() else '')
                application = ApplicationInfo(desktop_entry.get_id(),
                                              desktop_entry.get_name(),
                                              description, icon_name,
                                              desktop_entry.should_show())
                # Skip existing files
                if application.filename not in existing_files:
                    self.model_applications.add_data(application)
            except Exception as e:
                print 'error for', desktop_entry.get_id(), e
        self.model_applications.set_all_rows_visibility(
            preferences.get(preferences.APP_PICKER_SHOW_HIDDEN))
        # Connect signals from the glade file to the module functions
        self.ui.connect_signals(self)

    def show(self):
        """Show the application picker dialog"""
        settings.positions.restore_window_position(
            self.ui.dialog_application_picker, SECTION_WINDOW_NAME)
        response = self.ui.dialog_application_picker.run()
        self.ui.dialog_application_picker.hide()
        return response

    def destroy(self):
        """Destroy the application picker dialog"""
        settings.positions.save_window_position(
            self.ui.dialog_application_picker, SECTION_WINDOW_NAME)
        self.ui.dialog_application_picker.destroy()
        self.ui.dialog_application_picker = None

    def on_action_close_activate(self, action):
        """Close the application picker dialog"""
        self.ui.dialog_application_picker.response(Gtk.ResponseType.CLOSE)

    def on_action_add_activate(self, action):
        """Add the selected application to the current AppFolder"""
        self.selected_applications = []
        for row in get_treeview_selected_rows(self.ui.treeview_applications):
            application = self.model_applications.get_key(
                self.ui.filter_applications.convert_path_to_child_path(row))
            self.selected_applications.append(application)
        self.ui.dialog_application_picker.response(Gtk.ResponseType.OK)

    def on_treeview_applications_row_activated(self, widget, path, column):
        """Add the selected application on row activation"""
        self.ui.action_add.activate()

    def on_treeview_selection_applications_changed(self, widget):
        """Set action sensitiveness on selection change"""
        selected_rows = get_treeview_selected_rows(
            self.ui.treeview_applications)
        self.ui.action_add.set_sensitive(bool(selected_rows))

    def on_action_preferences_toggled(self, widget):
        """Change a preference value"""
        for setting_name, action in self.dict_settings_map.items():
            if action is widget:
                preferences.set(setting_name, widget.get_active())

    def on_action_show_hidden_toggled(self, widget):
        """Set the visibility for all the Gtk.TreeModelRows"""
        self.model_applications.set_all_rows_visibility(
            self.ui.action_show_hidden.get_active())
class UICreateAppFolder(object):
    def __init__(self, parent, existing_folders):
        """Prepare the AppFolder creation dialog"""
        # Load the user interface
        self.ui = GtkBuilderLoader(get_ui_file('create_appfolder.ui'))
        self.ui.dialog_create_appfolder.set_titlebar(self.ui.header_bar)
        # Initialize actions
        for widget in self.ui.get_objects_by_type(Gtk.Action):
            # Connect the actions accelerators
            widget.connect_accelerator()
            # Set labels
            label = widget.get_label()
            if not label:
                label = widget.get_short_label()
            widget.set_short_label(text(label))
            widget.set_label(text(label))
        # Initialize labels
        for widget in self.ui.get_objects_by_type(Gtk.Label):
            widget.set_label(text(widget.get_label()))
        # Initialize tooltips
        for widget in self.ui.get_objects_by_type(Gtk.Button):
            action = widget.get_related_action()
            if action:
                widget.set_tooltip_text(action.get_label().replace('_', ''))
        # Set various properties
        self.ui.dialog_create_appfolder.set_transient_for(parent)
        set_style_suggested_action(self.ui.button_ok)
        self.existing_folders = existing_folders
        self.ui.button_ok.grab_default()
        self.folder_name = ''
        self.folder_title = ''
        # Connect signals from the glade file to the module functions
        self.ui.connect_signals(self)

    def show(self, name, title):
        """Show the dialog"""
        settings.positions.restore_window_position(
            self.ui.dialog_create_appfolder, SECTION_WINDOW_NAME)
        # Set initial values
        self.folder_name = name
        self.ui.entry_name.set_text(name)
        self.folder_title = title
        self.ui.entry_title.set_text(title)
        # Change label from Create folder to Save if a folder name was provided
        if name:
            self.ui.entry_name.set_sensitive(False)
            self.ui.button_ok.set_related_action(self.ui.action_save)
            self.ui.button_ok.set_tooltip_text(
                self.ui.action_save.get_label().replace('_', ''))
        response = self.ui.dialog_create_appfolder.run()
        self.ui.dialog_create_appfolder.hide()
        return response

    def destroy(self):
        """Destroy the dialog"""
        settings.positions.save_window_position(
            self.ui.dialog_create_appfolder, SECTION_WINDOW_NAME)
        self.ui.dialog_create_appfolder.destroy()
        self.ui.dialog_create_appfolder = None

    def on_action_close_activate(self, action):
        """Close the dialog"""
        self.ui.dialog_create_appfolder.response(Gtk.ResponseType.CLOSE)

    def on_action_confirm_activate(self, action):
        """Accept the folder name and title and close the dialog"""
        self.folder_name = self.ui.entry_name.get_text().strip()
        self.folder_title = self.ui.entry_title.get_text().strip()
        self.ui.dialog_create_appfolder.response(Gtk.ResponseType.OK)

    def on_entry_name_changed(self, widget):
        """Check if the folder already exists"""
        name = self.ui.entry_name.get_text().strip()
        existing = (name in self.existing_folders and name != self.folder_name)
        # If a folder with the specified name already exists
        # then disable the creation and show an icon
        self.ui.action_create.set_sensitive(bool(name) and not existing)
        self.ui.entry_name.set_icon_from_icon_name(
            Gtk.EntryIconPosition.SECONDARY,
            'dialog-error' if existing else None)
        self.ui.entry_name.set_icon_tooltip_text(
            Gtk.EntryIconPosition.SECONDARY,
            text('A folder with that name already exists')
            if existing else None)
class UIApplicationPicker(object):
    def __init__(self, parent, existing_files):
        """Prepare the application picker dialog"""
        # Load the user interface
        self.ui = GtkBuilderLoader(get_ui_file('application_picker.ui'))
        self.ui.dialog_application_picker.set_titlebar(self.ui.header_bar)
        # Prepares the models for the applications
        self.model_applications = ModelApplications(self.ui.store_applications)
        self.model_applications.model.set_sort_column_id(
            self.ui.treeview_column_applications.get_sort_column_id(),
            Gtk.SortType.ASCENDING)
        self.ui.filter_applications.set_visible_column(
            ModelApplications.COL_VISIBLE)
        # Initialize actions
        for widget in self.ui.get_objects_by_type(Gtk.Action):
            # Connect the actions accelerators
            widget.connect_accelerator()
            # Set labels
            label = widget.get_label()
            if not label:
                label = widget.get_short_label()
            widget.set_short_label(text(label))
            widget.set_label(text(label))
        # Initialize tooltips
        for widget in self.ui.get_objects_by_type(Gtk.Button):
            action = widget.get_related_action()
            if action:
                widget.set_tooltip_text(action.get_label().replace('_', ''))
        # Set various properties
        self.ui.dialog_application_picker.set_transient_for(parent)
        set_style_suggested_action(self.ui.button_add)
        self.selected_applications = None
        # Set preferences button icon
        icon_name = self.ui.image_preferences.get_icon_name()[0]
        if preferences.get(preferences.HEADERBARS_SYMBOLIC_ICONS):
            icon_name += '-symbolic'
        # Get desired icon size
        icon_size = (Gtk.IconSize.BUTTON
                     if preferences.get(preferences.HEADERBARS_SMALL_ICONS)
                     else Gtk.IconSize.LARGE_TOOLBAR)
        self.ui.image_preferences.set_from_icon_name(icon_name, icon_size)
        # Load settings
        self.dict_settings_map = {
            preferences.APP_PICKER_SHOW_HIDDEN:
                self.ui.action_show_hidden
        }
        for setting_name, action in self.dict_settings_map.items():
            action.set_active(preferences.get(setting_name))
        # Prepares the applications list
        for desktop_entry in Gio.app_info_get_all():
            try:
                icon_name = None
                icon = desktop_entry.get_icon()
                if isinstance(icon, Gio.ThemedIcon):
                    # From Gio.ThemedIcon get the icon name only
                    icons = desktop_entry.get_icon().get_names()
                    icon_name = icons[0] if icons else None
                elif isinstance(icon, Gio.FileIcon):
                    # From Gio.FileIcon get the full file name
                    icon_name = icon.get_file().get_parse_name()
                description = (desktop_entry.get_description()
                               if desktop_entry.get_description() else '')
                application = ApplicationInfo(desktop_entry.get_id(),
                                              desktop_entry.get_name(),
                                              description,
                                              icon_name,
                                              desktop_entry.should_show())
                # Skip existing files
                if application.filename not in existing_files:
                    self.model_applications.add_data(application)
            except Exception as e:
                print 'error for', desktop_entry.get_id(), e
        self.model_applications.set_all_rows_visibility(
            preferences.get(preferences.APP_PICKER_SHOW_HIDDEN))
        # Connect signals from the glade file to the module functions
        self.ui.connect_signals(self)

    def show(self):
        """Show the application picker dialog"""
        settings.positions.restore_window_position(
            self.ui.dialog_application_picker, SECTION_WINDOW_NAME)
        response = self.ui.dialog_application_picker.run()
        self.ui.dialog_application_picker.hide()
        return response

    def destroy(self):
        """Destroy the application picker dialog"""
        settings.positions.save_window_position(
            self.ui.dialog_application_picker, SECTION_WINDOW_NAME)
        self.ui.dialog_application_picker.destroy()
        self.ui.dialog_application_picker = None

    def on_action_close_activate(self, action):
        """Close the application picker dialog"""
        self.ui.dialog_application_picker.response(Gtk.ResponseType.CLOSE)

    def on_action_add_activate(self, action):
        """Add the selected application to the current AppFolder"""
        self.selected_applications = []
        for row in get_treeview_selected_rows(self.ui.treeview_applications):
            application = self.model_applications.get_key(
                self.ui.filter_applications.convert_path_to_child_path(row))
            self.selected_applications.append(application)
        self.ui.dialog_application_picker.response(Gtk.ResponseType.OK)

    def on_treeview_applications_row_activated(self, widget, path, column):
        """Add the selected application on row activation"""
        self.ui.action_add.activate()

    def on_treeview_selection_applications_changed(self, widget):
        """Set action sensitiveness on selection change"""
        selected_rows = get_treeview_selected_rows(
            self.ui.treeview_applications)
        self.ui.action_add.set_sensitive(bool(selected_rows))

    def on_action_preferences_toggled(self, widget):
        """Change a preference value"""
        for setting_name, action in self.dict_settings_map.items():
            if action is widget:
                preferences.set(setting_name, widget.get_active())

    def on_action_show_hidden_toggled(self, widget):
        """Set the visibility for all the Gtk.TreeModelRows"""
        self.model_applications.set_all_rows_visibility(
            self.ui.action_show_hidden.get_active())
class UICreateAppFolder(object):
    def __init__(self, parent, existing_folders):
        """Prepare the AppFolder creation dialog"""
        # Load the user interface
        self.ui = GtkBuilderLoader(get_ui_file('create_appfolder.ui'))
        self.ui.dialog_create_appfolder.set_titlebar(self.ui.header_bar)
        # Initialize actions
        for widget in self.ui.get_objects_by_type(Gtk.Action):
            # Connect the actions accelerators
            widget.connect_accelerator()
            # Set labels
            label = widget.get_label()
            if not label:
                label = widget.get_short_label()
            widget.set_short_label(text(label))
            widget.set_label(text(label))
        # Initialize labels
        for widget in self.ui.get_objects_by_type(Gtk.Label):
            widget.set_label(text(widget.get_label()))
        # Initialize tooltips
        for widget in self.ui.get_objects_by_type(Gtk.Button):
            action = widget.get_related_action()
            if action:
                widget.set_tooltip_text(action.get_label().replace('_', ''))
        # Set various properties
        self.ui.dialog_create_appfolder.set_transient_for(parent)
        set_style_suggested_action(self.ui.button_ok)
        self.existing_folders = existing_folders
        self.ui.button_ok.grab_default()
        self.folder_name = ''
        self.folder_title = ''
        # Connect signals from the glade file to the module functions
        self.ui.connect_signals(self)

    def show(self, name, title):
        """Show the dialog"""
        settings.positions.restore_window_position(
            self.ui.dialog_create_appfolder, SECTION_WINDOW_NAME)
        # Set initial values
        self.folder_name = name
        self.ui.entry_name.set_text(name)
        self.folder_title = title
        self.ui.entry_title.set_text(title)
        # Change label from Create folder to Save if a folder name was provided
        if name:
            self.ui.entry_name.set_sensitive(False)
            self.ui.button_ok.set_related_action(self.ui.action_save)
            self.ui.button_ok.set_tooltip_text(
                self.ui.action_save.get_label().replace('_', ''))
        response = self.ui.dialog_create_appfolder.run()
        self.ui.dialog_create_appfolder.hide()
        return response

    def destroy(self):
        """Destroy the dialog"""
        settings.positions.save_window_position(
            self.ui.dialog_create_appfolder, SECTION_WINDOW_NAME)
        self.ui.dialog_create_appfolder.destroy()
        self.ui.dialog_create_appfolder = None

    def on_action_close_activate(self, action):
        """Close the dialog"""
        self.ui.dialog_create_appfolder.response(Gtk.ResponseType.CLOSE)

    def on_action_confirm_activate(self, action):
        """Accept the folder name and title and close the dialog"""
        self.folder_name = self.ui.entry_name.get_text().strip()
        self.folder_title = self.ui.entry_title.get_text().strip()
        self.ui.dialog_create_appfolder.response(Gtk.ResponseType.OK)

    def on_entry_name_changed(self, widget):
        """Check if the folder already exists"""
        name = self.ui.entry_name.get_text().strip()
        existing = (name in self.existing_folders and name != self.folder_name)
        # If a folder with the specified name already exists
        # then disable the creation and show an icon
        self.ui.action_create.set_sensitive(bool(name) and not existing)
        self.ui.entry_name.set_icon_from_icon_name(
            Gtk.EntryIconPosition.SECONDARY,
            'dialog-error' if existing else None)
        self.ui.entry_name.set_icon_tooltip_text(
            Gtk.EntryIconPosition.SECONDARY,
            text('A folder with that name already exists')
            if existing else None)
class UIMain(object):
    def __init__(self, application):
        self.application = application
        # Load settings
        settings.settings = settings.Settings(FILE_SETTINGS, False)
        settings.positions = settings.Settings(FILE_WINDOWS_POSITION, False)
        self.folders = {}
        preferences.preferences = preferences.Preferences()
        self.loadUI()
        # Prepares the models for folders and applications
        self.model_folders = ModelAppFolders(self.ui.store_folders)
        self.model_applications = ModelApplications(self.ui.store_applications)
        self.model_applications.model.set_sort_column_id(
            self.ui.treeview_column_applications.get_sort_column_id(),
            Gtk.SortType.ASCENDING)
        # Detect the AppFolders and select the first one automatically
        self.reload_folders()
        if self.model_folders.count() > 0:
            self.ui.treeview_folders.set_cursor(0)
        self.ui.treeview_folders.grab_focus()
        # Restore the saved size and position
        settings.positions.restore_window_position(self.ui.win_main,
                                                   SECTION_WINDOW_NAME)

    def loadUI(self):
        """Load the interface UI"""
        self.ui = GtkBuilderLoader(get_ui_file('main.ui'))
        self.ui.win_main.set_application(self.application)
        self.ui.win_main.set_title(APP_NAME)
        # Initialize actions
        for widget in self.ui.get_objects_by_type(Gtk.Action):
            # Connect the actions accelerators
            widget.connect_accelerator()
            # Set labels
            label = widget.get_label()
            if not label:
                label = widget.get_short_label()
            widget.set_label(text(label))
            widget.set_short_label(label)
        # Initialize labels
        for widget in self.ui.get_objects_by_type(Gtk.Label):
            widget.set_label(text(widget.get_label()))
        # Initialize tooltips
        for widget in self.ui.get_objects_by_type(Gtk.Button):
            action = widget.get_related_action()
            if action:
                widget.set_tooltip_text(action.get_label().replace('_', ''))
        # Initialize Gtk.HeaderBar
        self.ui.header_bar.props.title = self.ui.win_main.get_title()
        self.ui.win_main.set_titlebar(self.ui.header_bar)
        for button in (
                self.ui.button_folder_new,
                self.ui.button_folder_remove,
                self.ui.button_folder_properties,
                self.ui.button_files_add,
                self.ui.button_files_remove,
                self.ui.button_files_save,
                self.ui.button_about,
        ):
            action = button.get_related_action()
            icon_name = action.get_icon_name()
            if preferences.get(preferences.HEADERBARS_SYMBOLIC_ICONS):
                icon_name += '-symbolic'
            # Get desired icon size
            icon_size = (Gtk.IconSize.BUTTON
                         if preferences.get(preferences.HEADERBARS_SMALL_ICONS)
                         else Gtk.IconSize.LARGE_TOOLBAR)
            button.set_image(Gtk.Image.new_from_icon_name(
                icon_name, icon_size))
            # Remove the button label
            button.props.label = None
            # Set the tooltip from the action label
            button.set_tooltip_text(action.get_label().replace('_', ''))
        # Set preferences button icon
        icon_name = self.ui.image_preferences.get_icon_name()[0]
        if preferences.get(preferences.HEADERBARS_SYMBOLIC_ICONS):
            icon_name += '-symbolic'
        self.ui.image_preferences.set_from_icon_name(icon_name, icon_size)
        # Load settings
        self.dict_settings_map = {
            preferences.HEADERBARS_SMALL_ICONS:
            self.ui.action_preferences_small_icons,
            preferences.HEADERBARS_SYMBOLIC_ICONS:
            self.ui.action_preferences_symbolic_icons,
            preferences.PREFERENCES_SHOW_MISSING:
            self.ui.action_preferences_show_missing_files
        }
        for setting_name, action in self.dict_settings_map.items():
            action.set_active(preferences.get(setting_name))
        # Connect signals from the glade file to the module functions
        self.ui.connect_signals(self)

    def run(self):
        """Show the UI"""
        self.ui.win_main.show_all()

    def on_win_main_delete_event(self, widget, event):
        """Save the settings and close the application"""
        settings.positions.save_window_position(self.ui.win_main,
                                                SECTION_WINDOW_NAME)
        settings.positions.save()
        settings.settings.save()
        self.application.quit()

    def on_action_about_activate(self, action):
        """Show the about dialog"""
        dialog = UIAbout(self.ui.win_main)
        dialog.show()
        dialog.destroy()

    def on_action_shortcuts_activate(self, action):
        """Show the shortcuts dialog"""
        dialog = UIShortcuts(self.ui.win_main)
        dialog.show()

    def on_action_quit_activate(self, action):
        """Close the application by closing the main window"""
        event = Gdk.Event()
        event.key.type = Gdk.EventType.DELETE
        self.ui.win_main.event(event)

    def reload_folders(self):
        """Reload the Application Folders"""
        self.folders = {}
        self.model_folders.clear()
        settings_folders = Gio.Settings.new(SCHEMA_FOLDERS)
        list_folders = settings_folders.get_strv('folder-children')
        for folder_name in list_folders:
            folder_info = FolderInfo(folder_name)
            appfolder = AppFolderInfo(folder_info)
            self.model_folders.add_data(appfolder)
            self.folders[folder_info.folder] = folder_info

    def on_treeview_folders_cursor_changed(self, widget):
        selected_row = get_treeview_selected_row(self.ui.treeview_folders)
        if selected_row:
            folder_name = self.model_folders.get_key(selected_row)
            # Check if the folder still exists
            # (model erased while the cursor moves through the Gtk.TreeView)
            if folder_name in self.folders:
                folder_info = self.folders[folder_name]
                # Clear any previous application icon
                self.model_applications.clear()
                # Add new application icons
                applications = folder_info.get_applications()
                for application in applications:
                    desktop_file = applications[application]
                    if desktop_file or preferences.get(
                            preferences.PREFERENCES_SHOW_MISSING):
                        application_file = applications[application]
                        application_info = ApplicationInfo(
                            application,
                            application_file.getName()
                            if desktop_file else 'Missing desktop file',
                            application_file.getComment()
                            if desktop_file else application,
                            application_file.getIcon()
                            if desktop_file else None,
                            # Always show any application, also if hidden
                            True)
                        self.model_applications.add_data(application_info)
            # Disable folder content saving
            self.ui.action_files_save.set_sensitive(False)

    def on_treeview_selection_folders_changed(self, widget):
        """Set action sensitiveness on selection change"""
        selected_row = get_treeview_selected_row(self.ui.treeview_folders)
        for widget in (self.ui.action_folders_remove,
                       self.ui.action_folders_properties,
                       self.ui.action_files_new):
            widget.set_sensitive(bool(selected_row))
        if not selected_row:
            self.ui.action_files_new.set_sensitive(False)
            self.ui.action_files_remove.set_sensitive(False)
            self.ui.action_files_save.set_sensitive(False)
            self.model_applications.clear()

    def on_action_files_new_activate(self, action):
        """Show an application picker to add to the current AppFolder"""
        dialog = UIApplicationPicker(self.ui.win_main,
                                     self.model_applications.rows.keys())
        if dialog.show() == Gtk.ResponseType.OK:
            if dialog.selected_applications:
                for application in dialog.selected_applications:
                    # Get the selected application in the application picker
                    # and add it to the current AppFolder
                    application_info = dialog.model_applications.items[
                        application]
                    self.model_applications.add_data(application_info)
                    # Automatically select the newly added application
                    filename = application_info.filename
                    treeiter = self.model_applications.rows[filename]
                if treeiter:
                    # Automatically select the last added application
                    self.ui.treeview_applications.set_cursor(
                        self.model_applications.get_path(treeiter))
                # Enable folder content saving
                self.ui.action_files_save.set_sensitive(True)
        dialog.destroy()

    def on_treeview_selection_applications_changed(self, widget):
        """Set action sensitiveness on selection change"""
        selected_row = get_treeview_selected_row(self.ui.treeview_applications)
        self.ui.action_files_remove.set_sensitive(bool(selected_row))

    def on_action_files_remove_activate(self, action):
        """Remove the selected application from the current AppFolder"""
        selected_row = get_treeview_selected_row(self.ui.treeview_applications)
        if selected_row:
            self.model_applications.remove(selected_row)
            # Enable folder content saving
            self.ui.action_files_save.set_sensitive(True)

    def on_action_files_save_activate(self, action):
        """Save the current AppFolder"""
        selected_row = get_treeview_selected_row(self.ui.treeview_folders)
        if selected_row:
            folder_name = self.model_folders.get_key(selected_row)
            folder_info = self.folders[folder_name]
            folder_info.set_applications(self.model_applications.rows.keys())
        # Disable folder content saving
        self.ui.action_files_save.set_sensitive(False)

    def on_action_folders_remove_activate(self, action):
        """Remove the current AppFolder"""
        selected_row = get_treeview_selected_row(self.ui.treeview_folders)
        if selected_row:
            folder_name = self.model_folders.get_key(selected_row)
            if show_message_dialog(class_=UIMessageDialogNoYes,
                                   parent=self.ui.win_main,
                                   message_type=Gtk.MessageType.QUESTION,
                                   title=None,
                                   msg1=_('Remove the selected folder?'),
                                   msg2=_('Are you sure you want to remove '
                                          'the folder %s?') % folder_name,
                                   is_response_id=Gtk.ResponseType.YES):
                # Remove the AppFolder from settings
                folder_info = self.folders[folder_name]
                folder_info.remove()
                self.folders.pop(folder_name)
                # Remove the folder name from the folders list
                settings_folders = Gio.Settings.new(SCHEMA_FOLDERS)
                list_folders = settings_folders.get_strv('folder-children')
                list_folders.remove(folder_name)
                settings_folders.set_strv('folder-children', list_folders)
                # Clear the applications model
                self.model_applications.clear()
                # Remove the folder from the folders model
                self.model_folders.remove(selected_row)

    def on_action_folders_new_activate(self, action):
        """Creat a new AppFolder"""
        dialog = UICreateAppFolder(self.ui.win_main,
                                   self.model_folders.rows.keys())
        if dialog.show(name='', title='') == Gtk.ResponseType.OK:
            # Create a new FolderInfo object and set its title
            folder_info = FolderInfo(dialog.folder_name)
            folder_info.set_title(dialog.folder_title)
            # Add the folder to the folders list
            settings_folders = Gio.Settings.new(SCHEMA_FOLDERS)
            list_folders = settings_folders.get_strv('folder-children')
            list_folders.append(dialog.folder_name)
            settings_folders.set_strv('folder-children', list_folders)
            # Reload folders list
            self.reload_folders()
        dialog.destroy()

    def on_action_folders_properties_activate(self, action):
        """Set the AppFolder properties"""
        selected_row = get_treeview_selected_row(self.ui.treeview_folders)
        if selected_row:
            name = self.model_folders.get_key(selected_row)
            title = self.model_folders.get_title(selected_row)
            dialog = UICreateAppFolder(self.ui.win_main,
                                       self.model_folders.rows.keys())
            if dialog.show(name=name, title=title) == Gtk.ResponseType.OK:
                folder_name = dialog.folder_name
                folder_title = dialog.folder_title
                # Update the folder title
                folder_info = self.folders[folder_name]
                folder_info.name = folder_title
                folder_info.set_title(folder_title)
                # Reload the folders list and select the folder again
                self.reload_folders()
                self.ui.treeview_folders.set_cursor(
                    self.model_folders.get_path_by_name(folder_name))
            dialog.destroy()

    def on_action_preferences_toggled(self, widget):
        """Change a preference value"""
        for setting_name, action in self.dict_settings_map.items():
            if action is widget:
                preferences.set(setting_name, widget.get_active())

    def on_action_preferences_need_restart_toggled(self, widget):
        """Show the infobar to require application restart"""
        self.ui.label_infobar.set_label(
            _('The application must be restarted to apply the settings'))
        self.ui.infobar.show()

    def on_action_preferences_show_missing_files_toggled(self, widget):
        """Show and hide the missing desktop files"""
        self.on_treeview_folders_cursor_changed(self.ui.treeview_folders)

    def on_treeview_folders_row_activated(self, widget, path, column):
        """Show folder properties on activation"""
        self.ui.action_folders_properties.activate()