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