📄 dirdiff.py
字号:
### Copyright (C) 2002-2006 Stephen Kennedy <stevek@gnome.org>### This program is free software; you can redistribute it and/or modify### it under the terms of the GNU General Public License as published by### the Free Software Foundation; either version 2 of the License, or### (at your option) any later version.### This program is distributed in the hope that it will be useful,### but WITHOUT ANY WARRANTY; without even the implied warranty of### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the### GNU General Public License for more details.### You should have received a copy of the GNU General Public License### along with this program; if not, write to the Free Software### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USAfrom __future__ import generatorsimport pathsimport gnomegladeimport gobjectimport gtkimport gtk.keysymsimport mathimport miscimport osimport shutilimport melddocimport treeimport reimport statimport timegdk = gtk.gdk################################################################################## Local Functions#################################################################################def uniq(l): i = iter(l) a = i.next() yield a while 1: b = i.next() if a != b: yield b a = b_cache = {}def _files_same(lof, regexes): """Return 1 if all the files in 'lof' have the same contents. If the files are the same after the regular expression substitution, return 2. Finally, return 0 if the files still differ. """ # early out if only one file if len(lof) <= 1: return 1 # get sigs lof = tuple(lof) def sig(f): s = os.stat(f) return misc.struct(mode=stat.S_IFMT(s.st_mode), size=s.st_size, time=s.st_mtime) def all_same(l): for i in l[1:]: if l[0] != i: return 0 return 1 sigs = tuple( [ sig(f) for f in lof ] ) # check for directories arefiles = [ stat.S_ISREG(s.mode) for s in sigs ] if arefiles.count(0) == len(arefiles): # all dirs return 1 elif arefiles.count(0): # mixture return 0 # if no substitutions look for different sizes if len(regexes) == 0 and all_same( [s.size for s in sigs] ) == 0: return 0 # try cache try: cache = _cache[ lof ] except KeyError: pass else: if cache.sigs == sigs: # up to date return cache.result # do it contents = [ open(f, "r").read() for f in lof ] if all_same(contents): result = 1 else: for r in regexes: contents = [ re.sub(r, "", c) for c in contents ] result = all_same(contents) and 2 _cache[ lof ] = misc.struct(sigs=sigs, result=result) return resultdef _not_none(l): """Return list with Nones filtered out""" return filter(lambda x: x!=None, l)join = os.path.joinCOL_EMBLEM = tree.COL_END + 1pixbuf_newer = gnomeglade.load_pixbuf( paths.share_dir("glade2/pixmaps/tree-file-newer.png"), 14)TYPE_PIXBUF = type(pixbuf_newer)################################################################################## DirDiffTreeStore#################################################################################class DirDiffTreeStore(tree.DiffTreeStore): def __init__(self, ntree): types = [type("")] * COL_EMBLEM * ntree types[tree.COL_ICON*ntree:tree.COL_ICON*ntree+ntree] = [TYPE_PIXBUF] * ntree types[COL_EMBLEM*ntree:COL_EMBLEM*ntree+ntree] = [TYPE_PIXBUF] * ntree gtk.TreeStore.__init__(self, *types) self.ntree = ntree self._setup_default_styles()################################################################################## EmblemCellRenderer#################################################################################class EmblemCellRenderer(gtk.GenericCellRenderer): __gproperties__ = { 'pixbuf': (gtk.gdk.Pixbuf, 'pixmap property', 'the base pixmap', gobject.PARAM_READWRITE), 'emblem': (gtk.gdk.Pixbuf, 'emblem property', 'the emblem pixmap', gobject.PARAM_READWRITE), } def __init__(self): self.__gobject_init__() self.renderer = gtk.CellRendererPixbuf() self.pixbuf = None self.emblem = None def do_set_property(self, pspec, value): if not hasattr(self, pspec.name): raise AttributeError, 'unknown property %s' % pspec.name setattr(self, pspec.name, value) def do_get_property(self, pspec): return getattr(self, pspec.name) def on_render(self, window, widget, background_area, cell_area, expose_area, flags): r = self.renderer r.set_property("pixbuf", self.pixbuf) r.render(window, widget, background_area, cell_area, expose_area, flags) r.set_property("pixbuf", self.emblem) r.render(window, widget, background_area, cell_area, expose_area, flags) def on_get_size(self, widget, cell_area): if not hasattr(self, "size"): r = self.renderer r.set_property("pixbuf", self.pixbuf) self.size = r.get_size(widget, cell_area) return self.sizeif gobject.pygtk_version < (2,8,0): gobject.type_register(EmblemCellRenderer)################################################################################## DirDiffMenu#################################################################################class DirDiffMenu(gnomeglade.Component): def __init__(self, app): gladefile = paths.share_dir("glade2/dirdiff.glade") gnomeglade.Component.__init__(self, gladefile, "popup") self.parent = app def popup_in_pane( self, pane ): self.copy_left.set_sensitive( pane > 0 ) self.copy_right.set_sensitive( pane+1 < self.parent.num_panes ) self.widget.popup( None, None, None, 3, gtk.get_current_event_time() ) def on_popup_compare_activate(self, menuitem): self.parent.launch_comparisons_on_selected() def on_popup_copy_left_activate(self, menuitem): self.parent.on_button_copy_left_clicked( None ) def on_popup_copy_right_activate(self, menuitem): self.parent.on_button_copy_right_clicked( None ) def on_popup_delete_activate(self, menuitem): self.parent.on_button_delete_clicked( None ) def on_popup_edit_activate(self, menuitem): self.parent.on_button_edit_clicked( None )################################################################################## TypeFilter#################################################################################class TypeFilter(object): __slots__ = ("label", "filter", "active") def __init__(self, label, active, filter): self.label = label self.active = active self.filter = filter################################################################################## DirDiff#################################################################################class DirDiff(melddoc.MeldDoc, gnomeglade.Component): """Two or three way diff of directories""" def __init__(self, prefs, num_panes): melddoc.MeldDoc.__init__(self, prefs) gnomeglade.Component.__init__(self, paths.share_dir("glade2/dirdiff.glade"), "dirdiff") self.toolbar.set_style( self.prefs.get_toolbar_style() ) self._map_widgets_into_lists( ["treeview", "fileentry", "diffmap", "scrolledwindow", "linkmap"] ) self.popup_menu = DirDiffMenu(self) self.set_num_panes(num_panes) self.on_treeview_focus_out_event(None, None) self.treeview_focussed = None for i in range(3): self.treeview[i].get_selection().set_mode(gtk.SELECTION_MULTIPLE) column = gtk.TreeViewColumn() rentext = gtk.CellRendererText() renicon = EmblemCellRenderer() column.pack_start(renicon, expand=0) column.pack_start(rentext, expand=1) column.set_attributes(renicon, pixbuf=self.model.column_index(tree.COL_ICON,i), emblem=self.model.column_index(COL_EMBLEM,i)) column.set_attributes(rentext, markup=self.model.column_index(tree.COL_TEXT,i)) self.treeview[i].append_column(column) self.scrolledwindow[i].get_vadjustment().connect("value-changed", self._sync_vscroll ) self.scrolledwindow[i].get_hadjustment().connect("value-changed", self._sync_hscroll ) self.linediffs = [[], []] self.state_filters = [ tree.STATE_NORMAL, tree.STATE_MODIFIED, tree.STATE_NEW, ] self.create_name_filters() self.update_regexes() def update_regexes(self): self.regexes = [] for r in [ misc.ListItem(i) for i in self.prefs.regexes.split("\n") ]: if r.active: try: self.regexes.append( re.compile(r.value+"(?m)") ) except re.error: misc.run_dialog( text=_("Error converting pattern '%s' to regular expression") % r.value ) def create_name_filters(self): self.name_filters_available = [] for f in [misc.ListItem(s) for s in self.prefs.filters.split("\n") ]: bits = f.value.split() if len(bits) > 1: regex = "(%s)$" % ")|(".join( [misc.shell_to_regex(b)[:-1] for b in bits] ) elif len(bits): regex = misc.shell_to_regex(bits[0]) else: # an empty pattern would match anything, skip it continue try: cregex = re.compile(regex) except re.error: misc.run_dialog( _("Error converting pattern '%s' to regular expression") % f.value, self ) else: func = lambda x, r=cregex : r.match(x) == None self.name_filters_available.append( TypeFilter(f.name, f.active, func) ) self.name_filters = [] tips = gtk.Tooltips() for i,f in enumerate(self.name_filters_available): icon = gtk.Image() icon.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_LARGE_TOOLBAR) toggle = gtk.ToggleToolButton() toggle.set_property("label",f.label) toggle.set_icon_widget(icon) toggle.connect("toggled", lambda b,i=i : self._update_name_filter(b,i) ) toggle.set_active(f.active) self.toolbar.insert(toggle, -1) toggle.show_all() toggle.set_tooltip(tips, _("Hide %s") % f.label ) def on_preference_changed(self, key, value): if key == "toolbar_style": self.toolbar.set_style( self.prefs.get_toolbar_style() ) elif key == "regexes": self.update_regexes() def _do_to_others(self, master, objects, methodname, args): if not hasattr(self, "do_to_others_lock"): self.do_to_others_lock = 1 try: for o in filter(lambda x:x!=master, objects[:self.num_panes]): method = getattr(o,methodname) method(*args) finally: delattr(self, "do_to_others_lock") def _sync_vscroll(self, adjustment): adjs = map(lambda x: x.get_vadjustment(), self.scrolledwindow) self._do_to_others( adjustment, adjs, "set_value", (adjustment.value,) ) def _sync_hscroll(self, adjustment): adjs = map(lambda x: x.get_hadjustment(), self.scrolledwindow) self._do_to_others( adjustment, adjs, "set_value", (adjustment.value,) ) def _get_focused_pane(self): focus = [ t.is_focus() for t in self.treeview ] try: return focus.index(1) except ValueError: return None def file_deleted(self, path, pane): # is file still extant in other pane? it = self.model.get_iter(path) files = self.model.value_paths(it) is_present = [ os.path.exists(f) for f in files ] if 1 in is_present: self._update_item_state(it) else: # nope its gone self.model.remove(it) self._update_diffmaps() def file_created(self, path, pane): it = self.model.get_iter(path) while it and self.model.get_path(it) != (0,): self._update_item_state( it ) it = self.model.iter_parent(it) self._update_diffmaps() def on_fileentry_activate(self, entry): locs = [ e.get_full_path(0) for e in self.fileentry[:self.num_panes] ] self.set_locations(locs)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -