ea1a9307c8831ada4376c01e83f46057970425d8
[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         def _load_settings(self):
282                 config = ConfigParser.SafeConfigParser()
283                 config.read(self._user_settings)
284                 self.load_settings(config)
285
286         def load_settings(self, config):
287                 isPortrait = False
288                 window_in_fullscreen = False
289                 try:
290                         isPortrait = config.getboolean(constants.__pretty_app_name__, "portrait")
291                         window_in_fullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
292                 except ConfigParser.NoSectionError, e:
293                         _moduleLogger.info(
294                                 "Settings file %s is missing section %s" % (
295                                         self._user_settings,
296                                         e.section,
297                                 )
298                         )
299
300                 if isPortrait ^ self.__isPortrait:
301                         if isPortrait:
302                                 orientation = gtk.ORIENTATION_VERTICAL
303                         else:
304                                 orientation = gtk.ORIENTATION_HORIZONTAL
305                         self.set_orientation(orientation)
306
307                 self.__window_in_fullscreen = window_in_fullscreen
308                 if self.__window_in_fullscreen:
309                         self.window.fullscreen()
310                 else:
311                         self.window.unfullscreen()
312
313         def _toggle_search(self):
314                 if self._search.get_property("visible"):
315                         self._search.hide()
316                 else:
317                         self._search.show()
318
319         def set_orientation(self, orientation):
320                 if orientation == gtk.ORIENTATION_VERTICAL:
321                         hildonize.window_to_portrait(self.window)
322                         self.bottombar.set_orientation(gtk.ORIENTATION_VERTICAL)
323                         self.selection.set_orientation(gtk.ORIENTATION_VERTICAL)
324                         self.__isPortrait = True
325                 elif orientation == gtk.ORIENTATION_HORIZONTAL:
326                         hildonize.window_to_landscape(self.window)
327                         self.bottombar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
328                         self.selection.set_orientation(gtk.ORIENTATION_HORIZONTAL)
329                         self.__isPortrait = False
330                 else:
331                         raise NotImplementedError(orientation)
332
333         def get_orientation(self):
334                 return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL
335
336         def _toggle_rotate(self):
337                 if self.__isPortrait:
338                         self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
339                 else:
340                         self.set_orientation(gtk.ORIENTATION_VERTICAL)
341
342         @gtk_toolbox.log_exception(_moduleLogger)
343         def _on_checkout_all(self, widget):
344                 self.liststorehandler.checkout_rows()
345
346         @gtk_toolbox.log_exception(_moduleLogger)
347         def _on_search(self, widget):
348                 self.liststorehandler.get_liststore(self._search.get_search_pattern())
349
350         @gtk_toolbox.log_exception(_moduleLogger)
351         def _on_click_menu_filter(self, button, val):
352                 self.liststorehandler.set_filter(val)
353
354         @gtk_toolbox.log_exception(_moduleLogger)
355         def _on_toggle_search(self, *args):
356                 self._toggle_search()
357
358         @gtk_toolbox.log_exception(_moduleLogger)
359         def _on_toggle_filter(self, *args):
360                 if self.liststorehandler.get_filter() == self.liststorehandler.SHOW_ALL:
361                         self.liststorehandler.set_filter(self.liststorehandler.SHOW_ACTIVE)
362                 elif self.liststorehandler.get_filter() == self.liststorehandler.SHOW_ACTIVE:
363                         self.liststorehandler.set_filter(self.liststorehandler.SHOW_ALL)
364                 else:
365                         assert False, "Unknown"
366
367         @gtk_toolbox.log_exception(_moduleLogger)
368         def on_key_press(self, widget, event, *args):
369                 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
370                 isCtrl = bool(event.get_state() & gtk.gdk.CONTROL_MASK)
371                 if (
372                         event.keyval == gtk.keysyms.F6 or
373                         event.keyval in RETURN_TYPES and isCtrl
374                 ):
375                         # The "Full screen" hardware key has been pressed 
376                         if self.__window_in_fullscreen:
377                                 self.window.unfullscreen ()
378                         else:
379                                 self.window.fullscreen ()
380                         return True
381                 elif event.keyval == gtk.keysyms.o and isCtrl:
382                         self._toggle_rotate()
383                         return True
384                 elif event.keyval == gtk.keysyms.f and isCtrl:
385                         self._toggle_search()
386                         return True
387                 elif (
388                         event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
389                         event.get_state() & gtk.gdk.CONTROL_MASK
390                 ):
391                         self.window.destroy()
392                 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
393                         with open(constants._user_logpath_, "r") as f:
394                                 logLines = f.xreadlines()
395                                 log = "".join(logLines)
396                                 self._clipboard.set_text(str(log))
397                         return True
398
399         @gtk_toolbox.log_exception(_moduleLogger)
400         def _on_window_state_change(self, widget, event, *args):
401                 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
402                         self.__window_in_fullscreen = True
403                 else:
404                         self.__window_in_fullscreen = False
405
406         @gtk_toolbox.log_exception(_moduleLogger)
407         def _on_sync_finished(self, data = None, data2 = None):
408                 self.selection.comboList_changed()
409                 self.selection.comboCategory_changed()
410                 self.liststorehandler.update_list()
411
412         def _prepare_sync_dialog(self):
413                 self.sync_dialog = gtk.Dialog(_("Sync"), None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
414
415                 self.sync_dialog.set_position(gtk.WIN_POS_CENTER)
416                 sync = libsync.Sync(self.db, self.window, 50503)
417                 sync.connect("syncFinished", self._on_sync_finished)
418                 self.sync_dialog.vbox.pack_start(sync, True, True, 0)
419                 self.sync_dialog.set_size_request(500, 350)
420                 self.sync_dialog.vbox.show_all()
421
422         @gtk_toolbox.log_exception(_moduleLogger)
423         def sync_notes(self, widget = None, data = None):
424                 if self.sync_dialog is None:
425                         self._prepare_sync_dialog()
426                 self.sync_dialog.run()
427                 self.sync_dialog.hide()
428
429         @gtk_toolbox.log_exception(_moduleLogger)
430         def _on_settings(self, *args):
431                 if self.__settingsWindow is None:
432                         vbox = gtk.VBox()
433                         self.__settingsManager = settings.SettingsDialog(vbox, self.db, self.liststorehandler)
434
435                         self.__settingsWindow = gtk.Window()
436                         self.__settingsWindow.add(vbox)
437                         self.__settingsWindow = hildonize.hildonize_window(self, self.__settingsWindow)
438
439                         self.__settingsWindow.set_title(_("Settings"))
440                         self.__settingsWindow.set_transient_for(self.window)
441                         self.__settingsWindow.set_default_size(*self.window.get_size())
442                         self.__settingsWindow.connect("delete-event", self._on_settings_delete)
443                 self.__settingsManager.set_portrait_state(self.__isPortrait)
444                 self.__settingsWindow.set_modal(True)
445                 self.__settingsWindow.show_all()
446
447         @gtk_toolbox.log_exception(_moduleLogger)
448         def _on_settings_delete(self, *args):
449                 self.__settingsWindow.emit_stop_by_name("delete-event")
450                 self.__settingsWindow.hide()
451                 self.__settingsWindow.set_modal(False)
452
453                 logging.info("changing columns")
454                 self.__settingsManager.save(self.db)
455                 self.view.reload_view()
456
457                 isPortrait = self.__settingsManager.is_portrait()
458                 if isPortrait ^ self.__isPortrait:
459                         if isPortrait:
460                                 orientation = gtk.ORIENTATION_VERTICAL
461                         else:
462                                 orientation = gtk.ORIENTATION_HORIZONTAL
463                         self.set_orientation(orientation)
464
465                 return True
466
467         @gtk_toolbox.log_exception(_moduleLogger)
468         def _on_destroy(self, widget = None, data = None):
469                 try:
470                         self.db.close()
471                         self._save_settings()
472
473                         try:
474                                 self._osso_c.close()
475                         except AttributeError:
476                                 pass # Either None or close was removed (in Fremantle)
477                 finally:
478                         gtk.main_quit()
479
480         @gtk_toolbox.log_exception(_moduleLogger)
481         def _on_delete_event(self, widget, event, data = None):
482                 return False
483
484         @gtk_toolbox.log_exception(_moduleLogger)
485         def _on_view_sql_history(self, widget = None, data = None, data2 = None):
486                 sqldiag = sqldialog.SqlDialog(self.db)
487                 res = sqldiag.run()
488                 sqldiag.hide()
489
490                 try:
491                         if res != gtk.RESPONSE_OK:
492                                 return
493                         logging.info("exporting sql")
494
495                         if not isHildon:
496                                 dlg = gtk.FileChooserDialog(
497                                         parent = self.window,
498                                         action = gtk.FILE_CHOOSER_ACTION_SAVE
499                                 )
500                                 dlg.add_button( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
501                                 dlg.add_button( gtk.STOCK_OK, gtk.RESPONSE_OK)
502                         else:
503                                 dlg = hildon.FileChooserDialog(self.window, gtk.FILE_CHOOSER_ACTION_SAVE)
504
505                         dlg.set_title(_("Select SQL export file"))
506                         exportFileResponse = dlg.run()
507                         try:
508                                 if exportFileResponse == gtk.RESPONSE_OK:
509                                         fileName = dlg.get_filename()
510                                         sqldiag.exportSQL(fileName)
511                         finally:
512                                 dlg.destroy()
513                 finally:
514                         sqldiag.destroy()
515
516         @gtk_toolbox.log_exception(_moduleLogger)
517         def _on_optimize_sql(self, widget = None, data = None, data2 = None):
518                 #optimiere sql
519                 self.db.speichereSQL("VACUUM", log = False)
520
521         @gtk_toolbox.log_exception(_moduleLogger)
522         def _on_select_db(self, widget = None, data = None, data2 = None):
523                 dlg = hildon.FileChooserDialog(parent=self._window, action=gtk.FILE_CHOOSER_ACTION_SAVE)
524
525                 if self.db.ladeDirekt('datenbank'):
526                         dlg.set_filename(self.db.ladeDirekt('datenbank'))
527                 dlg.set_title(_("Choose your database file"))
528                 resp = dlg.run()
529                 try:
530                         if resp == gtk.RESPONSE_OK:
531                                 fileName = dlg.get_filename()
532                                 self.db.speichereDirekt('datenbank', fileName)
533                                 self.db.openDB()
534                 finally:
535                         dlg.destroy()
536
537         @gtk_toolbox.log_exception(_moduleLogger)
538         def _on_about(self, widget = None, data = None):
539                 dialog = gtk.AboutDialog()
540                 dialog.set_position(gtk.WIN_POS_CENTER)
541                 dialog.set_name(constants.__pretty_app_name__)
542                 dialog.set_version(constants.__version__)
543                 dialog.set_copyright("")
544                 dialog.set_website("http://axique.de/f = Multilist")
545                 comments = "%s is a program to handle multiple lists." % constants.__pretty_app_name__
546                 dialog.set_comments(comments)
547                 dialog.set_authors(["Christoph Wurstle <n800@axique.net>", "Ed Page <eopage@byu.net> (Blame him for the most recent bugs)"])
548                 dialog.run()
549                 dialog.destroy()
550
551
552 def run_multilist():
553         if hildonize.IS_HILDON_SUPPORTED:
554                 gtk.set_application_name(constants.__pretty_app_name__)
555         app = Multilist()
556         if not PROFILE_STARTUP:
557                 gtk.main()
558
559
560 if __name__ == "__main__":
561         logging.basicConfig(level = logging.DEBUG)
562         run_multilist()