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