From: epage Date: Sat, 30 May 2009 22:11:24 +0000 (+0000) Subject: Adding settings and cleanup X-Git-Url: http://git.maemo.org/git/?p=quicknote;a=commitdiff_plain;h=592130d3af48b7837d96c3bea8e787d33ae3843c Adding settings and cleanup git-svn-id: file:///svnroot/quicknote/trunk@49 bb7704e3-badb-4cfa-9ab3-9374dc87eaa2 --- diff --git a/Makefile b/Makefile index 03aed86..240fca6 100644 --- 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 diff --git a/src/__init__.py b/src/__init__.py index 8b13789..4265cc3 100755 --- a/src/__init__.py +++ b/src/__init__.py @@ -1 +1 @@ - +#!/usr/bin/env python diff --git a/src/constants.py b/src/constants.py new file mode 100644 index 0000000..0e25a0d --- /dev/null +++ b/src/constants.py @@ -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 index 0000000..1490c3d --- /dev/null +++ b/src/gtk_toolbox.py @@ -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() diff --git a/src/libnotizen.py b/src/libnotizen.py index a614d5e..a2edfda 100644 --- a/src/libnotizen.py +++ b/src/libnotizen.py @@ -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() diff --git a/src/libquicknote.py b/src/libquicknote.py index dafd390..46253bf 100644 --- a/src/libquicknote.py +++ b/src/libquicknote.py @@ -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() diff --git a/support/builddeb.py b/support/builddeb.py index 703bdc9..2f83338 100755 --- a/support/builddeb.py +++ b/support/builddeb.py @@ -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" diff --git a/support/pylint.rc b/support/pylint.rc index 37b9725..2a371a1 100644 --- a/support/pylint.rc +++ b/support/pylint.rc @@ -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]