[PATCH] add the foundations for the add repository dialog

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This adds the dialog for adding additional repositories. It supports
filtering of repositories (currently two hardcoded examples) and url entry.

All checks (not coded atm) and filters are executed after a 1 sec pause in
typing to save bandwidth and cpu cycles.

Typed url will be checked for validity and repo existence, but if the user
changes the url before the download and check are complete, self.epoch goes
up to prevent reporting status of his old url.

---
 pyanaconda/ui/gui/spokes/software.py |    9 +-
 pyanaconda/ui/gui/spokes/source.py   |  145 +++++++++++-
 pyanaconda/ui/gui/spokes/source.ui   |  420 +++++++++++++++++++++++++++++++++-
 3 files changed, 568 insertions(+), 6 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..b0b291f 100644
--- a/pyanaconda/ui/gui/spokes/source.py
+++ b/pyanaconda/ui/gui/spokes/source.py
@@ -25,7 +25,7 @@ N_ = lambda x: x
 
 import os.path
 
-from gi.repository import AnacondaWidgets, GLib, Gtk
+from gi.repository import AnacondaWidgets, GLib, Gtk 
 
 from pyanaconda.image import opticalInstallMedia, potentialHdisoSources
 from pyanaconda.ui.gui import UIObject, communication
@@ -229,6 +229,149 @@ 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, filterEntry):
+        return self._peopleRepositoriesFilterValue in model[iter][0]
+
+    def on_source_changed(self, w = None):
+        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):
+        print "click1"
+        self._sourceSelectionList.set_active(True)
+
+    def on_url_title_clicked(self, w, d):
+        print "click2"
+        self._sourceSelectionUrl.set_active(True)
+
+    def on_url_timeout(self, w):
+        # 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):
+        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):
+        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):
+        # 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):
+        self._peopleRepositoriesFilterValue = w.get_text()
+        self._peopleRepositoriesFilter.refilter()
+        return False
+    
+    def on_filter_changed(self, w):
+        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):
+        pass
+
+    def on_filter_icon_press(self, w, pos, event):
+        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..e51a46f 100644
--- a/pyanaconda/ui/gui/spokes/source.ui
+++ b/pyanaconda/ui/gui/spokes/source.ui
@@ -2,6 +2,367 @@
 <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="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="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>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </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>
+              </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>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </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>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</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 +580,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">&lt;b&gt;User repo&lt;/b&gt;
+my testing repository</col>
+        <col id="1" translatable="yes">http://url</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">&lt;b&gt;TeXLive&lt;/b&gt;
+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 +701,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 +743,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 +851,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.1

_______________________________________________
Anaconda-devel-list mailing list
Anaconda-devel-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/anaconda-devel-list



[Index of Archives]     [Kickstart]     [Fedora Users]     [Fedora Legacy List]     [Fedora Maintainers]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [Yosemite Photos]     [KDE Users]     [Fedora Tools]
  Powered by Linux