"Fossies" - the Fresh Open Source Software Archive

Member "revelation-0.5.4/src/lib/ui.py" (4 Oct 2020, 48295 Bytes) of package /linux/privat/revelation-0.5.4.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "ui.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.5.3_vs_0.5.4.

    1 #
    2 # Revelation - a password manager for GNOME 2
    3 # http://oss.codepoet.no/revelation/
    4 # $Id$
    5 #
    6 # Module for UI functionality
    7 #
    8 #
    9 # Copyright (c) 2003-2006 Erik Grinaker
   10 #
   11 # This program is free software; you can redistribute it and/or
   12 # modify it under the terms of the GNU General Public License
   13 # as published by the Free Software Foundation; either version 2
   14 # of the License, or (at your option) any later version.
   15 #
   16 # This program is distributed in the hope that it will be useful,
   17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   19 # GNU General Public License for more details.
   20 #
   21 # You should have received a copy of the GNU General Public License
   22 # along with this program; if not, write to the Free Software
   23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
   24 #
   25 
   26 from . import config, data, dialog, entry, io, util
   27 
   28 import gi
   29 gi.require_version('Gtk', '3.0')
   30 from gi.repository import GObject, Gtk, Gdk, Gio, Pango
   31 import gettext, time
   32 
   33 _ = gettext.gettext
   34 
   35 
   36 STOCK_CONTINUE          = _("_Continue")               # "revelation-continue"
   37 STOCK_DISCARD           = "revelation-discard"
   38 STOCK_EDIT              = "revelation-edit"
   39 STOCK_EXPORT            = _("_Export")                 # "revelation-export"
   40 STOCK_FOLDER            = "revelation-folder"
   41 STOCK_GENERATE          = _("_Generate")               # "revelation-generate"
   42 STOCK_IMPORT            = _("_Import")                 # "revelation-import"
   43 STOCK_GOTO              = "revelation-goto"
   44 STOCK_LOCK              = "revelation-lock"
   45 STOCK_NEW_ENTRY         = _("_Add Entry")              # "revelation-new-entry"
   46 STOCK_NEW_FOLDER        = _("_Add Folder")             # "revelation-new-folder"
   47 STOCK_NEXT              = "go-down"                    # "revelation-next"
   48 STOCK_PASSWORD_CHANGE   = _("_Change")                 # "revelation-password-change"
   49 STOCK_PASSWORD_CHECK    = "revelation-password-check"
   50 STOCK_PASSWORD_STRONG   = "security-high"              # "revelation-password-strong"
   51 STOCK_PASSWORD_WEAK     = "security-low"               # "revelation-password-weak"
   52 STOCK_PREVIOUS          = "go-up"                      # "revelation-previous"
   53 STOCK_RELOAD            = _("_Reload")                 # "revelation-reload"
   54 STOCK_REMOVE            = "revelation-remove"
   55 STOCK_REPLACE           = _("_Replace")                # "revelation-replace"
   56 STOCK_UNKNOWN           = "dialog-question"            # "revelation-unknown"
   57 STOCK_UNLOCK            = _("_Unlock")                 # "revelation-unlock"
   58 STOCK_UPDATE            = _("_Update")                 # "revelation-update"
   59 
   60 
   61 STOCK_ENTRY_FOLDER      = "folder"              # "revelation-account-folder"
   62 STOCK_ENTRY_FOLDER_OPEN = "folder-open"         # "revelation-account-folder-open"
   63 STOCK_ENTRY_CREDITCARD  = "x-office-contact"    # "revelation-account-creditcard"
   64 STOCK_ENTRY_CRYPTOKEY   = "dialog-password"     # "revelation-account-cryptokey"
   65 STOCK_ENTRY_DATABASE    = "server-database"     # "revelation-account-database"
   66 STOCK_ENTRY_DOOR        = "changes-allow"       # "revelation-account-door"
   67 STOCK_ENTRY_EMAIL       = "emblem-mail"         # "revelation-account-email"
   68 STOCK_ENTRY_FTP         = "system-file-manager" # "revelation-account-ftp"
   69 STOCK_ENTRY_GENERIC     = "document-new"        # "revelation-account-generic"
   70 STOCK_ENTRY_PHONE       = "phone"               # "revelation-account-phone"
   71 STOCK_ENTRY_SHELL       = "utilities-terminal"  # "revelation-account-shell"
   72 STOCK_ENTRY_REMOTEDESKTOP = "preferences-desktop-remote-desktop" # "revelation-account-remotedesktop"
   73 STOCK_ENTRY_WEBSITE     = "web-browser"         # "revelation-account-website"
   74 
   75 
   76 ICON_SIZE_APPLET        = Gtk.IconSize.LARGE_TOOLBAR
   77 ICON_SIZE_DATAVIEW      = Gtk.IconSize.LARGE_TOOLBAR
   78 ICON_SIZE_DROPDOWN      = Gtk.IconSize.SMALL_TOOLBAR
   79 ICON_SIZE_ENTRY         = Gtk.IconSize.MENU
   80 ICON_SIZE_FALLBACK      = Gtk.IconSize.LARGE_TOOLBAR
   81 ICON_SIZE_HEADLINE      = Gtk.IconSize.LARGE_TOOLBAR
   82 ICON_SIZE_LABEL         = Gtk.IconSize.MENU
   83 ICON_SIZE_LOGO          = Gtk.IconSize.DND
   84 ICON_SIZE_TREEVIEW      = Gtk.IconSize.MENU
   85 
   86 STOCK_ICONS = (
   87     ( STOCK_ENTRY_CREDITCARD,   "contact-new",          ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
   88     ( STOCK_ENTRY_CRYPTOKEY,    "dialog-password",      ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
   89     ( STOCK_ENTRY_DATABASE,     "package_system",       ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
   90     ( STOCK_ENTRY_DOOR,         "changes-allow",        ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
   91     ( STOCK_ENTRY_EMAIL,        "emblem-mail",          ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
   92     ( STOCK_ENTRY_FTP,          "system-file-manager",  ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
   93     ( STOCK_ENTRY_GENERIC,      "document-new",         ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
   94     ( STOCK_ENTRY_PHONE,        "phone",                ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
   95     ( STOCK_ENTRY_SHELL,        "utilities-terminal",   ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
   96     ( STOCK_ENTRY_REMOTEDESKTOP,"preferences-desktop-remote-desktop",( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
   97     ( STOCK_ENTRY_WEBSITE,      "web-browser",          ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
   98     ( STOCK_ENTRY_FOLDER,       "folder",               ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
   99     ( STOCK_ENTRY_FOLDER_OPEN,  "folder-open",          ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
  100 )
  101 
  102 STOCK_ITEMS = (
  103     ( STOCK_CONTINUE,       _('_Continue'),     "stock_test-mode" ),
  104     ( STOCK_DISCARD,        _('_Discard'),      Gtk.STOCK_DELETE ),
  105     ( STOCK_EDIT,           _('_Edit'),         Gtk.STOCK_EDIT ),
  106     ( STOCK_EXPORT,         _('_Export'),       Gtk.STOCK_EXECUTE ),
  107     ( STOCK_FOLDER,         '',                 "stock_folder" ),
  108     ( STOCK_GENERATE,       _('_Generate'),     Gtk.STOCK_EXECUTE ),
  109     ( STOCK_GOTO,           _('_Go to'),        Gtk.STOCK_JUMP_TO ),
  110     ( STOCK_IMPORT,         _('_Import'),       Gtk.STOCK_CONVERT ),
  111     ( STOCK_LOCK,           _('_Lock'),         "stock_lock" ),
  112     ( STOCK_NEW_ENTRY,      _('_Add Entry'),    Gtk.STOCK_ADD ),
  113     ( STOCK_NEW_FOLDER,     _('_Add Folder'),   "stock_folder" ),
  114     ( STOCK_NEXT,           _('Next'),          Gtk.STOCK_GO_DOWN ),
  115     ( STOCK_PASSWORD_CHANGE,_('_Change'),       "stock_lock-ok" ),
  116     ( STOCK_PASSWORD_CHECK, _('_Check'),        "stock_lock-ok" ),
  117     ( STOCK_PASSWORD_STRONG,'',                 "stock_lock-ok" ),
  118     ( STOCK_PASSWORD_WEAK,  '',                 "stock_lock-broken" ),
  119     ( STOCK_PREVIOUS,       _('Previous'),      Gtk.STOCK_GO_UP ),
  120     ( STOCK_RELOAD,         _('_Reload'),       Gtk.STOCK_REFRESH ),
  121     ( STOCK_REMOVE,         _('Re_move'),       Gtk.STOCK_DELETE ),
  122     ( STOCK_REPLACE,        _('_Replace'),      Gtk.STOCK_SAVE_AS ),
  123     ( STOCK_UNKNOWN,        _('Unknown'),       "dialog-question" ),
  124     ( STOCK_UNLOCK,         _('_Unlock'),       "stock_lock-open" ),
  125     ( STOCK_UPDATE,         _('_Update'),       "stock_edit" ),
  126 )
  127 
  128 
  129 
  130 ##### EXCEPTIONS #####
  131 
  132 class DataError(Exception):
  133     "Exception for invalid data"
  134     pass
  135 
  136 
  137 
  138 ##### FUNCTIONS #####
  139 
  140 def generate_field_display_widget(field, cfg = None, userdata = None):
  141     "Generates a widget for displaying a field value"
  142 
  143     if field.datatype == entry.DATATYPE_EMAIL:
  144         widget = LinkButton("mailto:%s" % field.value, util.escape_markup(field.value))
  145 
  146     elif field.datatype == entry.DATATYPE_PASSWORD:
  147         widget = PasswordLabel(util.escape_markup(field.value), cfg, userdata)
  148 
  149     elif field.datatype == entry.DATATYPE_URL:
  150         widget = LinkButton(field.value, util.escape_markup(field.value))
  151 
  152     else:
  153         widget = Label(util.escape_markup(field.value))
  154         widget.set_selectable(True)
  155 
  156     return widget
  157 
  158 
  159 def generate_field_edit_widget(field, cfg = None, userdata = None):
  160     "Generates a widget for editing a field"
  161 
  162     if type(field) == entry.PasswordField:
  163         widget = PasswordEntryGenerate(None, cfg, userdata)
  164 
  165     elif type(field) == entry.UsernameField:
  166         widget = Gtk.ComboBox.new_with_entry()
  167         setup_comboboxentry(widget, userdata)
  168 
  169     elif field.datatype == entry.DATATYPE_FILE:
  170         widget = FileEntry()
  171 
  172     elif field.datatype == entry.DATATYPE_PASSWORD:
  173         widget = PasswordEntry(None, cfg, userdata)
  174 
  175     else:
  176         widget = Entry()
  177 
  178     widget.set_text(field.value or "")
  179 
  180     return widget
  181 
  182 def setup_comboboxentry(widget, userdata=None):
  183     widget.entry = widget.get_child()
  184     widget.entry.set_activates_default(True)
  185 
  186     widget.set_text = widget.entry.set_text
  187     widget.get_text = widget.entry.get_text
  188 
  189     widget.model = Gtk.ListStore(GObject.TYPE_STRING)
  190     widget.set_model(widget.model)
  191     widget.set_entry_text_column(0)
  192 
  193     widget.completion = Gtk.EntryCompletion()
  194     widget.completion.set_model(widget.model)
  195     widget.completion.set_text_column(0)
  196     widget.completion.set_minimum_key_length(1)
  197     widget.entry.set_completion(widget.completion)
  198 
  199     def set_values(vlist):
  200         "Sets the values for the dropdown"
  201 
  202         widget.model.clear()
  203 
  204         for item in vlist:
  205             widget.model.append((item,))
  206 
  207     widget.set_values = set_values
  208 
  209     if userdata is not None:
  210         widget.set_values(userdata)
  211 
  212 
  213 ##### CONTAINERS #####
  214 
  215 class HBox(Gtk.HBox):
  216     "A horizontal container"
  217 
  218     def __init__(self, *args):
  219         Gtk.HBox.__init__(self)
  220 
  221         self.set_spacing(6)
  222         self.set_border_width(0)
  223 
  224         for widget in args:
  225             self.pack_start(widget, True, True, 0)
  226 
  227 
  228 
  229 class HButtonBox(Gtk.HButtonBox):
  230     "A horizontal button box"
  231 
  232     def __init__(self, *args):
  233         Gtk.HButtonBox.__init__(self)
  234 
  235         self.set_layout(Gtk.ButtonBoxStyle.SPREAD)
  236         self.set_spacing(12)
  237 
  238         for button in args:
  239             self.pack_start(button, True, True, 0)
  240 
  241 
  242 
  243 class VBox(Gtk.VBox):
  244     "A vertical container"
  245 
  246     def __init__(self, *args):
  247         Gtk.VBox.__init__(self)
  248 
  249         self.set_spacing(6)
  250         self.set_border_width(0)
  251 
  252         for widget in args:
  253             self.pack_start(widget, True, True, 0)
  254 
  255 
  256 
  257 class Notebook(Gtk.Notebook):
  258     "A notebook (tabbed view)"
  259 
  260     def __init__(self):
  261         Gtk.Notebook.__init__(self)
  262 
  263 
  264     def create_page(self, title):
  265         "Creates a notebook page"
  266 
  267         page = NotebookPage()
  268         self.append_page(page, Label(title))
  269 
  270         return page
  271 
  272 
  273 
  274 class NotebookPage(VBox):
  275     "A notebook page"
  276 
  277     def __init__(self):
  278         VBox.__init__(self)
  279 
  280         self.sizegroup = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
  281         self.set_border_width(12)
  282         self.set_spacing(18)
  283 
  284 
  285     def add_section(self, title, description = None):
  286         "Adds an input section to the notebook"
  287 
  288         section = InputSection(title, description, self.sizegroup)
  289         self.pack_start(section, False, False, 0)
  290 
  291         return section
  292 
  293 
  294 
  295 class ScrolledWindow(Gtk.ScrolledWindow):
  296     "A scrolled window for partially displaying a child widget"
  297 
  298     def __init__(self, contents = None):
  299         Gtk.ScrolledWindow.__init__(self)
  300 
  301         self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
  302 
  303         if contents is not None:
  304             self.add(contents)
  305 
  306 
  307 
  308 class Toolbar(Gtk.Toolbar):
  309     "A Toolbar subclass"
  310 
  311     def append_space(self):
  312         "Appends a space to the toolbar"
  313 
  314         space = Gtk.SeparatorToolItem()
  315         space.set_draw(False)
  316 
  317         self.insert(space, -1)
  318 
  319 
  320     def append_widget(self, widget, tooltip = None):
  321         "Appends a widget to the toolbar"
  322 
  323         toolitem = Gtk.ToolItem()
  324         toolitem.add(widget)
  325 
  326         if tooltip != None:
  327             toolitem.set_tooltip_text(tooltip)
  328 
  329         self.insert(toolitem, -1)
  330 
  331 
  332 
  333 class InputSection(VBox):
  334     "A section of input fields"
  335 
  336     def __init__(self, title = None, description = None, sizegroup = None):
  337         VBox.__init__(self)
  338 
  339         self.title  = None
  340         self.desc   = None
  341         self.sizegroup  = sizegroup
  342 
  343         if title is not None:
  344             self.title = Label("<span weight=\"bold\">%s</span>" % util.escape_markup(title))
  345             self.pack_start(self.title, False, True, 0)
  346 
  347         if description is not None:
  348             self.desc = Label(util.escape_markup(description))
  349             self.pack_start(self.desc, False, True, 0)
  350 
  351         if sizegroup is None:
  352             self.sizegroup = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
  353 
  354 
  355     def append_widget(self, title, widget, indent = True):
  356         "Adds a widget to the section"
  357 
  358         row = HBox()
  359         row.set_spacing(12)
  360         self.pack_start(row, False, False, 0)
  361 
  362         if self.title is not None and indent == True:
  363             row.pack_start(Label(""), False, False, 0)
  364 
  365         if title is not None:
  366             label = Label("%s:" % util.escape_markup(title))
  367             self.sizegroup.add_widget(label)
  368             row.pack_start(label, False, False, 0)
  369 
  370         row.pack_start(widget, True, True, 0)
  371 
  372 
  373     def clear(self):
  374         "Removes all widgets"
  375 
  376         for child in self.get_children():
  377             if child not in ( self.title, self.desc ):
  378                 child.destroy()
  379 
  380 
  381 
  382 ##### DISPLAY WIDGETS #####
  383 
  384 class EventBox(Gtk.EventBox):
  385     "A container which handles events for a widget (for tooltips etc)"
  386 
  387     def __init__(self, widget = None):
  388         Gtk.EventBox.__init__(self)
  389 
  390         if widget is not None:
  391             self.add(widget)
  392 
  393 
  394 
  395 class Image(Gtk.Image):
  396     "A widget for displaying an image"
  397 
  398     def __init__(self, stock = None, size = None):
  399         Gtk.Image.__init__(self)
  400 
  401         if stock is not None:
  402             self.set_from_icon_name(stock, size)
  403 
  404 
  405 
  406 class ImageLabel(HBox):
  407     "A label with an image"
  408 
  409     def __init__(self, text = None, stock = None, size = ICON_SIZE_LABEL):
  410         HBox.__init__(self)
  411 
  412         self.image = Image()
  413         self.pack_start(self.image, False, True, 0)
  414 
  415         self.label = Label(text)
  416         self.pack_start(self.label, True, True, 0)
  417 
  418         if text != None:
  419             self.set_text(text)
  420 
  421         if stock != None:
  422             self.set_stock(stock, size)
  423 
  424 
  425     def set_ellipsize(self, ellipsize):
  426         "Sets label ellisization"
  427 
  428         self.label.set_ellipsize(ellipsize)
  429 
  430 
  431     def set_stock(self, stock, size):
  432         "Sets the image"
  433 
  434         self.image.set_from_icon_name(stock, size)
  435 
  436 
  437     def set_text(self, text):
  438         "Sets the label text"
  439 
  440         self.label.set_text(text)
  441 
  442 
  443 
  444 class Label(Gtk.Label):
  445     "A text label"
  446 
  447     def __init__(self, text = None, justify = Gtk.Justification.LEFT):
  448         Gtk.Label.__init__(self)
  449 
  450         self.set_text(text)
  451         self.set_justify(justify)
  452         self.set_use_markup(True)
  453         self.set_line_wrap(True)
  454 
  455         self.set_valign(Gtk.Align.CENTER)
  456         if justify == Gtk.Justification.LEFT:
  457             self.set_halign(Gtk.Align.START)
  458 
  459         elif justify == Gtk.Justification.CENTER:
  460             self.set_halign(Gtk.Align.CENTER)
  461 
  462         elif justify == Gtk.Justification.RIGHT:
  463             self.set_halign(Gtk.Align.END)
  464 
  465 
  466     def set_text(self, text):
  467         "Sets the text of the label"
  468 
  469         if text is None:
  470             Gtk.Label.set_text(self, "")
  471 
  472         else:
  473             Gtk.Label.set_markup(self, text)
  474 
  475 
  476 
  477 class PasswordLabel(EventBox):
  478     "A label for displaying passwords"
  479 
  480     def __init__(self, password = "", cfg = None, clipboard = None, justify = Gtk.Justification.LEFT):
  481         EventBox.__init__(self)
  482 
  483         self.password   = util.unescape_markup(password)
  484         self.config = cfg
  485         self.clipboard  = clipboard
  486 
  487         self.label = Label(util.escape_markup(self.password), justify)
  488         self.label.set_selectable(True)
  489         self.add(self.label)
  490 
  491         self.show_password(cfg.get_boolean("view-passwords"))
  492         self.config.connect('changed::view-passwords', lambda w, k: self.show_password(w.get_boolean(k)))
  493 
  494         self.connect("button-press-event", self.__cb_button_press)
  495         self.connect("drag-data-get", self.__cb_drag_data_get)
  496 
  497 
  498     def __cb_drag_data_get(self, widget, context, selection, info, timestamp, data = None):
  499         "Provides data for a drag operation"
  500 
  501         selection.set_text(self.password, -1)
  502 
  503 
  504     def __cb_button_press(self, widget, data = None):
  505         "Populates the popup menu"
  506 
  507         if self.label.get_selectable() == True:
  508             return False
  509 
  510         elif data.button == 3:
  511             menu = Menu()
  512 
  513             menuitem = ImageMenuItem(Gtk.STOCK_COPY, _('Copy password'))
  514             menuitem.connect("activate", lambda w: self.clipboard.set([self.password], True))
  515             menu.append(menuitem)
  516 
  517             menu.show_all()
  518             menu.popup_at_pointer(data)
  519 
  520             return True
  521 
  522 
  523     def set_ellipsize(self, ellipsize):
  524         "Sets ellipsize for the label"
  525 
  526         self.label.set_ellipsize(ellipsize)
  527 
  528 
  529     def show_password(self, show = True):
  530         "Sets whether to display the password"
  531 
  532         if show == True:
  533             self.label.set_text(util.escape_markup(self.password))
  534             self.label.set_selectable(True)
  535             self.drag_source_unset()
  536 
  537         else:
  538             self.label.set_text(Gtk.Entry().get_invisible_char()*6)
  539             self.label.set_selectable(False)
  540 
  541             self.drag_source_set(
  542                 Gdk.ModifierType.BUTTON1_MASK,
  543                 [
  544                     Gtk.TargetEntry.new("text/plain",    0, 0),
  545                     Gtk.TargetEntry.new("TEXT",          0, 1),
  546                     Gtk.TargetEntry.new("STRING",        0, 2),
  547                     Gtk.TargetEntry.new("COMPOUND TEXT", 0, 3),
  548                     Gtk.TargetEntry.new("UTF8_STRING",   0, 4)
  549                 ],
  550                 Gdk.DragAction.COPY
  551             )
  552 
  553 
  554 
  555 class EditableTextView(Gtk.ScrolledWindow):
  556     "An editable text view"
  557 
  558     def __init__(self, buffer = None, text = None):
  559 
  560         Gtk.ScrolledWindow.__init__(self)
  561         self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
  562         self.set_shadow_type(Gtk.ShadowType.ETCHED_OUT)
  563         self.textview = Gtk.TextView(buffer=buffer)
  564         self.textbuffer = self.textview.get_buffer()
  565         self.add(self.textview)
  566 
  567         if text is not None:
  568             self.textview.get_buffer().set_text(text)
  569 
  570     def set_text(self, text):
  571         "Sets the entry contents"
  572 
  573         if text is None:
  574             self.textbuffer.set_text("")
  575 
  576         self.textbuffer.set_text(text)
  577 
  578     def get_text(self):
  579         "Returns the text of the entry"
  580 
  581         return self.textbuffer.get_text(self.textbuffer.get_start_iter(), self.textbuffer.get_end_iter(), False)
  582 
  583 class TextView(Gtk.TextView):
  584     "A text view"
  585 
  586     def __init__(self, buffer = None, text = None):
  587         Gtk.TextView.__init__(self)
  588         self.set_buffer(buffer)
  589 
  590         self.set_editable(False)
  591         self.set_wrap_mode(Gtk.WrapMode.NONE)
  592         self.set_cursor_visible(False)
  593         self.modify_font(Pango.FontDescription("Monospace"))
  594 
  595         if text is not None:
  596             self.get_buffer().set_text(text)
  597 
  598 
  599 
  600 ##### TEXT ENTRIES #####
  601 
  602 class Entry(Gtk.Entry):
  603     "A normal text entry"
  604 
  605     def __init__(self, text = None):
  606         Gtk.Entry.__init__(self)
  607 
  608         self.set_activates_default(True)
  609         self.set_text(text)
  610 
  611 
  612     def set_text(self, text):
  613         "Sets the entry contents"
  614 
  615         if text is None:
  616             text = ""
  617 
  618         Gtk.Entry.set_text(self, text)
  619 
  620 
  621 
  622 class FileEntry(HBox):
  623     "A file entry"
  624 
  625     def __init__(self, title = None, file = None, type = Gtk.FileChooserAction.OPEN):
  626         HBox.__init__(self)
  627 
  628         self.title = title is not None and title or _('Select File')
  629         self.type = type
  630 
  631         self.entry = Entry()
  632         self.entry.connect("changed", lambda w: self.emit("changed"))
  633         self.pack_start(self.entry, True, True, 0)
  634 
  635         self.button = Button(_('Browse...'), self.__cb_filesel)
  636         self.pack_start(self.button, False, False, 0)
  637 
  638         if file is not None:
  639             self.set_filename(file)
  640 
  641 
  642     def __cb_filesel(self, widget, data = None):
  643         "Displays a file selector when Browse is pressed"
  644 
  645         try:
  646             fsel = dialog.FileSelector(None, self.title, self.type)
  647             file = self.get_filename()
  648 
  649             if file != None:
  650                 fsel.set_filename(file)
  651 
  652             self.set_filename(fsel.run())
  653 
  654         except dialog.CancelError:
  655             pass
  656 
  657 
  658     def get_filename(self):
  659         "Gets the current filename"
  660 
  661         return io.file_normpath(self.entry.get_text())
  662 
  663 
  664     def get_text(self):
  665         "Wrapper to emulate Entry"
  666 
  667         return self.entry.get_text()
  668 
  669 
  670     def set_filename(self, filename):
  671         "Sets the current filename"
  672 
  673         self.entry.set_text(io.file_normpath(filename))
  674         self.entry.set_position(-1)
  675 
  676 
  677     def set_text(self, text):
  678         "Wrapper to emulate Entry"
  679 
  680         self.entry.set_text(text)
  681 
  682 
  683 GObject.type_register(FileEntry)
  684 GObject.signal_new("changed", FileEntry, GObject.SignalFlags.ACTION,
  685                    GObject.TYPE_BOOLEAN, ())
  686 
  687 
  688 
  689 class PasswordEntry(Gtk.Entry):
  690     "An entry for editing a password (follows the 'show passwords' preference)"
  691 
  692     def __init__(self, password = None, cfg = None, clipboard = None):
  693         Gtk.Entry.__init__(self)
  694         self.set_visibility(False)
  695         if password:
  696             self.set_text(password)
  697 
  698         self.autocheck  = True
  699         self.config = cfg
  700         self.clipboard  = clipboard
  701 
  702         self.connect("changed", self.__cb_check_password)
  703         self.connect("populate-popup", self.__cb_popup)
  704 
  705         if cfg != None:
  706             self.config.bind('view-passwords', self, "visibility", Gio.SettingsBindFlags.DEFAULT)
  707 
  708 
  709     def __cb_check_password(self, widget, data = None):
  710         "Callback for changed, checks the password"
  711 
  712         if self.autocheck == False:
  713             return
  714 
  715         password = self.get_text()
  716 
  717         if len(password) == 0:
  718             self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,None)
  719 
  720         else:
  721             try:
  722                 util.check_password(password)
  723 
  724             except ValueError as reason:
  725                 self.set_password_strong(False, _('The password %s') % str(reason))
  726 
  727             else:
  728                 self.set_password_strong(True, _('The password seems good'))
  729 
  730 
  731     def __cb_popup(self, widget, menu):
  732         "Populates the popup menu"
  733 
  734         if self.clipboard != None:
  735             menuitem = ImageMenuItem(Gtk.STOCK_COPY, _('Copy password'))
  736             menuitem.connect("activate", lambda w: self.clipboard.set([self.get_text()], True))
  737 
  738             menu.insert(menuitem, 2)
  739 
  740         menu.show_all()
  741 
  742 
  743     def set_password_strong(self, strong, reason = ""):
  744         "Sets whether the password is strong or not"
  745 
  746         self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, strong and STOCK_PASSWORD_STRONG or STOCK_PASSWORD_WEAK)
  747         self.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, reason)
  748 
  749 
  750 
  751 class PasswordEntryGenerate(HBox):
  752     "A password entry with a generator button"
  753 
  754     def __init__(self, password = None, cfg = None, clipboard = None):
  755         HBox.__init__(self)
  756         self.config = cfg
  757 
  758         self.pwentry = PasswordEntry(password, cfg, clipboard)
  759         self.pack_start(self.pwentry, True, True, 0)
  760 
  761         self.button = Button(_('Generate'), lambda w: self.generate())
  762         self.pack_start(self.button, False, False, 0)
  763 
  764         self.entry = self.pwentry
  765 
  766 
  767     def generate(self):
  768         "Generates a password for the entry"
  769 
  770         password = util.generate_password(self.config.get_int("passwordgen-length"), self.config.get_boolean("passwordgen-punctuation"))
  771         self.pwentry.set_text(password)
  772 
  773 
  774     def get_text(self):
  775         "Wrapper for the entry"
  776 
  777         return self.pwentry.get_text()
  778 
  779 
  780     def set_text(self, text):
  781         "Wrapper for the entry"
  782 
  783         self.pwentry.set_text(text)
  784 
  785 
  786 
  787 class SpinEntry(Gtk.SpinButton):
  788     "An entry for numbers"
  789 
  790     def __init__(self, adjustment = None, climb_rate = 0.0, digits = 0):
  791         Gtk.SpinButton.__init__(self)
  792         self.configure(adjustment, climb_rate, digits)
  793 
  794         self.set_increments(1, 5)
  795         self.set_range(0, 100000)
  796         self.set_numeric(True)
  797 
  798 
  799 
  800 ##### BUTTONS #####
  801 
  802 class Button(Gtk.Button):
  803     "A normal button"
  804 
  805     def __init__(self, label, callback = None):
  806         Gtk.Button.__init__(self, label)
  807 
  808         self.set_use_stock(True)
  809 
  810         if callback is not None:
  811             self.connect("clicked", callback)
  812 
  813 
  814 
  815 class CheckButton(Gtk.CheckButton):
  816     "A checkbutton"
  817 
  818     def __init__(self, label = None):
  819         Gtk.CheckButton.__init__(self, label)
  820 
  821 
  822 
  823 class DropDown(Gtk.ComboBox):
  824     "A dropdown button"
  825 
  826     def __init__(self, icons = False):
  827         Gtk.ComboBox.__init__(self)
  828 
  829         self.model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_PYOBJECT)
  830         self.set_model(self.model)
  831 
  832         if icons == True:
  833             cr = Gtk.CellRendererPixbuf()
  834             cr.set_fixed_size(Gtk.icon_size_lookup(ICON_SIZE_DROPDOWN)[1] + 5, -1)
  835             self.pack_start(cr, False)
  836             self.add_attribute(cr, "icon-name", 1)
  837 
  838         cr = Gtk.CellRendererText()
  839         self.pack_start(cr, True)
  840         self.add_attribute(cr, "text", 0)
  841 
  842         self.connect("realize", self.__cb_show)
  843 
  844 
  845     def __cb_show(self, widget, data = None):
  846         "Callback for when widget is shown"
  847 
  848         if self.get_active() == -1:
  849             self.set_active(0)
  850 
  851 
  852     def append_item(self, text, stock = None, data = None):
  853         "Appends an item to the dropdown"
  854 
  855         self.model.append( ( text, stock, data ) )
  856 
  857 
  858     def delete_item(self, index):
  859         "Removes an item from the dropdown"
  860 
  861         if self.model.iter_n_children(None) > index:
  862             iter = self.model.iter_nth_child(None, index)
  863             self.model.remove(iter)
  864 
  865 
  866     def get_active_item(self):
  867         "Returns a tuple with data for the current item"
  868 
  869         iter = self.model.iter_nth_child(None, self.get_active())
  870         return self.model.get(iter, 0, 1, 2)
  871 
  872 
  873     def get_item(self, index):
  874         "Returns data for an item"
  875 
  876         return self.model.get(self.model.iter_nth_child(None, index), 0, 1, 2)
  877 
  878 
  879     def get_num_items(self):
  880         "Returns the number of items in the dropdown"
  881 
  882         return self.model.iter_n_children(None)
  883 
  884 
  885     def insert_item(self, index, text, stock = None, data = None):
  886         "Inserts an item in the dropdown"
  887 
  888         self.model.insert(index, ( text, stock, data ) )
  889 
  890 
  891 
  892 class EntryDropDown(DropDown):
  893     "An entry type dropdown"
  894 
  895     def __init__(self):
  896         DropDown.__init__(self, True)
  897 
  898         for e in entry.ENTRYLIST:
  899             if e != entry.FolderEntry:
  900                 self.append_item(e().typename, e().icon, e)
  901 
  902 
  903     def get_active_type(self):
  904         "Get the currently active type"
  905 
  906         item = self.get_active_item()
  907 
  908         if item is not None:
  909             return item[2]
  910 
  911 
  912     def set_active_type(self, entrytype):
  913         "Set the active type"
  914 
  915         for i in range(self.model.iter_n_children(None)):
  916             iter = self.model.iter_nth_child(None, i)
  917 
  918             if self.model.get_value(iter, 2) == entrytype:
  919                 self.set_active(i)
  920 
  921 
  922 class FileButton(Gtk.FileChooserButton):
  923     "A file chooser button"
  924 
  925     def __init__(self, title = None, file = None, type = Gtk.FileChooserAction.OPEN):
  926         Gtk.FileChooserButton.__init__(self, title)
  927         self.set_action(type)
  928         self.set_local_only(False)
  929 
  930         if file != None:
  931             self.set_filename(file)
  932 
  933 
  934     def get_filename(self):
  935         "Gets the filename"
  936 
  937         return io.file_normpath(self.get_uri())
  938 
  939 
  940     def set_filename(self, filename):
  941         "Sets the filename"
  942 
  943         filename = io.file_normpath(filename)
  944 
  945         if filename != io.file_normpath(self.get_filename()):
  946             Gtk.FileChooserButton.set_filename(self, filename)
  947 
  948 
  949 class LinkButton(Gtk.LinkButton):
  950     "A link button"
  951 
  952     def __init__(self, url, label):
  953         Gtk.LinkButton.__init__(self, url, label)
  954         self.set_alignment(0, 0.5)
  955 
  956         self.label = self.get_children()[0]
  957 
  958         "If URI is too long reduce it for the label"
  959         if len(label) > 60:
  960             self.label.set_text(label[0:59] + " (...)")
  961 
  962     def set_ellipsize(self, ellipsize):
  963         "Sets ellipsize for label"
  964 
  965         self.label.set_ellipsize(ellipsize)
  966 
  967 
  968     def set_justify(self, justify):
  969         "Sets justify for label"
  970 
  971         self.label.set_justify(justify)
  972 
  973 
  974 class RadioButton(Gtk.RadioButton):
  975     "A radio button"
  976 
  977     def __init__(self, group, label):
  978         Gtk.RadioButton.__init__(self, group, label)
  979 
  980 
  981 
  982 ##### MENUS AND MENU ITEMS #####
  983 
  984 class ImageMenuItem(Gtk.ImageMenuItem):
  985     "A menuitem with a stock icon"
  986 
  987     def __init__(self, stock, text = None):
  988         Gtk.ImageMenuItem.__init__(self, stock)
  989 
  990         self.label = self.get_children()[0]
  991         self.image = self.get_image()
  992 
  993         if text is not None:
  994             self.set_text(text)
  995 
  996 
  997     def set_stock(self, stock):
  998         "Set the stock item to use as icon"
  999 
 1000         self.image.set_from_icon_name(stock, Gtk.IconSize.MENU)
 1001 
 1002 
 1003     def set_text(self, text):
 1004         "Set the item text"
 1005 
 1006         self.label.set_text(text)
 1007 
 1008 
 1009 
 1010 class Menu(Gtk.Menu):
 1011     "A menu"
 1012 
 1013     def __init__(self):
 1014         Gtk.Menu.__init__(self)
 1015 
 1016 
 1017 
 1018 ##### MISCELLANEOUS WIDGETS #####
 1019 
 1020 class TreeView(Gtk.TreeView):
 1021     "A tree display"
 1022 
 1023     def __init__(self, model):
 1024         Gtk.TreeView.__init__(self, model=model)
 1025         self.set_headers_visible(False)
 1026         self.model = model
 1027 
 1028         self.__cbid_drag_motion = None
 1029         self.__cbid_drag_end    = None
 1030 
 1031         self.selection = self.get_selection()
 1032         self.selection.set_mode(Gtk.SelectionMode.MULTIPLE)
 1033 
 1034         self.connect("button-press-event", self.__cb_buttonpress)
 1035         self.connect("key-press-event", self.__cb_keypress)
 1036 
 1037 
 1038     def __cb_buttonpress(self, widget, data):
 1039         "Callback for handling mouse clicks"
 1040 
 1041         path = self.get_path_at_pos(int(data.x), int(data.y))
 1042 
 1043         # handle click outside entry
 1044         if path is None:
 1045             self.unselect_all()
 1046 
 1047         # handle doubleclick
 1048         if data.button == 1 and data.type == Gdk.EventType._2BUTTON_PRESS and path != None:
 1049             iter = self.model.get_iter(path[0])
 1050             self.toggle_expanded(iter)
 1051 
 1052             if iter != None:
 1053                 self.emit("doubleclick", iter)
 1054 
 1055         # display popup on right-click
 1056         elif data.button == 3:
 1057             if path != None and self.selection.iter_is_selected(self.model.get_iter(path[0])) == False:
 1058                 self.set_cursor(path[0], path[1], False)
 1059 
 1060             self.emit("popup", data)
 1061 
 1062             return True
 1063 
 1064         # handle drag-and-drop of multiple rows
 1065         elif self.__cbid_drag_motion is None and data.button in ( 1, 2 ) and data.type == Gdk.EventType.BUTTON_PRESS and path != None and self.selection.iter_is_selected(self.model.get_iter(path[0])) == True and len(self.get_selected()) > 1:
 1066             self.__cbid_drag_motion = self.connect("motion-notify-event", self.__cb_drag_motion, data.copy() )
 1067             self.__cbid_drag_end = self.connect("button-release-event", self.__cb_button_release, data.copy() )
 1068 
 1069             return True
 1070 
 1071 
 1072     def __cb_button_release(self, widget, data, userdata = None):
 1073         "Ends a drag"
 1074 
 1075         self.emit("button-press-event", userdata)
 1076         self.__drag_check_end()
 1077 
 1078 
 1079     def __cb_drag_motion(self, widget, data, userdata = None):
 1080         "Monitors drag motion"
 1081 
 1082         if self.drag_check_threshold(int(userdata.x), int(userdata.y), int(data.x), int(data.y)) == True:
 1083             self.__drag_check_end()
 1084             uritarget = Gtk.TargetEntry.new( "revelation/treerow", Gtk.TargetFlags.SAME_APP | Gtk.TargetFlags.SAME_WIDGET, 0)
 1085             self.drag_begin_with_coordinates( Gtk.TargetList([uritarget]), Gdk.DragAction.MOVE, userdata.button.button, userdata, userdata.x, userdata.y)
 1086 
 1087 
 1088     def __cb_keypress(self, widget, data = None):
 1089         "Callback for handling key presses"
 1090 
 1091         # expand/collapse node on space
 1092         if data.keyval == Gdk.KEY_space:
 1093             self.toggle_expanded(self.get_active())
 1094 
 1095 
 1096     def __drag_check_end(self):
 1097         "Ends a drag check"
 1098 
 1099         self.disconnect(self.__cbid_drag_motion)
 1100         self.disconnect(self.__cbid_drag_end)
 1101 
 1102         self.__cbid_drag_motion = None
 1103         self.__cbid_drag_end = None
 1104 
 1105 
 1106     def collapse_row(self, iter):
 1107         "Collapse a tree row"
 1108 
 1109         Gtk.TreeView.collapse_row(self, self.model.get_path(iter))
 1110 
 1111 
 1112     def expand_row(self, iter):
 1113         "Expand a tree row"
 1114 
 1115         if iter is not None and self.model.iter_n_children(iter) > 0:
 1116             Gtk.TreeView.expand_row(self, self.model.get_path(iter), False)
 1117 
 1118 
 1119     def expand_to_iter(self, iter):
 1120         "Expand all items up to and including a given iter"
 1121 
 1122         path = self.model.get_path(iter)
 1123 
 1124         for i in range(len(path)):
 1125             iter = self.model.get_iter(path[0:i])
 1126             self.expand_row(iter)
 1127 
 1128 
 1129     def get_active(self):
 1130         "Get the currently active row"
 1131 
 1132         if self.model is None:
 1133             return None
 1134 
 1135         iter = self.model.get_iter(self.get_cursor()[0])
 1136 
 1137         if iter is None or self.selection.iter_is_selected(iter) == False:
 1138             return None
 1139 
 1140         return iter
 1141 
 1142     def get_selected(self):
 1143         "Get a list of currently selected rows"
 1144 
 1145         list = []
 1146         self.selection.selected_foreach(lambda model, path, iter: list.append(iter))
 1147 
 1148         return list
 1149 
 1150 
 1151     def select(self, iter):
 1152         "Select a particular row"
 1153 
 1154         if iter is None:
 1155             self.unselect_all()
 1156 
 1157         else:
 1158             self.expand_to_iter(iter)
 1159             self.set_cursor(self.model.get_path(iter))
 1160 
 1161 
 1162     def select_all(self):
 1163         "Select all rows in the tree"
 1164 
 1165         self.selection.select_all()
 1166         self.selection.emit("changed")
 1167         self.emit("cursor_changed")
 1168 
 1169 
 1170     def set_model(self, model):
 1171         "Change the tree model which is being displayed"
 1172 
 1173         Gtk.TreeView.set_model(self, model)
 1174         self.model = model
 1175 
 1176 
 1177     def toggle_expanded(self, iter):
 1178         "Toggle the expanded state of a row"
 1179 
 1180         if iter is None:
 1181             return
 1182 
 1183         elif self.row_expanded(self.model.get_path(iter)):
 1184             self.collapse_row(iter)
 1185 
 1186         else:
 1187             self.expand_row(iter)
 1188 
 1189 
 1190     def unselect_all(self):
 1191         "Unselect all rows in the tree"
 1192 
 1193         self.selection.unselect_all()
 1194         self.selection.emit("changed")
 1195         self.emit("cursor_changed")
 1196         self.emit("unselect_all")
 1197 
 1198 
 1199 GObject.signal_new("doubleclick", TreeView, GObject.SignalFlags.ACTION,
 1200                    GObject.TYPE_BOOLEAN, (GObject.TYPE_PYOBJECT, ))
 1201 GObject.signal_new("popup", TreeView, GObject.SignalFlags.ACTION,
 1202                    GObject.TYPE_BOOLEAN, (GObject.TYPE_PYOBJECT, ))
 1203 
 1204 
 1205 
 1206 class EntryTree(TreeView):
 1207     "An entry tree"
 1208 
 1209     def __init__(self, entrystore):
 1210         TreeView.__init__(self, entrystore)
 1211 
 1212         column = Gtk.TreeViewColumn()
 1213         self.append_column(column)
 1214 
 1215         cr = Gtk.CellRendererPixbuf()
 1216         column.pack_start(cr, False)
 1217         column.add_attribute(cr, "icon-name", data.COLUMN_ICON)
 1218         cr.set_property("stock-size", ICON_SIZE_TREEVIEW)
 1219 
 1220         cr = Gtk.CellRendererText()
 1221         column.pack_start(cr, True)
 1222         column.add_attribute(cr, "text", data.COLUMN_NAME)
 1223 
 1224         self.connect("doubleclick", self.__cb_doubleclick)
 1225         self.connect("row-expanded", self.__cb_row_expanded)
 1226         self.connect("row-collapsed", self.__cb_row_collapsed)
 1227 
 1228 
 1229     def __cb_doubleclick(self, widget, iter):
 1230         "Stop doubleclick emission on folder"
 1231 
 1232         if type(self.model.get_entry(iter)) == entry.FolderEntry:
 1233             self.stop_emission("doubleclick")
 1234 
 1235 
 1236     def __cb_row_collapsed(self, object, iter, extra):
 1237         "Updates folder icons when collapsed"
 1238 
 1239         self.model.folder_expanded(iter, False)
 1240 
 1241 
 1242     def __cb_row_expanded(self, object, iter, extra):
 1243         "Updates folder icons when expanded"
 1244 
 1245         # make sure all children are collapsed (some may have lingering expand icons)
 1246         for i in range(self.model.iter_n_children(iter)):
 1247             child = self.model.iter_nth_child(iter, i)
 1248 
 1249             if self.row_expanded(self.model.get_path(child)) == False:
 1250                 self.model.folder_expanded(child, False)
 1251 
 1252         self.model.folder_expanded(iter, True)
 1253 
 1254 
 1255     def set_model(self, model):
 1256         "Sets the model displayed by the tree view"
 1257 
 1258         TreeView.set_model(self, model)
 1259 
 1260         if model is None:
 1261             return
 1262 
 1263         for i in range(model.iter_n_children(None)):
 1264             model.folder_expanded(model.iter_nth_child(None, i), False)
 1265 
 1266 
 1267 
 1268 class Statusbar(Gtk.Statusbar):
 1269     "An application statusbar"
 1270 
 1271     def __init__(self):
 1272         Gtk.Statusbar.__init__(self)
 1273         self.contextid = self.get_context_id("statusbar")
 1274 
 1275 
 1276     def clear(self):
 1277         "Clears the statusbar"
 1278 
 1279         self.pop(self.contextid)
 1280 
 1281 
 1282     def set_status(self, text):
 1283         "Displays a text in the statusbar"
 1284 
 1285         self.clear()
 1286         self.push(self.contextid, text or "")
 1287 
 1288 
 1289 
 1290 ##### ACTION HANDLING #####
 1291 
 1292 class Action(Gtk.Action):
 1293     "UI Manager Action"
 1294 
 1295     def __init__(self, name, label = None, tooltip = None, stock = "", important = False):
 1296         Gtk.Action.__init__(self, name, label, tooltip, stock)
 1297 
 1298         if important == True:
 1299             self.set_property("is-important", True)
 1300 
 1301 
 1302 
 1303 class ActionGroup(Gtk.ActionGroup):
 1304     "UI Manager Actiongroup"
 1305 
 1306     def add_action(self, action, accel = None):
 1307         "Adds an action to the actiongroup"
 1308 
 1309         if accel is None:
 1310             Gtk.ActionGroup.add_action(self, action)
 1311 
 1312         else:
 1313             self.add_action_with_accel(action, accel)
 1314 
 1315 
 1316 
 1317 class ToggleAction(Gtk.ToggleAction):
 1318     "A toggle action item"
 1319 
 1320     def __init__(self, name, label, tooltip = None, stock = None):
 1321         Gtk.ToggleAction.__init__(self, name, label, tooltip, stock)
 1322 
 1323 
 1324 
 1325 class UIManager(Gtk.UIManager):
 1326     "UI item manager"
 1327 
 1328     def __init__(self):
 1329         Gtk.UIManager.__init__(self)
 1330 
 1331         self.connect("connect-proxy", self.__cb_connect_proxy)
 1332 
 1333 
 1334     def __cb_connect_proxy(self, uimanager, action, widget):
 1335         "Callback for connecting proxies to an action"
 1336 
 1337         if type(widget) in ( Gtk.MenuItem, Gtk.ImageMenuItem, Gtk.CheckMenuItem ):
 1338             widget.tooltip = action.get_property("tooltip")
 1339 
 1340         else:
 1341             widget.set_property("label", widget.get_property("label").replace("...", ""))
 1342 
 1343 
 1344     def add_ui_from_file(self, file):
 1345         "Loads ui from a file"
 1346 
 1347         try:
 1348             Gtk.UIManager.add_ui_from_file(self, file)
 1349 
 1350         except GObject.GError:
 1351             raise IOError
 1352 
 1353 
 1354     def append_action_group(self, actiongroup):
 1355         "Appends an action group"
 1356 
 1357         Gtk.UIManager.insert_action_group(self, actiongroup, len(self.get_action_groups()))
 1358 
 1359 
 1360     def get_action(self, name):
 1361         "Looks up an action in the managers actiongroups"
 1362 
 1363         for actiongroup in self.get_action_groups():
 1364             action = actiongroup.get_action(name)
 1365 
 1366             if action is not None:
 1367                 return action
 1368 
 1369 
 1370     def get_action_group(self, name):
 1371         "Returns the named action group"
 1372 
 1373         for actiongroup in self.get_action_groups():
 1374             if actiongroup.get_name() == name:
 1375                 return actiongroup
 1376 
 1377 
 1378 
 1379 ##### APPLICATION COMPONENTS #####
 1380 
 1381 class AppWindow(Gtk.ApplicationWindow):
 1382     "An application window"
 1383 
 1384     def __init__(self, *args, **kwargs):
 1385         super().__init__(*args, **kwargs)
 1386 
 1387 class App(Gtk.Application):
 1388     "An application"
 1389 
 1390     def __init__(self, appname):
 1391         Gtk.Application.__init__(self,
 1392                                  application_id='info.olasagasti.revelation')
 1393 
 1394         self.toolbars = {}
 1395 
 1396 
 1397     def __connect_menu_statusbar(self, menu):
 1398         "Connects a menus items to the statusbar"
 1399 
 1400         for item in menu.get_children():
 1401             if isinstance(item, Gtk.MenuItem) == True:
 1402                 item.connect("select", self.cb_menudesc, True)
 1403                 item.connect("deselect", self.cb_menudesc, False)
 1404 
 1405 
 1406     def cb_menudesc(self, item, show):
 1407         "Displays menu descriptions in the statusbar"
 1408 
 1409         if show == True:
 1410             self.statusbar.set_status(item.get_label())
 1411 
 1412 
 1413         else:
 1414             self.statusbar.clear()
 1415 
 1416 
 1417     def __cb_toolbar_hide(self, widget, name):
 1418         "Hides the toolbar dock when the toolbar is hidden"
 1419 
 1420         if name in self.toolbars:
 1421             self.toolbars[name].hide()
 1422 
 1423 
 1424     def __cb_toolbar_show(self, widget, name):
 1425         "Shows the toolbar dock when the toolbar is shown"
 1426 
 1427         if name in self.toolbars:
 1428             self.toolbars[name].show()
 1429 
 1430 
 1431     def add_toolbar(self, toolbar, name, band):
 1432         "Adds a toolbar"
 1433 
 1434         self.toolbars[name] = toolbar
 1435         self.main_vbox.pack_start(toolbar, False, True, 0)
 1436 
 1437         toolbar.connect("show", self.__cb_toolbar_show, name)
 1438         toolbar.connect("hide", self.__cb_toolbar_hide, name)
 1439 
 1440         toolbar.show_all()
 1441 
 1442 
 1443     def get_title(self):
 1444         "Returns the app title"
 1445 
 1446         title = Gtk.Window.get_title(self.window)
 1447 
 1448         return title.replace(" - " + config.APPNAME, "")
 1449 
 1450 
 1451     def popup(self, menu, button, time):
 1452         "Displays a popup menu"
 1453 
 1454         # get Gtk.Menu
 1455         gmenu = Gtk.Menu.new_from_model(menu)
 1456         gmenu.attach_to_widget(self.window,None)
 1457 
 1458         # transfer the tooltips from Gio.Menu to Gtk.Menu
 1459         menu_item_index = 0
 1460         menu_items=gmenu.get_children()
 1461         for sect in range(menu.get_n_items()):
 1462             for item in range(menu.get_item_link(sect,'section').get_n_items()):
 1463                 tooltip_text = menu.get_item_link(sect,'section').get_item_attribute_value(item,'tooltip',None)
 1464                 if tooltip_text:
 1465                     tooltip_text = tooltip_text.unpack()
 1466                 menu_items[menu_item_index].set_tooltip_text(tooltip_text)
 1467                 menu_item_index += 1
 1468             # skip section separator
 1469             menu_item_index += 1
 1470 
 1471         self.__connect_menu_statusbar(gmenu)
 1472         gmenu.popup(None, None, None, None, button, time)
 1473 
 1474 
 1475     def set_menus(self, menubar):
 1476         "Sets the menubar for the application"
 1477 
 1478         for item in menubar.get_children():
 1479             self.__connect_menu_statusbar(item.get_submenu())
 1480 
 1481         self.main_vbox.pack_start(menubar, False, True, 0)
 1482 
 1483 
 1484     def set_title(self, title):
 1485         "Sets the window title"
 1486 
 1487         Gtk.Window.set_title(self.window, title + " - " + config.APPNAME)
 1488 
 1489 
 1490     def set_toolbar(self, toolbar):
 1491         "Sets the application toolbar"
 1492 
 1493         self.main_vbox.pack_start(toolbar, False, True, 0)
 1494         toolbar.connect("show", self.__cb_toolbar_show, "Toolbar")
 1495         toolbar.connect("hide", self.__cb_toolbar_hide, "Toolbar")
 1496 
 1497     def set_contents(self, widget):
 1498         self.main_vbox.pack_start(widget, True, True, 0)
 1499 
 1500 
 1501 
 1502 class EntryView(VBox):
 1503     "A component for displaying an entry"
 1504 
 1505     def __init__(self, cfg = None, clipboard = None):
 1506         VBox.__init__(self)
 1507         self.set_spacing(12)
 1508         self.set_border_width(12)
 1509 
 1510         self.config     = cfg
 1511         self.clipboard  = clipboard
 1512         self.entry      = None
 1513 
 1514 
 1515     def clear(self, force = False):
 1516         "Clears the data view"
 1517 
 1518         self.entry = None
 1519 
 1520         for child in self.get_children():
 1521             child.destroy()
 1522 
 1523 
 1524     def display_entry(self, e):
 1525         "Displays info about an entry"
 1526 
 1527         self.clear()
 1528         self.entry = e
 1529 
 1530         if self.entry is None:
 1531             return
 1532 
 1533         # set up metadata display
 1534         metabox = VBox()
 1535         self.pack_start(metabox)
 1536 
 1537         label = ImageLabel(
 1538             "<span size=\"large\" weight=\"bold\">%s</span>" % util.escape_markup(e.name),
 1539             e.icon, ICON_SIZE_DATAVIEW
 1540         )
 1541         label.set_halign(Gtk.Align.CENTER)
 1542         label.set_valign(Gtk.Align.CENTER)
 1543         metabox.pack_start(label, True, True, 0)
 1544 
 1545         label = Label("<span weight=\"bold\">%s</span>%s" % ( e.typename + (e.description != "" and ": " or ""), util.escape_markup(e.description) ), Gtk.Justification.CENTER)
 1546         metabox.pack_start(label, True, True, 0)
 1547 
 1548         # set up field list
 1549         fields = [ field for field in e.fields if field.value != "" ]
 1550 
 1551         if len(fields) > 0:
 1552             table = Gtk.Grid()
 1553             self.pack_start(table)
 1554             table.set_column_spacing(10)
 1555             table.set_row_spacing(5)
 1556 
 1557             for rowindex, field in zip(range(len(fields)), fields):
 1558                 label = Label("<span weight=\"bold\">%s: </span>" % util.escape_markup(field.name))
 1559                 label.set_hexpand(True)
 1560                 table.attach(label, 0, rowindex, 1, 1)
 1561 
 1562                 widget = generate_field_display_widget(field, self.config, self.clipboard)
 1563                 widget.set_hexpand(True)
 1564                 table.attach(widget, 1, rowindex, 1, 1)
 1565 
 1566         # notes
 1567         label = Label("<span weight=\"bold\">%s</span>%s" % ((e.notes != "" and _("Notes: ") or ""),
 1568             util.escape_markup(e.notes) ), Gtk.Justification.LEFT)
 1569         self.pack_start(label)
 1570 
 1571         # display updatetime
 1572         if type(e) != entry.FolderEntry:
 1573             label = Label((_('Updated %s ago') + "\n%s") % ( util.time_period_rough(e.updated, time.time()), time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(e.updated)) ), Gtk.Justification.CENTER)
 1574             self.pack_start(label)
 1575 
 1576         self.show_all()
 1577 
 1578 
 1579     def pack_start(self, widget):
 1580         "Adds a widget to the data view"
 1581 
 1582         widget.set_halign(Gtk.Align.CENTER)
 1583         widget.set_valign(Gtk.Align.CENTER)
 1584         VBox.pack_start(self, widget, False, False, 0)
 1585 
 1586 
 1587 
 1588 class Searchbar(Toolbar):
 1589     "A toolbar for easy searching"
 1590 
 1591     def __init__(self):
 1592         Toolbar.__init__(self)
 1593 
 1594         self.entry      = Gtk.SearchEntry()
 1595         self.entry.set_tooltip_text(_('Text to search for'))
 1596         self.dropdown       = EntryDropDown()
 1597         self.dropdown.insert_item(0, _('Any type'), "help-about")
 1598 
 1599         box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
 1600         Gtk.StyleContext.add_class(box.get_style_context(), "linked")
 1601         self.button_prev = Gtk.Button.new_from_icon_name(STOCK_PREVIOUS,
 1602                                                          Gtk.IconSize.BUTTON)
 1603         self.button_prev.set_tooltip_text(_('Find the previous match'))
 1604         self.button_next = Gtk.Button.new_from_icon_name(STOCK_NEXT,
 1605                                                          Gtk.IconSize.BUTTON)
 1606         self.button_next.set_tooltip_text(_('Find the next match'))
 1607 
 1608         box.add(self.entry)
 1609         box.add(self.button_prev)
 1610         box.add(self.button_next)
 1611         box.add(self.dropdown)
 1612 
 1613         self.append_widget(box)
 1614 
 1615         self.connect("show", self.__cb_show)
 1616 
 1617         self.entry.connect("changed", self.__cb_entry_changed)
 1618         self.entry.connect("key-press-event", self.__cb_key_press)
 1619 
 1620         self.button_next.set_sensitive(False)
 1621         self.button_prev.set_sensitive(False)
 1622 
 1623 
 1624     def __cb_entry_changed(self, widget, data = None):
 1625         "Callback for entry changes"
 1626 
 1627         s = self.entry.get_text() != ""
 1628 
 1629         self.button_next.set_sensitive(s)
 1630         self.button_prev.set_sensitive(s)
 1631 
 1632 
 1633     def __cb_key_press(self, widget, data = None):
 1634         "Callback for key presses"
 1635 
 1636         # return
 1637         if data.keyval == Gdk.KEY_Return and widget.get_text() != "":
 1638             if (data.state & Gdk.ModifierType.SHIFT_MASK) == Gdk.ModifierType.SHIFT_MASK:
 1639                 self.button_prev.activate()
 1640 
 1641             else:
 1642                 self.button_next.activate()
 1643 
 1644             return True
 1645 
 1646 
 1647     def __cb_show(self, widget, data = None):
 1648         "Callback for widget display"
 1649 
 1650         self.entry.select_region(0, -1)
 1651         self.entry.grab_focus()
 1652