summaryrefslogtreecommitdiff
path: root/mat-gui
diff options
context:
space:
mode:
authorjvoisin2011-08-17 18:26:57 +0200
committerjvoisin2011-08-17 18:26:57 +0200
commit30a19f89ca4db25c3f2669283b39e837c4b4438d (patch)
treefdd4adf4962890a803102fc2322aa9f7368fde7b /mat-gui
parent89cbc8ce2e311e8487ac53fd71a058e2365c0644 (diff)
Correct "binaries" names, and fix the setup.py according to the changes
Diffstat (limited to 'mat-gui')
-rwxr-xr-xmat-gui571
1 files changed, 571 insertions, 0 deletions
diff --git a/mat-gui b/mat-gui
new file mode 100755
index 0000000..07b8d6c
--- /dev/null
+++ b/mat-gui
@@ -0,0 +1,571 @@
1#!/usr/bin/env python
2#charset utf-8
3
4'''
5 Metadata anonymisation toolkit - GUI edition
6'''
7
8import gtk
9import gobject
10
11import gettext
12import locale
13import logging
14import os
15import xml.sax
16
17from mat import mat
18
19__version__ = '0.1'
20__author__ = 'jvoisin'
21
22
23
24logging.basicConfig(level=mat.LOGGING_LEVEL)
25
26
27class CFile(object):
28 '''
29 Contain the "parser" class of the file "filename"
30 This class exist just to be "around" my parser.Generic_parser class,
31 since the gtk.ListStore does not accept it.
32 '''
33 def __init__(self, filename, backup, add2archive):
34 try:
35 self.file = mat.create_class_file(filename, backup, add2archive)
36 except:
37 self.file = None
38
39
40class GUI:
41 '''
42 Main GUI class
43 '''
44 def __init__(self):
45 # Preferences
46 self.force = False
47 self.backup = True
48 self.add2archive = True
49
50 # Main window
51 self.window = gtk.Window()
52 self.window.set_title('Metadata Anonymisation Toolkit %s' %
53 __version__)
54 self.window.connect('destroy', gtk.main_quit)
55 self.window.set_default_size(800, 600)
56
57 vbox = gtk.VBox()
58 self.window.add(vbox)
59
60 menubar = self.create_menu()
61 toolbar = self.create_toolbar()
62 content = gtk.ScrolledWindow()
63 vbox.pack_start(menubar, False, True, 0)
64 vbox.pack_start(toolbar, False, True, 0)
65 vbox.pack_start(content, True, True, 0)
66
67 # parser.class - name - type - cleaned
68 self.liststore = gtk.ListStore(object, str, str, str)
69
70 treeview = gtk.TreeView(model=self.liststore)
71 treeview.set_search_column(1) # name column is searchable
72 treeview.set_rules_hint(True) # alternate colors for rows
73 treeview.set_rubber_banding(True) # mouse selection
74 self.add_columns(treeview)
75 self.selection = treeview.get_selection()
76 self.selection.set_mode(gtk.SELECTION_MULTIPLE)
77
78 content.add(treeview)
79
80 self.statusbar = gtk.Statusbar()
81 self.statusbar.push(1, _('Ready'))
82 vbox.pack_start(self.statusbar, False, False, 0)
83
84 self.window.show_all()
85
86 def create_toolbar(self):
87 '''
88 Returns a vbox object, which contains a toolbar with buttons
89 '''
90 toolbar = gtk.Toolbar()
91
92 toolbutton = gtk.ToolButton(gtk.STOCK_ADD)
93 toolbutton.set_label(_('Add'))
94 toolbutton.connect('clicked', self.add_files)
95 toolbutton.set_tooltip_text(_('Add files'))
96 toolbar.add(toolbutton)
97
98 toolbutton = gtk.ToolButton(gtk.STOCK_PRINT_REPORT)
99 toolbutton.set_label(_('Clean'))
100 toolbutton.connect('clicked', self.process_files, self.mat_clean)
101 toolbutton.set_tooltip_text(_('Clean selected files without data loss'))
102 toolbar.add(toolbutton)
103
104 toolbutton = gtk.ToolButton(gtk.STOCK_PRINT_WARNING)
105 toolbutton.set_label(_('Brute Clean'))
106 toolbutton.connect('clicked', self.mat_clean_dirty)
107 toolbutton.set_tooltip_text(_('Clean selected files with possible \
108data loss'))
109 toolbar.add(toolbutton)
110
111 toolbutton = gtk.ToolButton(gtk.STOCK_FIND)
112 toolbutton.set_label(_('Check'))
113 toolbutton.connect('clicked', self.process_files, self.mat_check)
114 toolbutton.set_tooltip_text(_('Check selected files for harmful meta'))
115 toolbar.add(toolbutton)
116
117 toolbutton = gtk.ToolButton(stock_id=gtk.STOCK_QUIT)
118 toolbutton.set_label(_('Quit'))
119 toolbutton.connect('clicked', gtk.main_quit)
120 toolbar.add(toolbutton)
121
122 vbox = gtk.VBox(spacing=3)
123 vbox.pack_start(toolbar, False, False, 0)
124 return vbox
125
126 def add_columns(self, treeview):
127 '''
128 Create the columns, and add them to the treeview
129 '''
130 colname = [_('Filename'), _('Mimetype'), _('State')]
131
132 for i, j in enumerate(colname):
133 filename_column = gtk.CellRendererText()
134 column = gtk.TreeViewColumn(j, filename_column, text=i + 1)
135 column.set_sort_column_id(i + 1)
136 if i is 0: # place tooltip on this column
137 tips = TreeViewTooltips(column)
138 tips.add_view(treeview)
139 treeview.append_column(column)
140
141 def create_menu_item(self, name, func, menu, pix):
142 '''
143 Create a MenuItem() like Preferences, Quit, Add, Clean, ...
144 '''
145 item = gtk.ImageMenuItem()
146 picture = gtk.Image()
147 picture.set_from_stock(pix, gtk.ICON_SIZE_MENU)
148 item.set_image(picture)
149 item.set_label(name)
150 item.connect('activate', func)
151 menu.append(item)
152
153 def create_sub_menu(self, name, menubar):
154 '''
155 Create a submenu like File, Edit, Clean, ...
156 '''
157 submenu = gtk.Menu()
158 menuitem = gtk.MenuItem()
159 menuitem.set_submenu(submenu)
160 menuitem.set_label(name)
161 menubar.append(menuitem)
162 return submenu
163
164 def create_menu(self):
165 '''
166 Return a MenuBar
167 '''
168 menubar = gtk.MenuBar()
169
170 file_menu = self.create_sub_menu(_('Files'), menubar)
171 self.create_menu_item(_('Add files'), self.add_files, file_menu,
172 gtk.STOCK_ADD)
173 self.create_menu_item(_('Quit'), gtk.main_quit, file_menu,
174 gtk.STOCK_QUIT)
175
176 edit_menu = self.create_sub_menu(_('Edit'), menubar)
177 self.create_menu_item(_('Clear the filelist'),
178 lambda x: self.liststore.clear(), edit_menu, gtk.STOCK_REMOVE)
179 self.create_menu_item(_('Preferences'), self.preferences, edit_menu,
180 gtk.STOCK_PREFERENCES)
181
182 process_menu = self.create_sub_menu(_('Process'), menubar)
183 item = gtk.ImageMenuItem()
184 picture = gtk.Image()
185 picture.set_from_stock(gtk.STOCK_PRINT_REPORT, gtk.ICON_SIZE_MENU)
186 item.set_image(picture)
187 item.set_label(_('Clean'))
188 item.connect('activate', self.process_files, self.mat_clean)
189 process_menu.append(item)
190
191 item = gtk.ImageMenuItem()
192 picture = gtk.Image()
193 picture.set_from_stock(gtk.STOCK_PRINT_WARNING, gtk.ICON_SIZE_MENU)
194 item.set_image(picture)
195 item.set_label(_('Clean (lossy way)'))
196 item.connect('activate', self.process_files, self.mat_clean_dirty)
197 process_menu.append(item)
198
199 item = gtk.ImageMenuItem()
200 picture = gtk.Image()
201 picture.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
202 item.set_image(picture)
203 item.set_label(_('Check'))
204 item.connect('activate', self.process_files, self.mat_check)
205 process_menu.append(item)
206
207
208 help_menu = self.create_sub_menu(_('Help'), menubar)
209 self.create_menu_item(_('Supported formats'), self.supported, help_menu,
210 gtk.STOCK_INFO)
211 self.create_menu_item(_('About'), self.about, help_menu, gtk.STOCK_ABOUT)
212
213 return menubar
214
215 def add_files(self, button):
216 '''
217 Add the files chosed by the filechoser ("Add" button)
218 '''
219 chooser = gtk.FileChooserDialog(title=_('Choose files'),
220 parent=self.window, action=gtk.FILE_CHOOSER_ACTION_OPEN,
221 buttons=(gtk.STOCK_OK, 0, gtk.STOCK_CANCEL, 1))
222 chooser.set_default_response(0)
223 chooser.set_select_multiple(True)
224
225 all_filter = gtk.FileFilter() # filter that shows all files
226 all_filter.set_name(_('All files'))
227 all_filter.add_pattern('*')
228 chooser.add_filter(all_filter)
229
230 supported_filter = gtk.FileFilter()
231 # filter that shows only supported formats
232 [supported_filter.add_mime_type(i) for i in mat.STRIPPERS.keys()]
233 supported_filter.set_name(_('Supported files'))
234 chooser.add_filter(supported_filter)
235
236 response = chooser.run()
237
238 if response is 0: # gtk.STOCK_OK
239 filenames = chooser.get_filenames()
240 task = self.populate(filenames)
241 gobject.idle_add(task.next) # asynchrone processing
242 chooser.destroy()
243
244 def populate(self, filenames):
245 '''
246 Append selected files by add_file to the self.liststore
247 '''
248 for filename in filenames: # filenames : all selected files/folders
249 if os.path.isdir(filename): # if "filename" is a directory
250 for root, dirs, files in os.walk(filename):
251 for item in files:
252 path_to_file = os.path.join(root, item)
253 self.add_file_to_treeview(path_to_file)
254 else: # filename is a regular file
255 self.add_file_to_treeview(filename)
256 yield True
257 yield False
258
259 def add_file_to_treeview(self, filename):
260 '''
261 Add a file to the list if his format is supported
262 '''
263 cf = CFile(filename, self.backup, self.add2archive)
264 if cf.file is not None:
265 self.liststore.append([cf, cf.file.basename,
266 cf.file.mime, _('unknow')])
267
268
269 def about(self, button):
270 '''
271 About popup
272 '''
273 w = gtk.AboutDialog()
274 w.set_version(__version__)
275 w.set_copyright('GNU Public License v2')
276 w.set_comments(_('This software was coded during the GSoC 2011'))
277 w.set_website('https://gitweb.torproject.org/user/jvoisin/mat.git')
278 w.set_website_label(_('Website'))
279 w.set_authors(['Julien (jvoisin) Voisin', ])
280 w.set_program_name('Metadata Anonymistion Toolkit')
281 click = w.run()
282 if click:
283 w.destroy()
284
285 def supported(self, button):
286 '''
287 List the supported formats
288 '''
289 dialog = gtk.Dialog(_('Supported formats'), self.window, 0,
290 (gtk.STOCK_CLOSE, 0))
291 vbox = gtk.VBox(spacing=5)
292 dialog.get_content_area().pack_start(vbox, True, True, 0)
293
294 label = gtk.Label()
295 label.set_markup('<big><u>Supported fileformats</u></big>')
296 vbox.pack_start(label, True, True, 0)
297
298 #parsing xml
299 handler = mat.XMLParser()
300 parser = xml.sax.make_parser()
301 parser.setContentHandler(handler)
302 with open('FORMATS', 'r') as xmlfile:
303 parser.parse(xmlfile)
304
305 for item in handler.list: # list of dict : one dict per format
306 #create one expander per format
307 title = '%s (%s)' % (item['name'], item['extension'])
308 support = ('\t<b>%s</b> : %s' % ('support', item['support']))
309 metadata = '\n\t<b>metadata</b> : ' + item['metadata']
310 method = '\n\t<b>method</b> : ' + item['method']
311 content = support + metadata + method
312 if item['support'] == 'partial':
313 content += '\n\t<b>remaining</b> : ' + item['remaining']
314
315 expander = gtk.Expander(title)
316 vbox.pack_start(expander, False, False, 0)
317 label = gtk.Label()
318 label.set_markup(content)
319 expander.add(label)
320
321 dialog.show_all()
322 click = dialog.run()
323 if click is 0: # Close
324 dialog.destroy()
325
326 def preferences(self, button):
327 '''
328 Preferences popup
329 '''
330 dialog = gtk.Dialog(_('Preferences'), self.window, 0, (gtk.STOCK_OK, 0))
331 hbox = gtk.HBox()
332 dialog.get_content_area().pack_start(hbox, False, False, 0)
333
334 icon = gtk.Image()
335 icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG)
336 hbox.pack_start(icon, False, False, 0)
337
338 table = gtk.Table(3, 2, False) # nb rows, nb lines
339 hbox.pack_start(table, True, True, 0)
340
341 force = gtk.CheckButton(_('Force Clean'), False)
342 force.set_active(self.force)
343 force.connect('toggled', self.invert, 'force')
344 force.set_tooltip_text(_('Do not check if already clean before \
345cleaning'))
346 table.attach(force, 0, 1, 0, 1)
347
348 backup = gtk.CheckButton(_('Backup'), False)
349 backup.set_active(self.backup)
350 backup.connect('toggled', self.invert, 'backup')
351 backup.set_tooltip_text(_('Keep a backup copy'))
352 table.attach(backup, 0, 1, 1, 2)
353
354 add2archive = gtk.CheckButton(_('Add unsupported file to archives'),
355 False)
356 add2archive.set_active(self.add2archive)
357 add2archive.connect('toggled', self.invert, 'add2archive')
358 add2archive.set_tooltip_text(_('Add non-supported (and so \
359non-anonymised) file to outputed archive'))
360 table.attach(add2archive, 0, 1, 2, 3)
361
362 hbox.show_all()
363 response = dialog.run()
364 if response is 0: # gtk.STOCK_OK
365 dialog.destroy()
366
367 def invert(self, button, name):
368 '''
369 Invert a preference state
370 '''
371 if name == 'force':
372 self.force = not self.force
373 elif name == 'backup':
374 self.backup = not self.backup
375 elif name == 'add2archive':
376 self.add2archive = not self.add2archive
377
378 def process_files(self, button, func):
379 '''
380 Launch the function "function" in a asynchrone way
381 '''
382 iterator = self.selection.get_selected_rows()[1]
383 if not iterator: # if nothing is selected : select everything
384 iterator = xrange(len(self.liststore))
385 task = func(iterator) # launch func() in an asynchrone way
386 gobject.idle_add(task.next)
387
388 def mat_check(self, iterator):
389 '''
390 Check if selected elements are clean
391 '''
392 for line in iterator: # for each file in selection
393 if self.liststore[line][0].file.is_clean():
394 string = _('clean')
395 else:
396 string = _('dirty')
397 logging.info('%s is %s' % (self.liststore[line][1], string))
398 self.liststore[line][3] = string
399 yield True
400 yield False
401
402 def mat_clean(self, iterator):
403 '''
404 Clean selected elements
405 '''
406 for line in iterator: # for each file in selection
407 logging.info('Cleaning %s' % self.liststore[line][1])
408 if self.liststore[line][3] is not _('clean'):
409 if self.force or not self.liststore[line][0].file.is_clean():
410 self.liststore[line][0].file.remove_all()
411 self.liststore[line][3] = _('clean')
412 yield True
413 yield False
414
415 def mat_clean_dirty(self, iterator):
416 '''
417 Clean selected elements (ugly way)
418 '''
419 for line in iterator: # for each file in selection
420 logging.info('Cleaning (lossy way) %s' % self.liststore[line][1])
421 if self.liststore[line][3] is not _('clean'):
422 if self.force or not self.liststore[line][0].file.is_clean():
423 self.liststore[line][0].file.remove_all_ugly()
424 self.liststore[line][3] = _('clean')
425 yield True
426 yield False
427
428
429class TreeViewTooltips(object):
430 '''
431 A dirty hack losely based on treeviewtooltip from Daniel J. Popowich
432 (dpopowich AT astro dot umass dot edu), to display differents tooltips
433 for each cell of the first row of the GUI.
434 '''
435 # Copyright (c) 2006, Daniel J. Popowich
436 #
437 # Permission is hereby granted, free of charge, to any person
438 # obtaining a copy of this software and associated documentation files
439 # (the "Software"), to deal in the Software without restriction,
440 # including without limitation the rights to use, copy, modify, merge,
441 # publish, distribute, sublicense, and/or sell copies of the Software,
442 # and to permit persons to whom the Software is furnished to do so,
443 # subject to the following conditions:
444 #
445 # The above copyright notice and this permission notice shall be
446 # included in all copies or substantial portions of the Software.
447 def __init__(self, namecol):
448 '''
449 Initialize the tooltip.
450 window: the popup window that holds the tooltip text, an
451 instance of gtk.Window.
452 label: a gtk.Label that is packed into the window. The
453 tooltip text is set in the label with the
454 set_label() method, so the text can be plain or
455 markup text.
456 '''
457 # create the window
458 self.window = window = gtk.Window(gtk.WINDOW_POPUP)
459 window.set_name('gtk-tooltips')
460 window.set_resizable(False)
461 window.set_border_width(4)
462 window.set_app_paintable(True)
463 window.connect("expose-event", self.__on_expose_event)
464
465 # create the label
466 self.label = label = gtk.Label()
467 label.set_line_wrap(True)
468 label.set_alignment(0.5, 0.5)
469 label.show()
470 window.add(label)
471
472 self.namecol = namecol
473 self.__save = None # saves the current cell
474 self.__next = None # the timer id for the next tooltip to be shown
475 self.__shown = False # flag on whether the tooltip window is shown
476
477 def __show(self, tooltip, x, y):
478 '''
479 show the tooltip
480 '''
481 self.label.set_label(tooltip) # set label
482 self.window.move(x, y) # move the window
483 self.window.show() # show it
484 self.__shown = True
485
486 def __hide(self):
487 '''
488 hide the tooltip
489 '''
490 self.__queue_next()
491 self.window.hide()
492 self.__shown = False
493
494 def __motion_handler(self, view, event):
495 '''
496 as the pointer moves across the view, show a tooltip
497 '''
498 path_at_pos = view.get_path_at_pos(int(event.x), int(event.y))
499 if path_at_pos:
500 path, col, x, y = path_at_pos
501 tooltip = self.get_tooltip(view, col, path)
502 if tooltip:
503 tooltip = str(tooltip).strip()
504 self.__queue_next((path, col), tooltip, int(event.x_root),
505 int(event.y_root))
506 return
507 self.__hide()
508
509 def __queue_next(self, *args):
510 '''
511 queue next request to show a tooltip
512 if args is non-empty it means a request was made to show a
513 tooltip. if empty, no request is being made, but any
514 pending requests should be cancelled anyway
515 '''
516 cell = None
517
518 if args: # if called with args, break them out
519 cell, tooltip, x, y = args
520
521 # if it's the same cell as previously shown, just return
522 if self.__save == cell:
523 return
524
525 if self.__next: # if we have something queued up, cancel it
526 gobject.source_remove(self.__next)
527 self.__next = None
528
529 if cell: # if there was a request
530 if self.__shown: # if tooltip is already shown show the new one
531 self.__show(tooltip, x, y)
532 else: # else queue it up in 1/2 second
533 self.__next = gobject.timeout_add(500, self.__show,
534 tooltip, x, y)
535 self.__save = cell # save this cell
536
537 def __on_expose_event(self, window, event):
538 '''
539 this magic is required so the window appears with a 1-pixel
540 black border (default gtk Style).
541 '''
542 w, h = window.size_request()
543 window.style.paint_flat_box(window.window, gtk.STATE_NORMAL,
544 gtk.SHADOW_OUT, None, window, 'tooltip', 0, 0, w, h)
545
546 def add_view(self, view):
547 '''
548 add a gtk.TreeView to the tooltip
549 '''
550 view.connect('motion-notify-event', self.__motion_handler)
551 view.connect('leave-notify-event', lambda i, j: self.__hide())
552
553 def get_tooltip(self, view, column, path):
554 '''
555 See the module doc string for a description of this method
556 '''
557 if column is self.namecol:
558 model = view.get_model()
559 name = model[path][0]
560 return name.file.filename
561
562
563if __name__ == '__main__':
564 #Translations
565 t = gettext.translation('gui', 'locale', fallback=True)
566 _ = t.ugettext
567 t.install()
568
569 #Main
570 GUI()
571 gtk.main()