home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / python2.4 / site-packages / serpentine / gtkutil.py < prev    next >
Encoding:
Python Source  |  2006-08-23  |  34.5 KB  |  1,163 lines

  1. # Copyright (C) 2004 Tiago Cogumbreiro <cogumbreiro@users.sf.net>
  2. #
  3. # This library is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU Library General Public
  5. # License as published by the Free Software Foundation; either
  6. # version 2 of the License, or (at your option) any later version.
  7. #
  8. # This library is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  11. # Library General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Library General Public
  14. # License along with this library; if not, write to the
  15. # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  16. # Boston, MA 02111-1307, USA.
  17. #
  18. # Authors: Tiago Cogumbreiro <cogumbreiro@users.sf.net>
  19.  
  20. import gtk
  21. import gobject
  22. import datetime
  23.  
  24. from gettext import gettext as _
  25. from gettext import ngettext as N_
  26.  
  27. class NotFoundError(KeyError):
  28.     """This is raised when an element is not found"""
  29.  
  30.  
  31. class DictModelIterator:
  32.     def __init__ (self, parent):
  33.         self.parent = parent
  34.         self.index = 0
  35.         
  36.     def __iter__ (self):
  37.         return self
  38.     
  39.     def next (self):
  40.         if len(self.parent) <= self.index:
  41.             raise StopIteration
  42.         
  43.         ret = self.parent.get(self.index)
  44.         self.index += 1
  45.         return ret
  46.         
  47. class DictStore (gtk.ListStore):
  48.     def __init__ (self, *cols):
  49.         self.indexes = {}
  50.         self.cols = []
  51.         spec = []
  52.         index = 0
  53.         for col in cols:
  54.             # Generate indexes dict
  55.             self.indexes[col["name"]] = index
  56.             spec.append (col["type"])
  57.             self.cols.append (col["name"])
  58.             index += 1
  59.         gtk.ListStore.__init__ (self, *tuple(spec))
  60.         
  61.     def __dict_to_list (self, row):
  62.         values = []
  63.         for key in self.cols:
  64.             values.append (row[key])
  65.         return values
  66.         
  67.     def append (self, row):
  68.         gtk.ListStore.append (self, self.__dict_to_list (row))
  69.     
  70.     def insert_before (self, iter, row):
  71.         gtk.ListStore.insert_before (self, iter, self.__dict_to_list (row))
  72.     
  73.     def insert_after (self, iter, row):
  74.         gtk.ListStore.insert_after (self, iter, self.__dict_to_list (row))
  75.         
  76.     def index_of (self, key):
  77.         return self.indexes[key]
  78.     def get (self, index):
  79.         return DictModelRow(self, self[index])
  80.     def __iter__ (self):
  81.         return DictModelIterator(self)
  82.         
  83. gobject.type_register (DictStore)
  84.  
  85. class DictModelRow (object):
  86.     def __init__ (self, parent, row):
  87.         self.parent = parent
  88.         self.row = row
  89.         
  90.     def __getitem__ (self, key):
  91.         return self.row[self.parent.indexes[key]]
  92.         
  93.     def __setitem__ (self, key, value):
  94.         self.row[self.parent.indexes[key]] = value
  95.         
  96.     def __delitem__ (self, key):
  97.         del self.row[self.parent.indexes[key]]
  98.     
  99.     def keys(self):
  100.         return self.parent.cols
  101.     
  102.     def get (self, key, default):
  103.         if self.has_key (key):
  104.             return self[key]
  105.         return default
  106.     
  107.     def has_key (self, key):
  108.         return key in self.keys()
  109.  
  110.     __contains__ = has_key
  111.  
  112. class SimpleListWrapper:
  113.     def __init__ (self, store):
  114.         self.store = store
  115.         
  116.     def __getitem__ (self, index):
  117.         return self.store[index][0]
  118.         
  119.     def append (self, item):
  120.         self.store.append ([item])
  121.  
  122. class SimpleList (gtk.TreeView):
  123.     def __init__ (self, column_title, editable = True):
  124.         gtk.TreeView.__init__(self)
  125.         store = gtk.ListStore(str)
  126.         self.set_model (store)
  127.         rend = gtk.CellRendererText()
  128.         if editable:
  129.             rend.set_property ('editable', True)
  130.             rend.connect ('edited', self.__on_text_edited)
  131.             
  132.         if not column_title:
  133.             self.set_headers_visible (False)
  134.             column_title = ""
  135.         col = gtk.TreeViewColumn(column_title, rend, text = 0)
  136.         self.append_column (col)
  137.         self.wrapper = SimpleListWrapper (store)
  138.         
  139.     def get_simple_store(self):
  140.         return self.wrapper
  141.     
  142.     def __on_text_edited (self, cell, path, new_text, user_data = None):
  143.         self.get_model()[path][0] = new_text
  144.  
  145. gobject.type_register (SimpleList)
  146.  
  147. ################################################################################
  148. # rat.hig
  149. dialog_error = \
  150.     lambda primary_text, secondary_text, **kwargs:\
  151.         hig_alert(
  152.             primary_text,
  153.             secondary_text,
  154.             stock = gtk.STOCK_DIALOG_ERROR,
  155.             buttons =(gtk.STOCK_CLOSE, gtk.RESPONSE_OK),
  156.             **kwargs
  157.         )
  158.     
  159.  
  160. dialog_warn = \
  161.     lambda primary_text, secondary_text, **kwargs:\
  162.         hig_alert(
  163.             primary_text,
  164.             secondary_text,
  165.             stock = gtk.STOCK_DIALOG_WARNING,
  166.             buttons =(gtk.STOCK_CLOSE, gtk.RESPONSE_OK),
  167.             **kwargs
  168.         )
  169.  
  170. dialog_ok_cancel = \
  171.     lambda primary_text, secondary_text, ok_button=gtk.STOCK_OK, \
  172.         **kwargs: hig_alert(
  173.             primary_text,
  174.             secondary_text,
  175.             stock = gtk.STOCK_DIALOG_WARNING,
  176.             buttons =(
  177.                 gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
  178.                 ok_button, gtk.RESPONSE_OK
  179.             ),
  180.             **kwargs)
  181.  
  182. dialog_info = \
  183.     lambda primary_text, secondary_text, **kwargs:\
  184.         hig_alert(
  185.             primary_text,
  186.             secondary_text,
  187.             stock = gtk.STOCK_DIALOG_INFO,
  188.             buttons =(gtk.STOCK_CLOSE, gtk.RESPONSE_OK),
  189.             **kwargs
  190.         )
  191.  
  192. class WidgetCostumizer:
  193.     """
  194.     The WidgetCostumizer is a class template for defining chaining of asseblies
  195.     of interfaces. For example you can create a dialog with this simple lines of
  196.     code::
  197.     
  198.         creator.bind(SetupDialog()).bind(SetupAlert(primary_text, secondary_text, **kwargs))
  199.         dlg = creator()
  200.     
  201.     The costumization of a widget is like a pipeline of filters that act on a
  202.     certain widget and on a toplevel container.
  203.     
  204.     """
  205.     _to_attrs = True
  206.     _defaults = {}
  207.     _next_values = None
  208.     _next_iter = None
  209.     
  210.     def __init__(self, *args, **kwargs):
  211.         self._args  = args
  212.         self._kwargs = dict(self._defaults)
  213.         self._kwargs.update(kwargs)
  214.         self._next_values = []
  215.     
  216.     def _get_next(self):
  217.         return self._next_iter.next()
  218.     
  219.     def update(self, **kwargs):
  220.         self._kwargs.update(kwargs)
  221.     
  222.     def _run(self, *args, **kwargs):
  223.         pass
  224.     
  225.     def bind(self, *others):
  226.         for other in others:
  227.             if not isinstance(other, WidgetCostumizer):
  228.                 raise TypeError(type(other))
  229.             
  230.             self._next_values.append(other)
  231.  
  232.         return self
  233.     
  234.     def _call(self, widget, container):
  235.         if self._to_attrs:
  236.             for key, value in self._kwargs.iteritems():
  237.                 setattr(self, key, value)
  238.             
  239.         widget, container = self._run(widget, container)
  240.         
  241.         for costum in self._next_values:
  242.             widget, container = costum._call(widget, container)
  243.         
  244.         for key in self._kwargs:
  245.             delattr(self, key)
  246.         return widget, container
  247.         
  248.     def __call__(self, widget = None, container = None):
  249.         """This method is only called once"""
  250.         return self._call(widget, container)[0]
  251.  
  252. class SetupScrolledWindow(WidgetCostumizer):
  253.     def _run(self, scrolled, container):
  254.         assert container is None
  255.  
  256.         if scrolled is None:
  257.             scrolled = gtk.ScrolledWindow()
  258.         
  259.         scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
  260.         return scrolled, None
  261.         
  262. class SetupLabel(WidgetCostumizer):
  263.     """
  264.     Usage::
  265.  
  266.         lbl = SetupLabel("<b>foo</b>")(gtk.Label())
  267.         lbl.show()
  268.         
  269.     Or::
  270.     
  271.         lbl = SetupLabel("<b>foo</b>")()
  272.         lbl.show()
  273.     
  274.     """
  275.     def _run(self, lbl, container):
  276.         assert container is None
  277.         assert len(self._args) == 0 or len(self._args) == 1
  278.         
  279.         if lbl is None:
  280.             lbl = gtk.Label()
  281.             
  282.         lbl.set_alignment(0, 0)
  283.         
  284.         if len(self._args) == 1:
  285.             lbl.set_markup(self._args[0])
  286.             
  287.         lbl.set_selectable(True)
  288.         lbl.set_line_wrap(True)
  289.         
  290.         return lbl, container
  291.             
  292. def dialog_decorator(func):
  293.     def wrapper(self, dialog, container):
  294.         if container is None:
  295.             container = dialog.get_child()
  296.         return func(self, dialog, container)
  297.         
  298.     return wrapper
  299.  
  300. class SetupDialog(WidgetCostumizer):
  301.     def _run(self, dialog, container):
  302.         dialog.set_border_width(4)
  303.         dialog.set_has_separator(False)
  304.         dialog.set_title("")
  305.         dialog.set_resizable(False)
  306.  
  307.         align = gtk.Alignment()
  308.         align.set_padding(
  309.             padding_top = 0,
  310.             padding_bottom = 7,
  311.             padding_left = 0,
  312.             padding_right = 0
  313.         )
  314.         align.set_border_width(5)
  315.         align.show()
  316.         container.add(align)
  317.         
  318.         return dialog, align
  319.     
  320.     _run = dialog_decorator(_run)
  321.  
  322.  
  323. class SetupAlert(WidgetCostumizer):
  324.     class _PrimaryTextDecorator:
  325.         def __init__(self, label):
  326.             self.label = label
  327.             
  328.         def __call__(self, primary_text):
  329.             self.label.set_markup(
  330.                 '<span weight="bold" size="larger">'+primary_text+'</span>'
  331.             )
  332.         
  333.             
  334.     _defaults = {
  335.         "title": "",
  336.         "stock": gtk.STOCK_DIALOG_INFO
  337.     }
  338.     
  339.     def _before_text(self, dialog, vbox):
  340.         pass
  341.     
  342.     def _after_text(self, dialog, vbox):
  343.         pass
  344.     
  345.     def _run(self, dialog, container):
  346.         primary_text, secondary_text = self._args
  347.  
  348.         dialog.set_title(self.title)
  349.  
  350.         hbox = gtk.HBox(spacing = 12)
  351.         hbox.set_border_width(0)
  352.         hbox.show()
  353.         container.add(hbox)
  354.         
  355.         img = gtk.Image()
  356.         img.set_from_stock(self.stock, gtk.ICON_SIZE_DIALOG)
  357.         img.set_alignment(img.get_alignment()[0], 0.0)
  358.         img.show()
  359.         hbox.pack_start(img, False, False)
  360.         
  361.         vbox = gtk.VBox(spacing = 6)
  362.         vbox.show()
  363.         hbox.pack_start(vbox)
  364.         
  365.         
  366.         lbl = SetupLabel(
  367.             '<span weight="bold" size="larger">'+primary_text+'</span>'
  368.         )()
  369.         lbl.show()
  370.         
  371.         
  372.         dialog.set_primary_text = self._PrimaryTextDecorator(lbl)
  373.         
  374.         vbox.pack_start(lbl, False, False)
  375.         
  376.         lbl = SetupLabel(secondary_text)()
  377.         lbl.show()
  378.         dialog.set_secondary_text = lbl.set_text
  379.         
  380.         def on_destroy(widget):
  381.             delattr(widget, "set_secondary_text")
  382.             delattr(widget, "set_primary_text")
  383.         
  384.         dialog.connect("destroy", on_destroy)
  385.         
  386.         self._before_text(dialog, vbox)
  387.         vbox.pack_start(lbl, False, False)
  388.         self._after_text(dialog, vbox)
  389.         
  390.         return dialog, vbox
  391.  
  392.     _run = dialog_decorator(_run)
  393.  
  394.  
  395. class SetupRadioChoiceList(SetupAlert):
  396.     
  397.     def _after_text(self, dialog, container):
  398.         vbox = gtk.VBox(spacing=6)
  399.         vbox.show()
  400.         vbox.set_name("items")
  401.         
  402.         container.pack_start(vbox)
  403.         group = None
  404.         for item in self.items:
  405.             radio = gtk.RadioButton(group, item)
  406.             radio.show()
  407.             
  408.             if group is None:
  409.                 group = radio
  410.             
  411.             vbox.pack_start(radio, False, False)
  412.             
  413. class SetupListAlertTemplate(SetupAlert):
  414.     def get_list_title(self):
  415.         raise NotImplementedError
  416.     
  417.     def configure_widgets(self, dialog, tree):
  418.         raise NotImplementedError
  419.  
  420.     def create_store(self):
  421.         raise NotImplementedError
  422.     
  423.     def _before_text(self, dialog, vbox):
  424.         store = self.create_store()
  425.         
  426.         title = self.get_list_title()
  427.         
  428.         if title is not None:
  429.             lbl = SetupLabel(title)()
  430.             lbl.show()
  431.             vbox.pack_start(lbl, False, False)
  432.         
  433.         tree = gtk.TreeView()
  434.         tree.set_name("list_view")
  435.         tree.set_model(store)
  436.         tree.set_headers_visible(False)
  437.  
  438.         tree.show()
  439.         scroll = SetupScrolledWindow()()
  440.         scroll.add(tree)
  441.         scroll.show()
  442.         
  443.         vbox.add(scroll)
  444.  
  445.         self.configure_widgets(dialog, tree)
  446.  
  447.         return dialog, vbox
  448.  
  449. class SetupMultipleChoiceList(SetupListAlertTemplate):
  450.  
  451.     def get_list_title(self):
  452.         return self.list_title
  453.  
  454.     def configure_widgets(self, dialog, tree):
  455.         store = tree.get_model()
  456.         
  457.         
  458.         # Create the callback
  459.         def on_toggle(render, path, args):
  460.             dialog, model, min_sel, max_sel = args
  461.             
  462.             tree_iter = model.get_iter(path)
  463.             row = model[tree_iter]
  464.             row[0] = not row[0]
  465.             
  466.             if row[0]:
  467.                 model.enabled_rows += 1
  468.             else:
  469.                 model.enabled_rows -= 1
  470.             
  471.             if model.enabled_rows == 0:
  472.                 is_sensitive = False
  473.             elif max_sel >= 0:
  474.                 is_sensitive = min_sel <= model.enabled_rows <= max_sel
  475.             else:
  476.                 is_sensitive = min_sel <= model.enabled_rows
  477.             
  478.             dialog.set_response_sensitive(gtk.RESPONSE_OK, is_sensitive)
  479.         
  480.         args = (dialog, store, self.min_select, self.max_select)
  481.  
  482.         rend = gtk.CellRendererToggle()
  483.         rend.connect("toggled", on_toggle, args)
  484.         col = gtk.TreeViewColumn("", rend, active = 0)
  485.         tree.append_column(col)
  486.  
  487.         rend = gtk.CellRendererText()
  488.         col = gtk.TreeViewColumn("", rend, text = 1)
  489.         tree.append_column(col)
  490.  
  491.         dialog.set_response_sensitive(gtk.RESPONSE_OK, False)
  492.  
  493.  
  494.     def create_store(self):
  495.         store = gtk.ListStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING)
  496.         store.enabled_rows = 0
  497.         for item in self.items:
  498.             store.append((False, item))
  499.         return store
  500.  
  501.  
  502. class SetupListAlert(SetupListAlertTemplate):
  503.     
  504.     _defaults = {
  505.         "list_title": None
  506.     }
  507.     _defaults.update(SetupAlert._defaults)
  508.     
  509.     def get_list_title(self):
  510.         return self.list_title
  511.  
  512.     def configure_widgets(self, dialog, tree):
  513.         rend = gtk.CellRendererText()
  514.         col = gtk.TreeViewColumn("", rend, text = 0)
  515.         tree.append_column(col)
  516.         tree.get_selection().set_mode(gtk.SELECTION_NONE)
  517.  
  518.  
  519.     def create_store(self):
  520.         store = gtk.ListStore(gobject.TYPE_STRING)
  521.  
  522.         for item in self.items:
  523.             store.append((item,))
  524.  
  525.         return store
  526.     
  527.  
  528. class SetupSingleChoiceList(SetupListAlert):
  529.  
  530.     _defaults = {
  531.         "min_select": 1,
  532.     }
  533.     
  534.     _defaults.update(SetupListAlert._defaults)
  535.  
  536.     def configure_widgets(self, dialog, tree):
  537.         assert self.min_select in (0, 1)
  538.         
  539.         SetupListAlert.configure_widgets(self, dialog, tree)
  540.         selection = tree.get_selection()
  541.  
  542.         if self.min_select == 0:
  543.             selection_mode = gtk.SELECTION_SINGLE
  544.             def on_selection_changed(selection, dialog):
  545.                 is_sensitive = selection.count_selected_rows() > 0
  546.                 dialog.set_response_sensitive(gtk.RESPONSE_OK, is_sensitive)
  547.             selection.connect("changed", on_selection_changed, dialog)
  548.             
  549.         else:
  550.             selection_mode = gtk.SELECTION_BROWSE
  551.  
  552.         selection.set_mode(selection_mode)
  553.     
  554.  
  555.  
  556. class RunDialog(WidgetCostumizer):
  557.     """
  558.     This is a terminal costumizer because it swaps the gtk.Dialog recieved by
  559.     argument for its `gtk.Dialog.run`'s result.
  560.     """
  561.     def _run(self, dialog, container):
  562.         response = dialog.run()
  563.         dialog.destroy()
  564.         return response, None
  565.         
  566. def hig_alert(primary_text, secondary_text, parent = None, flags = 0, \
  567.               buttons =(gtk.STOCK_OK, gtk.RESPONSE_OK), run = True, \
  568.               _setup_alert = SetupAlert, **kwargs):
  569.               
  570.     if parent is None and "title" not in kwargs:
  571.         raise TypeError("When you don't define a parent you must define a "
  572.                         "title") 
  573.     dlg = gtk.Dialog(parent = parent, flags = flags, buttons = buttons)
  574.  
  575.     costumizer = SetupDialog()
  576.     
  577.     costumizer.bind(_setup_alert(primary_text, secondary_text, **kwargs))
  578.  
  579.     if run:
  580.         costumizer.bind(RunDialog())
  581.  
  582.     return costumizer(dlg)
  583.  
  584.  
  585. def list_dialog(primary_text, secondary_text, parent=None, items=(), **kwargs):
  586.     """
  587.     @param list_title: A label will be placed above the list of items describing
  588.         what's the content of the list. Optional.
  589.     
  590.     Every other argument that L{hig_alert} function does.
  591.     
  592.     Example::
  593.         primary_text = "Listing cool stuff"
  594.         secondary_text = "To select more of that stuff eat alot of cheese!"
  595.         list_title = "Your cool stuff:"
  596.         # Some random 20 elements
  597.         items = ["foo", "bar"] * 10
  598.         window_title = "Rat Demo"
  599.         list_dialog(
  600.             primary_text,
  601.             secondary_text,
  602.             items=items,
  603.             title=window_title,
  604.             list_title=list_title
  605.         )
  606.     """
  607.     return hig_alert(
  608.         primary_text,
  609.         secondary_text,
  610.         parent = parent,
  611.         _setup_alert = SetupListAlert,
  612.         items = items,
  613.         **kwargs
  614.     )
  615.  
  616. ################
  617. # choice_dialog
  618. class _OneStrategy:
  619.     accepts = lambda self, choices, min_select, max_select: choices == 1
  620.     
  621.     def before(self, kwargs):
  622.         pass
  623.  
  624.     def get_items(self, data):
  625.         return (0,)
  626.  
  627. class _BaseStrategy:
  628.  
  629.     def before(self, kwargs):
  630.         kwargs["_setup_alert"] = self.setup_factory
  631.  
  632.  
  633. class _MultipleStrategy(_BaseStrategy):
  634.     accepts = lambda self, choices, min_select, max_select: max_select == -1 or\
  635.                                                             max_select > 1
  636.     setup_factory = SetupMultipleChoiceList
  637.  
  638.     def get_items(self, dlg):
  639.         # Multiple selection
  640.         store = find_child_widget(dlg, "list_view").get_model()
  641.         return tuple(row.path[0] for row in store if row[0])
  642.  
  643. class _RadioStrategy(_BaseStrategy):
  644.  
  645.     accepts = lambda self, choices, min_select, max_select: choices < 5
  646.     setup_factory = SetupRadioChoiceList
  647.     
  648.     def get_items(self, dlg):
  649.         vbox = find_child_widget(dlg, "items")
  650.         counter = 0
  651.         for radio in vbox.get_children():
  652.             if radio.get_active():
  653.                 break
  654.             counter += 1
  655.         assert radio.get_active()
  656.             
  657.         return (counter,)
  658.  
  659. class _SingleListStrategy(_BaseStrategy):
  660.     accepts = lambda self, a, b, c: True
  661.     setup_factory = SetupSingleChoiceList
  662.     def get_items(self, dlg):
  663.         list_view = find_child_widget(dlg, "list_view")
  664.         rows = list_view.get_selection().get_selected_rows()[1]
  665.         get_element = lambda row: row[0]
  666.  
  667.         items = tuple(map(get_element, rows))
  668.         
  669. _STRATEGIES = (_OneStrategy, _MultipleStrategy, _RadioStrategy,
  670.                _SingleListStrategy)
  671. _STRATEGIES = tuple(factory() for factory in _STRATEGIES)
  672.  
  673. def choice_dialog(primary_text, secondary_text, parent=None, \
  674.                                                 allow_cancel=True, **kwargs):
  675.     """
  676.     @param items: the items you want to choose from
  677.     @param list_title: the title of the list. Optional.
  678.     @param allow_cancel: If the user can cancel/close the dialog.
  679.     @param min_select: The minimum number of elements to be selected.
  680.     @param max_select: The maximum number of elements to be selected.
  681.         -1 Means no limit.
  682.     
  683.     @param dialog_callback: This is a callback function that is going to be
  684.         called when the dialog is created. The argument is the dialog object.
  685.     @param one_item_text: when specified and if the number of `items` is one
  686.         this text will be the primary text. This string must contain a '%s'
  687.         which will be replaced by the item value.
  688.         Optional.
  689.     """
  690.  
  691.     if "run" in kwargs:
  692.         del kwargs["run"]
  693.     
  694.     choices = len(kwargs["items"])
  695.     min_select = kwargs.get("min_select", 1)
  696.     max_select = kwargs.get("max_select", -1)
  697.  
  698.     # Make sure the arguments are correct
  699.     assert choices > 0
  700.     assert (max_select == -1) ^ (min_select <= max_select <= choices)
  701.     assert 0 <= min_select <= choices
  702.     
  703.     buttons = (kwargs.get("ok_button", gtk.STOCK_OK), gtk.RESPONSE_OK)
  704.     
  705.     if allow_cancel:
  706.         buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) + buttons
  707.     else:
  708.         # TODO: make closing the window impossible    
  709.         pass
  710.  
  711.     if min_select == 0:
  712.         txt = N_("Don't select it", "Don't select any items", choices)
  713.         txt = kwargs.get("skip_button", txt)
  714.         buttons = (txt, gtk.RESPONSE_CLOSE) + buttons
  715.     
  716.     for strategy in _STRATEGIES:
  717.         if strategy.accepts(choices, min_select, max_select):
  718.             break
  719.     assert strategy.accepts(choices, min_select, max_select)
  720.     
  721.     if choices == 1:
  722.         if "one_item_text" in kwargs:
  723.             primary_text = kwargs["one_item_text"] % kwargs["items"][0]
  724.     
  725.     data = strategy.before(kwargs)
  726.     if data is not None:
  727.         primary_text = data
  728.     
  729.     dlg = hig_alert(
  730.         primary_text,
  731.         secondary_text,
  732.         parent = parent,
  733.         run = False,
  734.         buttons = buttons,
  735.         **kwargs
  736.     )
  737.     kwargs.get("dialog_callback", lambda foo: None)(dlg)
  738.     response = dlg.run()
  739.     
  740.     if response != gtk.RESPONSE_OK:
  741.         dlg.destroy()
  742.         return (), response
  743.     
  744.     items = strategy.get_items(dlg)
  745.     dlg.destroy()
  746.     
  747.     return items, response
  748.     
  749. #########
  750. # save_changes
  751. MIN_FRACTION = 60
  752. HOUR_FRACTION = 60 * MIN_FRACTION
  753. DAY_FRACTION = 24 * HOUR_FRACTION
  754. def humanize_seconds(elapsed_seconds, use_hours = True, use_days = True):
  755.     """
  756.     Turns a number of seconds into to a human readable string, example
  757.     125 seconds is: '2 minutes and 5 seconds'.
  758.     
  759.     @param elapsed_seconds: number of seconds you want to humanize
  760.     @param use_hours: wether or not to render the hours(if hours > 0)
  761.     @param use_days: wether or not to render the days(if days > 0)
  762.     """
  763.     
  764.     text = []
  765.     
  766.     duration = elapsed_seconds
  767.     
  768.     if duration == 0:
  769.         return _("0 seconds")
  770.     
  771.     days = duration / DAY_FRACTION
  772.     if use_days and days > 0:
  773.         text.append(N_("%d day", "%d days", days) % days)
  774.         duration %= DAY_FRACTION
  775.         
  776.     hours = duration / HOUR_FRACTION
  777.     if use_hours and hours > 0:
  778.         text.append(N_("%d hour", "%d hours", hours) % hours)
  779.         duration %= HOUR_FRACTION
  780.     
  781.     minutes = duration / MIN_FRACTION
  782.     if minutes > 0:
  783.         text.append(N_("%d minute", "%d minutes", minutes) % minutes)
  784.         duration %= MIN_FRACTION
  785.  
  786.     seconds = duration % 60
  787.     if seconds > 0:
  788.         text.append(N_("%d second", "%d seconds", seconds) % seconds)
  789.     
  790.     if len(text) > 2:
  791.         # To translators: this joins 3 or more time fractions
  792.         return _(", ").join(text[:-1]) + _(" and ") + text[-1]
  793.     else:
  794.         # To translators: this joins 2 or 1 time fractions
  795.         return _(" and ").join(text)
  796.  
  797. class _TimeUpdater:
  798.     def __init__(self, initial_time):
  799.         self.initial_time = initial_time
  800.     
  801.     def set_dialog(self, dialog):
  802.         self.dialog = dialog
  803.         self.dialog.connect("response", self.on_response)
  804.         self.source = gobject.timeout_add(500, self.on_tick)
  805.     
  806.     def on_response(self, *args):
  807.         gobject.source_remove(self.source)
  808.  
  809.     def get_text(self):
  810.         last_changes = datetime.datetime.now() - self.initial_time
  811.         # To translators %s is the time
  812.         secondary_text = _("If you don't save, changes from the last %s "
  813.                            "will be permanently lost.")
  814.         return secondary_text % humanize_seconds(last_changes.seconds)
  815.         
  816.         
  817.     def on_tick(self):
  818.         self.dialog.set_secondary_text(self.get_text())
  819.         return True
  820.  
  821. def save_changes(files, last_save=None, parent=None, **kwargs):
  822.     """
  823.     Shows up a Save changes dialog to a certain list of documents and returns
  824.     a tuple with two values, the first is a list of files that are to be saved
  825.     the second is the value of the response, which can be one of:
  826.       - gtk.RESPONSE_OK - the user wants to save
  827.       - gtk.RESPONSE_CANCEL - the user canceled the dialog
  828.       - gtk.RESPONSE_CLOSE - the user wants to close without saving
  829.       - gtk.RESPONSE_DELETE_EVENT - the user closed the window
  830.     
  831.     So if you want to check if the user canceled just check if the response is
  832.     equal to gtk.RESPONSE_CANCEL or gtk.RESPONSE_DELETE_EVENT
  833.     
  834.     When the `elapsed_time` argument is not `None` it should be a list of the
  835.     elapsed time since each was modified. It must be in the same order of
  836.     the `files` argument.
  837.     
  838.     This function also accepts every argument that a hig_alert function accepts,
  839.     which means it accepts `title`, etc. Note that this function overrides
  840.     the `run` argument and sets it to True, because it's not possible for a user
  841.     to know which files were saved since the dialog changes is structure
  842.     depending on the arguments.
  843.     
  844.     Simple usage example::
  845.         files_to_save, response = save_changes(["foo.bar"])
  846.  
  847.     @param files: a list of filenames to be saved
  848.     @param last_save: when you only want to save one file you can optionally
  849.         send the date of when the user saved the file most recently.
  850.         
  851.     @type last_save: datetime.datetime
  852.     @param parent: the window that will be parent of this window.
  853.     @param primary_text: optional, see hig_alert.
  854.     @param secondary_text: optional, see hig_alert.
  855.     @param one_item_text: optional, see choice_alert.
  856.     @param list_title: optional, see choice_alert.
  857.     @param kwargs: the remaining keyword arguments are the same as used on the function
  858.         hig_alert.
  859.     @return: a tuple with a list of entries the user chose to save and a gtk.RESPONSE_*
  860.         from the dialog
  861.     """
  862.     primary_text = N_("There is %d file with unsaved changes. "
  863.                       "Save changes before closing?",
  864.                       "There are %d files with unsaved " 
  865.                       "changes. Save changes before closing?", len(files)) 
  866.  
  867.     primary_text %= len(files)
  868.     
  869.     primary_text = kwargs.get("primary_text", primary_text)
  870.     
  871.     secondary_text = _("If you don't save, all your changes will be "
  872.                        "permanently lost.")
  873.     
  874.     secondary_text = kwargs.get("secondary_text", secondary_text)
  875.  
  876.     one_item_text = _("Save the changes to <i>%s</i> before closing?")
  877.     one_item_text = kwargs.get("one_item_text", one_item_text)
  878.     
  879.     list_title = _("Select the files you want to save:")
  880.     list_title = kwargs.get("list_title", list_title)
  881.     
  882.     if len(files) == 1 and last_save is not None:
  883.         updater = _TimeUpdater(last_save)
  884.         secondary_text = updater.get_text()
  885.         kwargs["dialog_callback"] = updater.set_dialog
  886.         
  887.     indexes, response = choice_dialog(
  888.         primary_text,
  889.         secondary_text,
  890.         min_select = 0,
  891.         max_select = -1,
  892.         skip_button = _("Close without saving"),
  893.         ok_button = gtk.STOCK_SAVE,
  894.         list_title = list_title,
  895.         items = files,
  896.         one_item_text = one_item_text,
  897.         **kwargs
  898.     )
  899.  
  900.     return map(files.__getitem__, indexes), response
  901.  
  902. ################################################################################
  903.  
  904. # widget iterators
  905. def _simple_iterate_widget_children(widget):
  906.     """This function iterates all over the widget children.
  907.     """
  908.     get_children = getattr(widget, "get_children", None)
  909.  
  910.     if get_children is None:
  911.         return
  912.     
  913.     for child in get_children():
  914.         yield child
  915.  
  916.     get_submenu = getattr(widget, "get_submenu", None)
  917.     
  918.     if get_submenu is None:
  919.         return
  920.     
  921.     sub_menu = get_submenu()
  922.     
  923.     if sub_menu is not None:
  924.         yield sub_menu
  925.  
  926. class _IterateWidgetChildren:
  927.     """This iterator class is used to recurse to child widgets, it uses
  928.     the _simple_iterate_widget_children function
  929.     
  930.     """
  931.     def __init__(self, widget):
  932.         self.widget = widget
  933.         self.children_widgets = iter(_simple_iterate_widget_children(self.widget))
  934.         self.next_iter = None
  935.         
  936.     def next(self):
  937.         if self.next_iter is None:
  938.             widget = self.children_widgets.next()
  939.             self.next_iter = _IterateWidgetChildren(widget)
  940.             return widget
  941.             
  942.         else:
  943.             try:
  944.                 return self.next_iter.next()
  945.             except StopIteration:
  946.                 self.next_iter = None
  947.                 return self.next()
  948.  
  949.     def __iter__(self):
  950.         return self
  951.         
  952. def iterate_widget_children(widget, recurse_children = False):
  953.     """
  954.     This function is used to iterate over the children of a given widget.
  955.     You can recurse to all the widgets contained in a certain widget.
  956.     
  957.     @param widget: The base widget of iteration
  958.     @param recurse_children: Wether or not to iterate recursively, by iterating
  959.         over the children's children.
  960.     
  961.     @return: an iterator
  962.     @rtype: C{GeneratorType}
  963.     """
  964.     if recurse_children:
  965.         return _IterateWidgetChildren(widget)
  966.     else:
  967.         return iter(_simple_iterate_widget_children(widget))
  968.  
  969. def iterate_widget_parents(widget):
  970.     """Iterate over the widget's parents.
  971.  
  972.     @param widget: The base widget of iteration
  973.     @return: an iterator
  974.     @rtype: C{GeneratorType}
  975.     """
  976.     
  977.     widget = widget.get_parent()
  978.     while widget is not None:
  979.         yield widget
  980.         widget = widget.get_parent()
  981.  
  982. def find_parent_widget(widget, name, find_self=True):
  983.     """
  984.     Finds a widget by name upwards the tree, by searching self and its parents
  985.     
  986.     @return: C{None} when it didn't find it, otherwise a C{gtk.Container}
  987.     @rtype: C{gtk.Container}
  988.     @param find_self: Set this to C{False} if you want to only find on the parents
  989.     @param name: The name of the widget
  990.     @param widget: The widget where this function will start searching
  991.     """
  992.     
  993.     assert widget is not None
  994.  
  995.     if find_self and widget.get_name() == name:
  996.         return widget
  997.  
  998.     for w in iterate_widget_parents(widget):
  999.         if w.get_name() == name:
  1000.             return w
  1001.  
  1002.     raise NotFoundError(name)
  1003.  
  1004. def find_child_widget(widget, name, find_self=True):
  1005.     """
  1006.     Finds the widget by name downwards the tree, by searching self and its
  1007.     children.
  1008.  
  1009.     @return: C{None} when it didn't find it, otherwise a C{gtk.Widget}
  1010.     @rtype: C{gtk.Widget}
  1011.     @param find_self: Set this to L{False} if you want to only find on the children
  1012.     @param name: The name of the widget
  1013.     @param widget: The widget where this function will start searching
  1014.     """
  1015.     
  1016.     assert widget is not None
  1017.     
  1018.     if find_self and widget.get_name() == name:
  1019.         return widget
  1020.     
  1021.     for w in iterate_widget_children(widget, True):
  1022.  
  1023.         if name == w.get_name():
  1024.             return w
  1025.     
  1026.     raise NotFoundError(name)
  1027.  
  1028.         
  1029.  
  1030. def get_root_parent(widget):
  1031.     """Returns the first widget of a tree. If this widget has no children
  1032.     it will return C{None}
  1033.     
  1034.     @return: C{None} when there is no parent widget, otherwise a C{gtk.Container}
  1035.     @rtype: C{gtk.Container} 
  1036.     """
  1037.     parents = list(iterate_widget_parents(widget))
  1038.     if len(parents) == 0:
  1039.         return None
  1040.     else:
  1041.         return parents[-1]
  1042.  
  1043. class HigProgress(gtk.Window):
  1044.     """
  1045.     HigProgress returns a window that contains a number of properties to
  1046.     access what a common Progress window should have.
  1047.     """
  1048.     def __init__(self):
  1049.         gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
  1050.         
  1051.         self.set_border_width(6)
  1052.         self.set_resizable(False)
  1053.         self.set_title('')
  1054.         # defaults to center location
  1055.         self.set_position(gtk.WIN_POS_CENTER)
  1056.         self.connect("delete-event", self._on_close)
  1057.         
  1058.         # main container
  1059.         main = gtk.VBox(spacing = 12)
  1060.         main.set_spacing(12)
  1061.         main.set_border_width(6)
  1062.         main.show()
  1063.         self.add(main)
  1064.         
  1065.         # primary text
  1066.         alg = gtk.Alignment()
  1067.         alg.set_padding(0, 6, 0, 0)
  1068.         alg.show()
  1069.         main.pack_start(alg, False, False)
  1070.         lbl = SetupLabel()()
  1071.         lbl.set_selectable(False)
  1072.         lbl.show()
  1073.         self._primary_label = lbl
  1074.         alg.add(lbl)
  1075.         
  1076.         # secondary text
  1077.         lbl = SetupLabel()()
  1078.         lbl.set_selectable(False)
  1079.         lbl.show()
  1080.         main.pack_start(lbl, False, False)
  1081.         self._secondary_label = lbl
  1082.         
  1083.         # Progress bar
  1084.         vbox = gtk.VBox()
  1085.         vbox.show()
  1086.         main.pack_start(vbox, False, False)
  1087.         
  1088.         prog = gtk.ProgressBar()
  1089.         prog.show()
  1090.         self._progress_bar = prog
  1091.         vbox.pack_start(prog, expand = False)
  1092.         
  1093.         lbl = SetupLabel()()
  1094.         lbl.set_selectable(False)
  1095.         lbl.show()
  1096.         self._sub_progress_label = lbl
  1097.         vbox.pack_start(lbl, False, False)
  1098.         
  1099.         # Buttons box
  1100.         bbox = gtk.HButtonBox()
  1101.         bbox.set_layout(gtk.BUTTONBOX_END)
  1102.         bbox.show()
  1103.         
  1104.         # Cancel Button
  1105.         cancel = gtk.Button(gtk.STOCK_CANCEL)
  1106.         cancel.set_use_stock(True)
  1107.         cancel.show()
  1108.         self._cancel = cancel
  1109.         bbox.add(cancel)
  1110.         main.add(bbox)
  1111.         
  1112.         # Close button, which is hidden by default
  1113.         close = gtk.Button(gtk.STOCK_CLOSE)
  1114.         close.set_use_stock(True)
  1115.         close.hide()
  1116.         bbox.add(close)
  1117.         self._close = close
  1118.         
  1119.     primary_label = property(lambda self: self._primary_label)
  1120.     secondary_label = property(lambda self: self._secondary_label)
  1121.     progress_bar = property(lambda self: self._progress_bar)
  1122.     sub_progress_label = property(lambda self: self._sub_progress_label)
  1123.     cancel_button = property(lambda self: self._cancel)
  1124.     close_button = property(lambda self: self._close)
  1125.     
  1126.     def set_primary_text(self, text):
  1127.         self.primary_label.set_markup(
  1128.             '<span weight="bold" size="larger">'+text+'</span>'
  1129.         )
  1130.         self.set_title(text)
  1131.     
  1132.     primary_text = property(fset = set_primary_text)
  1133.         
  1134.     def set_secondary_text(self, text):
  1135.         self.secondary_label.set_markup(text)
  1136.     
  1137.     secondary_text = property(fset = set_secondary_text)
  1138.     
  1139.     def set_progress_fraction(self, fraction):
  1140.         self.progress_bar.set_fraction(fraction)
  1141.     
  1142.     def get_progress_fraction(self):
  1143.         return self.progress_bar.get_fraction()
  1144.         
  1145.     progress_fraction = property(get_progress_fraction, set_progress_fraction)
  1146.     
  1147.     def set_progress_text(self, text):
  1148.         self.progress_bar.set_text(text)
  1149.  
  1150.     progress_text = property(fset = set_progress_text)
  1151.     
  1152.     def set_sub_progress_text(self, text):
  1153.         self.sub_progress_label.set_markup('<i>'+text+'</i>')
  1154.         
  1155.     sub_progress_text = property(fset = set_sub_progress_text)
  1156.     
  1157.     def _on_close(self, *args):
  1158.         if not self.cancel_button.get_property("sensitive"):
  1159.             return True
  1160.         # click on the cancel button
  1161.         self.cancel_button.clicked()
  1162.         # let the clicked event close the window if it likes too
  1163.         return True