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()):
69 super(Multilist, self).__init__()
70 self._clipboard = gtk.clipboard_get()
72 logging.info('Starting Multilist')
75 os.makedirs(constants._data_path_)
80 self.db = libspeichern.Speichern()
81 self.__window_in_fullscreen = False #The window isn't in full screen mode initially.
82 self.__isPortrait = False
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)
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)
97 if hildonize.GTK_MENU_USED:
100 menu_items = gtk.MenuItem(_("Choose database file"))
101 menu_items.connect("activate", self._on_select_db, None)
102 fileMenu.append(menu_items)
104 menu_items = gtk.MenuItem(_("SQL history"))
105 menu_items.connect("activate", self._on_view_sql_history, None)
106 fileMenu.append(menu_items)
108 menu_items = gtk.MenuItem(_("SQL optimize"))
109 menu_items.connect("activate", self._on_optimize_sql, None)
110 fileMenu.append(menu_items)
112 menu_items = gtk.MenuItem(_("Sync items"))
113 menu_items.connect("activate", self.sync_notes, None)
114 fileMenu.append(menu_items)
116 menu_items = gtk.MenuItem(_("Import"))
117 menu_items.connect("activate", self._on_import, None)
118 fileMenu.append(menu_items)
120 menu_items = gtk.MenuItem(_("Export"))
121 menu_items.connect("activate", self._on_export, None)
122 fileMenu.append(menu_items)
124 menu_items = gtk.MenuItem(_("Quit"))
125 menu_items.connect("activate", self._on_destroy, None)
126 fileMenu.append(menu_items)
128 fileMenuItem = gtk.MenuItem(_("File"))
130 fileMenuItem.set_submenu(fileMenu)
132 listmenu = gtk.Menu()
134 menu_items = gtk.MenuItem(_("Search"))
135 menu_items.connect("activate", self._on_toggle_search)
136 listmenu.append(menu_items)
138 menu_items = gtk.MenuItem(_("Checkout All"))
139 menu_items.connect("activate", self._on_checkout_all)
140 listmenu.append(menu_items)
142 menu_items = gtk.MenuItem(_("Rename List"))
143 menu_items.connect("activate", self.bottombar.rename_list, None)
144 listmenu.append(menu_items)
146 menu_items = gtk.MenuItem(_("Rename Category"))
147 menu_items.connect("activate", self.bottombar.rename_category, None)
148 listmenu.append(menu_items)
150 listMenuItem = gtk.MenuItem(_("List"))
152 listMenuItem.set_submenu(listmenu)
154 viewMenu = gtk.Menu()
156 menu_items = gtk.MenuItem(_("Show Active"))
157 menu_items.connect("activate", self._on_toggle_filter, None)
158 viewMenu.append(menu_items)
160 menu_items = gtk.MenuItem(_("Settings"))
161 menu_items.connect("activate", self._on_settings, None)
162 viewMenu.append(menu_items)
164 viewMenuItem = gtk.MenuItem(_("View"))
166 viewMenuItem.set_submenu(viewMenu)
168 helpMenu = gtk.Menu()
169 menu_items = gtk.MenuItem(_("About"))
170 helpMenu.append(menu_items)
171 menu_items.connect("activate", self._on_about, None)
173 helpMenuItem = gtk.MenuItem(_("Help"))
175 helpMenuItem.set_submenu(helpMenu)
177 menuBar = gtk.MenuBar()
179 menuBar.append (fileMenuItem)
180 menuBar.append (listMenuItem)
181 menuBar.append (viewMenuItem)
182 # unten -> damit als letztes menuBar.append (helpMenuItem)
184 menuBar.append (helpMenuItem)
186 self.vbox.pack_start(menuBar, False, False, 0)
188 menuBar = gtk.MenuBar()
190 self.vbox.pack_start(menuBar, False, False, 0)
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)
198 #Get the Main Window, and connect the "destroy" event
199 self.window.add(self.vbox)
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(
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)
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)
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)
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)
233 button = gtk.Button(_("Import"))
234 button.connect("clicked", self._on_import)
235 menuBar.append(button)
237 button = gtk.Button(_("Export"))
238 button.connect("clicked", self._on_export)
239 menuBar.append(button)
241 renameListButton= gtk.Button(_("Rename List"))
242 renameListButton.connect("clicked", self.bottombar.rename_list)
243 menuBar.append(renameListButton)
245 renameCategoryButton = gtk.Button(_("Rename Category"))
246 renameCategoryButton.connect("clicked", self.bottombar.rename_category)
247 menuBar.append(renameCategoryButton)
249 searchButton= gtk.Button(_("Search Category"))
250 searchButton.connect("clicked", self._on_toggle_search)
251 menuBar.append(searchButton)
253 searchButton= gtk.Button(_("Settings"))
254 searchButton.connect("clicked", self._on_settings)
255 menuBar.append(searchButton)
259 if not hildonize.IS_HILDON_SUPPORTED:
260 _moduleLogger.info("No hildonization support")
263 self.osso_c = osso.Context(
264 constants.__app_name__,
265 constants.__version__,
269 _moduleLogger.info("No osso support")
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)
278 self.window.show_all()
280 self._load_settings()
281 self._prepare_sync_dialog()
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)
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))
294 config.add_section("List")
295 self.liststorehandler.save_settings(config, "List")
297 def _load_settings(self):
298 config = ConfigParser.SafeConfigParser()
299 config.read(constants._user_settings_)
300 self.load_settings(config)
302 def load_settings(self, config):
304 window_in_fullscreen = False
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:
310 "Settings file %s is missing section %s" % (
311 constants._user_settings_,
316 if isPortrait ^ self.__isPortrait:
318 orientation = gtk.ORIENTATION_VERTICAL
320 orientation = gtk.ORIENTATION_HORIZONTAL
321 self.set_orientation(orientation)
323 self.__window_in_fullscreen = window_in_fullscreen
324 if self.__window_in_fullscreen:
325 self.window.fullscreen()
327 self.window.unfullscreen()
329 self.liststorehandler.load_settings(config, "List")
331 def _toggle_search(self):
332 if self._search.get_property("visible"):
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
349 raise NotImplementedError(orientation)
351 def get_orientation(self):
352 return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL
354 def _toggle_rotate(self):
355 if self.__isPortrait:
356 self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
358 self.set_orientation(gtk.ORIENTATION_VERTICAL)
360 @gtk_toolbox.log_exception(_moduleLogger)
361 def _on_checkout_all(self, widget):
362 self.liststorehandler.checkout_rows()
364 @gtk_toolbox.log_exception(_moduleLogger)
365 def _on_search(self, widget):
366 self.liststorehandler.get_liststore(self._search.get_search_pattern())
368 @gtk_toolbox.log_exception(_moduleLogger)
369 def _on_click_menu_filter(self, button, val):
370 self.liststorehandler.set_filter(val)
372 @gtk_toolbox.log_exception(_moduleLogger)
373 def _on_toggle_search(self, *args):
374 self._toggle_search()
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)
383 assert False, "Unknown"
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)
390 event.keyval == gtk.keysyms.F6 or
391 event.keyval in RETURN_TYPES and isCtrl
393 # The "Full screen" hardware key has been pressed
394 if self.__window_in_fullscreen:
395 self.window.unfullscreen ()
397 self.window.fullscreen ()
399 elif event.keyval == gtk.keysyms.o and isCtrl:
400 self._toggle_rotate()
402 elif event.keyval == gtk.keysyms.f and isCtrl:
403 self._toggle_search()
406 event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
407 event.get_state() & gtk.gdk.CONTROL_MASK
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))
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
422 self.__window_in_fullscreen = False
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()
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(
437 action=gtk.FILE_CHOOSER_ACTION_OPEN,
440 importFileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
441 importFileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
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)
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(
457 action=gtk.FILE_CHOOSER_ACTION_SAVE,
460 importFileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
461 importFileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
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)
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))
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()
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()
487 @gtk_toolbox.log_exception(_moduleLogger)
488 def _on_settings(self, *args):
489 if self.__settingsWindow is None:
491 self.__settingsManager = settings.SettingsDialog(vbox, self.db, self.liststorehandler)
493 self.__settingsWindow = gtk.Window()
494 self.__settingsWindow.add(vbox)
495 self.__settingsWindow = hildonize.hildonize_window(self, self.__settingsWindow)
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()
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)
511 logging.info("changing columns")
512 self.__settingsManager.save(self.db)
513 self.view.reload_view()
515 isPortrait = self.__settingsManager.is_portrait()
516 if isPortrait ^ self.__isPortrait:
518 orientation = gtk.ORIENTATION_VERTICAL
520 orientation = gtk.ORIENTATION_HORIZONTAL
521 self.set_orientation(orientation)
525 @gtk_toolbox.log_exception(_moduleLogger)
526 def _on_destroy(self, widget = None, data = None):
529 self._save_settings()
533 except AttributeError:
534 pass # Either None or close was removed (in Fremantle)
538 @gtk_toolbox.log_exception(_moduleLogger)
539 def _on_delete_event(self, widget, event, data = None):
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)
549 if res != gtk.RESPONSE_OK:
551 logging.info("exporting sql")
554 dlg = gtk.FileChooserDialog(
555 parent = self.window,
556 action = gtk.FILE_CHOOSER_ACTION_SAVE
558 dlg.add_button( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
559 dlg.add_button( gtk.STOCK_OK, gtk.RESPONSE_OK)
561 dlg = hildon.FileChooserDialog(self.window, gtk.FILE_CHOOSER_ACTION_SAVE)
563 dlg.set_title(_("Select SQL export file"))
564 exportFileResponse = dlg.run()
566 if exportFileResponse == gtk.RESPONSE_OK:
567 fileName = dlg.get_filename()
568 sqldiag.exportSQL(fileName)
574 @gtk_toolbox.log_exception(_moduleLogger)
575 def _on_optimize_sql(self, widget = None, data = None, data2 = None):
577 self.db.speichereSQL("VACUUM", log = False)
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)
583 if self.db.ladeDirekt('datenbank'):
584 dlg.set_filename(self.db.ladeDirekt('datenbank'))
585 dlg.set_title(_("Choose your database file"))
588 if resp == gtk.RESPONSE_OK:
589 fileName = dlg.get_filename()
590 self.db.speichereDirekt('datenbank', fileName)
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)"])
611 if hildonize.IS_HILDON_SUPPORTED:
612 gtk.set_application_name(constants.__pretty_app_name__)
614 if not PROFILE_STARTUP:
618 if __name__ == "__main__":
619 logging.basicConfig(level = logging.DEBUG)