Second version with improved documentation --- pyanaconda/ui/gui/spokes/software.py | 9 +- pyanaconda/ui/gui/spokes/source.py | 172 ++++++++++++++ pyanaconda/ui/gui/spokes/source.ui | 422 +++++++++++++++++++++++++++++++++- 3 files changed, 598 insertions(+), 5 deletions(-) diff --git a/pyanaconda/ui/gui/spokes/software.py b/pyanaconda/ui/gui/spokes/software.py index 582c3d1..36e8802 100644 --- a/pyanaconda/ui/gui/spokes/software.py +++ b/pyanaconda/ui/gui/spokes/software.py @@ -27,6 +27,8 @@ from pyanaconda.ui.gui import communication from pyanaconda.ui.gui.spokes import NormalSpoke from pyanaconda.ui.gui.utils import gdk_threaded from pyanaconda.ui.gui.categories.software import SoftwareCategory +from pyanaconda.ui.gui.utils import enlightbox +from .source import AdditionalReposDialog __all__ = ["SoftwareSelectionSpoke"] @@ -48,6 +50,8 @@ class SoftwareSelectionSpoke(NormalSpoke): self.excludedGroups = [] self.desktop = None + self._addRepoDialog = AdditionalReposDialog(self.data) + def apply(self): # NOTE: Other apply methods work directly with the ksdata, but this # one does not. However, selectGroup/deselectGroup modifies ksdata as @@ -226,5 +230,6 @@ class SoftwareSelectionSpoke(NormalSpoke): self.selectedGroups.remove(group) def on_custom_clicked(self, button): - # FIXME: does nothing for now - pass + with enlightbox(self.window, self._addRepoDialog.window): + response = self._addRepoDialog.run() + diff --git a/pyanaconda/ui/gui/spokes/source.py b/pyanaconda/ui/gui/spokes/source.py index f607cdc..df59434 100644 --- a/pyanaconda/ui/gui/spokes/source.py +++ b/pyanaconda/ui/gui/spokes/source.py @@ -17,6 +17,7 @@ # Red Hat, Inc. # # Red Hat Author(s): Chris Lumens <clumens@xxxxxxxxxx> +# Martin Sivak <msivak@xxxxxxxxxx> # import gettext @@ -229,6 +230,177 @@ class IsoChooser(UIObject): if not d.startswith(MOUNTPOINT): chooser.set_current_folder(MOUNTPOINT) +class AdditionalReposDialog(UIObject): + builderObjects = ["additionalReposDialog", "peopleRepositories", "peopleRepositoriesFilter"] + mainWidgetName = "additionalReposDialog" + uiFile = "spokes/source.ui" + + typingTimeout = 1 + + def __init__(self, *args, **kwargs): + UIObject.__init__(self, *args, **kwargs) + + self._filterTimer = None + self._urlTimer = None + self._timeoutAdd = GLib.timeout_add_seconds + self._timeoutRemove = GLib.source_remove + + # count the number of times the repository check was started + # so we allow only the last thread to update the validity and + # description of the entered repository url + self._epoch = 0 + + # Repository url + self._repositoryUrl = self.builder.get_object("addRepositoryUrl") + self._repositoryDesc = self.builder.get_object("addRepositoryDesc") + self._repositoryIcon = self.builder.get_object("addRepositoryIcon") + self._repositorySpinner = self.builder.get_object("addRepositorySpinner") + self._urlGrid = self.builder.get_object("urlGrid") + + # Repository list + self._peopleRepositories = self.builder.get_object("peopleRepositories") + self._peopleRepositoriesGrid = self.builder.get_object("peopleRepositoriesGrid") + self._peopleRepositoriesView = self.builder.get_object("addRepositoryList") + self._peopleRepositoriesFilter = self.builder.get_object("peopleRepositoriesFilter") + self._peopleRepositoriesFilterEntry = self.builder.get_object("addRepositoryFilter") + self._peopleRepositoriesFilterValue = "" + + self._peopleRepositoriesFilter.set_visible_func(self.repoVisible, self._peopleRepositoriesFilterEntry) + + # Radio button + self._sourceSelectionListLabel = self.builder.get_object("listGridLabel") + self._sourceSelectionList = self.builder.get_object("addRepositorySelectList") + self._sourceSelectionUrlLabel = self.builder.get_object("urlGridLabel") + self._sourceSelectionUrl = self.builder.get_object("addRepositorySelectUrl") + + def refresh(self, currentFile=""): + UIObject.refresh(self) + + def run(self): + retval = None + + self._peopleRepositoriesFilter.refilter() + self._peopleRepositoriesFilterValue = self._peopleRepositoriesFilterEntry.get_text() + self.on_source_changed() + + self.window.show() + rc = self.window.run() + if rc: + retval = "some value" + self.window.hide() + + return retval + + def repoVisible(self, model, iter, filterString): + """This method is responsible for people repositories list filtering, + it returns True only for fields which cointain filterString as a substring""" + return filterString in model[iter][0] + + def on_source_changed(self, w = None): + """Callbacks which gets called when the radio buttons change. + It makes proper areas (in)sensitive.""" + sourceArea = self._sourceSelectionList.get_active() + + self._peopleRepositoriesGrid.foreach(lambda w, v: w.set_sensitive(v), sourceArea) + self._urlGrid.foreach(lambda w, v: w.set_sensitive(v), not sourceArea) + + self._sourceSelectionList.set_sensitive(True) + self._sourceSelectionListLabel.set_sensitive(True) + self._sourceSelectionUrl.set_sensitive(True) + self._sourceSelectionUrlLabel.set_sensitive(True) + + def on_list_title_clicked(self, w, d): + """Callback that handles clicking on the EventBox around people + repositories label to mimick the standard radio button label + behaviour which we had to give up for the sake of design.""" + self._sourceSelectionList.set_active(True) + + def on_url_title_clicked(self, w, d): + """Callback that handles clicking on the EventBox around repo + url label to mimick the standard radio button label + behaviour which we had to give up for the sake of design.""" + self._sourceSelectionUrl.set_active(True) + + def on_url_timeout(self, w): + """This method starts url checker thread and updates the GUI + elements like spinner and description to notify the user + about it.""" + # start resolve thread with epoch info + + self._repositorySpinner.start() + self._repositoryDesc.set_text(_("Getting info about requested repository")) + self._repositoryIcon.set_from_stock("XXX_RESOLVING", Gtk.GTK_ICON_SIZE_MENU) + + return False + + def on_url_changed(self, w): + """This callback is called when the user changes anything in the url + text entry field. It resets the typing timer so that url checks + are not started for every character typed and it also increments + self._epoch counter to prevent older running url checks from + updating the dialog with now outdated data.""" + if self._urlTimer: + # optimistic locking, prevent old, but possibly running threads from updating status + self._epoch += 1 + self._timeoutRemove(self._urlTimer) + + if self._repositoryUrl.get_text(): + self._urlTimer = self._timeoutAdd(self.typingTimeout, self.on_url_timeout, w) + + def on_url_icon_press(self, w, pos, event): + """Callback for the delete all icon in url text field.""" + self._repositoryUrl.set_text("") + self.on_url_changed(w, None) + self.repository_status(None, _("enter URL of your desired repository")) + + def repository_status(self, valid, description, epoch = None, still_spinning = False): + """Helper method to update the icon, spinner and decription text + around the url text entry.""" + # if an older thread want to update status, do not let him + if epoch is not None and epoch != self._epoch: + return + + self._repositoryDesc.set_text(description) + + if valid is None: + self._repositoryIcon.set_from_stock("XXX_NONE", Gtk.GTK_ICON_SIZE_MENU) + elif valid: + self._repositoryIcon.set_from_stock("GTK_APPLY", Gtk.GTK_ICON_SIZE_MENU) + else: + self._repositoryIcon.set_from_stock("GTK_ERROR", Gtk.GTK_ICON_SIZE_MENU) + + if not still_spinning: + self._repositorySpinner.stop() + + def on_filter_timeout(self, w): + """Timer callback for delayed filtering of people repositories list.""" + self._peopleRepositoriesFilterValue = w.get_text() + self._peopleRepositoriesFilter.refilter() + return False + + def on_filter_changed(self, w): + """This method is called when user changes something in the people + repositories field. It resets a timer so the update is not done + until the user stopped typing for a while.""" + if self._filterTimer: + self._timeoutRemove(self._filterTimer) + + self._filterTimer = self._timeoutAdd(self.typingTimeout, + self.on_filter_timeout, + self._peopleRepositoriesFilterEntry) + + def on_selection_changed(self, selection): + """Callback called when user selects something in the people + repositories list.""" + pass + + def on_filter_icon_press(self, w, pos, event): + """Callback for delete all icon in the people repositories filter + text entry.""" + self._peopleRepositoriesFilter.set_text("") + self.on_filter_timeout(w, None) + + class SourceSpoke(NormalSpoke): builderObjects = ["isoChooser", "isoFilter", "partitionStore", "sourceWindow", "dirImage"] mainWidgetName = "sourceWindow" diff --git a/pyanaconda/ui/gui/spokes/source.ui b/pyanaconda/ui/gui/spokes/source.ui index 1c2cfa2..160d4cd 100644 --- a/pyanaconda/ui/gui/spokes/source.ui +++ b/pyanaconda/ui/gui/spokes/source.ui @@ -2,6 +2,369 @@ <interface> <!-- interface-requires gtk+ 3.0 --> <!-- interface-requires AnacondaWidgets 1.0 --> + <object class="GtkDialog" id="additionalReposDialog"> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="border_width">5</property> + <property name="type_hint">dialog</property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox5"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area5"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="add_repo_cancel_button"> + <property name="label">gtk-cancel</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="add_repo_add_button"> + <property name="label" translatable="yes">Add</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">8</property> + <child> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">ADD A CUSTOM ADD-ON</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label13"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">WHERE IS THE YUM REPOSITORY FOR YOUR ADD-ON?</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="peopleRepositoriesGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="vexpand">True</property> + <property name="row_spacing">2</property> + <child> + <object class="GtkEntry" id="addRepositoryFilter"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="secondary_icon_stock">gtk-delete</property> + <signal name="changed" handler="on_filter_changed" swapped="no"/> + <signal name="icon-press" handler="on_filter_icon_press" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="vscrollbar_policy">always</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTreeView" id="addRepositoryList"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="model">peopleRepositoriesFilter</property> + <property name="headers_visible">False</property> + <property name="search_column">1</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection1"> + <signal name="changed" handler="on_selection_changed" swapped="no"/> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn1"> + <property name="title" translatable="yes">Repository</property> + <child> + <object class="GtkCellRendererText" id="repoRenderer"/> + <attributes> + <attribute name="markup">0</attribute> + <attribute name="wrap-width">2</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="addRepositorySelectList"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="xalign">0</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + <signal name="toggled" handler="on_source_changed" swapped="no"/> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEventBox" id="listGridLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_BUTTON_PRESS_MASK | GDK_STRUCTURE_MASK</property> + <signal name="button-press-event" handler="on_list_title_clicked" swapped="no"/> + <child> + <object class="GtkLabel" id="labelxyz1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Fedora People Repositories</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="urlGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">2</property> + <child> + <object class="GtkEntry" id="addRepositoryUrl"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="secondary_icon_stock">gtk-delete</property> + <signal name="changed" handler="on_url_changed" swapped="no"/> + <signal name="icon-press" handler="on_url_icon_press" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="addRepositorySelectUrl"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="xalign">0</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + <property name="group">addRepositorySelectList</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="addRepositoryDesc"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">enter URL of your desired repository</property> + <attributes> + <attribute name="style" value="italic"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkSpinner" id="addRepositorySpinner"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkImage" id="addRepositoryIcon"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="stock">gtk-apply</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEventBox" id="urlGridLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_BUTTON_PRESS_MASK | GDK_STRUCTURE_MASK</property> + <signal name="button-press-event" handler="on_url_title_clicked" swapped="no"/> + <child> + <object class="GtkLabel" id="labelxyz2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Standard YUM repository:</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">add_repo_cancel_button</action-widget> + <action-widget response="0">add_repo_add_button</action-widget> + </action-widgets> + </object> <object class="GtkFileFilter" id="isoFilter"> <patterns> <pattern>*.iso</pattern> @@ -219,6 +582,29 @@ <column type="gchararray"/> </columns> </object> + <object class="GtkListStore" id="peopleRepositories"> + <columns> + <!-- column-name repository --> + <column type="gchararray"/> + <!-- column-name repository_url --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes"><b>User repo</b> +my testing repository</col> + <col id="1" translatable="yes">http://url</col> + </row> + <row> + <col id="0" translatable="yes"><b>TeXLive</b> +TeXlive 2012 repository</col> + <col id="1" translatable="yes">http://jnovy.texlive</col> + </row> + </data> + </object> + <object class="GtkTreeModelFilter" id="peopleRepositoriesFilter"> + <property name="child_model">peopleRepositories</property> + </object> <object class="GtkDialog" id="proxyDialog"> <property name="can_focus">False</property> <property name="border_width">5</property> @@ -317,9 +703,6 @@ <property name="row_spacing">6</property> <property name="column_spacing">6</property> <child> - <placeholder/> - </child> - <child> <object class="GtkLabel" id="label5"> <property name="visible">True</property> <property name="can_focus">False</property> @@ -362,6 +745,24 @@ <property name="height">1</property> </packing> </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> </object> <packing> <property name="expand">False</property> @@ -452,6 +853,21 @@ <property name="height">1</property> </packing> </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> </object> <packing> <property name="expand">False</property> -- 1.7.10.2 _______________________________________________ Anaconda-devel-list mailing list Anaconda-devel-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/anaconda-devel-list