Fixing Maemo 5 support
[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
28 import gtk
29
30 try:
31         import hildon
32         isHildon = True
33 except:
34         isHildon = False
35
36 try:
37         import osso
38 except ImportError:
39         osso = None
40
41 import constants
42 import hildonize
43 import gtk_toolbox
44
45 import libspeichern
46 import search
47 import sqldialog
48 import settings
49 import libselection
50 import libview
51 import libliststorehandler
52 import libsync
53 import libbottombar
54
55 try:
56         _
57 except NameError:
58         _ = lambda x: x
59
60
61 _moduleLogger = logging.getLogger(__name__)
62 PROFILE_STARTUP = False
63
64
65 class Multilist(hildonize.get_app_class()):
66
67         _user_data = os.path.join(os.path.expanduser("~"), ".%s" % constants.__app_name__)
68         _user_settings = "%s/settings.ini" % _user_data
69
70         def __init__(self):
71                 super(Multilist, self).__init__()
72                 self._clipboard = gtk.clipboard_get()
73
74                 logging.info('Starting Multilist')
75
76                 try:
77                         os.makedirs(self._user_data)
78                 except OSError, e:
79                         if e.errno != 17:
80                                 raise
81
82                 self.db = libspeichern.Speichern()
83                 self.window_in_fullscreen = False #The window isn't in full screen mode initially.
84
85                 #Haupt vbox für alle Elemente
86                 self.window = gtk.Window()
87                 self.vbox = gtk.VBox(homogeneous = False, spacing = 0)
88
89                 self.selection = libselection.Selection(self.db, isHildon)
90                 self._search = search.Search()
91                 self.liststorehandler = libliststorehandler.Liststorehandler(self.db, self.selection)
92                 self.view = libview.View(self.db, self.liststorehandler, self.window)
93                 self.bottombar = libbottombar.Bottombar(self.db, self.view, isHildon)
94
95                 #Menue
96                 if hildonize.GTK_MENU_USED:
97                         fileMenu = gtk.Menu()
98
99                         menu_items = gtk.MenuItem(_("Choose database file"))
100                         menu_items.connect("activate", self._on_select_db, None)
101                         fileMenu.append(menu_items)
102
103                         menu_items = gtk.MenuItem(_("SQL history"))
104                         menu_items.connect("activate", self._on_view_sql_history, None)
105                         fileMenu.append(menu_items)
106
107                         menu_items = gtk.MenuItem(_("SQL optimize"))
108                         menu_items.connect("activate", self._on_optimize_sql, None)
109                         fileMenu.append(menu_items)
110
111                         menu_items = gtk.MenuItem(_("Sync items"))
112                         menu_items.connect("activate", self.sync_notes, None)
113                         fileMenu.append(menu_items)
114
115                         menu_items = gtk.MenuItem(_("Quit"))
116                         menu_items.connect("activate", self._on_destroy, None)
117                         fileMenu.append(menu_items)
118
119                         fileMenuItem = gtk.MenuItem(_("File"))
120                         fileMenuItem.show()
121                         fileMenuItem.set_submenu(fileMenu)
122
123                         listmenu = gtk.Menu()
124
125                         menu_items = gtk.MenuItem(_("Search"))
126                         menu_items.connect("activate", self._on_toggle_search)
127                         listmenu.append(menu_items)
128
129                         menu_items = gtk.MenuItem(_("Checkout All"))
130                         menu_items.connect("activate", self._on_checkout_all)
131                         listmenu.append(menu_items)
132
133                         menu_items = gtk.MenuItem(_("Rename List"))
134                         menu_items.connect("activate", self.bottombar.rename_list, None)
135                         listmenu.append(menu_items)
136
137                         menu_items = gtk.MenuItem(_("Rename Category"))
138                         menu_items.connect("activate", self.bottombar.rename_category, None)
139                         listmenu.append(menu_items)
140
141                         listMenuItem = gtk.MenuItem(_("List"))
142                         listMenuItem.show()
143                         listMenuItem.set_submenu(listmenu)
144
145                         viewMenu = gtk.Menu()
146
147                         menu_items = gtk.MenuItem(_("Show Active"))
148                         menu_items.connect("activate", self._on_toggle_filter, None)
149                         viewMenu.append(menu_items)
150
151                         menu_items = gtk.MenuItem(_("Settings"))
152                         menu_items.connect("activate", self._on_settings, None)
153                         viewMenu.append(menu_items)
154
155                         viewMenuItem = gtk.MenuItem(_("View"))
156                         viewMenuItem.show()
157                         viewMenuItem.set_submenu(viewMenu)
158
159                         helpMenu = gtk.Menu()
160                         menu_items = gtk.MenuItem(_("About"))
161                         helpMenu.append(menu_items)
162                         menu_items.connect("activate", self._on_about, None)
163
164                         helpMenuItem = gtk.MenuItem(_("Help"))
165                         helpMenuItem.show()
166                         helpMenuItem.set_submenu(helpMenu)
167
168                         menuBar = gtk.MenuBar()
169                         menuBar.show()
170                         menuBar.append (fileMenuItem)
171                         menuBar.append (listMenuItem)
172                         menuBar.append (viewMenuItem)
173                         # unten -> damit als letztes menuBar.append (helpMenuItem)
174                         #Als letztes menü
175                         menuBar.append (helpMenuItem)
176
177                         self.vbox.pack_start(menuBar, False, False, 0)
178                 else:
179                         menuBar = gtk.MenuBar()
180                         menuBar.show()
181                         self.vbox.pack_start(menuBar, False, False, 0)
182
183                 #add to vbox below (to get it on top)
184                 self.vbox.pack_end(self._search, expand = False, fill = True)
185                 self.vbox.pack_end(self.bottombar, expand = False, fill = True, padding = 0)
186                 self.vbox.pack_end(self.view, expand = True, fill = True, padding = 0)
187                 self.vbox.pack_end(self.selection, expand = False, fill = True, padding = 0)
188
189                 #Get the Main Window, and connect the "destroy" event
190                 self.window.add(self.vbox)
191
192                 self.window = hildonize.hildonize_window(self, self.window)
193                 hildonize.set_application_title(self.window, "%s" % constants.__pretty_app_name__)
194                 menuBar = hildonize.hildonize_menu(
195                         self.window,
196                         menuBar,
197                 )
198                 if hildonize.IS_FREMANTLE_SUPPORTED:
199                         button = hildonize.hildon.GtkRadioButton(gtk.HILDON_SIZE_AUTO, None)
200                         button.set_label("All")
201                         menuBar.add_filter(button)
202                         button.connect("clicked", self._on_click_menu_filter, self.liststorehandler.SHOW_ALL)
203                         button.set_mode(False)
204                         filterGroup = button
205
206                         button = hildonize.hildon.GtkRadioButton(gtk.HILDON_SIZE_AUTO, filterGroup)
207                         button.set_label("Active")
208                         menuBar.add_filter(button)
209                         button.connect("clicked", self._on_click_menu_filter, self.liststorehandler.SHOW_ACTIVE)
210                         button.set_mode(False)
211
212                         renameListButton= gtk.Button(_("Rename List"))
213                         renameListButton.connect("clicked", self.bottombar.rename_list)
214                         menuBar.append(renameListButton)
215
216                         renameCategoryButton = gtk.Button(_("Rename Category"))
217                         renameCategoryButton.connect("clicked", self.bottombar.rename_category)
218                         menuBar.append(renameCategoryButton)
219
220                         searchButton= gtk.Button(_("Search Category"))
221                         searchButton.connect("clicked", self._on_toggle_search)
222                         menuBar.append(searchButton)
223
224                         searchButton= gtk.Button(_("Settings"))
225                         searchButton.connect("clicked", self._on_settings)
226                         menuBar.append(searchButton)
227
228                         menuBar.show_all()
229
230                 if not hildonize.IS_HILDON_SUPPORTED:
231                         _moduleLogger.info("No hildonization support")
232
233                 if osso is not None:
234                         self.osso_c = osso.Context(
235                                 constants.__app_name__,
236                                 constants.__version__,
237                                 False
238                         )
239                 else:
240                         _moduleLogger.info("No osso support")
241                         self._osso_c = None
242
243                 self.window.connect("delete_event", self._on_delete_event)
244                 self.window.connect("destroy", self._on_destroy)
245                 self.window.connect("key-press-event", self.on_key_press)
246                 self.window.connect("window-state-event", self._on_window_state_change)
247                 self._search.connect("search_changed", self._on_search)
248
249                 self.window.show_all()
250                 self._search.hide()
251                 self._prepare_sync_dialog()
252
253         def _toggle_search(self):
254                 if self._search.get_property("visible"):
255                         self._search.hide()
256                 else:
257                         self._search.show()
258
259         @gtk_toolbox.log_exception(_moduleLogger)
260         def _on_checkout_all(self, widget):
261                 self.liststorehandler.checkout_rows()
262
263         @gtk_toolbox.log_exception(_moduleLogger)
264         def _on_search(self, widget):
265                 self.liststorehandler.get_liststore(self._search.get_search_pattern())
266
267         @gtk_toolbox.log_exception(_moduleLogger)
268         def _on_click_menu_filter(self, button, val):
269                 self.liststorehandler.set_filter(val)
270
271         @gtk_toolbox.log_exception(_moduleLogger)
272         def _on_toggle_search(self, *args):
273                 self._toggle_search()
274
275         @gtk_toolbox.log_exception(_moduleLogger)
276         def _on_toggle_filter(self, *args):
277                 if self.liststorehandler.get_filter() == self.liststorehandler.SHOW_ALL:
278                         self.liststorehandler.set_filter(self.liststorehandler.SHOW_ACTIVE)
279                 elif self.liststorehandler.get_filter() == self.liststorehandler.SHOW_ACTIVE:
280                         self.liststorehandler.set_filter(self.liststorehandler.SHOW_ALL)
281                 else:
282                         assert False, "Unknown"
283
284         @gtk_toolbox.log_exception(_moduleLogger)
285         def on_key_press(self, widget, event, *args):
286                 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
287                 isCtrl = bool(event.get_state() & gtk.gdk.CONTROL_MASK)
288                 if (
289                         event.keyval == gtk.keysyms.F6 or
290                         event.keyval in RETURN_TYPES and isCtrl
291                 ):
292                         # The "Full screen" hardware key has been pressed 
293                         if self.window_in_fullscreen:
294                                 self.window.unfullscreen ()
295                         else:
296                                 self.window.fullscreen ()
297                         return True
298                 elif event.keyval == gtk.keysyms.f and isCtrl:
299                         self._toggle_search()
300                         return True
301                 elif (
302                         event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
303                         event.get_state() & gtk.gdk.CONTROL_MASK
304                 ):
305                         self.window.destroy()
306                 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
307                         with open(constants._user_logpath_, "r") as f:
308                                 logLines = f.xreadlines()
309                                 log = "".join(logLines)
310                                 self._clipboard.set_text(str(log))
311                         return True
312
313         @gtk_toolbox.log_exception(_moduleLogger)
314         def _on_window_state_change(self, widget, event, *args):
315                 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
316                         self.window_in_fullscreen = True
317                 else:
318                         self.window_in_fullscreen = False
319
320         @gtk_toolbox.log_exception(_moduleLogger)
321         def _on_sync_finished(self, data = None, data2 = None):
322                 self.selection.comboList_changed()
323                 self.selection.comboCategory_changed()
324                 self.liststorehandler.update_list()
325
326         def _prepare_sync_dialog(self):
327                 self.sync_dialog = gtk.Dialog(_("Sync"), None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
328
329                 self.sync_dialog.set_position(gtk.WIN_POS_CENTER)
330                 sync = libsync.Sync(self.db, self.window, 50503)
331                 sync.connect("syncFinished", self._on_sync_finished)
332                 self.sync_dialog.vbox.pack_start(sync, True, True, 0)
333                 self.sync_dialog.set_size_request(500, 350)
334                 self.sync_dialog.vbox.show_all()
335
336         @gtk_toolbox.log_exception(_moduleLogger)
337         def sync_notes(self, widget = None, data = None):
338                 if self.sync_dialog is None:
339                         self._prepare_sync_dialog()
340                 self.sync_dialog.run()
341                 self.sync_dialog.hide()
342
343         @gtk_toolbox.log_exception(_moduleLogger)
344         def _on_settings(self, *args):
345                 col_dialog = gtk.Dialog(
346                         _("Settings"),
347                         self.window,
348                         gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
349                         (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
350                 )
351
352                 cols = settings.SettingsDialog(col_dialog.vbox, self.db, self.liststorehandler)
353                 col_dialog.show_all()
354
355                 resp = col_dialog.run()
356                 try:
357                         col_dialog.hide()
358                         if resp == gtk.RESPONSE_ACCEPT:
359                                 logging.info("changing columns")
360                                 cols.save(self.db)
361                                 self.view.reload_view()
362                 finally:
363                         col_dialog.destroy()
364
365         @gtk_toolbox.log_exception(_moduleLogger)
366         def _on_destroy(self, widget = None, data = None):
367                 try:
368                         self.db.close()
369                         try:
370                                 self._osso_c.close()
371                         except AttributeError:
372                                 pass # Either None or close was removed (in Fremantle)
373                 finally:
374                         gtk.main_quit()
375
376         @gtk_toolbox.log_exception(_moduleLogger)
377         def _on_delete_event(self, widget, event, data = None):
378                 return False
379
380         @gtk_toolbox.log_exception(_moduleLogger)
381         def _on_view_sql_history(self, widget = None, data = None, data2 = None):
382                 sqldiag = sqldialog.SqlDialog(self.db)
383                 res = sqldiag.run()
384                 sqldiag.hide()
385
386                 try:
387                         if res != gtk.RESPONSE_OK:
388                                 return
389                         logging.info("exporting sql")
390
391                         if not isHildon:
392                                 dlg = gtk.FileChooserDialog(
393                                         parent = self.window,
394                                         action = gtk.FILE_CHOOSER_ACTION_SAVE
395                                 )
396                                 dlg.add_button( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
397                                 dlg.add_button( gtk.STOCK_OK, gtk.RESPONSE_OK)
398                         else:
399                                 dlg = hildon.FileChooserDialog(self.window, gtk.FILE_CHOOSER_ACTION_SAVE)
400
401                         dlg.set_title(_("Select SQL export file"))
402                         exportFileResponse = dlg.run()
403                         try:
404                                 if exportFileResponse == gtk.RESPONSE_OK:
405                                         fileName = dlg.get_filename()
406                                         sqldiag.exportSQL(fileName)
407                         finally:
408                                 dlg.destroy()
409                 finally:
410                         sqldiag.destroy()
411
412         @gtk_toolbox.log_exception(_moduleLogger)
413         def _on_optimize_sql(self, widget = None, data = None, data2 = None):
414                 #optimiere sql
415                 self.db.speichereSQL("VACUUM", log = False)
416
417         @gtk_toolbox.log_exception(_moduleLogger)
418         def _on_select_db(self, widget = None, data = None, data2 = None):
419                 dlg = hildon.FileChooserDialog(parent=self._window, action=gtk.FILE_CHOOSER_ACTION_SAVE)
420
421                 if self.db.ladeDirekt('datenbank'):
422                         dlg.set_filename(self.db.ladeDirekt('datenbank'))
423                 dlg.set_title(_("Choose your database file"))
424                 resp = dlg.run()
425                 try:
426                         if resp == gtk.RESPONSE_OK:
427                                 fileName = dlg.get_filename()
428                                 self.db.speichereDirekt('datenbank', fileName)
429                                 self.db.openDB()
430                 finally:
431                         dlg.destroy()
432
433         @gtk_toolbox.log_exception(_moduleLogger)
434         def _on_about(self, widget = None, data = None):
435                 dialog = gtk.AboutDialog()
436                 dialog.set_position(gtk.WIN_POS_CENTER)
437                 dialog.set_name(constants.__pretty_app_name__)
438                 dialog.set_version(constants.__version__)
439                 dialog.set_copyright("")
440                 dialog.set_website("http://axique.de/f = Multilist")
441                 comments = "%s is a program to handle multiple lists." % constants.__pretty_app_name__
442                 dialog.set_comments(comments)
443                 dialog.set_authors(["Christoph Wurstle <n800@axique.net>", "Ed Page <eopage@byu.net> (Blame him for the most recent bugs)"])
444                 dialog.run()
445                 dialog.destroy()
446
447
448 def run_multilist():
449         if hildonize.IS_HILDON_SUPPORTED:
450                 gtk.set_application_name(constants.__pretty_app_name__)
451         app = Multilist()
452         if not PROFILE_STARTUP:
453                 gtk.main()
454
455
456 if __name__ == "__main__":
457         logging.basicConfig(level = logging.DEBUG)
458         run_multilist()