From 7cdd87a2eeea35aa4fa5fccb7e0ee31a88d8611a Mon Sep 17 00:00:00 2001
From: jvoisin
Date: Tue, 16 Aug 2011 18:39:23 +0200
Subject: Change names for usability
---
mat-cli.py | 152 ++++++++++++++++
mat-gui.py | 571 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 723 insertions(+)
create mode 100755 mat-cli.py
create mode 100644 mat-gui.py
diff --git a/mat-cli.py b/mat-cli.py
new file mode 100755
index 0000000..804c4cf
--- /dev/null
+++ b/mat-cli.py
@@ -0,0 +1,152 @@
+#!/usr/bin/python
+'''
+ Metadata anonymisation toolkit - CLI edition
+'''
+
+import sys
+import xml.sax
+import optparse
+
+import hachoir_core
+
+from mat import mat
+
+__version__ = '0.1'
+
+
+def parse():
+ '''
+ Get, and parse options passed to the program
+ '''
+ parser = optparse.OptionParser(usage='%prog [options] files')
+ parser.add_option('--add2archive', '-a', action='store_true',
+ default=False, help='Add to outputed archive non-supported filetypes')
+ parser.add_option('--backup', '-b', action='store_true', default=False,
+ help='Keep a backup copy')
+ parser.add_option('--check', '-c', action='store_true', default=False,
+ help='Check if a file is free of harmfull metadatas')
+ parser.add_option('--display', '-d', action='store_true', default=False,
+ help='List all the meta of a file without removing them')
+ parser.add_option('--force', '-f', action='store_true', default=False,
+ help='Don\'t check if files are clean before cleaning')
+ parser.add_option('--list', '-l', action='store_true', default=False,
+ help='List all supported fileformat')
+ parser.add_option('--ugly', '-u', action='store_true', default=False,
+ help='Remove harmful meta, but loss can occure')
+ parser.add_option('--version', '-v', action='callback',
+ callback=display_version, help='Display version and exit')
+
+ values, arguments = parser.parse_args()
+ if not arguments and values.list is False:
+ parser.print_help()
+ sys.exit(0)
+ return values, arguments
+
+
+def display_version(*_):
+ '''
+ Display the program's version, and exit
+ '''
+ print('Metadata Anonymisation Toolkit version %s') % mat.__version__
+ print('CLI version %s') % __version__
+ print('Hachoir version %s') % hachoir_core.__version__
+ sys.exit(0)
+
+
+def list_meta(class_file, filename, force):
+ '''
+ Print all the meta of 'filename' on stdout
+ '''
+ print('[+] File %s :' % filename)
+ if force is False and class_file.is_clean():
+ print('No harmful meta found')
+ else:
+ meta = class_file.get_meta()
+ if meta is None:
+ print('No harmful meta found')
+ else:
+ for key, value in class_file.get_meta().iteritems():
+ print(key + ' : ' + str(value))
+
+
+def is_clean(class_file, filename, force):
+ '''
+ Say if 'filename' is clean or not
+ '''
+ if class_file.is_clean():
+ print('[+] %s is clean' % filename)
+ else:
+ print('[+] %s is not clean' % filename)
+
+
+def clean_meta(class_file, filename, force):
+ '''
+ Clean the file 'filename'
+ '''
+ print('[+] Cleaning %s' % filename)
+ if force is False and class_file.is_clean():
+ print('%s is already clean' % filename)
+ else:
+ class_file.remove_all()
+ print('%s cleaned !' % filename)
+
+
+def clean_meta_ugly(class_file, filename, force):
+ '''
+ Clean the file 'filename', ugly way
+ '''
+ print('[+] Cleaning %s' % filename)
+ if force is False and class_file.is_clean():
+ print('%s is already clean' % filename)
+ else:
+ class_file.remove_all_ugly()
+ print('%s cleaned' % filename)
+
+
+def list_supported():
+ '''
+ Print all supported fileformat, and exit
+ '''
+ handler = mat.XMLParser()
+ parser = xml.sax.make_parser()
+ parser.setContentHandler(handler)
+ with open('FORMATS', 'r') as xmlfile:
+ parser.parse(xmlfile)
+
+ for item in handler.list:
+ print('%s (%s)' % (item['name'], item['extension']))
+ print('\tsupport : ' + item['support'])
+ print('\tmetadata : ' + item['metadata'])
+ print('\tmethod : ' + item['method'])
+ if item['support'] == 'partial':
+ print('\tremaining : ' + item['remaining'])
+ print('\n')
+ sys.exit(0)
+
+
+def main():
+ '''
+ main function : get args, and launch the appropriate function
+ '''
+ args, filenames = parse()
+
+ #func receive the function correponding to the options given as parameters
+ if args.display is True: # only print metadatas
+ func = list_meta
+ elif args.check is True: # only check if the file is clean
+ func = is_clean
+ elif args.ugly is True: # destructive anonymisation method
+ func = clean_meta_ugly
+ elif args.list is True: # print the list of all supported format
+ list_supported()
+ else: # clean the file
+ func = clean_meta
+
+ for filename in filenames:
+ class_file = mat.create_class_file(filename, args.backup,
+ args.add2archive)
+ if class_file is not None:
+ func(class_file, filename, args.force)
+
+if __name__ == '__main__':
+ main()
diff --git a/mat-gui.py b/mat-gui.py
new file mode 100644
index 0000000..07b8d6c
--- /dev/null
+++ b/mat-gui.py
@@ -0,0 +1,571 @@
+#!/usr/bin/env python
+#charset utf-8
+
+'''
+ Metadata anonymisation toolkit - GUI edition
+'''
+
+import gtk
+import gobject
+
+import gettext
+import locale
+import logging
+import os
+import xml.sax
+
+from mat import mat
+
+__version__ = '0.1'
+__author__ = 'jvoisin'
+
+
+
+logging.basicConfig(level=mat.LOGGING_LEVEL)
+
+
+class CFile(object):
+ '''
+ Contain the "parser" class of the file "filename"
+ This class exist just to be "around" my parser.Generic_parser class,
+ since the gtk.ListStore does not accept it.
+ '''
+ def __init__(self, filename, backup, add2archive):
+ try:
+ self.file = mat.create_class_file(filename, backup, add2archive)
+ except:
+ self.file = None
+
+
+class GUI:
+ '''
+ Main GUI class
+ '''
+ def __init__(self):
+ # Preferences
+ self.force = False
+ self.backup = True
+ self.add2archive = True
+
+ # Main window
+ self.window = gtk.Window()
+ self.window.set_title('Metadata Anonymisation Toolkit %s' %
+ __version__)
+ self.window.connect('destroy', gtk.main_quit)
+ self.window.set_default_size(800, 600)
+
+ vbox = gtk.VBox()
+ self.window.add(vbox)
+
+ menubar = self.create_menu()
+ toolbar = self.create_toolbar()
+ content = gtk.ScrolledWindow()
+ vbox.pack_start(menubar, False, True, 0)
+ vbox.pack_start(toolbar, False, True, 0)
+ vbox.pack_start(content, True, True, 0)
+
+ # parser.class - name - type - cleaned
+ self.liststore = gtk.ListStore(object, str, str, str)
+
+ treeview = gtk.TreeView(model=self.liststore)
+ treeview.set_search_column(1) # name column is searchable
+ treeview.set_rules_hint(True) # alternate colors for rows
+ treeview.set_rubber_banding(True) # mouse selection
+ self.add_columns(treeview)
+ self.selection = treeview.get_selection()
+ self.selection.set_mode(gtk.SELECTION_MULTIPLE)
+
+ content.add(treeview)
+
+ self.statusbar = gtk.Statusbar()
+ self.statusbar.push(1, _('Ready'))
+ vbox.pack_start(self.statusbar, False, False, 0)
+
+ self.window.show_all()
+
+ def create_toolbar(self):
+ '''
+ Returns a vbox object, which contains a toolbar with buttons
+ '''
+ toolbar = gtk.Toolbar()
+
+ toolbutton = gtk.ToolButton(gtk.STOCK_ADD)
+ toolbutton.set_label(_('Add'))
+ toolbutton.connect('clicked', self.add_files)
+ toolbutton.set_tooltip_text(_('Add files'))
+ toolbar.add(toolbutton)
+
+ toolbutton = gtk.ToolButton(gtk.STOCK_PRINT_REPORT)
+ toolbutton.set_label(_('Clean'))
+ toolbutton.connect('clicked', self.process_files, self.mat_clean)
+ toolbutton.set_tooltip_text(_('Clean selected files without data loss'))
+ toolbar.add(toolbutton)
+
+ toolbutton = gtk.ToolButton(gtk.STOCK_PRINT_WARNING)
+ toolbutton.set_label(_('Brute Clean'))
+ toolbutton.connect('clicked', self.mat_clean_dirty)
+ toolbutton.set_tooltip_text(_('Clean selected files with possible \
+data loss'))
+ toolbar.add(toolbutton)
+
+ toolbutton = gtk.ToolButton(gtk.STOCK_FIND)
+ toolbutton.set_label(_('Check'))
+ toolbutton.connect('clicked', self.process_files, self.mat_check)
+ toolbutton.set_tooltip_text(_('Check selected files for harmful meta'))
+ toolbar.add(toolbutton)
+
+ toolbutton = gtk.ToolButton(stock_id=gtk.STOCK_QUIT)
+ toolbutton.set_label(_('Quit'))
+ toolbutton.connect('clicked', gtk.main_quit)
+ toolbar.add(toolbutton)
+
+ vbox = gtk.VBox(spacing=3)
+ vbox.pack_start(toolbar, False, False, 0)
+ return vbox
+
+ def add_columns(self, treeview):
+ '''
+ Create the columns, and add them to the treeview
+ '''
+ colname = [_('Filename'), _('Mimetype'), _('State')]
+
+ for i, j in enumerate(colname):
+ filename_column = gtk.CellRendererText()
+ column = gtk.TreeViewColumn(j, filename_column, text=i + 1)
+ column.set_sort_column_id(i + 1)
+ if i is 0: # place tooltip on this column
+ tips = TreeViewTooltips(column)
+ tips.add_view(treeview)
+ treeview.append_column(column)
+
+ def create_menu_item(self, name, func, menu, pix):
+ '''
+ Create a MenuItem() like Preferences, Quit, Add, Clean, ...
+ '''
+ item = gtk.ImageMenuItem()
+ picture = gtk.Image()
+ picture.set_from_stock(pix, gtk.ICON_SIZE_MENU)
+ item.set_image(picture)
+ item.set_label(name)
+ item.connect('activate', func)
+ menu.append(item)
+
+ def create_sub_menu(self, name, menubar):
+ '''
+ Create a submenu like File, Edit, Clean, ...
+ '''
+ submenu = gtk.Menu()
+ menuitem = gtk.MenuItem()
+ menuitem.set_submenu(submenu)
+ menuitem.set_label(name)
+ menubar.append(menuitem)
+ return submenu
+
+ def create_menu(self):
+ '''
+ Return a MenuBar
+ '''
+ menubar = gtk.MenuBar()
+
+ file_menu = self.create_sub_menu(_('Files'), menubar)
+ self.create_menu_item(_('Add files'), self.add_files, file_menu,
+ gtk.STOCK_ADD)
+ self.create_menu_item(_('Quit'), gtk.main_quit, file_menu,
+ gtk.STOCK_QUIT)
+
+ edit_menu = self.create_sub_menu(_('Edit'), menubar)
+ self.create_menu_item(_('Clear the filelist'),
+ lambda x: self.liststore.clear(), edit_menu, gtk.STOCK_REMOVE)
+ self.create_menu_item(_('Preferences'), self.preferences, edit_menu,
+ gtk.STOCK_PREFERENCES)
+
+ process_menu = self.create_sub_menu(_('Process'), menubar)
+ item = gtk.ImageMenuItem()
+ picture = gtk.Image()
+ picture.set_from_stock(gtk.STOCK_PRINT_REPORT, gtk.ICON_SIZE_MENU)
+ item.set_image(picture)
+ item.set_label(_('Clean'))
+ item.connect('activate', self.process_files, self.mat_clean)
+ process_menu.append(item)
+
+ item = gtk.ImageMenuItem()
+ picture = gtk.Image()
+ picture.set_from_stock(gtk.STOCK_PRINT_WARNING, gtk.ICON_SIZE_MENU)
+ item.set_image(picture)
+ item.set_label(_('Clean (lossy way)'))
+ item.connect('activate', self.process_files, self.mat_clean_dirty)
+ process_menu.append(item)
+
+ item = gtk.ImageMenuItem()
+ picture = gtk.Image()
+ picture.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
+ item.set_image(picture)
+ item.set_label(_('Check'))
+ item.connect('activate', self.process_files, self.mat_check)
+ process_menu.append(item)
+
+
+ help_menu = self.create_sub_menu(_('Help'), menubar)
+ self.create_menu_item(_('Supported formats'), self.supported, help_menu,
+ gtk.STOCK_INFO)
+ self.create_menu_item(_('About'), self.about, help_menu, gtk.STOCK_ABOUT)
+
+ return menubar
+
+ def add_files(self, button):
+ '''
+ Add the files chosed by the filechoser ("Add" button)
+ '''
+ chooser = gtk.FileChooserDialog(title=_('Choose files'),
+ parent=self.window, action=gtk.FILE_CHOOSER_ACTION_OPEN,
+ buttons=(gtk.STOCK_OK, 0, gtk.STOCK_CANCEL, 1))
+ chooser.set_default_response(0)
+ chooser.set_select_multiple(True)
+
+ all_filter = gtk.FileFilter() # filter that shows all files
+ all_filter.set_name(_('All files'))
+ all_filter.add_pattern('*')
+ chooser.add_filter(all_filter)
+
+ supported_filter = gtk.FileFilter()
+ # filter that shows only supported formats
+ [supported_filter.add_mime_type(i) for i in mat.STRIPPERS.keys()]
+ supported_filter.set_name(_('Supported files'))
+ chooser.add_filter(supported_filter)
+
+ response = chooser.run()
+
+ if response is 0: # gtk.STOCK_OK
+ filenames = chooser.get_filenames()
+ task = self.populate(filenames)
+ gobject.idle_add(task.next) # asynchrone processing
+ chooser.destroy()
+
+ def populate(self, filenames):
+ '''
+ Append selected files by add_file to the self.liststore
+ '''
+ for filename in filenames: # filenames : all selected files/folders
+ if os.path.isdir(filename): # if "filename" is a directory
+ for root, dirs, files in os.walk(filename):
+ for item in files:
+ path_to_file = os.path.join(root, item)
+ self.add_file_to_treeview(path_to_file)
+ else: # filename is a regular file
+ self.add_file_to_treeview(filename)
+ yield True
+ yield False
+
+ def add_file_to_treeview(self, filename):
+ '''
+ Add a file to the list if his format is supported
+ '''
+ cf = CFile(filename, self.backup, self.add2archive)
+ if cf.file is not None:
+ self.liststore.append([cf, cf.file.basename,
+ cf.file.mime, _('unknow')])
+
+
+ def about(self, button):
+ '''
+ About popup
+ '''
+ w = gtk.AboutDialog()
+ w.set_version(__version__)
+ w.set_copyright('GNU Public License v2')
+ w.set_comments(_('This software was coded during the GSoC 2011'))
+ w.set_website('https://gitweb.torproject.org/user/jvoisin/mat.git')
+ w.set_website_label(_('Website'))
+ w.set_authors(['Julien (jvoisin) Voisin', ])
+ w.set_program_name('Metadata Anonymistion Toolkit')
+ click = w.run()
+ if click:
+ w.destroy()
+
+ def supported(self, button):
+ '''
+ List the supported formats
+ '''
+ dialog = gtk.Dialog(_('Supported formats'), self.window, 0,
+ (gtk.STOCK_CLOSE, 0))
+ vbox = gtk.VBox(spacing=5)
+ dialog.get_content_area().pack_start(vbox, True, True, 0)
+
+ label = gtk.Label()
+ label.set_markup('Supported fileformats')
+ vbox.pack_start(label, True, True, 0)
+
+ #parsing xml
+ handler = mat.XMLParser()
+ parser = xml.sax.make_parser()
+ parser.setContentHandler(handler)
+ with open('FORMATS', 'r') as xmlfile:
+ parser.parse(xmlfile)
+
+ for item in handler.list: # list of dict : one dict per format
+ #create one expander per format
+ title = '%s (%s)' % (item['name'], item['extension'])
+ support = ('\t%s : %s' % ('support', item['support']))
+ metadata = '\n\tmetadata : ' + item['metadata']
+ method = '\n\tmethod : ' + item['method']
+ content = support + metadata + method
+ if item['support'] == 'partial':
+ content += '\n\tremaining : ' + item['remaining']
+
+ expander = gtk.Expander(title)
+ vbox.pack_start(expander, False, False, 0)
+ label = gtk.Label()
+ label.set_markup(content)
+ expander.add(label)
+
+ dialog.show_all()
+ click = dialog.run()
+ if click is 0: # Close
+ dialog.destroy()
+
+ def preferences(self, button):
+ '''
+ Preferences popup
+ '''
+ dialog = gtk.Dialog(_('Preferences'), self.window, 0, (gtk.STOCK_OK, 0))
+ hbox = gtk.HBox()
+ dialog.get_content_area().pack_start(hbox, False, False, 0)
+
+ icon = gtk.Image()
+ icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG)
+ hbox.pack_start(icon, False, False, 0)
+
+ table = gtk.Table(3, 2, False) # nb rows, nb lines
+ hbox.pack_start(table, True, True, 0)
+
+ force = gtk.CheckButton(_('Force Clean'), False)
+ force.set_active(self.force)
+ force.connect('toggled', self.invert, 'force')
+ force.set_tooltip_text(_('Do not check if already clean before \
+cleaning'))
+ table.attach(force, 0, 1, 0, 1)
+
+ backup = gtk.CheckButton(_('Backup'), False)
+ backup.set_active(self.backup)
+ backup.connect('toggled', self.invert, 'backup')
+ backup.set_tooltip_text(_('Keep a backup copy'))
+ table.attach(backup, 0, 1, 1, 2)
+
+ add2archive = gtk.CheckButton(_('Add unsupported file to archives'),
+ False)
+ add2archive.set_active(self.add2archive)
+ add2archive.connect('toggled', self.invert, 'add2archive')
+ add2archive.set_tooltip_text(_('Add non-supported (and so \
+non-anonymised) file to outputed archive'))
+ table.attach(add2archive, 0, 1, 2, 3)
+
+ hbox.show_all()
+ response = dialog.run()
+ if response is 0: # gtk.STOCK_OK
+ dialog.destroy()
+
+ def invert(self, button, name):
+ '''
+ Invert a preference state
+ '''
+ if name == 'force':
+ self.force = not self.force
+ elif name == 'backup':
+ self.backup = not self.backup
+ elif name == 'add2archive':
+ self.add2archive = not self.add2archive
+
+ def process_files(self, button, func):
+ '''
+ Launch the function "function" in a asynchrone way
+ '''
+ iterator = self.selection.get_selected_rows()[1]
+ if not iterator: # if nothing is selected : select everything
+ iterator = xrange(len(self.liststore))
+ task = func(iterator) # launch func() in an asynchrone way
+ gobject.idle_add(task.next)
+
+ def mat_check(self, iterator):
+ '''
+ Check if selected elements are clean
+ '''
+ for line in iterator: # for each file in selection
+ if self.liststore[line][0].file.is_clean():
+ string = _('clean')
+ else:
+ string = _('dirty')
+ logging.info('%s is %s' % (self.liststore[line][1], string))
+ self.liststore[line][3] = string
+ yield True
+ yield False
+
+ def mat_clean(self, iterator):
+ '''
+ Clean selected elements
+ '''
+ for line in iterator: # for each file in selection
+ logging.info('Cleaning %s' % self.liststore[line][1])
+ if self.liststore[line][3] is not _('clean'):
+ if self.force or not self.liststore[line][0].file.is_clean():
+ self.liststore[line][0].file.remove_all()
+ self.liststore[line][3] = _('clean')
+ yield True
+ yield False
+
+ def mat_clean_dirty(self, iterator):
+ '''
+ Clean selected elements (ugly way)
+ '''
+ for line in iterator: # for each file in selection
+ logging.info('Cleaning (lossy way) %s' % self.liststore[line][1])
+ if self.liststore[line][3] is not _('clean'):
+ if self.force or not self.liststore[line][0].file.is_clean():
+ self.liststore[line][0].file.remove_all_ugly()
+ self.liststore[line][3] = _('clean')
+ yield True
+ yield False
+
+
+class TreeViewTooltips(object):
+ '''
+ A dirty hack losely based on treeviewtooltip from Daniel J. Popowich
+ (dpopowich AT astro dot umass dot edu), to display differents tooltips
+ for each cell of the first row of the GUI.
+ '''
+ # Copyright (c) 2006, Daniel J. Popowich
+ #
+ # Permission is hereby granted, free of charge, to any person
+ # obtaining a copy of this software and associated documentation files
+ # (the "Software"), to deal in the Software without restriction,
+ # including without limitation the rights to use, copy, modify, merge,
+ # publish, distribute, sublicense, and/or sell copies of the Software,
+ # and to permit persons to whom the Software is furnished to do so,
+ # subject to the following conditions:
+ #
+ # The above copyright notice and this permission notice shall be
+ # included in all copies or substantial portions of the Software.
+ def __init__(self, namecol):
+ '''
+ Initialize the tooltip.
+ window: the popup window that holds the tooltip text, an
+ instance of gtk.Window.
+ label: a gtk.Label that is packed into the window. The
+ tooltip text is set in the label with the
+ set_label() method, so the text can be plain or
+ markup text.
+ '''
+ # create the window
+ self.window = window = gtk.Window(gtk.WINDOW_POPUP)
+ window.set_name('gtk-tooltips')
+ window.set_resizable(False)
+ window.set_border_width(4)
+ window.set_app_paintable(True)
+ window.connect("expose-event", self.__on_expose_event)
+
+ # create the label
+ self.label = label = gtk.Label()
+ label.set_line_wrap(True)
+ label.set_alignment(0.5, 0.5)
+ label.show()
+ window.add(label)
+
+ self.namecol = namecol
+ self.__save = None # saves the current cell
+ self.__next = None # the timer id for the next tooltip to be shown
+ self.__shown = False # flag on whether the tooltip window is shown
+
+ def __show(self, tooltip, x, y):
+ '''
+ show the tooltip
+ '''
+ self.label.set_label(tooltip) # set label
+ self.window.move(x, y) # move the window
+ self.window.show() # show it
+ self.__shown = True
+
+ def __hide(self):
+ '''
+ hide the tooltip
+ '''
+ self.__queue_next()
+ self.window.hide()
+ self.__shown = False
+
+ def __motion_handler(self, view, event):
+ '''
+ as the pointer moves across the view, show a tooltip
+ '''
+ path_at_pos = view.get_path_at_pos(int(event.x), int(event.y))
+ if path_at_pos:
+ path, col, x, y = path_at_pos
+ tooltip = self.get_tooltip(view, col, path)
+ if tooltip:
+ tooltip = str(tooltip).strip()
+ self.__queue_next((path, col), tooltip, int(event.x_root),
+ int(event.y_root))
+ return
+ self.__hide()
+
+ def __queue_next(self, *args):
+ '''
+ queue next request to show a tooltip
+ if args is non-empty it means a request was made to show a
+ tooltip. if empty, no request is being made, but any
+ pending requests should be cancelled anyway
+ '''
+ cell = None
+
+ if args: # if called with args, break them out
+ cell, tooltip, x, y = args
+
+ # if it's the same cell as previously shown, just return
+ if self.__save == cell:
+ return
+
+ if self.__next: # if we have something queued up, cancel it
+ gobject.source_remove(self.__next)
+ self.__next = None
+
+ if cell: # if there was a request
+ if self.__shown: # if tooltip is already shown show the new one
+ self.__show(tooltip, x, y)
+ else: # else queue it up in 1/2 second
+ self.__next = gobject.timeout_add(500, self.__show,
+ tooltip, x, y)
+ self.__save = cell # save this cell
+
+ def __on_expose_event(self, window, event):
+ '''
+ this magic is required so the window appears with a 1-pixel
+ black border (default gtk Style).
+ '''
+ w, h = window.size_request()
+ window.style.paint_flat_box(window.window, gtk.STATE_NORMAL,
+ gtk.SHADOW_OUT, None, window, 'tooltip', 0, 0, w, h)
+
+ def add_view(self, view):
+ '''
+ add a gtk.TreeView to the tooltip
+ '''
+ view.connect('motion-notify-event', self.__motion_handler)
+ view.connect('leave-notify-event', lambda i, j: self.__hide())
+
+ def get_tooltip(self, view, column, path):
+ '''
+ See the module doc string for a description of this method
+ '''
+ if column is self.namecol:
+ model = view.get_model()
+ name = model[path][0]
+ return name.file.filename
+
+
+if __name__ == '__main__':
+ #Translations
+ t = gettext.translation('gui', 'locale', fallback=True)
+ _ = t.ugettext
+ t.install()
+
+ #Main
+ GUI()
+ gtk.main()
--
cgit v1.3