2 # -*- coding: utf-8 -*-
4 from __future__ import with_statement
7 This file is part of Multilist.
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.
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.
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/>.
22 Copyright (C) 2008 Christoph Würstle
52 import libliststorehandler
62 _moduleLogger = logging.getLogger(__name__)
63 PROFILE_STARTUP = False
66 class Multilist(hildonize.get_app_class()):
68 _user_data = os.path.join(os.path.expanduser("~"), ".%s" % constants.__app_name__)
69 _user_settings = "%s/settings.ini" % _user_data
72 super(Multilist, self).__init__()
73 self._clipboard = gtk.clipboard_get()
75 logging.info('Starting Multilist')
78 os.makedirs(self._user_data)
83 self.db = libspeichern.Speichern()
84 self.__window_in_fullscreen = False #The window isn't in full screen mode initially.
85 self.__isPortrait = False
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)
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)
100 if hildonize.GTK_MENU_USED:
101 fileMenu = gtk.Menu()
103 menu_items = gtk.MenuItem(_("Choose database file"))
104 menu_items.connect("activate", self._on_select_db, None)
105 fileMenu.append(menu_items)
107 menu_items = gtk.MenuItem(_("SQL history"))
108 menu_items.connect("activate", self._on_view_sql_history, None)
109 fileMenu.append(menu_items)
111 menu_items = gtk.MenuItem(_("SQL optimize"))
112 menu_items.connect("activate", self._on_optimize_sql, None)
113 fileMenu.append(menu_items)
115 menu_items = gtk.MenuItem(_("Sync items"))
116 menu_items.connect("activate", self.sync_notes, None)
117 fileMenu.append(menu_items)
119 menu_items = gtk.MenuItem(_("Import"))
120 menu_items.connect("activate", self._on_import, None)
121 fileMenu.append(menu_items)
123 menu_items = gtk.MenuItem(_("Export"))
124 menu_items.connect("activate", self._on_export, None)
125 fileMenu.append(menu_items)
127 menu_items = gtk.MenuItem(_("Quit"))
128 menu_items.connect("activate", self._on_destroy, None)
129 fileMenu.append(menu_items)
131 fileMenuItem = gtk.MenuItem(_("File"))
133 fileMenuItem.set_submenu(fileMenu)
135 listmenu = gtk.Menu()
137 menu_items = gtk.MenuItem(_("Search"))
138 menu_items.connect("activate", self._on_toggle_search)
139 listmenu.append(menu_items)
141 menu_items = gtk.MenuItem(_("Checkout All"))
142 menu_items.connect("activate", self._on_checkout_all)
143 listmenu.append(menu_items)
145 menu_items = gtk.MenuItem(_("Rename List"))
146 menu_items.connect("activate", self.bottombar.rename_list, None)
147 listmenu.append(menu_items)
149 menu_items = gtk.MenuItem(_("Rename Category"))
150 menu_items.connect("activate", self.bottombar.rename_category, None)
151 listmenu.append(menu_items)
153 listMenuItem = gtk.MenuItem(_("List"))
155 listMenuItem.set_submenu(listmenu)
157 viewMenu = gtk.Menu()
159 menu_items = gtk.MenuItem(_("Show Active"))
160 menu_items.connect("activate", self._on_toggle_filter, None)
161 viewMenu.append(menu_items)
163 menu_items = gtk.MenuItem(_("Settings"))
164 menu_items.connect("activate", self._on_settings, None)
165 viewMenu.append(menu_items)
167 viewMenuItem = gtk.MenuItem(_("View"))
169 viewMenuItem.set_submenu(viewMenu)
171 helpMenu = gtk.Menu()
172 menu_items = gtk.MenuItem(_("About"))
173 helpMenu.append(menu_items)
174 menu_items.connect("activate", self._on_about, None)
176 helpMenuItem = gtk.MenuItem(_("Help"))
178 helpMenuItem.set_submenu(helpMenu)
180 menuBar = gtk.MenuBar()
182 menuBar.append (fileMenuItem)
183 menuBar.append (listMenuItem)
184 menuBar.append (viewMenuItem)
185 # unten -> damit als letztes menuBar.append (helpMenuItem)
187 menuBar.append (helpMenuItem)
189 self.vbox.pack_start(menuBar, False, False, 0)
191 menuBar = gtk.MenuBar()
193 self.vbox.pack_start(menuBar, False, False, 0)
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)
201 #Get the Main Window, and connect the "destroy" event
202 self.window.add(self.vbox)
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(
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)
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)
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)
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)
236 button = gtk.Button(_("Import"))
237 button.connect("clicked", self._on_import)
238 menuBar.append(button)
240 button = gtk.Button(_("Export"))
241 button.connect("clicked", self._on_export)
242 menuBar.append(button)
244 renameListButton= gtk.Button(_("Rename List"))
245 renameListButton.connect("clicked", self.bottombar.rename_list)
246 menuBar.append(renameListButton)
248 renameCategoryButton = gtk.Button(_("Rename Category"))
249 renameCategoryButton.connect("clicked", self.bottombar.rename_category)
250 menuBar.append(renameCategoryButton)
252 searchButton= gtk.Button(_("Search Category"))
253 searchButton.connect("clicked", self._on_toggle_search)
254 menuBar.append(searchButton)
256 searchButton= gtk.Button(_("Settings"))
257 searchButton.connect("clicked", self._on_settings)
258 menuBar.append(searchButton)
262 if not hildonize.IS_HILDON_SUPPORTED:
263 _moduleLogger.info("No hildonization support")
266 self.osso_c = osso.Context(
267 constants.__app_name__,
268 constants.__version__,
272 _moduleLogger.info("No osso support")
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)
281 self.window.show_all()
283 self._load_settings()
284 self._prepare_sync_dialog()
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)
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))
297 config.add_section("List")
298 self.liststorehandler.save_settings(config, "List")
300 def _load_settings(self):
301 config = ConfigParser.SafeConfigParser()
302 config.read(self._user_settings)
303 self.load_settings(config)
305 def load_settings(self, config):
307 window_in_fullscreen = False
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:
313 "Settings file %s is missing section %s" % (
319 if isPortrait ^ self.__isPortrait:
321 orientation = gtk.ORIENTATION_VERTICAL
323 orientation = gtk.ORIENTATION_HORIZONTAL
324 self.set_orientation(orientation)
326 self.__window_in_fullscreen = window_in_fullscreen
327 if self.__window_in_fullscreen:
328 self.window.fullscreen()
330 self.window.unfullscreen()
332 self.liststorehandler.load_settings(config, "List")
334 def _toggle_search(self):
335 if self._search.get_property("visible"):
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
352 raise NotImplementedError(orientation)
354 def get_orientation(self):
355 return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL
357 def _toggle_rotate(self):
358 if self.__isPortrait:
359 self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
361 self.set_orientation(gtk.ORIENTATION_VERTICAL)
363 @gtk_toolbox.log_exception(_moduleLogger)
364 def _on_checkout_all(self, widget):
365 self.liststorehandler.checkout_rows()
367 @gtk_toolbox.log_exception(_moduleLogger)
368 def _on_search(self, widget):
369 self.liststorehandler.get_liststore(self._search.get_search_pattern())
371 @gtk_toolbox.log_exception(_moduleLogger)
372 def _on_click_menu_filter(self, button, val):
373 self.liststorehandler.set_filter(val)
375 @gtk_toolbox.log_exception(_moduleLogger)
376 def _on_toggle_search(self, *args):
377 self._toggle_search()
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)
386 assert False, "Unknown"
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)
393 event.keyval == gtk.keysyms.F6 or
394 event.keyval in RETURN_TYPES and isCtrl
396 # The "Full screen" hardware key has been pressed
397 if self.__window_in_fullscreen:
398 self.window.unfullscreen ()
400 self.window.fullscreen ()
402 elif event.keyval == gtk.keysyms.o and isCtrl:
403 self._toggle_rotate()
405 elif event.keyval == gtk.keysyms.f and isCtrl:
406 self._toggle_search()
409 event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
410 event.get_state() & gtk.gdk.CONTROL_MASK
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))
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
425 self.__window_in_fullscreen = False
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()
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(
440 action=gtk.FILE_CHOOSER_ACTION_OPEN,
443 importFileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
444 importFileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
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)
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(
460 action=gtk.FILE_CHOOSER_ACTION_SAVE,
463 importFileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
464 importFileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
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)
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))
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()
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()
490 @gtk_toolbox.log_exception(_moduleLogger)
491 def _on_settings(self, *args):
492 if self.__settingsWindow is None:
494 self.__settingsManager = settings.SettingsDialog(vbox, self.db, self.liststorehandler)
496 self.__settingsWindow = gtk.Window()
497 self.__settingsWindow.add(vbox)
498 self.__settingsWindow = hildonize.hildonize_window(self, self.__settingsWindow)
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()
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)
514 logging.info("changing columns")
515 self.__settingsManager.save(self.db)
516 self.view.reload_view()
518 isPortrait = self.__settingsManager.is_portrait()
519 if isPortrait ^ self.__isPortrait:
521 orientation = gtk.ORIENTATION_VERTICAL
523 orientation = gtk.ORIENTATION_HORIZONTAL
524 self.set_orientation(orientation)
528 @gtk_toolbox.log_exception(_moduleLogger)
529 def _on_destroy(self, widget = None, data = None):
532 self._save_settings()
536 except AttributeError:
537 pass # Either None or close was removed (in Fremantle)
541 @gtk_toolbox.log_exception(_moduleLogger)
542 def _on_delete_event(self, widget, event, data = None):
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)
552 if res != gtk.RESPONSE_OK:
554 logging.info("exporting sql")
557 dlg = gtk.FileChooserDialog(
558 parent = self.window,
559 action = gtk.FILE_CHOOSER_ACTION_SAVE
561 dlg.add_button( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
562 dlg.add_button( gtk.STOCK_OK, gtk.RESPONSE_OK)
564 dlg = hildon.FileChooserDialog(self.window, gtk.FILE_CHOOSER_ACTION_SAVE)
566 dlg.set_title(_("Select SQL export file"))
567 exportFileResponse = dlg.run()
569 if exportFileResponse == gtk.RESPONSE_OK:
570 fileName = dlg.get_filename()
571 sqldiag.exportSQL(fileName)
577 @gtk_toolbox.log_exception(_moduleLogger)
578 def _on_optimize_sql(self, widget = None, data = None, data2 = None):
580 self.db.speichereSQL("VACUUM", log = False)
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)
586 if self.db.ladeDirekt('datenbank'):
587 dlg.set_filename(self.db.ladeDirekt('datenbank'))
588 dlg.set_title(_("Choose your database file"))
591 if resp == gtk.RESPONSE_OK:
592 fileName = dlg.get_filename()
593 self.db.speichereDirekt('datenbank', fileName)
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)"])
614 if hildonize.IS_HILDON_SUPPORTED:
615 gtk.set_application_name(constants.__pretty_app_name__)
617 if not PROFILE_STARTUP:
621 if __name__ == "__main__":
622 logging.basicConfig(level = logging.DEBUG)