Adding settings and cleanup
authorepage <eopage@byu.net>
Sat, 30 May 2009 22:11:24 +0000 (22:11 +0000)
committerepage <eopage@byu.net>
Sat, 30 May 2009 22:11:24 +0000 (22:11 +0000)
git-svn-id: file:///svnroot/quicknote/trunk@49 bb7704e3-badb-4cfa-9ab3-9374dc87eaa2

Makefile
src/__init__.py
src/constants.py [new file with mode: 0644]
src/gtk_toolbox.py [new file with mode: 0644]
src/libnotizen.py
src/libquicknote.py
support/builddeb.py
support/pylint.rc

index 03aed86..240fca6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 PROJECT_NAME=quicknote
-PROJECT_VERSION=0.7.4
+PROJECT_VERSION=0.7.7
 SOURCE_PATH=src
 SOURCE=$(shell find $(SOURCE_PATH) -iname "*.py")
 LOCALE_PATH=locale
index 8b13789..4265cc3 100755 (executable)
@@ -1 +1 @@
-
+#!/usr/bin/env python
diff --git a/src/constants.py b/src/constants.py
new file mode 100644 (file)
index 0000000..0e25a0d
--- /dev/null
@@ -0,0 +1,4 @@
+__pretty_app_name__ = "Quicknote"
+__app_name__ = "quicknote"
+__version__ = "0.7.7"
+__app_magic__ = 0xdeadbeef
diff --git a/src/gtk_toolbox.py b/src/gtk_toolbox.py
new file mode 100644 (file)
index 0000000..1490c3d
--- /dev/null
@@ -0,0 +1,254 @@
+#!/usr/bin/python
+
+from __future__ import with_statement
+
+import sys
+import traceback
+import functools
+import contextlib
+import warnings
+
+import gobject
+import gtk
+
+
+@contextlib.contextmanager
+def gtk_lock():
+       gtk.gdk.threads_enter()
+       try:
+               yield
+       finally:
+               gtk.gdk.threads_leave()
+
+
+def find_parent_window(widget):
+       while True:
+               parent = widget.get_parent()
+               if isinstance(parent, gtk.Window):
+                       return parent
+               widget = parent
+
+
+def make_idler(func):
+       """
+       Decorator that makes a generator-function into a function that will continue execution on next call
+       """
+       a = []
+
+       @functools.wraps(func)
+       def decorated_func(*args, **kwds):
+               if not a:
+                       a.append(func(*args, **kwds))
+               try:
+                       a[0].next()
+                       return True
+               except StopIteration:
+                       del a[:]
+                       return False
+
+       return decorated_func
+
+
+def asynchronous_gtk_message(original_func):
+       """
+       @note Idea came from http://www.aclevername.com/articles/python-webgui/
+       """
+
+       def execute(allArgs):
+               args, kwargs = allArgs
+               with gtk_lock():
+                       original_func(*args, **kwargs)
+               return False
+
+       @functools.wraps(original_func)
+       def delayed_func(*args, **kwargs):
+               gobject.idle_add(execute, (args, kwargs))
+
+       return delayed_func
+
+
+def synchronous_gtk_message(original_func):
+       """
+       @note Idea came from http://www.aclevername.com/articles/python-webgui/
+       """
+
+       @functools.wraps(original_func)
+       def immediate_func(*args, **kwargs):
+               with gtk_lock():
+                       return original_func(*args, **kwargs)
+
+       return immediate_func
+
+
+class ErrorDisplay(object):
+
+       def __init__(self, widgetTree):
+               super(ErrorDisplay, self).__init__()
+               self.__errorBox = widgetTree.get_widget("errorEventBox")
+               self.__errorDescription = widgetTree.get_widget("errorDescription")
+               self.__errorClose = widgetTree.get_widget("errorClose")
+               self.__parentBox = self.__errorBox.get_parent()
+
+               self.__errorBox.connect("button_release_event", self._on_close)
+
+               self.__messages = []
+               self.__parentBox.remove(self.__errorBox)
+
+       def push_message_with_lock(self, message):
+               with gtk_lock():
+                       self.push_message(message)
+
+       def push_message(self, message):
+               if 0 < len(self.__messages):
+                       self.__messages.append(message)
+               else:
+                       self.__show_message(message)
+
+       def push_exception_with_lock(self, exception = None):
+               with gtk_lock():
+                       self.push_exception(exception)
+
+       def push_exception(self, exception = None):
+               if exception is None:
+                       userMessage = str(sys.exc_value)
+                       warningMessage = str(traceback.format_exc())
+               else:
+                       userMessage = str(exception)
+                       warningMessage = str(exception)
+               self.push_message(userMessage)
+               warnings.warn(warningMessage, stacklevel=3)
+
+       def pop_message(self):
+               if 0 < len(self.__messages):
+                       self.__show_message(self.__messages[0])
+                       del self.__messages[0]
+               else:
+                       self.__hide_message()
+
+       def _on_close(self, *args):
+               self.pop_message()
+
+       def __show_message(self, message):
+               self.__errorDescription.set_text(message)
+               self.__parentBox.pack_start(self.__errorBox, False, False)
+               self.__parentBox.reorder_child(self.__errorBox, 1)
+
+       def __hide_message(self):
+               self.__errorDescription.set_text("")
+               self.__parentBox.remove(self.__errorBox)
+
+
+class DummyErrorDisplay(object):
+
+       def __init__(self):
+               super(DummyErrorDisplay, self).__init__()
+
+               self.__messages = []
+
+       def push_message_with_lock(self, message):
+               self.push_message(message)
+
+       def push_message(self, message):
+               if 0 < len(self.__messages):
+                       self.__messages.append(message)
+               else:
+                       self.__show_message(message)
+
+       def push_exception(self, exception = None):
+               if exception is None:
+                       warningMessage = traceback.format_exc()
+               else:
+                       warningMessage = exception
+               warnings.warn(warningMessage, stacklevel=3)
+
+       def pop_message(self):
+               if 0 < len(self.__messages):
+                       self.__show_message(self.__messages[0])
+                       del self.__messages[0]
+
+       def __show_message(self, message):
+               warnings.warn(message, stacklevel=2)
+
+
+class MessageBox(gtk.MessageDialog):
+
+       def __init__(self, message):
+               parent = None
+               gtk.MessageDialog.__init__(
+                       self,
+                       parent,
+                       gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
+                       gtk.MESSAGE_ERROR,
+                       gtk.BUTTONS_OK,
+                       message,
+               )
+               self.set_default_response(gtk.RESPONSE_OK)
+               self.connect('response', self._handle_clicked)
+
+       def _handle_clicked(self, *args):
+               self.destroy()
+
+
+class MessageBox2(gtk.MessageDialog):
+
+       def __init__(self, message):
+               parent = None
+               gtk.MessageDialog.__init__(
+                       self,
+                       parent,
+                       gtk.DIALOG_DESTROY_WITH_PARENT,
+                       gtk.MESSAGE_ERROR,
+                       gtk.BUTTONS_OK,
+                       message,
+               )
+               self.set_default_response(gtk.RESPONSE_OK)
+               self.connect('response', self._handle_clicked)
+
+       def _handle_clicked(self, *args):
+               self.destroy()
+
+
+class PopupCalendar(object):
+
+       def __init__(self, parent, displayDate, title = ""):
+               self._displayDate = displayDate
+
+               self._calendar = gtk.Calendar()
+               self._calendar.select_month(self._displayDate.month, self._displayDate.year)
+               self._calendar.select_day(self._displayDate.day)
+               self._calendar.set_display_options(
+                       gtk.CALENDAR_SHOW_HEADING |
+                       gtk.CALENDAR_SHOW_DAY_NAMES |
+                       gtk.CALENDAR_NO_MONTH_CHANGE |
+                       0
+               )
+               self._calendar.connect("day-selected", self._on_day_selected)
+
+               self._popupWindow = gtk.Window()
+               self._popupWindow.set_title(title)
+               self._popupWindow.add(self._calendar)
+               self._popupWindow.set_transient_for(parent)
+               self._popupWindow.set_modal(True)
+               self._popupWindow.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+               self._popupWindow.set_skip_pager_hint(True)
+               self._popupWindow.set_skip_taskbar_hint(True)
+
+       def run(self):
+               self._popupWindow.show_all()
+
+       def _on_day_selected(self, *args):
+               try:
+                       self._calendar.select_month(self._displayDate.month, self._displayDate.year)
+                       self._calendar.select_day(self._displayDate.day)
+               except StandardError, e:
+                       warnings.warn(e.message)
+
+
+if __name__ == "__main__":
+       if False:
+               import datetime
+               cal = PopupCalendar(None, datetime.datetime.now())
+               cal._popupWindow.connect("destroy", lambda w: gtk.main_quit())
+               cal.run()
+
+       gtk.main()
index a614d5e..a2edfda 100644 (file)
@@ -113,7 +113,7 @@ class Notizen(gtk.HBox):
        def load_notes(self, data = None):
                logging.info("load_notes params: pos:"+str(self._pos)+" noteid:"+str(self.noteId))
                self._noteslist.clear_items()
