"Fossies" - the Fresh Open Source Software Archive

Member "revelation-0.5.4/src/lib/data.py" (4 Oct 2020, 17658 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 "data.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 containing data-related 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 datahandler, entry
   27 
   28 from gi.repository import GObject, Gtk, Gdk, GLib
   29 import time
   30 
   31 
   32 COLUMN_NAME  = 0
   33 COLUMN_ICON  = 1
   34 COLUMN_ENTRY = 2
   35 
   36 SEARCH_NEXT     = "next"
   37 SEARCH_PREVIOUS = "prev"
   38 
   39 
   40 
   41 class Clipboard(GObject.GObject):
   42     "A normal text-clipboard"
   43 
   44     def __init__(self):
   45         GObject.GObject.__init__(self)
   46 
   47         self.clip_clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
   48         self.clip_primary   = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY)
   49 
   50         self.cleartimer     = Timer(10)
   51         self.cleartimeout   = 60
   52         self.cleartimer.connect("ring", self.__cb_clear_ring)
   53 
   54         self.content        = None
   55         self.contentpointer = 0
   56 
   57 
   58     def __cb_clear(self, clipboard, data = None):
   59         "Clears the clipboard data"
   60 
   61         self.clip_clipboard.clear()
   62         self.clip_primary.clear()
   63 
   64 
   65     def __cb_clear_ring(self, widget):
   66         "Handles cleartimer rings"
   67 
   68         self.content        = None
   69         self.contentpointer = 0
   70         self.set("", False)
   71 
   72 
   73     def __cb_get(self, clipboard, selectiondata, info, data):
   74         "Returns text for clipboard requests"
   75 
   76         if self.content is None:
   77             text = ""
   78 
   79         elif type(self.content) == list:
   80 
   81             if len(self.content) == 0:
   82                 text = ""
   83 
   84             else:
   85                 text = self.content[self.contentpointer]
   86 
   87             if self.contentpointer < len(self.content) - 1:
   88                 self.contentpointer += 1
   89 
   90         else:
   91             text = str(self.content)
   92 
   93         selectiondata.set_text(text, len(text))
   94 
   95 
   96     def clear(self):
   97         "Clears the clipboard"
   98 
   99         self.clip_clipboard.clear()
  100         self.clip_primary.clear()
  101 
  102 
  103     def get(self):
  104         "Fetches text from the clipboard"
  105 
  106         text = self.clip_clipboard.wait_for_text()
  107 
  108         if text is None:
  109             text = ""
  110 
  111         return text
  112 
  113 
  114     def has_contents(self):
  115         "Checks if the clipboard has any contents"
  116 
  117         return self.clip_clipboard.wait_is_text_available()
  118 
  119 
  120     def set(self, content, secret = False):
  121         "Copies text to the clipboard"
  122 
  123         self.content        = content
  124         self.contentpointer = 0
  125 
  126         targets = [
  127             Gtk.TargetEntry.new( "text/plain",    0, 0 ),
  128             Gtk.TargetEntry.new( "STRING",        0, 0 ),
  129             Gtk.TargetEntry.new( "TEXT",          0, 0 ),
  130             Gtk.TargetEntry.new( "COMPOUND_TEXT", 0, 0 ),
  131             Gtk.TargetEntry.new( "UTF8_STRING",   0, 0 )
  132         ]
  133 
  134         self.clip_clipboard.set_text(' '.join(self.content), -1)
  135         self.clip_primary.set_text(' '.join(self.content), -1)
  136 
  137 
  138         if secret == True:
  139             self.cleartimer.start(self.cleartimeout)
  140 
  141         else:
  142             self.cleartimer.stop()
  143 
  144 
  145 
  146 class EntryClipboard(GObject.GObject):
  147     "A clipboard for entries"
  148 
  149     def __init__(self):
  150         GObject.GObject.__init__(self)
  151 
  152         self.clipboard = Gtk.Clipboard.get_for_display(display=Gdk.Display.get_default(), selection=Gdk.Atom.intern("_REVELATION_ENTRY",False))
  153         self.__has_contents = False
  154 
  155         GLib.timeout_add(500, lambda: self.__check_contents())
  156 
  157 
  158     def __check_contents(self):
  159         "Callback which check the clipboard"
  160 
  161         state = self.has_contents()
  162 
  163         if state != self.__has_contents:
  164             self.emit("content-toggled", state)
  165             self.__has_contents = state
  166 
  167         return True
  168 
  169 
  170     def clear(self):
  171         "Clears the clipboard"
  172 
  173         self.clipboard.clear()
  174         self.__check_contents()
  175 
  176 
  177     def get(self):
  178         "Fetches entries from the clipboard"
  179 
  180         try:
  181             xml = self.clipboard.wait_for_text()
  182 
  183             if xml in ( None, "" ):
  184                 return None
  185 
  186             handler = datahandler.RevelationXML()
  187             entrystore = handler.import_data(xml)
  188 
  189             return entrystore
  190 
  191         except datahandler.HandlerError:
  192             return None
  193 
  194 
  195     def has_contents(self):
  196         "Checks if the clipboard has any contents"
  197 
  198         return self.clipboard.wait_for_text() is not None
  199 
  200 
  201     def set(self, entrystore, iters):
  202         "Copies entries from an entrystore to the clipboard"
  203 
  204         copystore = EntryStore()
  205 
  206         for iter in entrystore.filter_parents(iters):
  207             copystore.import_entry(entrystore, iter)
  208 
  209         xml = datahandler.RevelationXML().export_data(copystore)
  210         self.clipboard.set_text(xml,-1)
  211 
  212         self.__check_contents()
  213 
  214 
  215 GObject.signal_new("content-toggled", EntryClipboard,
  216                    GObject.SignalFlags.ACTION, GObject.TYPE_BOOLEAN,
  217                    ( GObject.TYPE_BOOLEAN, ))
  218 
  219 
  220 
  221 class EntrySearch(GObject.GObject):
  222     "Handles searching in an EntryStore"
  223 
  224     def __init__(self, entrystore):
  225         GObject.GObject.__init__(self)
  226         self.entrystore = entrystore
  227 
  228         self.folders        = True
  229         self.namedesconly   = False
  230         self.casesensitive  = False
  231 
  232 
  233     def find(self, string, entrytype = None, offset = None, direction = SEARCH_NEXT):
  234         "Searches for an entry, starting at the given offset"
  235 
  236         iter = offset
  237 
  238         while True:
  239 
  240             if direction == SEARCH_NEXT:
  241                 iter = self.entrystore.iter_traverse_next(iter)
  242 
  243             else:
  244                 iter = self.entrystore.iter_traverse_prev(iter)
  245 
  246             # if we've wrapped around, return None
  247             if self.entrystore.get_path(iter) == self.entrystore.get_path(offset):
  248                 return None
  249 
  250             if self.match(iter, string, entrytype) == True:
  251                 return iter
  252 
  253 
  254     def find_all(self, string, entrytype = None):
  255         "Searches for all entries matching a term"
  256 
  257         matches = []
  258         iter = self.entrystore.iter_children(None)
  259 
  260         while iter != None:
  261 
  262             if self.match(iter, string, entrytype) == True:
  263                 matches.append(iter)
  264 
  265             iter = self.entrystore.iter_traverse_next(iter)
  266 
  267         return matches
  268 
  269 
  270     def match(self, iter, string, entrytype = None):
  271         "Check if an entry matches the search criteria"
  272 
  273         if iter is None or not string:
  274             return False
  275 
  276         e = self.entrystore.get_entry(iter)
  277 
  278 
  279         # check entry type
  280         if type(e) == entry.FolderEntry and self.folders == False:
  281             return False
  282 
  283         if entrytype is not None and type(e) not in ( entrytype, entry.FolderEntry ):
  284             return False
  285 
  286 
  287         # check entry fields
  288         items = [ e.name, e.description, e.notes ]
  289 
  290         if self.namedesconly == False:
  291             items.extend([ field.value for field in e.fields if field.value != "" ])
  292 
  293 
  294         # run the search
  295         for item in items:
  296             if self.casesensitive == True and string in item:
  297                 return True
  298 
  299             elif self.casesensitive == False and string.lower() in item.lower():
  300                 return True
  301 
  302         return False
  303 
  304 
  305 
  306 class EntryStore(Gtk.TreeStore):
  307     "A data structure for storing entries"
  308 
  309     def __init__(self):
  310         Gtk.TreeStore.__init__(
  311             self,
  312             GObject.TYPE_STRING,    # name
  313             GObject.TYPE_STRING,    # icon
  314             GObject.TYPE_PYOBJECT   # entry
  315         )
  316 
  317         self.changed = False
  318         self.connect("row-has-child-toggled", self.__cb_iter_has_child)
  319 
  320         self.set_sort_func(COLUMN_NAME, self.__cmp)
  321         self.set_sort_column_id(COLUMN_NAME, Gtk.SortType.ASCENDING)
  322 
  323 
  324     def __cmp(self, treemodel, iter1, iter2, user_data=None):
  325         name1 = treemodel.get_value(iter1, COLUMN_NAME).strip().lower()
  326         name2 = treemodel.get_value(iter2, COLUMN_NAME).strip().lower()
  327 
  328         return (name1 > name2) - (name1 < name2)
  329 
  330 
  331     def __cb_iter_has_child(self, widget, path, iter):
  332         "Callback for iters having children"
  333 
  334         if self.iter_n_children(iter) == 0:
  335             self.folder_expanded(iter, False)
  336 
  337 
  338     def add_entry(self, e, parent = None, sibling = None):
  339         "Adds an entry"
  340 
  341         # place after parent if it's not a folder
  342         if parent is not None and type(self.get_entry(parent)) != entry.FolderEntry:
  343             iter = self.insert_after(self.iter_parent(parent), parent)
  344 
  345         # place before sibling, if given
  346         elif sibling is not None:
  347             iter = self.insert_before(parent, sibling)
  348 
  349         # otherwise, append to parent
  350         else:
  351             iter = self.append(parent)
  352 
  353         self.update_entry(iter, e)
  354         self.changed = True
  355 
  356         return iter
  357 
  358 
  359     def clear(self):
  360         "Removes all entries"
  361 
  362         Gtk.TreeStore.clear(self)
  363         self.changed = False
  364 
  365 
  366     def copy_entry(self, iter, parent = None, sibling = None):
  367         "Copies an entry recursively"
  368 
  369         newiter = self.add_entry(self.get_entry(iter), parent, sibling)
  370 
  371         for i in range(self.iter_n_children(iter)):
  372             child = self.iter_nth_child(iter, i)
  373             self.copy_entry(child, newiter)
  374 
  375         return newiter
  376 
  377 
  378     def filter_parents(self, iters):
  379         "Removes all descendants from the list of iters"
  380 
  381         parents = []
  382 
  383         for child in iters:
  384             for parent in iters:
  385                 if self.is_ancestor(parent, child):
  386                     break
  387 
  388             else:
  389                 parents.append(child)
  390 
  391         return parents
  392 
  393 
  394     def folder_expanded(self, iter, expanded):
  395         "Sets the expanded state of an entry"
  396 
  397         e = self.get_entry(iter)
  398 
  399         if e is None or type(e) != entry.FolderEntry:
  400             return
  401 
  402         elif expanded == True:
  403             self.set_value(iter, COLUMN_ICON, e.openicon)
  404 
  405         else:
  406             self.set_value(iter, COLUMN_ICON, e.icon)
  407 
  408 
  409     def get_entry(self, iter):
  410         "Fetches data for an entry"
  411 
  412         if iter is None:
  413             return None
  414 
  415         e = self.get_value(iter, COLUMN_ENTRY)
  416 
  417         if e is None:
  418             return None
  419 
  420         else:
  421             return e.copy()
  422 
  423 
  424     def get_iter(self, path):
  425         "Gets an iter from a path"
  426 
  427         try:
  428             if not path:
  429                 return None
  430 
  431             if isinstance(path, list):
  432                 path = tuple(path)
  433 
  434             return Gtk.TreeStore.get_iter(self, path)
  435 
  436         except ValueError:
  437             return None
  438 
  439 
  440     def get_path(self, iter):
  441         "Gets a path from an iter"
  442 
  443         return iter is not None and Gtk.TreeStore.get_path(self, iter) or None
  444 
  445 
  446     def get_popular_values(self, fieldtype, threshold = 3):
  447         "Gets popular values for a field type"
  448 
  449         valuecount = {}
  450         iter = self.iter_nth_child(None, 0)
  451 
  452         while iter is not None:
  453             e = self.get_entry(iter)
  454 
  455             if e.has_field(fieldtype) == False:
  456                 iter = self.iter_traverse_next(iter)
  457                 continue
  458 
  459             value = e[fieldtype].strip()
  460 
  461             if value != "":
  462                 if value not in valuecount:
  463                     valuecount[value] = 0
  464 
  465                 valuecount[value] += 1
  466 
  467             iter = self.iter_traverse_next(iter)
  468 
  469         popular = [ value for value, count in valuecount.items() if count >= threshold ]
  470         popular.sort()
  471 
  472         return popular
  473 
  474 
  475     def import_entry(self, source, iter, parent = None, sibling = None):
  476         "Recursively copies an entry from a different entrystore"
  477 
  478         if iter is not None:
  479             copy = self.add_entry(source.get_entry(iter), parent, sibling)
  480             parent, sibling = copy, None
  481 
  482         else:
  483             copy = None
  484 
  485         newiters = []
  486         for i in range(source.iter_n_children(iter)):
  487             child = source.iter_nth_child(iter, i)
  488             newiter = self.import_entry(source, child, parent, sibling)
  489             newiters.append(newiter)
  490 
  491         return copy is not None and copy or newiters
  492 
  493 
  494     def iter_traverse_next(self, iter):
  495         "Gets the 'logically next' iter"
  496 
  497         # get the first child, if any
  498         child = self.iter_nth_child(iter, 0)
  499         if child is not None:
  500             return child
  501 
  502         # check for a sibling or, if not found, a sibling of any ancestors
  503         parent = iter
  504         while parent is not None:
  505             sibling = parent.copy()
  506             sibling = self.iter_next(sibling)
  507 
  508             if sibling is not None:
  509                 return sibling
  510 
  511             parent = self.iter_parent(parent)
  512 
  513         return None
  514 
  515 
  516     def iter_traverse_prev(self, iter):
  517         "Gets the 'logically previous' iter"
  518 
  519         # get the previous sibling, or parent, of the iter - if any
  520         if iter is not None:
  521             parent = self.iter_parent(iter)
  522             index = self.get_path(iter)[-1]
  523 
  524             # if no sibling is found, return the parent
  525             if index == 0:
  526                 return parent
  527 
  528             # otherwise, get the sibling
  529             iter = self.iter_nth_child(parent, index - 1)
  530 
  531         # get the last, deepest child of the sibling or root, if any
  532         while self.iter_n_children(iter) > 0:
  533             iter = self.iter_nth_child(iter, self.iter_n_children(iter) - 1)
  534 
  535         return iter
  536 
  537 
  538     def move_entry(self, iter, parent = None, sibling = None):
  539         "Moves an entry"
  540 
  541         newiter = self.copy_entry(iter, parent, sibling)
  542         self.remove_entry(iter)
  543 
  544         return newiter
  545 
  546 
  547     def remove_entry(self, iter):
  548         "Removes an entry, and its children if any"
  549 
  550         if iter is None:
  551             return None
  552 
  553         self.remove(iter)
  554         self.changed = True
  555 
  556 
  557     def update_entry(self, iter, e):
  558         "Updates an entry"
  559 
  560         if None in ( iter, e):
  561             return None
  562 
  563         self.set_value(iter, COLUMN_NAME, e.name)
  564         self.set_value(iter, COLUMN_ICON, e.icon)
  565         self.set_value(iter, COLUMN_ENTRY, e.copy())
  566 
  567         self.changed = True
  568 
  569 
  570 
  571 class Timer(GObject.GObject):
  572     "Handles timeouts etc"
  573 
  574     def __init__(self, resolution = 1):
  575         GObject.GObject.__init__(self)
  576 
  577         self.offset  = None
  578         self.timeout = None
  579 
  580         GLib.timeout_add(resolution * 1000, self.__cb_check)
  581 
  582 
  583     def __cb_check(self):
  584         "Checks if the timeout has been reached"
  585 
  586         if None not in (self.offset, self.timeout) and int(time.time()) >= (self.offset + self.timeout):
  587             self.stop()
  588             self.emit("ring")
  589 
  590         return True
  591 
  592 
  593     def reset(self):
  594         "Resets the timer"
  595 
  596         if self.offset != None:
  597             self.offset = int(time.time())
  598 
  599 
  600     def start(self, timeout):
  601         "Starts the timer"
  602 
  603         if timeout == 0:
  604             self.stop()
  605 
  606         else:
  607             self.offset = int(time.time())
  608             self.timeout = timeout
  609 
  610 
  611     def stop(self):
  612         "Stops the timer"
  613 
  614         self.offset = None
  615         self.timeout = None
  616 
  617 
  618 GObject.signal_new("ring", Timer, GObject.SignalFlags.ACTION,
  619                    GObject.TYPE_BOOLEAN, ())
  620 
  621 
  622 
  623 class UndoQueue(GObject.GObject):
  624     "Handles undo/redo tracking"
  625 
  626     def __init__(self):
  627         GObject.GObject.__init__(self)
  628 
  629         self.queue  = []
  630         self.pointer    = 0
  631 
  632 
  633     def add_action(self, name, cb_undo, cb_redo, actiondata):
  634         "Adds an action to the undo queue"
  635 
  636         del self.queue[self.pointer:]
  637 
  638         self.queue.append(( name, cb_undo, cb_redo, actiondata ))
  639         self.pointer = len(self.queue)
  640 
  641         self.emit("changed")
  642 
  643 
  644     def can_redo(self):
  645         "Checks if a redo action is possible"
  646 
  647         return self.pointer < len(self.queue)
  648 
  649 
  650     def can_undo(self):
  651         "Checks if an undo action is possible"
  652 
  653         return self.pointer > 0
  654 
  655 
  656     def clear(self):
  657         "Clears the queue"
  658 
  659         self.queue = []
  660         self.pointer = 0
  661 
  662         self.emit("changed")
  663 
  664 
  665     def get_redo_action(self):
  666         "Returns data for the next redo operation"
  667 
  668         if self.can_redo() == False:
  669             return None
  670 
  671         name, cb_undo, cb_redo, actiondata = self.queue[self.pointer]
  672 
  673         return cb_redo, name, actiondata
  674 
  675 
  676     def get_undo_action(self):
  677         "Returns data for the next undo operation"
  678 
  679         if self.can_undo() == False:
  680             return None
  681 
  682         name, cb_undo, cb_redo, actiondata = self.queue[self.pointer - 1]
  683 
  684         return cb_undo, name, actiondata
  685 
  686 
  687     def redo(self):
  688         "Executes a redo operation"
  689 
  690         if self.can_redo() == False:
  691             return None
  692 
  693         cb_redo, name, actiondata = self.get_redo_action()
  694         self.pointer += 1
  695 
  696         cb_redo(name, actiondata)
  697         self.emit("changed")
  698 
  699 
  700     def undo(self):
  701         "Executes an undo operation"
  702 
  703         if self.can_undo() == False:
  704             return None
  705 
  706         cb_undo, name, actiondata = self.get_undo_action()
  707         self.pointer -= 1
  708 
  709         cb_undo(name, actiondata)
  710         self.emit("changed")
  711 
  712 
  713 GObject.type_register(UndoQueue)
  714 GObject.signal_new("changed", UndoQueue, GObject.SignalFlags.ACTION,
  715                    GObject.TYPE_BOOLEAN, ())
  716