From: epage Date: Sat, 16 May 2009 12:51:59 +0000 (+0000) Subject: Setting up support for branches and tags X-Git-Url: http://git.maemo.org/git/?p=quicknote;a=commitdiff_plain;h=21f6c0c26b18e00ae28818e8ce03bc442b84dac2 Setting up support for branches and tags git-svn-id: file:///svnroot/quicknote/trunk@31 bb7704e3-badb-4cfa-9ab3-9374dc87eaa2 --- 21f6c0c26b18e00ae28818e8ce03bc442b84dac2 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..03aed86 --- /dev/null +++ b/Makefile @@ -0,0 +1,95 @@ +PROJECT_NAME=quicknote +PROJECT_VERSION=0.7.4 +SOURCE_PATH=src +SOURCE=$(shell find $(SOURCE_PATH) -iname "*.py") +LOCALE_PATH=locale +LOCALE_FILES=$(shell find $(LOCALE_PATH) -iname "*.mo") +PROGRAM=$(SOURCE_PATH)/$(PROJECT_NAME).py +OBJ=$(SOURCE:.py=.pyc) +BUILD_PATH=./builddeb/ + +TEXT_DOMAIN=$(PROJECT_NAME) +POTFILES=$(wildcard src/quicknoteclasses/*.py) + +UNIT_TEST=nosetests --with-doctest -w . +SYNTAX_TEST=support/test_syntax.py +LINT_RC=./support/pylint.rc +LINT=pylint --rcfile=$(LINT_RC) +PROFILE_GEN=python -m cProfile -o .profile +PROFILE_VIEW=python -m pstats .profile + +.PHONY: all run profile test lint clean distclean install update_po build_mo + +all: build_mo + python2.5 setup.py build + +run: + $(PROGRAM) + +profile: + $(PROFILE_GEN) $(PROGRAM) + $(PROFILE_VIEW) + +test: $(OBJ) + $(UNIT_TEST) + +package: clean $(OBJ) all + dpkg-buildpackage -rfakeroot + dpkg -i ../$(PROJECT_NAME)_$(PROJECT_VERSION)_all.deb + +update_po: po/templates.pot + @for lang in $(basename $(notdir $(wildcard po/*.po))); do \ + msgmerge -U --strict --no-wrap po/$$lang.po po/templates.pot; \ + done + +po/templates.pot: $(POTFILES) + xgettext --language=Python --strict --no-wrap --output=$@ $(POTFILES) + +build_mo: + @for lang in $(basename $(notdir $(wildcard po/*.po))); do \ + mkdir -p locale/$$lang/LC_MESSAGES; \ + msgfmt --statistics -c -o locale/$$lang/LC_MESSAGES/$(TEXT_DOMAIN).mo po/$$lang.po; \ + done + +build: $(OBJ) build_mo + rm -Rf $(BUILD_PATH) + mkdir $(BUILD_PATH) + cp $(PROGRAM) $(BUILD_PATH) + $(foreach file, $(DATA), cp $(file) $(BUILD_PATH)/$(subst /,-,$(file)) ; ) + $(foreach file, $(SOURCE), cp $(file) $(BUILD_PATH)/$(subst /,-,$(file)) ; ) + $(foreach file, $(OBJ), cp $(file) $(BUILD_PATH)/$(subst /,-,$(file)) ; ) + $(foreach file, $(LOCALE_FILES), cp $(file) $(BUILD_PATH)/$(subst /,-,$(file)) ; ) + cp data/$(PROJECT_NAME).desktop $(BUILD_PATH) + cp data/$(PROJECT_NAME).service $(BUILD_PATH) + cp data/low/$(PROJECT_NAME).png $(BUILD_PATH)/26x26-$(PROJECT_NAME).png + cp data/40/$(PROJECT_NAME).png $(BUILD_PATH)/40x40-$(PROJECT_NAME).png + cp data/48/$(PROJECT_NAME).png $(BUILD_PATH)/48x48-$(PROJECT_NAME).png + cp data/scale/$(PROJECT_NAME).png $(BUILD_PATH)/scale-$(PROJECT_NAME).png + cp support/builddeb.py $(BUILD_PATH) + cp support/fake_py2deb.py $(BUILD_PATH) + +lint: $(OBJ) + $(foreach file, $(SOURCE), $(LINT) $(file) ; ) + +clean: + rm -rf ./locale + rm -rf $(OBJ) + rm -Rf $(BUILD_PATH) + python2.5 setup.py clean --all + +distclean: clean + find $(SOURCE_PATH) -name "*.*~" | xargs rm -f + find $(SOURCE_PATH) -name "*.swp" | xargs rm -f + find $(SOURCE_PATH) -name "*.bak" | xargs rm -f + find $(SOURCE_PATH) -name ".*.swp" | xargs rm -f + +install: build_mo + python2.5 setup.py install --root $(DESTDIR) + +%.pyc: %.py + $(SYNTAX_TEST) $< + +#Makefile Debugging +#Target to print any variable, can be added to the dependencies of any other target +#Userfule flags for make, -d, -p, -n +print-%: ; @$(error $* is $($*) ($(value $*)) (from $(origin $*))) diff --git a/data/40/quicknote.png b/data/40/quicknote.png new file mode 100644 index 0000000..d6aa8d9 Binary files /dev/null and b/data/40/quicknote.png differ diff --git a/data/48/quicknote.png b/data/48/quicknote.png new file mode 100644 index 0000000..7c9f7af Binary files /dev/null and b/data/48/quicknote.png differ diff --git a/data/low/quicknote.png b/data/low/quicknote.png new file mode 100644 index 0000000..f7fc618 Binary files /dev/null and b/data/low/quicknote.png differ diff --git a/data/quicknote.desktop b/data/quicknote.desktop new file mode 100644 index 0000000..84ed6e2 --- /dev/null +++ b/data/quicknote.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Quicknote +Comment=Quicknote +Type=Application +Exec=/usr/bin/quicknote.py +Icon=quicknote +X-Window-Icon=quicknote +X-Window-Icon-Dimmed=quicknote +#X-Osso-Service=quicknote +X-Osso-Type=application/x-executable diff --git a/data/quicknote.png b/data/quicknote.png new file mode 100644 index 0000000..46c8c02 Binary files /dev/null and b/data/quicknote.png differ diff --git a/data/quicknote.service b/data/quicknote.service new file mode 100644 index 0000000..3e94c03 --- /dev/null +++ b/data/quicknote.service @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=com.nokia.quicknote +Exec=/usr/bin/quicknote.py diff --git a/data/scale/quicknote.png b/data/scale/quicknote.png new file mode 100644 index 0000000..da2d5f5 Binary files /dev/null and b/data/scale/quicknote.png differ diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..624ba89 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,35 @@ +quicknote (0.7.4) unstable; urgency=low + + * fixed small bugs + * move category + + -- unknown Thu, 22 May 2008 08:12:53 +0100 + + +quicknote (0.7.3) unstable; urgency=low + + * fixed small bugs + * move category + + -- unknown Wed, 28 Jan 2008 08:12:53 +0100 + +quicknote (0.7.2) unstable; urgency=low + + * improved sync, fixed a small bug + + -- unknown Wed, 26 Nov 2007 08:12:53 +0100 + +quicknote (0.7.1) unstable; urgency=low + + * improved sync + + -- unknown Wed, 7 Nov 2007 08:12:53 +0100 + + + +quicknote (0.7.0) unstable; urgency=low + + * Initial Release. + + -- unknown Mon, 6 Nov 2007 08:12:53 +0100 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..b8626c4 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +4 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..90ad0c2 --- /dev/null +++ b/debian/control @@ -0,0 +1,21 @@ +Source: quicknote +Section: user/other +Priority: optional +Maintainer: Christoph Würstle +Build-Depends: debhelper (>= 4.0.0), python2.5 +Standards-Version: 3.6.1 + +Package: quicknote +Architecture: all +Depends: python2.5-hildon, python2.5-gtk2, python2.5-osso, python2.5-runtime +Description: Quicknote +XB-Maemo-Icon-26: + iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A + /wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9cLAwkANf/o52QAAAAddEVYdENv + bW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAARNJREFUSMfNlsENhCAQRT9oPFiDmFCN + iRW7DViBnj1gEXog7AkXERUQk/0nCMpzZr4DRCkFAGjbVg3DgLIs8VRSSlBK0XUd4ZwDAHK9OI4j + +r5HKtV1jWVZtjnVgxSR2KKUHkFv6xLEGANj7F2QDyDkQ/IriBDiFqqfiQLZL/tuFl2jUDWsigfZ + aYs1SO67uRDiUDvfaE5Brs1ckIZV+Ih5m5vjKDPcGcIGvm6GIJDvj+pbm9PUmUV3pc1c+4jZG0Sv + amR3hivXRdnbhpn/jg25M8Et6G9aUGiHyEOOCbNmLtM8PibOIghJaZLOEFQjKWXybqCvcruIsixL + dj/QIoT8xpo6TZNa13W3+FScc1IUBQDgC9noggh/sT9oAAAAAElFTkSuQmCC + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..3c80733 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,12 @@ +This package was debianized by Christoph Würstle on +Mon, 22 Jan 2007 22:44:33 +0200. + +It was downloaded from + +Copyright: + +Upstream Author(s): + +License: + +Special drop a mail diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 0000000..ca882bb --- /dev/null +++ b/debian/dirs @@ -0,0 +1,2 @@ +usr/bin +usr/sbin diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..724e084 --- /dev/null +++ b/debian/docs @@ -0,0 +1,2 @@ +README +TODO diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..1df47d1 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,3 @@ +#!/bin/sh -e +gtk-update-icon-cache -f /usr/share/icons/hicolor +exit 0 \ No newline at end of file diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..273008d --- /dev/null +++ b/debian/rules @@ -0,0 +1,93 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + + + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + INSTALL_PROGRAM += -s +endif + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + + # Add here commands to compile the package. + $(MAKE) + + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + + # Add here commands to clean up after the build process. + -$(MAKE) clean + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Add here commands to install the package into debian/quicknote + $(MAKE) install DESTDIR=$(CURDIR)/debian/quicknote + + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs +# dh_installdocs + dh_installexamples +# dh_install +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl +# dh_python +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/po/de.po b/po/de.po new file mode 100644 index 0000000..a83382f --- /dev/null +++ b/po/de.po @@ -0,0 +1,228 @@ +# German translations for quicknote package +# German messages for quicknote. +# Copyright (C) 2008 THE quicknote'S COPYRIGHT HOLDER +# This file is distributed under the same license as the quicknote package. +# Christoph Würstle , 2008. +# +msgid "" +msgstr "" +"Project-Id-Version: quicknote\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2008-06-03 16:25+0400\n" +"PO-Revision-Date: 2008-06-03 16:07+0400\n" +"Last-Translator: Christoph Würstle \n" +"Language-Team: German\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +# +# File: src/quicknoteclasses/libhistory.py, line: 30 +msgid "History:" +msgstr "Historie:" +# +# File: src/quicknoteclasses/libhistory.py, line: 43 +# File: src/quicknoteclasses/libsqldialog.py, line: 65 +msgid "Timestamp" +msgstr "Zeitstempel" +# +# File: src/quicknoteclasses/libhistory.py, line: 44 +# File: src/quicknoteclasses/libnotizen.py, line: 234 +msgid "Note" +msgstr "Notiz" +# +# File: src/quicknoteclasses/libkopfzeile.py, line: 48 +# File: src/quicknoteclasses/libkopfzeile.py, line: 106 +msgid "all" +msgstr "alle" +# +# File: src/quicknoteclasses/libkopfzeile.py, line: 131 +msgid "Search: " +msgstr "Suche:" +# +# File: src/quicknoteclasses/libkopfzeile.py, line: 141 +msgid "Category: " +msgstr "Kategorie" +# +# File: src/quicknoteclasses/libnotizen.py, line: 105 +msgid "Last change: %d.%m.%y %H:%M" +msgstr "Letzte Änderung: %d.%m.%y %H:%M" +# +# File: src/quicknoteclasses/libnotizen.py, line: 116 +msgid "Really delete?" +msgstr "Wirklich löschen?" +# +# File: src/quicknoteclasses/libnotizen.py, line: 131 +msgid "new Note" +msgstr "neue Notiz" +# +# File: src/quicknoteclasses/libnotizen.py, line: 150 +# File: src/quicknoteclasses/libquicknote.py, line: 183 +msgid "No note selected." +msgstr "Keine Notiz markiert" +# +# File: src/quicknoteclasses/libnotizen.py, line: 209 +msgid "Titles" +msgstr "Titel" +# +# File: src/quicknoteclasses/libnotizen.py, line: 213 +msgid "add note" +msgstr "Notiz hinzufügen" +# +# File: src/quicknoteclasses/libnotizen.py, line: 217 +msgid "del note" +msgstr "Notiz löschen" +# +# File: src/quicknoteclasses/libnotizen.py, line: 247 +msgid "History" +msgstr "Historie" +# +# File: src/quicknoteclasses/libquicknote.py, line: 78 +msgid "Choose database file" +msgstr "Datenbankdatei auswählen" +# +# File: src/quicknoteclasses/libquicknote.py, line: 95 +#, python-format +msgid "%s is a note taking program; it is optimised for quick save and search of notes" +msgstr "%s ist ein Programm um Notizen zu verwaltn; es ist für schnelles speichern und finden optimiert" +# +# File: src/quicknoteclasses/libquicknote.py, line: 117 +msgid "Select SQL export file" +msgstr "Wähle SQL-Export-Datei" +# +# File: src/quicknoteclasses/libquicknote.py, line: 130 +msgid "This category can not be deleted" +msgstr "Diese Kategorie kann nicht gelöscht werden" +# +# File: src/quicknoteclasses/libquicknote.py, line: 136 +msgid "Are you sure to delete the current category?" +msgstr "Wirklich aktuell gewählte Kategorie löschen?" +# +# File: src/quicknoteclasses/libquicknote.py, line: 152 +msgid "Choose category" +msgstr "Wähle Kategorie" +# +# File: src/quicknoteclasses/libquicknote.py, line: 196 +msgid "Sync" +msgstr "Syncronisation" +# +# File: src/quicknoteclasses/libquicknote.py, line: 269 +msgid "Set DB file" +msgstr "Setzte Datenbaknkdatei" +# +# File: src/quicknoteclasses/libquicknote.py, line: 273 +msgid "SQL History" +msgstr "SQL Historie" +# +# File: src/quicknoteclasses/libquicknote.py, line: 277 +msgid "Sync notes" +msgstr "Syncronisiere Notizen" +# +# File: src/quicknoteclasses/libquicknote.py, line: 281 +msgid "Quit" +msgstr "Beenden" +# +# File: src/quicknoteclasses/libquicknote.py, line: 285 +msgid "File" +msgstr "Datei" +# +# File: src/quicknoteclasses/libquicknote.py, line: 296 +msgid "delete" +msgstr "löschen" +# +# File: src/quicknoteclasses/libquicknote.py, line: 300 +msgid "move to category" +msgstr "verschiebe zu Kategorie" +# +# File: src/quicknoteclasses/libquicknote.py, line: 304 +msgid "Category" +msgstr "Kategorie" +# +# File: src/quicknoteclasses/libquicknote.py, line: 310 +msgid "About" +msgstr "Über" +# +# File: src/quicknoteclasses/libquicknote.py, line: 314 +msgid "Help" +msgstr "Hilfe" +# +# File: src/quicknoteclasses/libsqldialog.py, line: 46 +msgid "SQL History (the past two days):" +msgstr "SQL Historie (der letzten 2 Tage):" +# +# File: src/quicknoteclasses/libsqldialog.py, line: 67 +msgid "Parameter" +msgstr "Parameter" +# +# File: src/quicknoteclasses/libsqldialog.py, line: 105 +msgid "%d.%m.%y %H:%M:%S " +msgstr "%d.%m.%y %H:%M:%S " +# +# File: src/quicknoteclasses/libsync.py, line: 43 +msgid "Sync process" +msgstr "Syncronisationsprozess" +# +# File: src/quicknoteclasses/libsync.py, line: 48 +msgid "Sync process running...please wait" +msgstr "Syncronisationsprozess läuft...bitte warten" +# +# File: src/quicknoteclasses/libsync.py, line: 50 +msgid "(this can take some minutes)" +msgstr "(dies kann einige Minuten dauern)" +# +# File: src/quicknoteclasses/libsync.py, line: 90 +msgid "Query" +msgstr "Anfrage" +# +# File: src/quicknoteclasses/libsync.py, line: 242 +msgid "Syncserver running..." +msgstr "Syncronisationsprozess läuft..." +# +# File: src/quicknoteclasses/libsync.py, line: 250 +msgid "Could not start SyncServer. Check IP, port settings." +msgstr "Konnte Syncronisationsserver nicht starten. Bitte IP und Port-Einstellungen überprüfen." +# +# File: src/quicknoteclasses/libsync.py, line: 263 +# File: src/quicknoteclasses/libsync.py, line: 409 +msgid "SyncServer stopped" +msgstr "Syncronisationsprozess gestoppt." +# +# File: src/quicknoteclasses/libsync.py, line: 280 +# File: src/quicknoteclasses/libsync.py, line: 316 +# File: src/quicknoteclasses/libsync.py, line: 324 +# File: src/quicknoteclasses/libsync.py, line: 331 +# File: src/quicknoteclasses/libsync.py, line: 422 +msgid "no sync process (at the moment)" +msgstr "Kein Syncronisationsprozess (im Moment)" +# +# File: src/quicknoteclasses/libsync.py, line: 289 +msgid "sync process running" +msgstr "Syncronisationsprozess läuft" +# +# File: src/quicknoteclasses/libsync.py, line: 318 +msgid "Synchronization successfully completed" +msgstr "Synchronisation erfolgreich beendet" +# +# File: src/quicknoteclasses/libsync.py, line: 325 +msgid "The clocks are not synchronized between stations" +msgstr "Zeit differiert zu viel zwischen den Systemen" +# +# File: src/quicknoteclasses/libsync.py, line: 332 +msgid "Sync failed, reason: " +msgstr "Sync gescheitert. Fehler:" +# +# File: src/quicknoteclasses/libsync.py, line: 387 +msgid "Local SyncServer (port " +msgstr "LokalerSync-Server (Port " +# +# File: src/quicknoteclasses/libsync.py, line: 405 +msgid "Start/Stop SyncServer" +msgstr "SyncServer starten/stoppen" +# +# File: src/quicknoteclasses/libsync.py, line: 412 +msgid "Remote SyncServer (port " +msgstr "RemoteSync-Server (Port " +# +# File: src/quicknoteclasses/libsync.py, line: 418 +msgid "Connect to remote SyncServer" +msgstr "Verbinde zu Remote-SyncServer" diff --git a/po/ru.po b/po/ru.po new file mode 100644 index 0000000..398d498 --- /dev/null +++ b/po/ru.po @@ -0,0 +1,226 @@ +# Russian translations for quicknote package +# Copyright (C) 2008 THE quicknote'S COPYRIGHT HOLDER +# This file is distributed under the same license as the quicknote package. +# Nikolay Logvinov , 2008. +# +msgid "" +msgstr "" +"Project-Id-Version: quicknote\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2008-06-03 16:25+0400\n" +"PO-Revision-Date: 2008-06-03 15:38+0400\n" +"Last-Translator: Nikolay Logvinov \n" +"Language-Team: Russian\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +# +# File: src/quicknoteclasses/libhistory.py, line: 30 +msgid "History:" +msgstr "История изменений" +# +# File: src/quicknoteclasses/libhistory.py, line: 43 +# File: src/quicknoteclasses/libsqldialog.py, line: 65 +msgid "Timestamp" +msgstr "Время" +# +# File: src/quicknoteclasses/libhistory.py, line: 44 +# File: src/quicknoteclasses/libnotizen.py, line: 234 +msgid "Note" +msgstr "Заметка" +# +# File: src/quicknoteclasses/libkopfzeile.py, line: 48 +# File: src/quicknoteclasses/libkopfzeile.py, line: 106 +msgid "all" +msgstr "все" +# +# File: src/quicknoteclasses/libkopfzeile.py, line: 131 +msgid "Search: " +msgstr "Поиск: " +# +# File: src/quicknoteclasses/libkopfzeile.py, line: 141 +msgid "Category: " +msgstr "Категория: " +# +# File: src/quicknoteclasses/libnotizen.py, line: 105 +msgid "Last change: %d.%m.%y %H:%M" +msgstr "Редактировалась %d.%m.%y в %H:%M" +# +# File: src/quicknoteclasses/libnotizen.py, line: 116 +msgid "Really delete?" +msgstr "Всё-таки удалять?" +# +# File: src/quicknoteclasses/libnotizen.py, line: 131 +msgid "new Note" +msgstr "__ новая __" +# +# File: src/quicknoteclasses/libnotizen.py, line: 150 +# File: src/quicknoteclasses/libquicknote.py, line: 183 +msgid "No note selected." +msgstr "Выберите сначала заметку" +# +# File: src/quicknoteclasses/libnotizen.py, line: 209 +msgid "Titles" +msgstr "Заголовки" +# +# File: src/quicknoteclasses/libnotizen.py, line: 213 +msgid "add note" +msgstr "добавить Заметку" +# +# File: src/quicknoteclasses/libnotizen.py, line: 217 +msgid "del note" +msgstr "удалить Заметку" +# +# File: src/quicknoteclasses/libnotizen.py, line: 247 +msgid "History" +msgstr "История изменений" +# +# File: src/quicknoteclasses/libquicknote.py, line: 78 +msgid "Choose database file" +msgstr "Выберите файл для хранения заметок" +# +# File: src/quicknoteclasses/libquicknote.py, line: 95 +msgid "%s is a note taking program; it is optimised for quick save and search of notes" +msgstr "%s - приложение для создания, хранения и быстрого поиска заметок" +# +# File: src/quicknoteclasses/libquicknote.py, line: 117 +msgid "Select SQL export file" +msgstr "Выберите файл для экспорта запросов SQL" +# +# File: src/quicknoteclasses/libquicknote.py, line: 130 +msgid "This category can not be deleted" +msgstr "Эту категорию нельзя удалять" +# +# File: src/quicknoteclasses/libquicknote.py, line: 136 +msgid "Are you sure to delete the current category?" +msgstr "Вы уверены в удалении текущей категории?" +# +# File: src/quicknoteclasses/libquicknote.py, line: 152 +msgid "Choose category" +msgstr "Категория:" +# +# File: src/quicknoteclasses/libquicknote.py, line: 196 +msgid "Sync" +msgstr "Синхронизация" +# +# File: src/quicknoteclasses/libquicknote.py, line: 269 +msgid "Set DB file" +msgstr "Хранилище" +# +# File: src/quicknoteclasses/libquicknote.py, line: 273 +msgid "SQL History" +msgstr "SQL запросы" +# +# File: src/quicknoteclasses/libquicknote.py, line: 277 +msgid "Sync notes" +msgstr "Синхронизация" +# +# File: src/quicknoteclasses/libquicknote.py, line: 281 +msgid "Quit" +msgstr "Выход" +# +# File: src/quicknoteclasses/libquicknote.py, line: 285 +msgid "File" +msgstr "Файл" +# +# File: src/quicknoteclasses/libquicknote.py, line: 296 +msgid "delete" +msgstr "убрать" +# +# File: src/quicknoteclasses/libquicknote.py, line: 300 +msgid "move to category" +msgstr "назначить" +# +# File: src/quicknoteclasses/libquicknote.py, line: 304 +msgid "Category" +msgstr "Категория" +# +# File: src/quicknoteclasses/libquicknote.py, line: 310 +msgid "About" +msgstr "О программе" +# +# File: src/quicknoteclasses/libquicknote.py, line: 314 +msgid "Help" +msgstr "Справка" +# +# File: src/quicknoteclasses/libsqldialog.py, line: 46 +msgid "SQL History (the past two days):" +msgstr "SQL запросы (за 2 последних дня)" +# +# File: src/quicknoteclasses/libsqldialog.py, line: 67 +msgid "Parameter" +msgstr "Параметры" +# +# File: src/quicknoteclasses/libsqldialog.py, line: 105 +msgid "%d.%m.%y %H:%M:%S " +msgstr "%d.%m.%y %H:%M:%S " +# +# File: src/quicknoteclasses/libsync.py, line: 43 +msgid "Sync process" +msgstr "Синхронизация" +# +# File: src/quicknoteclasses/libsync.py, line: 48 +msgid "Sync process running...please wait" +msgstr "Синхронизируются заметки, подождите ..." +# +# File: src/quicknoteclasses/libsync.py, line: 50 +msgid "(this can take some minutes)" +msgstr "(некоторое время)" +# +# File: src/quicknoteclasses/libsync.py, line: 90 +msgid "Query" +msgstr "Запрос" +# +# File: src/quicknoteclasses/libsync.py, line: 242 +msgid "Syncserver running..." +msgstr "Сервер синхронизации работает ..." +# +# File: src/quicknoteclasses/libsync.py, line: 250 +msgid "Could not start SyncServer. Check IP, port settings." +msgstr "Не удаётся стартовать Сервер синхронизации. Проверьте IP-адрес и порт." +# +# File: src/quicknoteclasses/libsync.py, line: 263 +# File: src/quicknoteclasses/libsync.py, line: 409 +msgid "SyncServer stopped" +msgstr "Сервер синхронизации остановлен" +# +# File: src/quicknoteclasses/libsync.py, line: 280 +# File: src/quicknoteclasses/libsync.py, line: 316 +# File: src/quicknoteclasses/libsync.py, line: 324 +# File: src/quicknoteclasses/libsync.py, line: 331 +# File: src/quicknoteclasses/libsync.py, line: 422 +msgid "no sync process (at the moment)" +msgstr "сейчас синхронизация не выполняется" +# +# File: src/quicknoteclasses/libsync.py, line: 289 +msgid "sync process running" +msgstr "идёт синхронизация" +# +# File: src/quicknoteclasses/libsync.py, line: 318 +msgid "Synchronization successfully completed" +msgstr "Синхронизация успешно завершенa" +# +# File: src/quicknoteclasses/libsync.py, line: 325 +msgid "The clocks are not synchronized between stations" +msgstr "Согласуйте время между системами" +# +# File: src/quicknoteclasses/libsync.py, line: 332 +msgid "Sync failed, reason: " +msgstr "Синхронизовать заметки не удалось по причине: " +# +# File: src/quicknoteclasses/libsync.py, line: 387 +msgid "Local SyncServer (port " +msgstr "Местный сервер синхронизации (порт " +# +# File: src/quicknoteclasses/libsync.py, line: 405 +msgid "Start/Stop SyncServer" +msgstr "Запуск/Остановка сервера синхронизации" +# +# File: src/quicknoteclasses/libsync.py, line: 412 +msgid "Remote SyncServer (port " +msgstr "Удалённый сервер синхронизации (порт " +# +# File: src/quicknoteclasses/libsync.py, line: 418 +msgid "Connect to remote SyncServer" +msgstr "Установить соединение" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0fcf954 --- /dev/null +++ b/setup.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python2.5 +# -*- coding: utf-8 -*- + + +from distutils.core import setup + + +setup( + name='quicknote', + version='1.0', + scripts=['src/quicknote.py'], + packages=['quicknote'], + package_dir={'quicknote': 'src/'}, + data_files = [ + ('share/icons/hicolor/26x26/hildon', ['data/low/quicknote.png']), + ('share/icons/hicolor/40x40/hildon', ['data/40/quicknote.png']), + #('share/icons/hicolor/48x48/apps', ['data/48/quicknote.png']), + ('share/icons/hicolor/scalable/hildon', ['data/scale/quicknote.png']), + ('share/applications/hildon', ['data/quicknote.desktop']), + ('share/dbus-1/services', ['data/quicknote.service']), + # I18N + ('share/locale/de/LC_MESSAGES', ['locale/de/LC_MESSAGES/quicknote.mo']), + ('share/locale/ru/LC_MESSAGES', ['locale/ru/LC_MESSAGES/quicknote.mo']), + ] +) diff --git a/src/__init__.py b/src/__init__.py new file mode 100755 index 0000000..8b13789 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1 @@ + diff --git a/src/fakehildon.py b/src/fakehildon.py new file mode 100644 index 0000000..cf32c9f --- /dev/null +++ b/src/fakehildon.py @@ -0,0 +1,31 @@ +#/usr/bin/env python2.5 +# -*- coding: utf-8 -*- + +import gtk + + +class FileChooserDialog(gtk.FileChooserDialog): + """ + @bug The buttons currently don't do anything + """ + + def __init__(self, *args, **kwds): + super(FileChooserDialog, self).__init__(*args, **kwds) + self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) + self.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + + +class Window(gtk.Window): + + def __init__(self): + super(Window, self).__init__(gtk.WINDOW_TOPLEVEL) + self.set_default_size(700, 500) + + +class Program(object): + + def __init__(self): + pass + + def add_window(self, window): + pass diff --git a/src/libhistory.py b/src/libhistory.py new file mode 100644 index 0000000..4c23f73 --- /dev/null +++ b/src/libhistory.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python2.5 +# -*- coding: utf-8 -*- + +""" + Copyright (C) 2007 Christoph Würstle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. +""" + + +import gtk + + +try: + _ +except NameError: + _ = lambda x: x + + +class Dialog(gtk.Dialog): + + def __init__(self, daten = None): + gtk.Dialog.__init__(self, _("History:"), None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + self.set_position(gtk.WIN_POS_CENTER) + + self.liststore = gtk.ListStore(int, str, str, str, str) + #pcdatum, datum, sql, param # param schön + + # create the TreeView using liststore + self.treeview = gtk.TreeView(self.liststore) + # create a CellRenderers to render the data + self.cell1 = gtk.CellRendererText() + self.cell2 = gtk.CellRendererText() + + # create the TreeViewColumns to display the data + self.tvcolumn1 = gtk.TreeViewColumn(_('Timestamp')) + self.tvcolumn2 = gtk.TreeViewColumn(_('Note')) + # add columns to treeview + self.treeview.append_column(self.tvcolumn1) + self.treeview.append_column(self.tvcolumn2) + + # add the cells to the columns - 2 in the first + self.tvcolumn1.pack_start(self.cell1, True) + self.tvcolumn2.pack_start(self.cell2, True) + self.tvcolumn1.set_attributes(self.cell1, text = 1) #Spalten setzten hier!!!! + self.tvcolumn2.set_attributes(self.cell2, text = 4) + + self.treeview.set_reorderable(False) + + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled_window.add(self.treeview) + self.vbox.pack_start(scrolled_window, expand = True, fill = True, padding = 0) + + self.liststore.clear() + + if daten is not None: + for data in daten: + self.liststore.append(data) + + def get_selected_row(self): + path = self.treeview.get_cursor()[0] + if path is None or path == "": + return None + + iter1 = self.treeview.get_model().get_iter(path) + return self.treeview.get_model().get(iter1, 0, 1, 2, 3, 4) diff --git a/src/libkopfzeile.py b/src/libkopfzeile.py new file mode 100644 index 0000000..90d297d --- /dev/null +++ b/src/libkopfzeile.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python2.5 +# -*- coding: utf-8 -*- + +""" + Copyright (C) 2007 Christoph Würstle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. +""" + + +import logging + +import gobject +import gtk + + +try: + _ +except NameError: + _ = lambda x: x + + +class Kopfzeile(gtk.HBox): + + __gsignals__ = { + 'reload_notes' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + } + + def __init__(self, db): + self.lastCategory = "" + self.db = db + + gtk.HBox.__init__(self, homogeneous = False, spacing = 3) + logging.info("libkopfzeile, init") + + categoryHBox = gtk.HBox() + self.pack_start(categoryHBox, expand = False, fill = True, padding = 0) + + label = gtk.Label(_("Category: ")) + categoryHBox.pack_start(label, expand = False, fill = True, padding = 0) + + self.comboCategory = gtk.combo_box_entry_new_text() + categoryHBox.pack_start(self.comboCategory, expand = True, fill = True, padding = 0) + self.loadCategories() + self.comboCategory.connect("changed", self.comboCategoryChanged, None) + + searchHBox = gtk.HBox() + self.pack_start(searchHBox, expand = True, fill = True, padding = 0) + + label = gtk.Label(_("Search: ")) + searchHBox.pack_start(label, expand = False, fill = True, padding = 0) + + self.searchEntry = gtk.Entry() + searchHBox.pack_start(self.searchEntry, expand = True, fill = True, padding = 0) + self.searchEntry.connect("changed", self.searchEntryChanged, None) + + def comboCategoryChanged(self, widget = None, data = None): + logging.debug("comboCategoryChanged") + if self.lastCategory != self.comboCategory.get_active(): + sql = "UPDATE categories SET liste = ? WHERE id = 1" + self.db.speichereSQL(sql, (self.comboCategory.get_active(), )) + + self.emit("reload_notes") + + def searchEntryChanged(self, widget = None, data = None): + logging.debug("searchEntryChanged") + self.emit("reload_notes") + + def getCategory(self): + entry = self.comboCategory.get_child() + category = entry.get_text() + if category == _("all"): + category = "%" + if category == "": + category = "undefined" + self.comboCategory.set_active(1) + self.comboCategory.show() + return category + + def defineThisCategory(self): + category = self.getCategory() + + model = self.comboCategory.get_model() + n = len(self.comboCategory.get_model()) + i = 0 + active = -1 + cats = [] + while i < n: + if (model[i][0] == category): + #self.comboCategory.set_active(i) + active = i + if (model[i][0]!= "%"): + cats.append(model[i][0]) + i += 1 + + if (active == -1) and (category!= "%"): + self.comboCategory.append_text(category) + sql = "INSERT INTO categories (id, liste) VALUES (0, ?)" + self.db.speichereSQL(sql, (category, )) + self.comboCategory.set_active(i) + + def getSearchPattern(self): + return self.searchEntry.get_text() + + def loadCategories(self): + sql = "CREATE TABLE categories (id TEXT , liste TEXT)" + self.db.speichereSQL(sql) + + sql = "SELECT id, liste FROM categories WHERE id = 0 ORDER BY liste" + rows = self.db.ladeSQL(sql) + cats = [] + if rows is not None and 0 < len(rows): + for row in rows: + cats.append(row[1]) + + sql = "SELECT * FROM categories WHERE id = 1" + rows = self.db.ladeSQL(sql) + if rows is None or len(rows) == 0: + sql = "INSERT INTO categories (id, liste) VALUES (1, 1)" + self.db.speichereSQL(sql) + + #self.comboCategory.clear() + while 0 < len(self.comboCategory.get_model()): + self.comboCategory.remove_text(0) + + self.comboCategory.append_text(_('all')) + self.comboCategory.append_text('undefined') + + if cats is not None and 0 < len(cats): + for cat in cats: + self.comboCategory.append_text(cat) + + sql = "SELECT * FROM categories WHERE id = 1" + rows = self.db.ladeSQL(sql) + if rows is not None and 0 < len(rows): + self.comboCategory.set_active(int(rows[0][1])) + else: + self.comboCategory.set_active(1) + + self.lastCategory = self.comboCategory.get_active() diff --git a/src/libnotizen.py b/src/libnotizen.py new file mode 100644 index 0000000..a7f6e6a --- /dev/null +++ b/src/libnotizen.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python2.5 +# -*- coding: utf-8 -*- + +""" + Copyright (C) 2007 Christoph Würstle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. + +@todo Add hiding of the history button on zoom +""" + +import time +import logging +import uuid + +import gobject +import gtk + +import simple_list + + +try: + _ +except NameError: + _ = lambda x: x + + +class Notizen(gtk.HBox): + + def __init__(self, db, topBox): + self.db = db + self.topBox = topBox + self.noteid = -1 + self.pos = -1 + self.note = None #Last notetext + self.category = "" + + gtk.HBox.__init__(self, homogeneous = False, spacing = 0) + logging.info("libnotizen, init") + + self.noteslist = simple_list.SimpleList() + self.noteslist.set_eventfunction__cursor_changed(self.noteslist_changed) + + self.noteslist.set_size_request(250, -1) + + vbox = gtk.VBox(homogeneous = False, spacing = 0) + + frame = gtk.Frame(_("Titles")) + frame.add(self.noteslist) + vbox.pack_start(frame, expand = True, fill = True, padding = 3) + + buttonHBox = gtk.HBox() + vbox.pack_start(buttonHBox, expand = False, fill = True, padding = 3) + + button = gtk.Button(stock = gtk.STOCK_ADD) + button.connect("clicked", self.add_note, None) + buttonHBox.pack_start(button, expand = True, fill = True, padding = 3) + + button = gtk.Button(stock = gtk.STOCK_DELETE) + button.connect("clicked", self.del_note, None) + buttonHBox.pack_start(button, expand = True, fill = True, padding = 3) + + self.pack_start(vbox, expand = False, fill = True, padding = 3) + + self.textviewNote = gtk.TextView() + self.textviewNote.connect("focus-out-event", self.saveNote, "focus-out-event") + buf = self.textviewNote.get_buffer() + buf.set_text("") + buf.connect("changed", self.noteChanged, None) + + #self.textviewNotiz.set_size_request(-1, 50) + self.scrolled_window = gtk.ScrolledWindow() + self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.scrolled_window.add(self.textviewNote) + + frame = gtk.Frame(_("Note")) + frame.add(self.scrolled_window) + + vbox = gtk.VBox(homogeneous = False, spacing = 0) + vbox.pack_start(frame, expand = True, fill = True, padding = 3) + + hbox = gtk.HBox(homogeneous = False, spacing = 0) + + self.statuslabel = gtk.Label("Test") + self.statuslabel.set_alignment(0.0, 0.5) + hbox.pack_start(self.statuslabel, expand = True, fill = True, padding = 3) + + button = gtk.Button(_("History")) + button.connect("clicked", self.show_history, None) + hbox.pack_start(button, expand = True, fill = True, padding = 3) + + vbox.pack_start(hbox, expand = False, fill = True, padding = 3) + + self.pack_start(vbox, expand = True, fill = True, padding = 3) + + self.loadNotes() + self.topBox.connect("reload_notes", self.loadNotes) + + def set_wordwrap(self, enableWordWrap): + if enableWordWrap: + self.scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.textviewNote.set_wrap_mode(gtk.WRAP_WORD) + else: + self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.textviewNote.set_wrap_mode(gtk.WRAP_NONE) + + def getTitle(self, buf): + eol = buf.find("\n") + if -1 == eol: + eol = len(buf) + title = buf[:eol] + return title + + def noteChanged(self, widget = None, data = None): + if self.pos == -1 or self.noteid == -1: + return + + buf = self.textviewNote.get_buffer().get_text(self.textviewNote.get_buffer().get_start_iter(), self.textviewNote.get_buffer().get_end_iter()) + + title = self.getTitle(buf) + value, key = self.noteslist.get_item(self.pos) + + if value != title: + self.noteslist.change_item(self.pos, title, self.noteid) + + def saveNote(self, widget = None, data = None, data2 = None): + logging.info("saveNote params: pos:"+str(self.pos)+" noteid:"+str(self.noteid)) + #print "params:", data, data2 + buf = self.textviewNote.get_buffer().get_text(self.textviewNote.get_buffer().get_start_iter(), self.textviewNote.get_buffer().get_end_iter()) + if buf is None or len(buf) == 0: + return + + if buf == self.note: + return + + logging.info("Saving note: "+buf) + if self.pos == -1 or self.noteid == -1: + self.pos = -1 + self.noteid = str(uuid.uuid4()) + self.db.saveNote(self.noteid, buf, self.category) + self.noteslist.append_item(self.getTitle(buf), self.noteid) + self.pos = self.noteslist.select_last_item() + else: + self.db.saveNote(self.noteid, buf, self.category) + + self.topBox.defineThisCategory() + + def setFocus(self): + self.textviewNote.grab_focus() + return False + + def noteslist_changed(self, data = None, data2 = None): + if self.pos != -1: + self.saveNote() + + try: + (pos, key, value) = self.noteslist.get_selection_data() + if (pos == -1): + return + except StandardError: + if data != "new": + return + key = None + + if key == "new" or data == "new": + #both methods supported click add note or new note (first one disabled) + self.noteid = str(uuid.uuid4()) + self.db.saveNote(self.noteid, "", self.category) + self.pos = -1 + self.noteslist.append_item("", self.noteid) + self.textviewNote.get_buffer().set_text("") + self.pos = self.noteslist.select_last_item() + else: + self.pos = pos + self.noteid, pcdatum, self.category, self.note = self.db.loadNote(key) + self.statuslabel.set_text(time.strftime(_("Last change: %d.%m.%y %H:%M"), time.localtime(pcdatum))) + buf = self.textviewNote.get_buffer() + buf.set_text(self.note) + + gobject.timeout_add(200, self.setFocus) + + def add_note(self, widget = None, data = None): + self.noteslist_changed("new") + + def del_note(self, widget = None, data = None): + if (self.noteid == -1): + return + mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO, _("Really delete?")) + response = mbox.run() + mbox.hide() + mbox.destroy() + if response == gtk.RESPONSE_YES: + self.db.delNote(self.noteid) + self.noteid = -1 + self.noteslist.remove_item(self.pos) + self.pos = -1 + self.textviewNote.get_buffer().set_text("") + + def loadNotes(self, data = None): + logging.info("loadNotes params: pos:"+str(self.pos)+" noteid:"+str(self.noteid)) + self.noteslist.clear_items() + self.noteslist.append_item(_("new Note"), "new") + + self.category = self.topBox.getCategory() + search = self.topBox.getSearchPattern() + notes = self.db.searchNotes(search, self.category) + + if notes is not None: + for note in notes: + noteid, category, noteText = note + title = self.getTitle(noteText) + self.noteslist.append_item(title, noteid) + + self.noteid = -1 + self.pos = -1 + self.textviewNote.get_buffer().set_text("") + + def show_history(self, widget = None, data = None, label = None): + if self.noteid == -1: + mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("No note selected.")) + response = mbox.run() + mbox.hide() + mbox.destroy() + return + + rows = self.db.getNoteHistory(self.noteid) + + import libhistory + dialog = libhistory.Dialog() + + lastNoteStr = "" + for row in rows: + #for x in row: + # print x + daten = row[4][1] + if (daten != "")and(lastNoteStr != daten): + lastNoteStr = daten + dialog.liststore.append([row[0], row[1], row[2], row[3], daten+"\n"]) + + dialog.vbox.show_all() + dialog.set_size_request(600, 380) + + if dialog.run() == gtk.RESPONSE_ACCEPT: + print "saving" + self.saveNote() + data = dialog.get_selected_row() + if data is not None: + self.db.speichereSQL(data[2], data[3].split(" <> "), rowid = self.noteid) + logging.info("loading History") + self.noteslist_changed() + + dialog.destroy() diff --git a/src/libquicknote.py b/src/libquicknote.py new file mode 100644 index 0000000..a89cb94 --- /dev/null +++ b/src/libquicknote.py @@ -0,0 +1,355 @@ +#/usr/bin/env python2.5 +# -*- coding: utf-8 -*- + +""" + Copyright (C) 2007 Christoph Würstle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. + +@todo Add an edit menu with select all, cut, copy, and paste +@todo Add undo directly into the UI +@todo Add Note Export (txt File) and Export All (json dump?) +@todo Save word wrap and zoom setting +""" + + +import os +import gc +import logging + +import gtk + +try: + import hildon + IS_HILDON = True +except ImportError: + import fakehildon as hildon + IS_HILDON = False + +try: + import osso +except ImportError: + osso = None + +import libspeichern +import libkopfzeile +import libnotizen +import libsync + + +try: + _ +except NameError: + _ = lambda x: x + + +class quicknoteclass(hildon.Program): + + __pretty_app_name__ = "quicknote" + __app_name__ = "quicknote" + __version__ = "0.7.5" + + def __init__(self): + super(quicknoteclass, self).__init__() + + home_dir = os.path.expanduser('~') + dblog = os.path.join(home_dir, "quicknote.log") + + # define a Handler which writes INFO messages or higher to the sys.stderr + console = logging.StreamHandler() + console.setLevel(logging.DEBUG) + # set a format which is simpler for console use + formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s') + # tell the handler to use this format + console.setFormatter(formatter) + # add the handler to the root logger + logging.getLogger('').addHandler(console) + + logging.info('Starting quicknote') + + if osso is not None: + self._osso_c = osso.Context(self.__app_name__, self.__version__, False) + self.device = osso.DeviceState(self._osso_c) + self.device.set_device_state_callback(self._on_device_state_change, 0) + else: + self._osso_c = None + + #Get the Main Window, and connect the "destroy" event + self.window = hildon.Window() + self.add_window(self.window) + + self.window.set_title(self.__pretty_app_name__) + self.window.connect("delete_event", self.delete_event) + self.window.connect("destroy", self.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.db = libspeichern.Speichern() + self.prepare_sync_dialog() + + #Create GUI main vbox + vbox = gtk.VBox(homogeneous = False, spacing = 0) + + #Create Menu and apply it for hildon + filemenu = gtk.Menu() + + menu_items = gtk.MenuItem(_("Set DB file")) + filemenu.append(menu_items) + menu_items.connect("activate", self.set_db_file, None) + + menu_items = gtk.MenuItem(_("SQL History")) + filemenu.append(menu_items) + menu_items.connect("activate", self.view_sql_history, None) + + menu_items = gtk.MenuItem(_("Sync notes")) + filemenu.append(menu_items) + menu_items.connect("activate", self.sync_notes, None) + + menu_items = gtk.MenuItem(_("Quit")) + filemenu.append(menu_items) + menu_items.connect("activate", self.destroy, None) + + file_menu = gtk.MenuItem(_("File")) + file_menu.show() + file_menu.set_submenu(filemenu) + + categorymenu = gtk.Menu() + + menu_items = gtk.MenuItem(_("delete")) + categorymenu.append(menu_items) + menu_items.connect("activate", self.delete_Category, None) + + menu_items = gtk.MenuItem(_("move to category")) + categorymenu.append(menu_items) + menu_items.connect("activate", self.move_Category, None) + + category_menu = gtk.MenuItem(_("Category")) + category_menu.show() + category_menu.set_submenu(categorymenu) + + viewmenu = gtk.Menu() + + menu_items = gtk.MenuItem(_("Word Wrap")) + viewmenu.append(menu_items) + menu_items.connect("activate", self.toggle_word_wrap, None) + self._wordWrapEnabled = False + + view_menu = gtk.MenuItem(_("View")) + view_menu.show() + view_menu.set_submenu(viewmenu) + + helpmenu = gtk.Menu() + + menu_items = gtk.MenuItem(_("About")) + helpmenu.append(menu_items) + menu_items.connect("activate", self.show_about, None) + + help_menu = gtk.MenuItem(_("Help")) + help_menu.show() + help_menu.set_submenu(helpmenu) + + menu_bar = gtk.MenuBar() + menu_bar.show() + menu_bar.append (file_menu) + menu_bar.append (category_menu) + menu_bar.append (view_menu) + menu_bar.append (help_menu) + + menu_bar.show() + if IS_HILDON: + menu = gtk.Menu() + for child in menu_bar.get_children(): + child.reparent(menu) + self.window.set_menu(menu) + menu_bar.destroy() + else: + vbox.pack_start(menu_bar, False, False, 0) + + #Create GUI elements + self.topBox = libkopfzeile.Kopfzeile(self.db) + vbox.pack_start(self.topBox, False, False, 0) + + 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.toggle_word_wrap() + + def set_db_file(self, widget = None, data = None): + dlg = hildon.FileChooserDialog(parent=self.window, action=gtk.FILE_CHOOSER_ACTION_SAVE) + + if self.db.ladeDirekt('datenbank'): + dlg.set_filename(self.db.ladeDirekt('datenbank')) + + dlg.set_title(_("Choose database file")) + if dlg.run() == gtk.RESPONSE_OK: + fileName = dlg.get_filename() + self.db.speichereDirekt('datenbank', fileName) + + self.db.openDB() + self.topBox.loadCategories() + self.notizen.loadNotes() + dlg.destroy() + + def 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_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__ + dialog.set_comments(comments) + dialog.run() + dialog.destroy() + + def view_sql_history(self, widget = None, data = None, data2 = None): + import libsqldialog + sqldiag = libsqldialog.sqlDialog(self.db) + res = sqldiag.run() + sqldiag.hide() + if res == 444: + logging.info("exporting sql") + + dlg = hildon.FileChooserDialog(parent=self.window, action=gtk.FILE_CHOOSER_ACTION_SAVE) + + 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() + + sqldiag.destroy() + + def delete_Category(self, widget = None, data = None): + if (self.topBox.getCategory() == "%") or (self.topBox.getCategory() == "undefined"): + mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("This category can not be deleted")) + response = mbox.run() + mbox.hide() + mbox.destroy() + return + + mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO, _("Are you sure to delete the current category?")) + response = mbox.run() + mbox.hide() + mbox.destroy() + if response == gtk.RESPONSE_YES: + sql = "UPDATE notes SET category = ? WHERE category = ?" + self.db.speichereSQL(sql, ("undefined", self.topBox.getCategory())) + sql = "DELETE FROM categories WHERE liste = ?" + self.db.speichereSQL(sql, (self.topBox.getCategory(), )) + model = self.topBox.comboCategory.get_model() + pos = self.topBox.comboCategory.get_active() + if (pos>1): + self.topBox.comboCategory.remove_text(pos) + self.topBox.comboCategory.set_active(0) + + def move_Category(self, widget = None, data = None): + 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)) + + dialog.set_position(gtk.WIN_POS_CENTER) + comboCategory = gtk.combo_box_new_text() + + comboCategory.append_text('undefined') + sql = "SELECT id, liste FROM categories WHERE id = 0 ORDER BY liste" + rows = self.db.ladeSQL(sql) + for row in rows: + comboCategory.append_text(row[1]) + + dialog.vbox.pack_start(comboCategory, True, True, 0) + + dialog.vbox.show_all() + #dialog.set_size_request(400, 300) + + if dialog.run() == gtk.RESPONSE_ACCEPT: + n = comboCategory.get_active() + if (n>-1) and (self.notizen.noteid!= -1): + model = comboCategory.get_model() + active = comboCategory.get_active() + if active < 0: + return None + cat_id = model[active][0] + + noteid, category, note = self.db.loadNote(self.notizen.noteid) + #print noteid, category, cat_id + self.db.saveNote(noteid, note, cat_id, pcdatum = None) + self.topBox.comboCategoryChanged() + else: + mbox = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("No note selected.")) + response = mbox.run() + mbox.hide() + mbox.destroy() + + dialog.destroy() + + def sync_finished(self, data = None, data2 = None): + self.topBox.loadCategories() + self.notizen.loadNotes() + + def prepare_sync_dialog(self): + self.sync_dialog = gtk.Dialog(_("Sync"), None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + + self.sync_dialog.set_position(gtk.WIN_POS_CENTER) + sync = libsync.Sync(self.db, self.window, 50504) + self.sync_dialog.vbox.pack_start(sync, True, True, 0) + self.sync_dialog.set_size_request(500, 350) + self.sync_dialog.vbox.show_all() + sync.connect("syncFinished", self.sync_finished) + + def sync_notes(self, widget = None, data = None): + self.sync_dialog.run() + self.sync_dialog.hide() + + def toggle_word_wrap(self, *args): + self._wordWrapEnabled = not self._wordWrapEnabled + self.notizen.set_wordwrap(self._wordWrapEnabled) + + def delete_event(self, widget, event, data = None): + return False + + def destroy(self, widget = None, data = None): + self.db.close() + if self._osso_c: + self._osso_c.close() + gtk.main_quit() + + 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 + + @note Hildon specific + """ + if memory_low: + gc.collect() + + if save_unsaved_data or shutdown: + pass + + def on_window_state_change(self, widget, event, *args): + if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: + self.window_in_fullscreen = True + else: + self.window_in_fullscreen = False + + def on_key_press(self, widget, event, *args): + if event.keyval == gtk.keysyms.F6: + # The "Full screen" hardware key has been pressed + if self.window_in_fullscreen: + self.window.unfullscreen () + else: + self.window.fullscreen () + elif event.keyval == gtk.keysyms.F7: + # Zoom In + self.topBox.hide() + elif event.keyval == gtk.keysyms.F8: + # Zoom Out + self.topBox.show() + + def main(self): + gtk.main() diff --git a/src/libspeichern.py b/src/libspeichern.py new file mode 100644 index 0000000..6e0d9e4 --- /dev/null +++ b/src/libspeichern.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" + Copyright (C) 2007 Christoph Würstle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. +""" + + +import sys +import os +import time +import sqlite3 +import shelve +import logging + + +try: + _ +except NameError: + _ = lambda x: x + + +class Speichern(): + + def __init__(self): + home_dir = os.path.expanduser('~') + filename = os.path.join(home_dir, ".quicknote.dat") + self.d = shelve.open(filename) + self.openDB() + + def speichereDirekt(self, schluessel, daten): + self.d[schluessel] = daten + logging.info("speichereDirekt "+str(schluessel)+" "+str(daten)+" lesen: "+str(self.d[schluessel])) + + def ladeDirekt(self, schluessel, default = ""): + if (self.d.has_key(schluessel) == True): + data = self.d[schluessel] + return data + else: + return default + + def speichereSQL(self, sql, tupel = None, commit = True, host = "self", log = True, pcdatum = None, rowid = ""): + try: + programSQLError = True + if tupel is None: + self.cur.execute(sql) + else: + self.cur.execute(sql, tupel) + programSQLError = False + + if (log == True): + strtupel = [] + if tupel is not None: + for t in tupel: + strtupel.append(str(t)) + + if pcdatum is None: + pcdatum = int(time.time()) + self.cur.execute("INSERT INTO logtable ( pcdatum, sql, param, host, rowid ) VALUES (?, ?, ?, ?, ?)", (pcdatum, sql, " <> ".join(strtupel), host, str(rowid) )) + if commit: + self.conn.commit() + + return True + except StandardError: + s = str(sys.exc_info()) + if s.find(" already exists") == -1: + if (programSQLError == True): + logging.error("speichereSQL-Exception "+str(sys.exc_info())+" "+str(sql)+" "+str(tupel)) + else: + logging.error("speichereSQL-Exception in Logging!!!! :"+str(sys.exc_info())+" "+str(sql)+" "+str(tupel)) + return False + + def commitSQL(self): + self.conn.commit() + + def ladeSQL(self, sql, tupel = None): + #print sql, tupel + try: + if tupel is None: + self.cur.execute(sql) + else: + self.cur.execute(sql, tupel) + return self.cur.fetchall() + except StandardError: + logging.error("ladeSQL-Exception "+str(sys.exc_info())+" "+str(sql)+" "+str(tupel)) + return () + + def ladeHistory(self, sql_condition, param_condition): + sql = "SELECT * FROM logtable WHERE sql LIKE '%"+str(sql_condition)+"%' AND param LIKE '%"+str(param_condition)+"%'" + rows = self.ladeSQL(sql) + #print rows + erg = [] + for row in rows: + datum = time.strftime("%d.%m.%y %H:%M:%S", (time.localtime(row[1]))) + erg.append([row[1], datum, row[2], row[3], row[3].split(" <> ")]) + + return erg + + def openDB(self): + try: + self.cur.close() + except StandardError: + pass + try: + self.conn.close() + except StandardError: + pass + + db = self.ladeDirekt("datenbank") + if db == "": + home_dir = os.path.expanduser('~') + + #on hildon user not home-dir but /home/user/MyDocs + if home_dir == "/home/user": + if os.path.exists(home_dir+os.sep+"MyDocs/"): + home_dir = home_dir+os.sep+"MyDocs/" + db = os.path.join(home_dir, "quicknote.s3db") + + self.conn = sqlite3.connect(db) + self.cur = self.conn.cursor() + try: + sql = "CREATE TABLE logtable (id INTEGER PRIMARY KEY AUTOINCREMENT, pcdatum INTEGER , sql TEXT, param TEXT, host TEXT, rowid TEXT)" + self.cur.execute(sql) + self.conn.commit() + except StandardError: + pass + + #Add rowid line (not in old versions included) + try: + sql = "ALTER TABLE logtable ADD rowid TEXT" + self.cur.execute(sql) + self.conn.commit() + except StandardError: + pass + + #Create notes table + try: + sql = "CREATE TABLE notes (noteid TEXT, pcdatum INTEGER , category TEXT, note TEXT)" + self.cur.execute(sql) + self.conn.commit() + except StandardError: + pass + + def saveNote(self, noteid, note, category, pcdatum = None): + if category == "%": + category = "" + sql = "SELECT noteid, pcdatum, category, note FROM notes WHERE noteid = ?" + rows = self.ladeSQL(sql, (noteid, )) + + if rows is None or len(rows) == 0: + sql = "INSERT INTO notes (noteid, pcdatum, category, note) VALUES (?, ?, ?, ?)" + if pcdatum is None: + pcdatum = int(time.time()) + self.speichereSQL(sql, (noteid, pcdatum, category, note), rowid = noteid) + else: + sql = "UPDATE notes SET category = ?, note = ?, pcdatum = ? WHERE noteid = ?" + self.speichereSQL(sql, (category, note, str(int(time.time())), noteid), rowid = noteid) + + def loadNote(self, noteid): + if noteid is None or str(noteid) == "": + return (None, None, None) + sql = "SELECT noteid, pcdatum, category, note FROM notes WHERE noteid = ?" + rows = self.ladeSQL(sql, (noteid, )) + if rows is None or len(rows) == 0: + return None + else: + noteid, pcdatum, category, note = rows[0] + return (noteid, pcdatum, category, note) + + def delNote(self, noteid): + sql = "DELETE FROM notes WHERE noteid = ?" + self.speichereSQL(sql, (noteid, ), rowid = noteid) + + def searchNotes(self, searchstring, category): + sql = "SELECT noteid, category, note FROM notes WHERE note like ? AND category like ? ORDER BY note" + rows = self.ladeSQL(sql, ("%"+searchstring+"%", category)) + if rows is None or len(rows) == 0: + return None + else: + return rows + + def getNoteHistory(self, noteid): + return self.ladeHistory("UPDATE notes ", noteid) + + def close(self): + try: + self.d.close() + except StandardError: + pass + try: + self.cur.close() + except StandardError: + pass + try: + self.conn.close() + except StandardError: + pass + logging.info("Alle Data saved") + + def __del__(self): + self.close() diff --git a/src/libsqldialog.py b/src/libsqldialog.py new file mode 100755 index 0000000..e674692 --- /dev/null +++ b/src/libsqldialog.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" + Copyright (C) 2007 Christoph Würstle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. +""" + + +import time +import logging + +import gtk + + +try: + _ +except NameError: + _ = lambda x: x + + +class sqlDialog(gtk.Dialog): + + def __init__(self, db): + self.db = db + + logging.info("sqldialog, init") + + gtk.Dialog.__init__(self, _("SQL History (the past two days):"), None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + + self.add_button("Export", 444) + self.set_position(gtk.WIN_POS_CENTER) + + self.liststore = gtk.ListStore(str, str, str) + + # create the TreeView using liststore + self.treeview = gtk.TreeView(self.liststore) + + # create a CellRenderers to render the data + self.cell1 = gtk.CellRendererText() + self.cell2 = gtk.CellRendererText() + self.cell3 = gtk.CellRendererText() + + # create the TreeViewColumns to display the data + self.tvcolumn1 = gtk.TreeViewColumn(_('Timestamp')) + self.tvcolumn2 = gtk.TreeViewColumn('SQL') + self.tvcolumn3 = gtk.TreeViewColumn(_('Parameter')) + + # add columns to treeview + self.treeview.append_column(self.tvcolumn1) + self.treeview.append_column(self.tvcolumn2) + self.treeview.append_column(self.tvcolumn3) + + + self.tvcolumn1.pack_start(self.cell1, True) + self.tvcolumn2.pack_start(self.cell2, True) + self.tvcolumn3.pack_start(self.cell3, True) + + self.tvcolumn1.set_attributes(self.cell1, text = 0) #Spalten setzten hier!!!! + self.tvcolumn2.set_attributes(self.cell2, text = 1) + self.tvcolumn3.set_attributes(self.cell3, text = 2) + + # Allow NOT drag and drop reordering of rows + self.treeview.set_reorderable(False) + + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled_window.add(self.treeview) + + self.vbox.pack_start(scrolled_window, True, True, 0) + + self.vbox.show_all() + + msgstring = "" + sql = "SELECT pcdatum, sql, param FROM logtable WHERE pcdatum>? ORDER BY pcdatum DESC" + rows = db.ladeSQL(sql, (time.time()-3*24*3600, )) + for row in rows: + pcdatum, sql, param = row + datum = str(time.strftime(_("%d.%m.%y %H:%M:%S "), (time.localtime(pcdatum)))) + self.liststore.append([datum, sql, param]) + + self.set_size_request(500, 400) + + def exportSQL(self, filename): + f = open(filename, 'w') + try: + msgstring = "" + sql = "SELECT pcdatum, sql, param FROM logtable WHERE pcdatum>? ORDER BY pcdatum DESC" + rows = self.db.ladeSQL(sql, (time.time()-2*24*3600, )) + for row in rows: + pcdatum, sql, param = row + datum = str(time.strftime("%d.%m.%y %H:%M:%S ", (time.localtime(pcdatum)))) + f.write( datum +"\t" + sql + "\t\t" + param+ "\n") + finally: + f.close() diff --git a/src/libsync.py b/src/libsync.py new file mode 100755 index 0000000..48bcc86 --- /dev/null +++ b/src/libsync.py @@ -0,0 +1,365 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + + +import sys +import time +import SimpleXMLRPCServer +import xmlrpclib +import select +#import fcntl +import uuid +import logging +import socket +socket.setdefaulttimeout(60) # Timeout auf 60 sec. setzen + +import gtk +import gobject + + +try: + _ +except NameError: + _ = lambda x: x + + +class ProgressDialog(gtk.Dialog): + + def __init__(self, title = _("Sync process"), parent = None): + gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, ()) + + logging.info("ProgressDialog, init") + + label = gtk.Label(_("Sync process running...please wait")) + self.vbox.pack_start(label, True, True, 0) + label = gtk.Label(_("(this can take some minutes)")) + self.vbox.pack_start(label, True, True, 0) + + self.vbox.show_all() + self.show() + + def pulse(self): + pass + + +class Sync(gtk.VBox): + + __gsignals__ = { + 'syncFinished' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, )), + 'syncBeforeStart' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, )), + } + + def __init__(self, db, parentwindow, port): + gtk.VBox.__init__(self, homogeneous = False, spacing = 0) + + logging.info("Sync, init") + self.db = db + self.progress = None + self.server = None + self.port = int(port) + self.parentwindow = parentwindow + self.concernedRows = None + + sql = "CREATE TABLE sync (id INTEGER PRIMARY KEY, syncpartner TEXT, uuid TEXT, pcdatum INTEGER)" + self.db.speichereSQL(sql, log = False) + + sql = "SELECT uuid, pcdatum FROM sync WHERE syncpartner = ?" + rows = self.db.ladeSQL(sql, ("self", )) #Eigene Id feststellen + + if (rows is None)or(len(rows)!= 1): + sql = "DELETE FROM sync WHERE syncpartner = ?" + self.db.speichereSQL(sql, ("self", ), log = False) + + self.sync_uuid = str(uuid.uuid4()) + sql = "INSERT INTO sync (syncpartner, uuid, pcdatum) VALUES (?, ?, ?)" + self.db.speichereSQL(sql, ("self", str(self.sync_uuid), int(time.time())), log = False) + else: + sync_uuid, pcdatum = rows[0] + self.sync_uuid = sync_uuid + + frame = gtk.Frame(_("Local SyncServer (port ")+str(self.port)+")") + + self.comboIP = gtk.combo_box_entry_new_text() + + self.comboIP.append_text("") #self.get_ip_address("eth0")) + + frame.add(self.comboIP) + serverbutton = gtk.ToggleButton(_("Start/Stop SyncServer")) + serverbutton.connect("clicked", self.startServer, (None, )) + self.pack_start(frame, expand = False, fill = True, padding = 1) + self.pack_start(serverbutton, expand = False, fill = True, padding = 1) + self.syncServerStatusLabel = gtk.Label(_("SyncServer stopped")) + self.pack_start(self.syncServerStatusLabel, expand = False, fill = True, padding = 1) + + frame = gtk.Frame(_("Remote SyncServer (port ")+str(self.port)+")") + self.comboRemoteIP = gtk.combo_box_entry_new_text() + self.comboRemoteIP.append_text("192.168.0.?") + self.comboRemoteIP.append_text("192.168.1.?") + self.comboRemoteIP.append_text("192.168.176.?") + frame.add(self.comboRemoteIP) + syncbutton = gtk.Button(_("Connect to remote SyncServer")) + syncbutton.connect("clicked", self.syncButton, (None, )) + self.pack_start(frame, expand = False, fill = True, padding = 1) + self.pack_start(syncbutton, expand = False, fill = True, padding = 1) + self.syncStatusLabel = gtk.Label(_("no sync process (at the moment)")) + self.pack_start(self.syncStatusLabel, expand = False, fill = True, padding = 1) + + self.comboRemoteIP.get_child().set_text(self.db.ladeDirekt("syncRemoteIP")) + self.comboIP.get_child().set_text(self.db.ladeDirekt("syncServerIP")) + + #load + if self.db.ladeDirekt("startSyncServer", False): + serverbutton.set_active(True) + + def changeSyncStatus(self, active, title): + self.syncStatusLabel.set_text(title) + if active == True: + if self.progress is None: + self.progress = ProgressDialog(parent = self.parentwindow) + self.emit("syncBeforeStart", "syncBeforeStart") + else: + if self.progress is not None: + self.progress.hide() + self.progress.destroy() + self.progress = None + self.emit("syncFinished", "syncFinished") + + def pulse(self): + if self.progress is not None: + self.progress.pulse() + + def getUeberblickBox(self): + frame = gtk.Frame(_("Query")) + return frame + + def handleRPC(self): + try: + if self.rpcserver is None: + return False + except StandardError: + return False + + while 0 < len(self.poll.poll(0)): + self.rpcserver.handle_request() + return True + + def get_ip_address(self, ifname): + return socket.gethostbyname(socket.gethostname()) + + def getLastSyncDate(self, sync_uuid): + sql = "SELECT syncpartner, pcdatum FROM sync WHERE uuid = ?" + rows = self.db.ladeSQL(sql, (sync_uuid, )) + if rows is not None and len(rows) == 1: + syncpartner, pcdatum = rows[0] + else: + pcdatum = -1 + logging.info("LastSyncDatum: "+str(pcdatum)+" Jetzt "+str(int(time.time()))) + return pcdatum + + def check4commit(self, newSQL, lastdate): + logging.info("check4commit 1") + if self.concernedRows is None: + logging.info("check4commit Updatung concernedRows") + sql = "SELECT pcdatum, rowid FROM logtable WHERE pcdatum>? ORDER BY pcdatum DESC" + self.concernedRows = self.db.ladeSQL(sql, (lastdate, )) + + if self.concernedRows is not None and 0 < len(self.concernedRows): + id1, pcdatum, sql, param, host, rowid = newSQL + + if 0 < len(rowid): + for x in self.concernedRows: + if x[1] == rowid: + if pcdatum < x[0]: + logging.info("newer sync entry, ignoring old one") + return False + else: + return True + + return True + + def writeSQLTupel(self, newSQLs, lastdate): + if newSQLs is None: + return + + self.concernedRows = None + pausenzaehler = 0 + logging.info("writeSQLTupel got "+str(len(newSQLs))+" sql tupels") + for newSQL in newSQLs: + if newSQL[3] != "": + param = newSQL[3].split(" <> ") + else: + param = None + + if 2 < len(newSQL): + commitSQL = True + + if (newSQL[5]!= None)and(len(newSQL[5])>0): + commitSQL = self.check4commit(newSQL, lastdate) + + if (commitSQL == True): + self.db.speichereSQL(newSQL[2], param, commit = False, pcdatum = newSQL[1], rowid = newSQL[5]) + else: + logging.error("writeSQLTupel: Error") + + pausenzaehler += 1 + if (pausenzaehler % 10) == 0: + self.pulse() + while gtk.events_pending(): + gtk.main_iteration() + + logging.info("Alle SQLs an sqlite geschickt, commiting now") + self.db.commitSQL() + logging.info("Alle SQLs commited") + + def doSync(self, sync_uuid, pcdatum, newSQLs, pcdatumjetzt): + self.changeSyncStatus(True, "sync process running") + self.pulse() + + while gtk.events_pending(): + gtk.main_iteration() + diff = abs(time.time() - pcdatumjetzt) + if 30 < diff: + return -1 + + logging.info("doSync read sqls") + sql = "SELECT * FROM logtable WHERE pcdatum>?" + rows = self.db.ladeSQL(sql, (pcdatum, )) + logging.info("doSync read sqls") + self.writeSQLTupel(newSQLs, pcdatum) + logging.info("doSync wrote "+str(len(newSQLs))+" sqls") + logging.info("doSync sending "+str(len(rows))+" sqls") + return rows + + def getRemoteSyncUUID(self): + return self.sync_uuid + + def startServer(self, widget, data = None): + #Starte RPCServer + self.db.speichereDirekt("syncServerIP", self.comboIP.get_child().get_text()) + + if widget.get_active(): + logging.info("Starting Server") + + try: + ip = self.comboIP.get_child().get_text() + self.rpcserver = SimpleXMLRPCServer.SimpleXMLRPCServer((ip, self.port), allow_none = True) + self.rpcserver.register_function(pow) + self.rpcserver.register_function(self.getLastSyncDate) + self.rpcserver.register_function(self.doSync) + self.rpcserver.register_function(self.getRemoteSyncUUID) + self.rpcserver.register_function(self.doSaveFinalTime) + self.rpcserver.register_function(self.pulse) + self.poll = select.poll() + self.poll.register(self.rpcserver.fileno()) + gobject.timeout_add(1000, self.handleRPC) + self.syncServerStatusLabel.set_text(_("Syncserver running...")) + + #save + self.db.speichereDirekt("startSyncServer", True) + + except StandardError: + s = str(sys.exc_info()) + logging.error("libsync: could not start server. Error: "+s) + mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Could not start SyncServer. Check IP, port settings.")) #gtk.DIALOG_MODAL + mbox.set_modal(False) + response = mbox.run() + mbox.hide() + mbox.destroy() + widget.set_active(False) + else: + logging.info("Stopping Server") + try: + del self.rpcserver + except StandardError: + pass + self.syncServerStatusLabel.set_text(_("SyncServer stopped")) + #save + self.db.speichereDirekt("startSyncServer", False) + + def doSaveFinalTime(self, sync_uuid, pcdatum = None): + if pcdatum is None: + pcdatum = int(time.time()) + if pcdatum < time.time(): + pcdatum = int(time.time()) #größere Zeit nehmen + + self.pulse() + + #fime save time+uuid + sql = "DELETE FROM sync WHERE uuid = ?" + self.db.speichereSQL(sql, (sync_uuid, ), log = False) + sql = "INSERT INTO sync (syncpartner, uuid, pcdatum) VALUES (?, ?, ?)" + self.db.speichereSQL(sql, ("x", str(sync_uuid), pcdatum), log = False) + self.pulse() + self.changeSyncStatus(False, _("no sync process (at the moment)")) + return (self.sync_uuid, pcdatum) + + def syncButton(self, widget, data = None): + logging.info("Syncing") + + self.changeSyncStatus(True, _("sync process running")) + while (gtk.events_pending()): + gtk.main_iteration() + + self.db.speichereDirekt("syncRemoteIP", self.comboRemoteIP.get_child().get_text()) + try: + self.server = xmlrpclib.ServerProxy("http://"+self.comboRemoteIP.get_child().get_text()+":"+str(self.port), allow_none = True) + server_sync_uuid = self.server.getRemoteSyncUUID() + lastDate = self.getLastSyncDate(str(server_sync_uuid)) + + sql = "SELECT * FROM logtable WHERE pcdatum>?" + rows = self.db.ladeSQL(sql, (lastDate, )) + + logging.info("loaded concerned rows") + + newSQLs = self.server.doSync(self.sync_uuid, lastDate, rows, time.time()) + + logging.info("did do sync, processing sqls now") + if newSQLs != -1: + self.writeSQLTupel(newSQLs, lastDate) + + sync_uuid, finalpcdatum = self.server.doSaveFinalTime(self.sync_uuid) + self.doSaveFinalTime(sync_uuid, finalpcdatum) + + self.changeSyncStatus(False, _("no sync process (at the moment)")) + + mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("Synchronization successfully completed")) + response = mbox.run() + mbox.hide() + mbox.destroy() + else: + logging.warning("Zeitdiff zu groß/oder anderer db-Fehler") + self.changeSyncStatus(False, _("no sync process (at the moment)")) + mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("The clocks are not synchronized between stations")) + response = mbox.run() + mbox.hide() + mbox.destroy() + except StandardError: + logging.warning("Sync connect failed") + self.changeSyncStatus(False, _("no sync process (at the moment)")) + mbox = gtk.MessageDialog( + None, + gtk.DIALOG_MODAL, + gtk.MESSAGE_INFO, + gtk.BUTTONS_OK, + _("Sync failed, reason: ")+unicode(sys.exc_info()[1][1]) + ) + response = mbox.run() + mbox.hide() + mbox.destroy() + self.server = None + self.server = None diff --git a/src/quicknote.py b/src/quicknote.py new file mode 100755 index 0000000..c471891 --- /dev/null +++ b/src/quicknote.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2007 Christoph Würstle +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. +""" + +import os +import sys +sys.path.append('/usr/lib/quicknote') + +import locale +import gettext +gettext.install('quicknote', unicode = 1) + +import libquicknote + +if __name__ == "__main__": + app = libquicknote.quicknoteclass() + app.main() diff --git a/src/simple_list.py b/src/simple_list.py new file mode 100644 index 0000000..027185a --- /dev/null +++ b/src/simple_list.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python2.5 +# -*- coding: utf-8 -*- + +""" + Copyright (C) 2007 Christoph Würstle and in big parts by Gerold Penz + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. +""" + + +import pygtk +pygtk.require("2.0") +import gtk + + +try: + _ +except NameError: + _ = lambda x: x + + +class SimpleList(gtk.ScrolledWindow): + """ + Stellt eine einfache Liste mit Laufbalken dar. Das wird mit + den Objekten ScrolledWindow und TreeView erreicht. + """ + + def __init__(self): + """ + Initialisieren + """ + + gtk.ScrolledWindow.__init__(self) + self.selected_item = None # (, , ) + + # Liste + self.list_store = gtk.ListStore(str, str) + + # ScrolledWindow + self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.set_shadow_type(gtk.SHADOW_IN) + + # Treeview + self.tree_view = gtk.TreeView(self.list_store) + self.tree_view.set_headers_visible(False) + self.tree_view.get_selection().set_mode(gtk.SELECTION_BROWSE) + self.tree_view.connect("cursor-changed", self._on_cursor_changed) + self.tree_view.connect("row-activated", self._on_row_activated) + self.tree_view.show() + + # Key-Spalte hinzuf�gen + self.key_cell = gtk.CellRendererText() + self.key_column = gtk.TreeViewColumn("Key") + self.key_column.pack_start(self.key_cell, True) + self.key_column.add_attribute(self.key_cell, "text", 0) + self.key_column.set_visible(False) + self.tree_view.append_column(self.key_column) + + # Value-Spalte hinzufügen + self.value_cell = gtk.CellRendererText() + self.value_column = gtk.TreeViewColumn("Caption") + self.value_column.pack_start(self.value_cell, True) + self.value_column.add_attribute(self.value_cell, "text", 1) + self.tree_view.append_column(self.value_column) + + # Suchspalte setzen + # Leider funktioniert die Suche im Moment nicht so + # wie ich das möchte. Deshalb habe ich die Suche abgeschaltet. + self.tree_view.set_enable_search(False) + + # Anzeigen + self.add(self.tree_view) + self.show() + + def append_item(self, value, key = ""): + """ + F�gt der Liste Werte und wenn gew�nscht, Schl�ssel hinzu. + """ + + self.list_store.append([key, value]) + + def select_last_item(self): + path = str(len(self.list_store)-1) + self.tree_view.set_cursor(path, self.value_column) + return len(self.list_store)-1 + + def change_item(self, pos, value, key = ""): + self.list_store[pos] = [key, value] + + def remove_item(self, pos): + model = self.tree_view.get_model() + self.list_store.remove(model.get_iter(str(pos))) + + def get_item(self, pos): + return self.list_store[pos] + + def clear_items(self): + self.list_store.clear() + + def _on_row_activated(self, treeview, path, view_column, data = None): + """ + Setzt den Wert von self.selected_items. Dieser Wert kann + mit der Methode "get_selection_data" abgerufen werden. + """ + + iter = self.list_store.get_iter(path) + + if iter: + self.selected_item = ( + path[0], # Position + self.list_store.get_value(iter, 0), # Key + self.list_store.get_value(iter, 1) # Value + ) + + def _on_cursor_changed(self, widget, data1 = None, data2 = None): + """ + Setzt den Wert von self.selected_items. Dieser Wert kann + mit der Methode "get_selection_data" abgerufen werden. + """ + + selection = widget.get_selection() + (model, iter) = selection.get_selected() + + if iter: + self.selected_item = ( + int(selection.get_selected_rows()[1][0][0]), # Position + str(model.get_value(iter, 0)), # Key + str(model.get_value(iter, 1)) # Value + ) + + def get_selection_data(self): + """ + Gibt die Variable self.selected_item zur�ck. + Diese enth�lt ein Tupel. (, , ) + """ + + return self.selected_item # (, , ) + + def set_eventfunction__cursor_changed(self, function): + """ + Verbindet die �bergebene Funktion mit dem + Signal "cursor-changed". + """ + + self.tree_view.connect("cursor-changed", function) diff --git a/support/builddeb.py b/support/builddeb.py new file mode 100755 index 0000000..3b5b76c --- /dev/null +++ b/support/builddeb.py @@ -0,0 +1,118 @@ +#!/usr/bin/python2.5 + +import os + +try: + import py2deb +except ImportError: + import fake_py2deb as py2deb + + +__appname__ = "quicknote" +__description__ = "Simple note taking application in a similar vein as PalmOS Memos" +__author__ = "Christoph Wurstle" +__email__ = "n800@axique.net" +__version__ = "0.7.6" +__build__ = 1 +__changelog__ = '''\ +0.7.6 + * Line-wrap + * Zoom + +0.7.4 + * fixed small bugs + * move category + +0.7.3 + * fixed small bugs + * move category + +0.7.2 + * improved sync, fixed a small bug + +0.7.1 + * improved sync + +0.7.0 + * Initial Release. +''' + + +__postinstall__ = '''#!/bin/sh -e + +gtk-update-icon-cache -f /usr/share/icons/hicolor +exit 0 +''' + + +def find_files(path, root): + print path, root + for unusedRoot, dirs, files in os.walk(path): + for file in files: + if file.startswith(root+"-"): + print "\t", root, file + fileParts = file.split("-") + unused, relPathParts, newName = fileParts[0], fileParts[1:-1], fileParts[-1] + assert unused == root + relPath = os.sep.join(relPathParts) + yield relPath, file, newName + + +def unflatten_files(files): + d = {} + for relPath, oldName, newName in files: + if relPath not in d: + d[relPath] = [] + d[relPath].append((oldName, newName)) + return d + + +if __name__ == "__main__": + try: + os.chdir(os.path.dirname(sys.argv[0])) + except: + pass + + p = py2deb.Py2deb(__appname__) + p.description = __description__ + p.author = __author__ + p.mail = __email__ + p.license = "lgpl" + p.depends = "python2.5, python2.5-gtk2" + p.section = "user/other" + p.arch = "all" + p.urgency = "low" + p.distribution = "chinook diablo" + p.repository = "extras-devel" + p.changelog = __changelog__ + p.postinstall = __postinstall__ + p.icon = "26x26-quicknote.png" + p["/usr/bin"] = [ "quicknote.py" ] + for relPath, files in unflatten_files(find_files(".", "locale")).iteritems(): + fullPath = "/usr/share/locale" + if relPath: + fullPath += os.sep+relPath + p[fullPath] = list( + "|".join((oldName, newName)) + for (oldName, newName) in files + ) + for relPath, files in unflatten_files(find_files(".", "src")).iteritems(): + fullPath = "/usr/lib/quicknote" + if relPath: + fullPath += os.sep+relPath + p[fullPath] = list( + "|".join((oldName, newName)) + for (oldName, newName) in files + ) + p["/usr/share/applications/hildon"] = ["quicknote.desktop"] + p["/usr/share/dbus-1/services"] = ["quicknote.service"] + p["/usr/share/icons/hicolor/26x26/hildon"] = ["26x26-quicknote.png|quicknote.png"] + p["/usr/share/icons/hicolor/40x40/hildon"] = ["40x40-quicknote.png|quicknote.png"] + p["/usr/share/icons/hicolor/48x48/hildon"] = ["48x48-quicknote.png|quicknote.png"] + p["/usr/share/icons/hicolor/scalable/hildon"] = ["scale-quicknote.png|quicknote.png"] + + print p + print p.generate( + __version__, __build__, changelog=__changelog__, + tar=True, dsc=True, changes=True, build=False, src=True + ) diff --git a/support/fake_py2deb.py b/support/fake_py2deb.py new file mode 100644 index 0000000..5d6149d --- /dev/null +++ b/support/fake_py2deb.py @@ -0,0 +1,56 @@ +import pprint + + +class Py2deb(object): + + def __init__(self, appName): + self._appName = appName + self.description = "" + self.author = "" + self.mail = "" + self.license = "" + self.depends = "" + self.section = "" + self.arch = "" + self.ugency = "" + self.distribution = "" + self.repository = "" + self.changelog = "" + self.postinstall = "" + self.icon = "" + self._install = {} + + def generate(self, appVersion, appBuild, changelog, tar, dsc, changes, build, src): + return """ +Package: %s +version: %s-%s +Changes: +%s + +Build Options: + Tar: %s + Dsc: %s + Changes: %s + Build: %s + Src: %s + """ % ( + self._appName, appVersion, appBuild, changelog, tar, dsc, changes, build, src + ) + + def __str__(self): + parts = [] + parts.append("%s Package Settings:" % (self._appName, )) + for settingName in dir(self): + if settingName.startswith("_"): + continue + parts.append("\t%s: %s" % (settingName, getattr(self, settingName))) + + parts.append(pprint.pformat(self._install)) + + return "\n".join(parts) + + def __getitem__(self, key): + return self._install[key] + + def __setitem__(self, key, item): + self._install[key] = item diff --git a/support/pylint.rc b/support/pylint.rc new file mode 100644 index 0000000..37b9725 --- /dev/null +++ b/support/pylint.rc @@ -0,0 +1,305 @@ +# lint Python modules using external checkers. +# +# This is the main checker controling the other ones and the reports +# generation. It is itself both a raw checker and an astng checker in order +# to: +# * handle message activation / deactivation at the module level +# * handle some basic but necessary stats'data (number of classes, methods...) +# +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add to the black list. It should be a base name, not a +# path. You may set this option multiple times. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# Set the cache size for astng objects. +cache-size=500 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable only checker(s) with the given id(s). This option conflicts with the +# disable-checker option +#enable-checker= + +# Enable all checker(s) except those with the given id(s). This option +# conflicts with the enable-checker option +#disable-checker= + +# Enable all messages in the listed categories. +#enable-msg-cat= + +# Disable all messages in the listed categories. +#disable-msg-cat= + +# Enable the message(s) with the given id(s). +#enable-msg= + +# Disable the message(s) with the given id(s). +disable-msg=W0403,W0612,W0613,C0103,C0111,C0301,R0903,W0142,W0603,R0904 + +[REPORTS] + +# set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=colorized + +# Include message's id in output +include-ids=yes + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells wether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note).You have access to the variables errors warning, statement which +# respectivly contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (R0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (R0004). +comment=no + +# Enable the report(s) with the given id(s). +#enable-report= + +# Disable the report(s) with the given id(s). +#disable-report= + + +# checks for +# * unused variables / imports +# * undefined variables +# * redefinition of variable from builtins or from an outer scope +# * use of variable before assigment +# +[VARIABLES] + +# Tells wether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching names used for dummy variables (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +# checks for : +# * doc strings +# * modules / classes / functions / methods / arguments / variables name +# * number of arguments, local variables, branchs, returns and statements in +# functions, methods +# * required module attributes +# * dangerous default values as arguments +# * redefinition of function / method / class +# * uses of the global statement +# +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-zA-Z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-zA-Z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-zA-Z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + + +# try to find bugs in the code using type inference +# +[TYPECHECK] + +# Tells wether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# When zope mode is activated, consider the acquired-members option to ignore +# access to some undefined attributes. +zope=no + +# List of members which are usually get through zope's acquisition mecanism and +# so shouldn't trigger E0201 when accessed (need zope=yes to be considered). +acquired-members=REQUEST,acl_users,aq_parent + + +# checks for sign of poor/misdesign: +# * number of methods, attributes, local variables... +# * size, complexity of functions, methods +# +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=15 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=1 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +# checks for : +# * methods without self as first argument +# * overridden methods signature +# * access only to existant members via self +# * attributes not defined in the __init__ method +# * supported interfaces implementation +# * unreachable code +# +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + + +# checks for +# * external modules dependencies +# * relative / wildcard imports +# * cyclic imports +# * uses of deprecated modules +# +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report R0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report R0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report R0402 must +# not be disabled) +int-import-graph= + + +# checks for similarities and duplicated code. This computation may be +# memory / CPU intensive, so you should disable it if you experiments some +# problems. +# +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + + +# checks for: +# * warning notes in the code like FIXME, XXX +# * PEP 263: source code with non ascii character but no encoding declaration +# +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +# checks for : +# * unauthorized constructions +# * strict indentation +# * line length +# * use of <> instead of != +# +[FORMAT] + +# Maximum number of characters on a single line. +# @note Limiting this to the most extreme cases +max-line-length=100 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string='\t' diff --git a/support/test_syntax.py b/support/test_syntax.py new file mode 100755 index 0000000..65a373c --- /dev/null +++ b/support/test_syntax.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +import commands + + +verbose = False + + +def syntax_test(file): + commandTemplate = """ + python -t -t -W all -c "import py_compile; py_compile.compile ('%(filename)s', doraise=False)" """ + compileCommand = commandTemplate % {"filename": file} + (status, text) = commands.getstatusoutput (compileCommand) + text = text.rstrip() + passed = len(text) == 0 + + if passed: + output = ("Syntax is correct for "+file) if verbose else "" + else: + output = ("Syntax is invalid for %s\n" % file) if verbose else "" + output += text + return (passed, output) + + +if __name__ == "__main__": + import sys + import os + import optparse + + opar = optparse.OptionParser() + opar.add_option("-v", "--verbose", dest="verbose", help="Toggle verbosity", action="store_true", default=False) + options, args = opar.parse_args(sys.argv[1:]) + verbose = options.verbose + + completeOutput = [] + allPassed = True + for filename in args: + passed, output = syntax_test(filename) + if not passed: + allPassed = False + if output.strip(): + completeOutput.append(output) + print "\n".join(completeOutput) + + sys.exit(0 if allPassed else 1);