-               self._noteslist.append_item(_("new Note"), "new")
+               self._noteslist.append_item(_("New Note..."), "new")
 
                self._categoryName = self._topBox.get_category()
                search = self._topBox.get_search_pattern()
index dafd390..46253bf 100644 (file)
@@ -9,13 +9,17 @@ it under the terms of the GNU General Public License version 2 as
 published by the Free Software Foundation.
 
 @todo Add Note Export (txt File) and Export All (json dump?)
-@todo Save word wrap and zoom setting 
+@todo Remove confirmation on deleting empty notes
+@todo Try to switch to more passive notifications (rather than message boxes)
 """
 
+from __future__ import with_statement
 
 import os
 import gc
 import logging
+import warnings
+import ConfigParser
 
 import gtk
 
@@ -31,6 +35,8 @@ try:
 except ImportError:
        osso = None
 
+import constants
+
 import libspeichern
 import libkopfzeile
 import libnotizen
@@ -45,15 +51,13 @@ except NameError:
 
 class QuicknoteProgram(hildon.Program):
 
-       __pretty_app_name__ = "quicknote"
-       __app_name__ = "quicknote"
-       __version__ = "0.7.7"
+       _user_data = os.path.join(os.path.expanduser("~"), ".%s" % constants.__app_name__)
+       _user_settings = "%s/settings.ini" % _user_data
 
        def __init__(self):
                super(QuicknoteProgram, self).__init__()
 
-               home_dir = os.path.expanduser('~')
-               dblog = os.path.join(home_dir, "quicknote.log")
+               dblog = os.path.join(self._user_data, "quicknote.log")
 
                # define a Handler which writes INFO messages or higher to the sys.stderr
                console = logging.StreamHandler()
@@ -68,7 +72,7 @@ class QuicknoteProgram(hildon.Program):
                logging.info('Starting quicknote')
 
                if osso is not None:
-                       self._osso_c = osso.Context(self.__app_name__, self.__version__, False)
+                       self._osso_c = osso.Context(constants.__app_name__, constants.__version__, False)
                        self._deviceState = osso.DeviceState(self._osso_c)
                        self._deviceState.set_device_state_callback(self._on_device_state_change, 0)
                else:
@@ -79,12 +83,13 @@ class QuicknoteProgram(hildon.Program):
                self._window = hildon.Window()
                self.add_window(self._window)
 
-               self._window.set_title(self.__pretty_app_name__)
+               self._window.set_title(constants.__pretty_app_name__)
                self._window.connect("delete_event", self._on_delete_event)
                self._window.connect("destroy", self._on_destroy)
                self._window.connect("key-press-event", self._on_key_press)
                self._window.connect("window-state-event", self._on_window_state_change)
                self._window_in_fullscreen = False #The window isn't in full screen mode initially.
+               self._isZoomEnabled = False
 
                self._db = libspeichern.Speichern()
                self._syncDialog = None
@@ -174,14 +179,61 @@ class QuicknoteProgram(hildon.Program):
 
                self._notizen = libnotizen.Notizen(self._db, self._topBox)
                vbox.pack_start(self._notizen, True, True, 0)
-
                self._window.add(vbox)
-               self._window.show_all()
+
                self._on_toggle_word_wrap()
 
+               try:
+                       os.makedirs(self._user_data)
+               except OSError, e:
+                       if e.errno != 17:
+                               raise
+               self._window.show_all()
+               self._load_settings()
+
        def main(self):
                gtk.main()
 
+       def _save_settings(self):
+               config = ConfigParser.SafeConfigParser()
+               self.save_settings(config)
+               with open(self._user_settings, "wb") as configFile:
+                       config.write(configFile)
+
+       def save_settings(self, config):
+               config.add_section(constants.__pretty_app_name__)
+               config.set(constants.__pretty_app_name__, "wordwrap", str(self._wordWrapEnabled))
+               config.set(constants.__pretty_app_name__, "zoom", str(self._isZoomEnabled))
+               config.set(constants.__pretty_app_name__, "fullscreen", str(self._window_in_fullscreen))
+
+       def _load_settings(self):
+               config = ConfigParser.SafeConfigParser()
+               config.read(self._user_settings)
+               self.load_settings(config)
+
+       def load_settings(self, config):
+               try:
+                       self._wordWrapEnabled = config.getboolean(constants.__pretty_app_name__, "wordwrap")
+                       self._isZoomEnabled = config.getboolean(constants.__pretty_app_name__, "zoom")
+                       self._window_in_fullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
+               except ConfigParser.NoSectionError, e:
+                       warnings.warn(
+                               "Settings file %s is missing section %s" % (
+                                       self._user_settings,
+                                       e.section,
+                               ),
+                               stacklevel=2
+                       )
+
+               self._notizen.set_wordwrap(self._wordWrapEnabled)
+
+               self.enable_zoom(self._isZoomEnabled)
+
+               if self._window_in_fullscreen:
+                       self._window.fullscreen()
+               else:
+                       self._window.unfullscreen()
+
        def set_db_file(self, widget = None, data = None):
                dlg = hildon.FileChooserDialog(parent=self._window, action=gtk.FILE_CHOOSER_ACTION_SAVE)
 
@@ -196,7 +248,7 @@ class QuicknoteProgram(hildon.Program):
                        self._db.openDB()
                        self._topBox.load_categories()
                        self._notizen.load_notes()
-                       dlg.destroy()
+               dlg.destroy()
 
        def _prepare_sync_dialog(self):
                self._syncDialog = gtk.Dialog(_("Sync"), None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
@@ -208,6 +260,15 @@ class QuicknoteProgram(hildon.Program):
                self._syncDialog.vbox.show_all()
                sync.connect("syncFinished", self._on_sync_finished)
 
+       def enable_zoom(self, zoomEnabled):
+               self._isZoomEnabled = zoomEnabled
+               if zoomEnabled:
+                       self._topBox.hide()
+                       self._notizen.show_history_area(False)
+               else:
+                       self._topBox.show()
+                       self._notizen.show_history_area(True)
+
        def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
                """
                For system_inactivity, we have no background tasks to pause
