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