Adding import/export capability
[multilist] / src / multilist_gtk.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from __future__ import with_statement
5
6 """
7 This file is part of Multilist.
8
9 Multilist is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Multilist is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with Multilist.  If not, see <http://www.gnu.org/licenses/>.
21
22 Copyright (C) 2008 Christoph Würstle
23 """
24
25 import os
26 import logging
27 import ConfigParser
28
29 import gtk
30
31 try:
32         import hildon
33         isHildon = True
34 except:
35         isHildon = False
36
37 try:
38         import osso
39 except ImportError:
40         osso = None
41
42 import constants
43 import hildonize
44 import gtk_toolbox
45
46 import libspeichern
47 import search
48 import sqldialog
49 import settings
50 import libselection
51 import libview
52 import libliststorehandler
53 import libsync
54 import libbottombar
55
56 try:
57         _
58 except NameError:
59         _ = lambda x: x
60
61
62 _moduleLogger = logging.getLogger(__name__)
63 PROFILE_STARTUP = False
64
65
66 class Multilist(hildonize.get_app_class()):
67
68         _user_data = os.path.join(os.path.expanduser("~"), ".%s" % constants.__app_name__)
69         _user_settings = "%s/settings.ini" % _user_data
70
71         def __init__(self):
72                 super(Multilist, self).__init__()
73                 self._clipboard = gtk.clipboard_get()
74
75                 logging.info('Starting Multilist')
76
77                 try:
78                         os.makedirs(self._user_data)
79                 except OSError, e:
80                         if e.errno != 17:
81                                 raise
82
83                 self.db = libspeichern.Speichern()
84                 self.__window_in_fullscreen = False #The window isn't in full screen mode initially.
85                 self.__isPortrait = False
86
87                 #Haupt vbox für alle Elemente
88                 self.window = gtk.Window()
89                 self.__settingsWindow = None
90                 self.__settingsManager = None
91                 self.vbox = gtk.VBox(homogeneous = False, spacing = 0)
92
93                 self.selection = libselection.Selection(self.db, isHildon)
94                 self._search = search.Search()
95                 self.liststorehandler = libliststorehandler.Liststorehandler(self.db, self.selection)
96                 self.view = libview.View(self.db, self.liststorehandler, self.window)
97                 self.bottombar = libbottombar.Bottombar(self.db, self.view, isHildon)
98
99                 #Menue
100                 if hildonize.GTK_MENU_USED:
101                         fileMenu = gtk.Menu()
102
103                         menu_items = gtk.MenuItem(_("Choose database file"))
104                         menu_items.connect("activate", self._on_select_db, None)
105                         fileMenu.append(menu_items)
106
107                         menu_items = gtk.MenuItem(_("SQL history"))
108                         menu_items.connect("activate", self._on_view_sql_history, None)
109                         fileMenu.append(menu_items)
110
111                         menu_items = gtk.MenuItem(_("SQL optimize"))
112                         menu_items.connect("activate", self._on_optimize_sql, None)
113                         fileMenu.append(menu_items)
114
115                         menu_items = gtk.MenuItem(_("Sync items"))
116                         menu_items.connect("activate", self.sync_notes, None)
117                         fileMenu.append(menu_items)
118
119                         menu_items = gtk.MenuItem(_("Import"))
120                         menu_items.connect("activate", self._on_import, None)
121                         fileMenu.append(menu_items)
122
123                         menu_items = gtk.MenuItem(_("Export"))
124                         menu_items.connect("activate", self._on_export, None)
125                         fileMenu.append(menu_items)
126
127                         menu_items = gtk.MenuItem(_("Quit"))
128                         menu_items.connect("activate", self._on_destroy, None)
129                         fileMenu.append(menu_items)
130
131                         fileMenuItem = gtk.MenuItem(_("File"))
132                         fileMenuItem.show()
133                         fileMenuItem.set_submenu(fileMenu)
134
135                         listmenu = gtk.Menu()
136
137                         menu_items = gtk.MenuItem(_("Search"))
138                         menu_items.connect("activate", self._on_toggle_search)
139                         listmenu.append(menu_items)
140
141                         menu_items = gtk.MenuItem(_("Checkout All"))
142                         menu_items.connect("activate", self._on_checkout_all)
143                         listmenu.append(menu_items)
144
145                         menu_items = gtk.MenuItem(_("Rename List"))
146                         menu_items.connect("activate", self.bottombar.rename_list, None)
147                         listmenu.append(menu_items)
148
149                         menu_items = gtk.MenuItem(_("Rename Category"))
150                         menu_items.connect("activate", self.bottombar.rename_category, None)
151                         listmenu.append(menu_items)
152
153                         listMenuItem = gtk.MenuItem(_("List"))
154                         listMenuItem.show()
155                         listMenuItem.set_submenu(listmenu)
156
157                         viewMenu = gtk.Menu()
158
159                         menu_items = gtk.MenuItem(_("Show Active"))
160                         menu_items.connect("activate", self._on_toggle_filter, None)
161                         viewMenu.append(menu_items)
162
163                         menu_items = gtk.MenuItem(_("Settings"))
164                         menu_items.connect("activate", self._on_settings, None)
165                         viewMenu.append(menu_items)
166
167                         viewMenuItem = gtk.MenuItem(_("View"))
168                         viewMenuItem.show()
169                         viewMenuItem.set_submenu(viewMenu)
170
171                         helpMenu = gtk.Menu()
172                         menu_items = gtk.MenuItem(_("About"))
173                         helpMenu.append(menu_items)
174                         menu_items.connect("activate", self._on_about, None)
175
176                         helpMenuItem = gtk.MenuItem(_("Help"))
177                         helpMenuItem.show()
178                         helpMenuItem.set_submenu(helpMenu)
179
180                         menuBar = gtk.MenuBar()
181                         menuBar.show()
182                         menuBar.append (fileMenuItem)
183                         menuBar.append (listMenuItem)
184                         menuBar.append (viewMenuItem)
185                         # unten -> damit als letztes menuBar.append (helpMenuItem)
186                         #Als letztes menü
187                         menuBar.append (helpMenuItem)
188
189                         self.vbox.pack_start(menuBar, False, False, 0)
190                 else:
191                         menuBar = gtk.MenuBar()
192                         menuBar.show()
193                         self.vbox.pack_start(menuBar, False, False, 0)
194
195                 #add to vbox below (to get it on top)
196                 self.vbox.pack_end(self._search, expand = False, fill = True)
197                 self.vbox.pack_end(self.bottombar, expand = False, fill = True, padding = 0)
198                 self.vbox.pack_end(self.view, expand = True, fill = True, padding = 0)
199                 self.vbox.pack_end(self.selection, expand = False, fill = True, padding = 0)
200
201                 #Get the Main Window, and connect the "destroy" event
202                 self.window.add(self.vbox)
203
204                 self.window = hildonize.hildonize_window(self, self.window)
205                 hildonize.set_application_title(self.window, "%s" % constants.__pretty_app_name__)
206                 menuBar = hildonize.hildonize_menu(
207                         self.window,
208                         menuBar,
209                 )
210                 if hildonize.IS_FREMANTLE_SUPPORTED:
211                         button = hildonize.hildon.GtkRadioButton(gtk.HILDON_SIZE_AUTO, None)
212                         button.set_label("All")
213                         menuBar.add_filter(button)
214                         button.connect("clicked", self._on_click_menu_filter, self.liststorehandler.SHOW_ALL)
215                         button.set_mode(False)
216                         filterGroup = button
217
218                         button = hildonize.hildon.GtkRadioButton(gtk.HILDON_SIZE_AUTO, filterGroup)
219                         button.set_label("New")
220                         menuBar.add_filter(button)
221                         button.connect("clicked", self._on_click_menu_filter, self.liststorehandler.SHOW_NEW)
222                         button.set_mode(False)
223
224                         button = hildonize.hildon.GtkRadioButton(gtk.HILDON_SIZE_AUTO, filterGroup)
225                         button.set_label("Active")
226                         menuBar.add_filter(button)
227                         button.connect("clicked", self._on_click_menu_filter, self.liststorehandler.SHOW_ACTIVE)
228                         button.set_mode(False)
229
230                         button = hildonize.hildon.GtkRadioButton(gtk.HILDON_SIZE_AUTO, filterGroup)
231                         button.set_label("Done")
232                         menuBar.add_filter(button)
233                         button.connect("clicked", self._on_click_menu_filter, self.liststorehandler.SHOW_COMPLETE)
234                         button.set_mode(False)
235
236                         button = gtk.Button(_("Import"))
237                         button.connect("clicked", self._on_import)
238                         menuBar.append(button)
239
240                         button = gtk.Button(_("Export"))
241                         button.connect("clicked", self._on_export)
242                         menuBar.append(button)
243
244                         renameListButton= gtk.Button(_("Rename List"))
245                         renameListButton.connect("clicked", self.bottombar.rename_list)
246                         menuBar.append(renameListButton)
247
248                         renameCategoryButton = gtk.Button(_("Rename Category"))
249                         renameCategoryButton.connect("clicked", self.bottombar.rename_category)
250                         menuBar.append(renameCategoryButton)
251
252                         searchButton= gtk.Button(_("Search Category"))
253                         searchButton.connect("clicked", self._on_toggle_search)
254                         menuBar.append(searchButton)
255
256                         searchButton= gtk.Button(_("Settings"))
257                         searchButton.connect("clicked", self._on_settings)
258                         menuBar.append(searchButton)
259
260                         menuBar.show_all()
261
262                 if not hildonize.IS_HILDON_SUPPORTED:
263                         _moduleLogger.info("No hildonization support")
264
265                 if osso is not None:
266                         self.osso_c = osso.Context(
267                                 constants.__app_name__,
268                                 constants.__version__,
269                                 False
270                         )
271                 else:
272                         _moduleLogger.info("No osso support")
273                         self._osso_c = None
274
275                 self.window.connect("delete_event", self._on_delete_event)
276                 self.window.connect("destroy", self._on_destroy)
277                 self.window.connect("key-press-event", self.on_key_press)
278                 self.window.connect("window-state-event", self._on_window_state_change)
279                 self._search.connect("search_changed", self._on_search)
280
281                 self.window.show_all()
282                 self._search.hide()
283                 self._load_settings()
284                 self._prepare_sync_dialog()
285
286         def _save_settings(self):
287                 config = ConfigParser.SafeConfigParser()
288                 self.save_settings(config)
289                 with open(self._user_settings, "wb") as configFile:
290                         config.write(configFile)
291
292         def save_settings(self, config):
293                 config.add_section(constants.__pretty_app_name__)
294                 config.set(constants.__pretty_app_name__, "portrait", str(self.__isPortrait))
295                 config.set(constants.__pretty_app_name__, "fullscreen", str(self.__window_in_fullscreen))
296
297                 config.add_section("List")
298                 self.liststorehandler.save_settings(config, "List")
299
300         def _load_settings(self):
301                 config = ConfigParser.SafeConfigParser()
302                 config.read(self._user_settings)
303                 self.load_settings(config)
304
305         def load_settings(self, config):
306                 isPortrait = False
307                 window_in_fullscreen = False
308                 try:
309                         isPortrait = config.getboolean(constants.__pretty_app_name__, "portrait")
310                         window_in_fullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
311                 except ConfigParser.NoSectionError, e:
312                         _moduleLogger.info(
313                                 "Settings file %s is missing section %s" % (
314                                         self._user_settings,
315                                         e.section,
316                                 )
317                         )
318
319                 if isPortrait ^ self.__isPortrait:
320                         if isPortrait:
321                                 orientation = gtk.ORIENTATION_VERTICAL
322                         else:
323                                 orientation = gtk.ORIENTATION_HORIZONTAL
324                         self.set_orientation(orientation)
325
326                 self.__window_in_fullscreen = window_in_fullscreen
327                 if self.__window_in_fullscreen:
328                         self.window.fullscreen()
329                 else:
330                         self.window.unfullscreen()
331
332                 self.liststorehandler.load_settings(config, "List")
333
334         def _toggle_search(self):
335                 if self._search.get_property("visible"):
336                         self._search.hide()
337                 else:
338                         self._search.show()
339
340         def set_orientation(self, orientation):
341                 if orientation == gtk.ORIENTATION_VERTICAL:
342                         hildonize.window_to_portrait(self.window)
343                         self.bottombar.set_orientation(gtk.ORIENTATION_VERTICAL)
344                         self.selection.set_orientation(gtk.ORIENTATION_VERTICAL)
345                         self.__isPortrait = True
346                 elif orientation == gtk.ORIENTATION_HORIZONTAL:
347                         hildonize.window_to_landscape(self.window)
348                         self.bottombar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
349                         self.selection.set_orientation(gtk.ORIENTATION_HORIZONTAL)
350                         self.__isPortrait = False
351                 else:
352                         raise NotImplementedError(orientation)
353
354         def get_orientation(self):
355                 return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL
356
357         def _toggle_rotate(self):
358                 if self.__isPortrait:
359                         self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
360                 else:
361                         self.set_orientation(gtk.ORIENTATION_VERTICAL)
362
363         @gtk_toolbox.log_exception(_moduleLogger)
364         def _on_checkout_all(self, widget):
365                 self.liststorehandler.checkout_rows()
366
367         @gtk_toolbox.log_exception(_moduleLogger)
368         def _on_search(self, widget):
369                 self.liststorehandler.get_liststore(self._search.get_search_pattern())
370
371         @gtk_toolbox.log_exception(_moduleLogger)
372         def _on_click_menu_filter(self, button, val):
373                 self.liststorehandler.set_filter(val)
374
375         @gtk_toolbox.log_exception(_moduleLogger)
376         def _on_toggle_search(self, *args):
377                 self._toggle_search()
378
379         @gtk_toolbox.log_exception(_moduleLogger)
380         def _on_toggle_filter(self, *args):
381                 if self.liststorehandler.get_filter() == self.liststorehandler.SHOW_ALL:
382                         self.liststorehandler.set_filter(self.liststorehandler.SHOW_ACTIVE)
383                 elif self.liststorehandler.get_filter() == self.liststorehandler.SHOW_ACTIVE:
384                         self.liststorehandler.set_filter(self.liststorehandler.SHOW_ALL)
385                 else:
386                         assert False, "Unknown"
387
388         @gtk_toolbox.log_exception(_moduleLogger)
389         def on_key_press(self, widget, event, *args):
390                 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
391                 isCtrl = bool(event.get_state() & gtk.gdk.CONTROL_MASK)
392                 if (
393                         event.keyval == gtk.keysyms.F6 or
394                         event.keyval in RETURN_TYPES and isCtrl
395                 ):
396                         # The "Full screen" hardware key has been pressed 
397                         if self.__window_in_fullscreen:
398                                 self.window.unfullscreen ()
399                         else:
400                                 self.window.fullscreen ()
401                         return True
402                 elif event.keyval == gtk.keysyms.o and isCtrl:
403                         self._toggle_rotate()
404                         return True
405                 elif event.keyval == gtk.keysyms.f and isCtrl:
406                         self._toggle_search()
407                         return True
408                 elif (
409                         event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
410                         event.get_state() & gtk.gdk.CONTROL_MASK
411                 ):
412                         self.window.destroy()
413                 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
414                         with open(constants._user_logpath_, "r") as f:
415                                 logLines = f.xreadlines()
416                                 log = "".join(logLines)
417                                 self._clipboard.set_text(str(log))
418                         return True
419
420         @gtk_toolbox.log_exception(_moduleLogger)
421         def _on_window_state_change(self, widget, event, *args):
422                 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
423                         self.__window_in_fullscreen = True
424                 else:
425                         self.__window_in_fullscreen = False
426
427         @gtk_toolbox.log_exception(_moduleLogger)
428         def _on_sync_finished(self, data = None, data2 = None):
429                 self.selection.comboList_changed()
430                 self.selection.comboCategory_changed()
431                 self.liststorehandler.update_list()
432
433         @gtk_toolbox.log_exception(_moduleLogger)
434         def _on_import(self, *args):
435                 csvFilter = gtk.FileFilter()
436                 csvFilter.set_name("Import Lists")
437                 csvFilter.add_pattern("*.csv")
438                 importFileChooser = gtk.FileChooserDialog(
439                         title="Contacts",
440                         action=gtk.FILE_CHOOSER_ACTION_OPEN,
441                         parent=self.window,
442                 )
443                 importFileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
444                 importFileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
445
446                 importFileChooser.set_property("filter", csvFilter)
447                 userResponse = importFileChooser.run()
448                 importFileChooser.hide()
449                 if userResponse == gtk.RESPONSE_OK:
450                         filename = importFileChooser.get_filename()
451                         self.liststorehandler.append_data(filename)
452
453         @gtk_toolbox.log_exception(_moduleLogger)
454         def _on_export(self, *args):
455                 csvFilter = gtk.FileFilter()
456                 csvFilter.set_name("Export Lists")
457                 csvFilter.add_pattern("*.csv")
458                 importFileChooser = gtk.FileChooserDialog(
459                         title="Contacts",
460                         action=gtk.FILE_CHOOSER_ACTION_SAVE,
461                         parent=self.window,
462                 )
463                 importFileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
464                 importFileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
465
466                 importFileChooser.set_property("filter", csvFilter)
467                 userResponse = importFileChooser.run()
468                 importFileChooser.hide()
469                 if userResponse == gtk.RESPONSE_OK:
470                         filename = importFileChooser.get_filename()
471                         self.liststorehandler.export_data(filename)
472
473         def _prepare_sync_dialog(self):
474                 self.sync_dialog = gtk.Dialog(_("Sync"), None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
475
476                 self.sync_dialog.set_position(gtk.WIN_POS_CENTER)
477                 sync = libsync.Sync(self.db, self.window, 50503)
478                 sync.connect("syncFinished", self._on_sync_finished)
479                 self.sync_dialog.vbox.pack_start(sync, True, True, 0)
480                 self.sync_dialog.set_size_request(500, 350)
481                 self.sync_dialog.vbox.show_all()
482
483         @gtk_toolbox.log_exception(_moduleLogger)
484         def sync_notes(self, widget = None, data = None):
485                 if self.sync_dialog is None:
486                         self._prepare_sync_dialog()
487                 self.sync_dialog.run()
488                 self.sync_dialog.hide()
489
490         @gtk_toolbox.log_exception(_moduleLogger)
491         def _on_settings(self, *args):
492                 if self.__settingsWindow is None:
493                         vbox = gtk.VBox()
494                         self.__settingsManager = settings.SettingsDialog(vbox, self.db, self.liststorehandler)
495
496                         self.__settingsWindow = gtk.Window()
497                         self.__settingsWindow.add(vbox)
498                         self.__settingsWindow = hildonize.hildonize_window(self, self.__settingsWindow)
499
500                         self.__settingsWindow.set_title(_("Settings"))
501                         self.__settingsWindow.set_transient_for(self.window)
502                         self.__settingsWindow.set_default_size(*self.window.get_size())
503                         self.__settingsWindow.connect("delete-event", self._on_settings_delete)
504                 self.__settingsManager.set_portrait_state(self.__isPortrait)
505                 self.__settingsWindow.set_modal(True)
506                 self.__settingsWindow.show_all()
507
508         @gtk_toolbox.log_exception(_moduleLogger)
509         def _on_settings_delete(self, *args):
510                 self.__settingsWindow.emit_stop_by_name("delete-event")
511                 self.__settingsWindow.hide()
512                 self.__settingsWindow.set_modal(False)
513
514                 logging.info("changing columns")
515                 self.__settingsManager.save(self.db)
516                 self.view.reload_view()
517
518                 isPortrait = self.__settingsManager.is_portrait()
519                 if isPortrait ^ self.__isPortrait:
520                         if isPortrait:
521                                 orientation = gtk.ORIENTATION_VERTICAL
522                         else:
523                                 orientation = gtk.ORIENTATION_HORIZONTAL
524                         self.set_orientation(orientation)
525
526                 return True
527
528         @gtk_toolbox.log_exception(_moduleLogger)
529         def _on_destroy(self, widget = None, data = None):
530                 try:
531                         self.db.close()
532                         self._save_settings()
533
534                         try:
535                                 self._osso_c.close()
536                         except AttributeError:
537                                 pass # Either None or close was removed (in Fremantle)
538                 finally:
539                         gtk.main_quit()
540
541         @gtk_toolbox.log_exception(_moduleLogger)
542         def _on_delete_event(self, widget, event, data = None):
543                 return False
544
545         @gtk_toolbox.log_exception(_moduleLogger)
546         def _on_view_sql_history(self, widget = None, data = None, data2 = None):
547                 sqldiag = sqldialog.SqlDialog(self.db)
548                 res = sqldiag.run()
549                 sqldiag.hide()
550
551                 try:
552                         if res != gtk.RESPONSE_OK:
553                                 return
554                         logging.info("exporting sql")
555
556                         if not isHildon:
557                                 dlg = gtk.FileChooserDialog(
558                                         parent = self.window,
559                                         action = gtk.FILE_CHOOSER_ACTION_SAVE
560                                 )
561                                 dlg.add_button( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
562                                 dlg.add_button( gtk.STOCK_OK, gtk.RESPONSE_OK)
563                         else:
564                                 dlg = hildon.FileChooserDialog(self.window, gtk.FILE_CHOOSER_ACTION_SAVE)
565
566                         dlg.set_title(_("Select SQL export file"))
567                         exportFileResponse = dlg.run()
568                         try:
569                                 if exportFileResponse == gtk.RESPONSE_OK:
570                                         fileName = dlg.get_filename()
571                                         sqldiag.exportSQL(fileName)
572                         finally:
573                                 dlg.destroy()
574                 finally:
575                         sqldiag.destroy()
576
577         @gtk_toolbox.log_exception(_moduleLogger)
578         def _on_optimize_sql(self, widget = None, data = None, data2 = None):
579                 #optimiere sql
580                 self.db.speichereSQL("VACUUM", log = False)
581
582         @gtk_toolbox.log_exception(_moduleLogger)
583         def _on_select_db(self, widget = None, data = None, data2 = None):
584                 dlg = hildon.FileChooserDialog(parent=self._window, action=gtk.FILE_CHOOSER_ACTION_SAVE)
585
586                 if self.db.ladeDirekt('datenbank'):
587                         dlg.set_filename(self.db.ladeDirekt('datenbank'))
588                 dlg.set_title(_("Choose your database file"))
589                 resp = dlg.run()
590                 try:
591                         if resp == gtk.RESPONSE_OK:
592                                 fileName = dlg.get_filename()
593                                 self.db.speichereDirekt('datenbank', fileName)
594                                 self.db.openDB()
595                 finally:
596                         dlg.destroy()
597
598         @gtk_toolbox.log_exception(_moduleLogger)
599         def _on_about(self, widget = None, data = None):
600                 dialog = gtk.AboutDialog()
601                 dialog.set_position(gtk.WIN_POS_CENTER)
602                 dialog.set_name(constants.__pretty_app_name__)
603                 dialog.set_version(constants.__version__)
604                 dialog.set_copyright("")
605                 dialog.set_website("http://axique.de/f = Multilist")
606                 comments = "%s is a program to handle multiple lists." % constants.__pretty_app_name__
607                 dialog.set_comments(comments)
608                 dialog.set_authors(["Christoph Wurstle <n800@axique.net>", "Ed Page <eopage@byu.net> (Blame him for the most recent bugs)"])
609                 dialog.run()
610                 dialog.destroy()
611
612
613 def run_multilist():
614         if hildonize.IS_HILDON_SUPPORTED:
615                 gtk.set_application_name(constants.__pretty_app_name__)
616         app = Multilist()
617         if not PROFILE_STARTUP:
618                 gtk.main()
619
620
621 if __name__ == "__main__":
622         logging.basicConfig(level = logging.DEBUG)
623         run_multilist()