@@ -218,7 +279,7 @@ class QuicknoteProgram(hildon.Program):
                        gc.collect()
 
                if save_unsaved_data or shutdown:
-                       pass
+                       self._save_settings()
 
        def _on_window_state_change(self, widget, event, *args):
                if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
@@ -235,12 +296,10 @@ class QuicknoteProgram(hildon.Program):
                                self._window.fullscreen ()
                elif event.keyval == gtk.keysyms.F7:
                        # Zoom In
-                       self._topBox.hide()
-                       self._notizen.show_history_area(False)
+                       self.enable_zoom(True)
                elif event.keyval == gtk.keysyms.F8:
                        # Zoom Out
-                       self._topBox.show()
-                       self._notizen.show_history_area(True)
+                       self.enable_zoom(False)
 
        def _on_view_sql_history(self, widget = None, data = None, data2 = None):
                import libsqldialog
@@ -255,10 +314,8 @@ class QuicknoteProgram(hildon.Program):
                        dlg.set_title(_("Select SQL export file"))
                        if dlg.run() == gtk.RESPONSE_OK:
                                fileName = dlg.get_filename()
-                               dlg.destroy()
                                sqldiag.exportSQL(fileName)
-                       else:
-                               dlg.destroy()
+                       dlg.destroy()
 
                sqldiag.destroy()
 
