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
--- /dev/null
+__pretty_app_name__ = "Quicknote"
+__app_name__ = "quicknote"
+__version__ = "0.7.7"
+__app_magic__ = 0xdeadbeef
--- /dev/null
+#!/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()
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()
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
except ImportError:
osso = None
+import constants
+
import libspeichern
import libkopfzeile
import libnotizen
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()
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:
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
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)
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))
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
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:
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
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()
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()
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
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"
#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]