Removign some @todo's and making the history status label default value make more...
[quicknote] / src / libquicknote.py
1 #/usr/bin/env python2.5
2 # -*- coding: utf-8 -*-
3
4 """
5  Copyright (C) 2007 Christoph Würstle
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License version 2 as
9 published by the Free Software Foundation.
10
11 @todo Add Note Export (txt File) and Export All (json dump?)
12 @todo Save word wrap and zoom setting 
13 """
14
15
16 import os
17 import gc
18 import logging
19
20 import gtk
21
22 try:
23         import hildon
24         IS_HILDON = True
25 except ImportError:
26         import fakehildon as hildon
27         IS_HILDON = False
28
29 try:
30         import osso
31 except ImportError:
32         osso = None
33
34 import libspeichern
35 import libkopfzeile
36 import libnotizen
37 import libsync
38
39
40 try:
41         _
42 except NameError:
43         _ = lambda x: x
44
45
46 class QuicknoteProgram(hildon.Program):
47
48         __pretty_app_name__ = "quicknote"
49         __app_name__ = "quicknote"
50         __version__ = "0.7.7"
51
52         def __init__(self):
53                 super(QuicknoteProgram, self).__init__()
54
55                 home_dir = os.path.expanduser('~')
56                 dblog = os.path.join(home_dir, "quicknote.log")
57
58                 # define a Handler which writes INFO messages or higher to the sys.stderr
59                 console = logging.StreamHandler()
60                 console.setLevel(logging.DEBUG)
61                 # set a format which is simpler for console use
62                 formatter = logging.Formatter('%(asctime)s  %(levelname)-8s %(message)s')
63                 # tell the handler to use this format
64                 console.setFormatter(formatter)
65                 # add the handler to the root logger
66                 logging.getLogger('').addHandler(console)
67
68                 logging.info('Starting quicknote')
69
70                 if osso is not None:
71                         self._osso_c = osso.Context(self.__app_name__, self.__version__, False)
72                         self._deviceState = osso.DeviceState(self._osso_c)
73                         self._deviceState.set_device_state_callback(self._on_device_state_change, 0)
74                 else:
75                         self._osso_c = None
76                         self._deviceState = None
77
78                 #Get the Main Window, and connect the "destroy" event
79                 self._window = hildon.Window()
80                 self.add_window(self._window)
81
82                 self._window.set_title(self.__pretty_app_name__)
83                 self._window.connect("delete_event", self._on_delete_event)
84                 self._window.connect("destroy", self._on_destroy)
85                 self._window.connect("key-press-event", self._on_key_press)
86                 self._window.connect("window-state-event", self._on_window_state_change)
87                 self._window_in_fullscreen = False #The window isn't in full screen mode initially.
88
89                 self._db = libspeichern.Speichern()
90                 self._syncDialog = None
91                 self._prepare_sync_dialog()
92
93                 #Create GUI main vbox
94                 vbox = gtk.VBox(homogeneous = False, spacing = 0)
95
96                 #Create Menu and apply it for hildon
97                 filemenu = gtk.Menu()
98
99                 menu_items = gtk.MenuItem(_("Set DB file"))
100                 filemenu.append(menu_items)
101                 menu_items.connect("activate", self.set_db_file, None)
102
103                 menu_items = gtk.MenuItem(_("SQL History"))
104                 filemenu.append(menu_items)
105                 menu_items.connect("activate", self._on_view_sql_history, None)
106
107                 menu_items = gtk.MenuItem(_("Sync notes"))
108                 filemenu.append(menu_items)
109                 menu_items.connect("activate", self._on_sync_notes, None)
110
111                 menu_items = gtk.MenuItem(_("Quit"))
112                 filemenu.append(menu_items)
113                 menu_items.connect("activate", self._on_destroy, None)
114
115                 file_menu = gtk.MenuItem(_("File"))
116                 file_menu.show()
117                 file_menu.set_submenu(filemenu)
118
119                 categorymenu = gtk.Menu()
120
121                 menu_items = gtk.MenuItem(_("delete"))
122                 categorymenu.append(menu_items)
123                 menu_items.connect("activate", self._on_delete_category, None)
124
125                 menu_items = gtk.MenuItem(_("move to category"))
126                 categorymenu.append(menu_items)
127                 menu_items.connect("activate", self._on_move_category, None)
128
129                 category_menu = gtk.MenuItem(_("Category"))
130                 category_menu.show()
131                 category_menu.set_submenu(categorymenu)
132
133                 viewmenu = gtk.Menu()
134
135                 menu_items = gtk.MenuItem(_("Word Wrap"))
136                 viewmenu.append(menu_items)
137                 menu_items.connect("activate", self._on_toggle_word_wrap, None)
138                 self._wordWrapEnabled = False
139
140                 view_menu = gtk.MenuItem(_("View"))
141                 view_menu.show()
142                 view_menu.set_submenu(viewmenu)
143
144                 helpmenu = gtk.Menu()
145
146                 menu_items = gtk.MenuItem(_("About"))
147                 helpmenu.append(menu_items)
148                 menu_items.connect("activate", self._on_show_about, None)
149
150                 help_menu = gtk.MenuItem(_("Help"))
151                 help_menu.show()
152                 help_menu.set_submenu(helpmenu)
153
154                 menu_bar = gtk.MenuBar()
155                 menu_bar.show()
156                 menu_bar.append (file_menu)
157                 menu_bar.append (category_menu)
158                 menu_bar.append (view_menu)
159                 menu_bar.append (help_menu)
160
161                 menu_bar.show()
162                 if IS_HILDON:
163                         menu = gtk.Menu()
164                         for child in menu_bar.get_children():
165                                 child.reparent(menu)
166                         self._window.set_menu(menu)
167                         menu_bar.destroy()
168                 else:
169                         vbox.pack_start(menu_bar, False, False, 0)
170
171                 #Create GUI elements
172                 self._topBox = libkopfzeile.Kopfzeile(self._db)
173                 vbox.pack_start(self._topBox, False, False, 0)
174
175                 self._notizen = libnotizen.Notizen(self._db, self._topBox)
176                 vbox.pack_start(self._notizen, True, True, 0)
177
178                 self._window.add(vbox)
179                 self._window.show_all()
180                 self._on_toggle_word_wrap()
181
182         def main(self):
183                 gtk.main()
184
185         def set_db_file(self, widget = None, data = None):
186                 dlg = hildon.FileChooserDialog(parent=self._window, action=gtk.FILE_CHOOSER_ACTION_SAVE)
187
188                 if self._db.ladeDirekt('datenbank'):
189                         dlg.set_filename(self._db.ladeDirekt('datenbank'))
190
191                 dlg.set_title(_("Choose database file"))
192                 if dlg.run() == gtk.RESPONSE_OK:
193                         fileName = dlg.get_filename()
194                         self._db.speichereDirekt('datenbank', fileName)
195
196                         self._db.openDB()
197                         self._topBox.load_categories()
198                         self._notizen.load_notes()
199                         dlg.destroy()
200
201         def _prepare_sync_dialog(self):
202                 self._syncDialog = gtk.Dialog(_("Sync"), None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
203
204                 self._syncDialog.set_position(gtk.WIN_POS_CENTER)
205                 sync = libsync.Sync(self._db, self._window, 50504)
206                 self._syncDialog.vbox.pack_start(sync, True, True, 0)
207                 self._syncDialog.set_size_request(500, 350)
208                 self._syncDialog.vbox.show_all()
209                 sync.connect("syncFinished", self._on_sync_finished)
210
211         def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
212                 """
213                 For system_inactivity, we have no background tasks to pause
214
215                 @note Hildon specific
216                 """
217                 if memory_low:
218                         gc.collect()
219
220                 if save_unsaved_data or shutdown:
221                         pass
222
223         def _on_window_state_change(self, widget, event, *args):
224                 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
225                         self._window_in_fullscreen = True
226                 else:
227                         self._window_in_fullscreen = False
228
229         def _on_key_press(self, widget, event, *args):
230                 if event.keyval == gtk.keysyms.F6:
231                         # The "Full screen" hardware key has been pressed 
232                         if self._window_in_fullscreen:
233                                 self._window.unfullscreen ()
234                         else:
235                                 self._window.fullscreen ()
236                 elif event.keyval == gtk.keysyms.F7:
237                         # Zoom In
238                         self._topBox.hide()
239                         self._notizen.show_history_area(False)
240                 elif event.keyval == gtk.keysyms.F8:
241                         # Zoom Out
242                         self._topBox.show()
243                         self._notizen.show_history_area(True)
244
245         def _on_view_sql_history(self, widget = None, data = None, data2 = None):
246                 import libsqldialog
247                 sqldiag = libsqldialog.sqlDialog(self._db)
248                 res = sqldiag.run()
249                 sqldiag.hide()
250                 if res == 444:
251                         logging.info("exporting sql")
252
253                         dlg = hildon.FileChooserDialog(parent=self._window, action=gtk.FILE_CHOOSER_ACTION_SAVE)
254
255                         dlg.set_title(_("Select SQL export file"))
256                         if dlg.run() == gtk.RESPONSE_OK:
257                                 fileName = dlg.get_filename()
258                                 dlg.destroy()
259                                 sqldiag.exportSQL(fileName)
260                         else:
261                                 dlg.destroy()
262
263                 sqldiag.destroy()
264
265         def _on_move_category(self, widget = None, data = None):
266                 dialog = gtk.Dialog(_("Choose category"), self._window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
267
268                 dialog.set_position(gtk.WIN_POS_CENTER)
269                 comboCategory = gtk.combo_box_new_text()
270
271                 comboCategory.append_text('undefined')
272                 sql = "SELECT id, liste FROM categories WHERE id = 0 ORDER BY liste"
273                 rows = self._db.ladeSQL(sql)
274                 for row in rows:
275                         comboCategory.append_text(row[1])
276
277                 dialog.vbox.pack_start(comboCategory, True, True, 0)
278
279                 dialog.vbox.show_all()
280                 #dialog.set_size_request(400, 300)
281
282                 if dialog.run() == gtk.RESPONSE_ACCEPT:
283                         n = comboCategory.get_active()
284                         if -1 < n and self._notizen.noteId != -1:
285                                 model = comboCategory.get_model()
286                                 active = comboCategory.get_active()
287                                 if active < 0:
288                                         return None
289                                 cat_id = model[active][0]
290
291                                 noteid, category, note = self._db.loadNote(self._notizen.noteId)
292                                 #print noteid, category, cat_id
293                                 self._db.saveNote(noteid, note, cat_id, pcdatum = None)
294                                 self._topBox.category_combo_changed()
295                         else:
296                                 mbox = gtk.MessageDialog(self._window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("No note selected."))
297                                 response = mbox.run()
298                                 mbox.hide()
299                                 mbox.destroy()
300
301                 dialog.destroy()
302
303         def _on_delete_category(self, widget = None, data = None):
304                 if self._topBox.get_category() == "%" or self._topBox.get_category() == "undefined":
305                         mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("This category can not be deleted"))
306                         response = mbox.run()
307                         mbox.hide()
308                         mbox.destroy()
309                         return
310
311                 mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO, _("Are you sure to delete the current category?"))
312                 response = mbox.run()
313                 mbox.hide()
314                 mbox.destroy()
315                 if response == gtk.RESPONSE_YES:
316                         sql = "UPDATE notes SET category = ? WHERE category = ?"
317                         self._db.speichereSQL(sql, ("undefined", self._topBox.get_category()))
318                         sql = "DELETE FROM categories WHERE liste = ?"
319                         self._db.speichereSQL(sql, (self._topBox.get_category(), ))
320                         model = self._topBox.categoryCombo.get_model()
321                         pos = self._topBox.categoryCombo.get_active()
322                         if (pos>1):
323                                 self._topBox.categoryCombo.remove_text(pos)
324                                 self._topBox.categoryCombo.set_active(0)
325
326         def _on_sync_finished(self, data = None, data2 = None):
327                 self._topBox.load_categories()
328                 self._notizen.load_notes()
329
330         def _on_sync_notes(self, widget = None, data = None):
331                 self._syncDialog.run()
332                 self._syncDialog.hide()
333
334         def _on_toggle_word_wrap(self, *args):
335                 self._wordWrapEnabled = not self._wordWrapEnabled
336                 self._notizen.set_wordwrap(self._wordWrapEnabled)
337
338         def _on_delete_event(self, widget, event, data = None):
339                 return False
340
341         def _on_destroy(self, widget = None, data = None):
342                 self._db.close()
343                 if self._osso_c:
344                         self._osso_c.close()
345                 gtk.main_quit()
346
347         def _on_show_about(self, widget = None, data = None):
348                 dialog = gtk.AboutDialog()
349                 dialog.set_position(gtk.WIN_POS_CENTER)
350                 dialog.set_name(self.__pretty_app_name__)
351                 dialog.set_version(self.__version__)
352                 dialog.set_copyright("")
353                 dialog.set_website("http://axique.de/index.php?f=Quicknote")
354                 comments = _("%s is a note taking program; it is optimised for quick save and search of notes") % self.__pretty_app_name__
355                 dialog.set_comments(comments)
356                 dialog.run()
357                 dialog.destroy()