@@ -339,19 +396,22 @@ class QuicknoteProgram(hildon.Program):
                return False
 
        def _on_destroy(self, widget = None, data = None):
-               self._db.close()
-               if self._osso_c:
-                       self._osso_c.close()
-               gtk.main_quit()
+               try:
+                       self._save_settings()
+                       self._db.close()
+                       if self._osso_c:
+                               self._osso_c.close()
+               finally:
+                       gtk.main_quit()
 
        def _on_show_about(self, widget = None, data = None):
                dialog = gtk.AboutDialog()
                dialog.set_position(gtk.WIN_POS_CENTER)
-               dialog.set_name(self.__pretty_app_name__)
-               dialog.set_version(self.__version__)
+               dialog.set_name(constants.__pretty_app_name__)
+               dialog.set_version(constants.__version__)
                dialog.set_copyright("")
                dialog.set_website("http://axique.de/index.php?f=Quicknote")
-               comments = _("%s is a note taking program; it is optimised for quick save and search of notes") % self.__pretty_app_name__
+               comments = _("%s is a note taking program; it is optimised for quick save and search of notes") % constants.__pretty_app_name__
                dialog.set_comments(comments)
                dialog.run()
                dialog.destroy()
index 703bdc9..2f83338 100755 (executable)
@@ -7,18 +7,20 @@ try:
 except ImportError:
        import fake_py2deb as py2deb
 
