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