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