+import constants
 
-__appname__ = "quicknote"
+__appname__ = constants.__app_name__
 __description__ = "Simple note taking application in a similar vein as PalmOS Memos"
 __author__ = "Christoph Wurstle"
 __email__ = "n800@axique.net"
-__version__ = "0.7.7"
+__version__ = constants.__version__
 __build__ = 0
 __changelog__ = '''
 0.7.7
  * Slight modifications to the note history and SQL dialogs
  * On zoom, also hiding the history status and button
  * Touched up the note list, making it ellipsize at the end rather than scroll
+ * Storing of zoom, wordwrap, and fullscreen settings
 
 0.7.6
   * Line-wrap
@@ -88,7 +90,7 @@ if __name__ == "__main__":
        p.arch = "all"
        p.urgency = "low"
        p.distribution = "chinook diablo"
-       p.repository = "extras-devel"
+       p.repository = "extras"
        p.changelog = __changelog__
        p.postinstall = __postinstall__
        p.icon = "26x26-quicknote.png"
index 37b9725..2a371a1 100644 (file)
@@ -53,7 +53,7 @@ load-plugins=
 #enable-msg=
 
 # Disable the message(s) with the given id(s).
-disable-msg=W0403,W0612,W0613,C0103,C0111,C0301,R0903,W0142,W0603,R0904
+disable-msg=W0403,W0612,W0613,C0103,C0111,C0301,R0903,W0142,W0603,R0904,R0921,R0201
 
 [REPORTS]