From 434df58b1624c4bc2f6cbb7de4488f4a4523b9c9 Mon Sep 17 00:00:00 2001 From: gryf Date: Mon, 4 May 2009 16:02:36 +0000 Subject: [PATCH] Head to v.2. Now it uses gtkmvc with version 1.99, makefile, i18n. --- Makefile | 67 +++++++++++++ README | 36 +++++++ generate_pot.py | 163 ++++++++++++++++++++++++++++++++ locale/pl.po | 243 ++++++++++++++++++++++++++++++++++++++++++++++++ pygtktalog.py | 64 +++++-------- 5 files changed, 533 insertions(+), 40 deletions(-) create mode 100644 Makefile create mode 100755 generate_pot.py create mode 100644 locale/pl.po diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a8e931a --- /dev/null +++ b/Makefile @@ -0,0 +1,67 @@ +RUN = PYTHONPATH=/home/gryf/Devel/Python/pyGTKtalog2:/home/gryf/.python_lib python +LOCALE = LC_ALL=pl_PL.utf8 +FILE = pygtktalog.py + +.PHONY: run +run: + @$(RUN) $(FILE) + +.PHONY: runpl +runpl: + @export $(LOCALE) && $(RUN) $(FILE) + +.PHONY: clean +clean: + @find . -name \*~ -exec rm '{}' ';' + @find . -name \*pyc -exec rm '{}' ';' + @find . -name \*pyo -exec rm '{}' ';' + @echo "cleaned." + +.PHONY: distclean +distclean: clean + @rm -fr locale/* + @echo "all cleaned up" + +.PHONY: pot +pot: + @if [ ! -d locale ]; then mkdir locale; fi + @python generate_pot.py pygtktalog pygtktalog > locale/pygtktalog.pot + @echo "locale/pygtktalog.pot (re)generated." + +.PHONY: pltrans +pltrans: pot + @if [ -f locale/pl.po ]; then \ + echo "Merging existing *.po file with regenerated *.pot"; \ + msgmerge -U locale/pl.po locale/pygtktalog.pot; \ + else \ + echo "Creating fresh *.po file"; \ + cp locale/pygtktalog.pot locale/pl.po; \ + fi; + @$$EDITOR locale/pl.po + @if [ ! -d locale/pl/LC_MESSAGES ]; then mkdir -p locale/pl/LC_MESSAGES; fi + @echo "Compile message catalog for pl_PL.utf8" + @msgfmt locale/pl.po -o locale/pl/LC_MESSAGES/pygtktalog.mo + @echo "Message catalog for pl_PL.utf8 saved in locale/pl/LC_MESSAGES/pygtktalog.mo" + +.PHONY: test +test: + cd test && python run_tests.py + +.PHONY: dist +dist: + echo "implement me" + +.PHONY: help +help: + @echo "Possible commands for make are:" + @echo + @echo " run: Run pyGTKtalog. Default." + @echo " runpl: Run pyGTKtalog with LC_ALL set to pl_PL.utf8." + @echo " clean: Remove .pyc, .pyo and .~ files." + @echo " distclean: As above, also removes generated locale files." + @echo " pot: Generate .pot file from sources and .glade files." + @echo " pltrans: Generate/merge polish translation file and then invoke editor." + @echo " Environment variable EDITOR is expected" + @echo " test: Launch unit tests for application." + @echo " dist: Make distribution egg." + @echo diff --git a/README b/README index 2287392..3592c60 100644 --- a/README +++ b/README @@ -121,6 +121,42 @@ design prevent from deleting any file from media directory (placed in ~/.pygtktalog/images). Functionality for exporting images and corresponding db file is planned. +UPDATE +------ +There can be added images for virtually any item in catalog. Therefore there is +some hazard with image filenames. +After long consideration and experiments I've decided, that images for every +item will have file name as follows: + +sha512("filename" + "file size" + "file modification date").hexdigest() + +for thumbnails: +sha512("filename" + "file size" + "file modification date").hexdigest() + "_t" + +Why that way? There is plenty ways to achive goal to keep thumbnails/data with +applications, however I wanted to keep all things in one place, just to prevent +mixing this up with existing, system specific (Gnome, KDE, maybe MacOS, or any +other which is capable to run this application) own solution. Another reason +lays on catalogs update mechanizm. Imagine, that you have large collection of +movie clips and want to frequently add and/or delete somethong from that. +Changing file names of virtually all files is rather rare case. However moving +them between directories will be much more frequent scenario. And now, if you +want to update things in catalog, program will just check if there is such +generated image from movie filename, size and dates, and then it will just +assign that image to file in catalog. No need to wase time again for generating +movie shots all over again. + +Of course there are some limits for such approach. There is relatively small +possibility to generate two filenames that are the same in two cases: + +1. There are two different files (movies or images) with the same name, same +size and same timestamp in different directories. This could happen in case of +images that have fixed size (like BMP) and then due to image/thumbnail creating +policy only the first one will be placed in images directory. + +2. Another possibility........ fuck. + + BUGS ==== diff --git a/generate_pot.py b/generate_pot.py new file mode 100755 index 0000000..f42b873 --- /dev/null +++ b/generate_pot.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Generate POT + ~~~~~~~~~~~~ + + Generate a pot file with all translations for PyGTK applications + based on the organization idea I shared in my article about i18n + in PyGTK applications. + + :copyright: 2006-2007 by Armin Ronacher. + :license: GNU GPL, see LICENSE for more details. +""" +import os +import sys +from xml.dom import minidom +from compiler import parse, ast +from datetime import datetime + +PO_HEADER = """# +# %(name)s Language File +# +msgid "" +msgstr "" +"Project-Id-Version: %(name)s\\n" +"POT-Creation-Date: %(time)s\\n" +"Last-Translator: Roman Dobosz \\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=utf-8\\n" +"Content-Transfer-Encoding: utf-8\\n" +"Generated-By: %(filename)s\\n"\ +""" + +EMPTY_STRING = '' +EMPTY_LINE = ['""\n'] +LINE_SHIFT = ['\\n"\n"'] + + +class StringCollection(object): + """Class for collecting strings.""" + + def __init__(self, basename): + self.db = {} + self.order = [] + self.offset = len(basename) + + def feed(self, file, line, string): + name = file[self.offset:].lstrip('/') + if string not in self.db: + self.db[string] = [(name, line)] + self.order.append(string) + else: + self.db[string].append((name, line)) + + def __iter__(self): + for string in self.order: + yield string, self.db[string] + + +def quote(s): + """Quotes a given string so that it is useable in a .po file.""" + result = ['"'] + firstmatch = True + for char in s: + if char == '\n': + if firstmatch: + result = EMPTY_LINE + result + firstmatch = False + result += LINE_SHIFT + continue + if char in '\t"': + result.append('\\') + result.append(char) + result.append('"') + return EMPTY_STRING.join(result) + + +def scan_python_file(filename, calls): + """Scan a python file for gettext calls.""" + def scan(nodelist): + for node in nodelist: + if isinstance(node, ast.CallFunc): + handle = False + for pos, n in enumerate(node): + if pos == 0: + if isinstance(n, ast.Name) and n.name in calls: + handle = True + elif pos == 1: + if handle: + if n.__class__ is ast.Const and \ + isinstance(n.value, basestring): + yield n.lineno, n.value + break + else: + for line in scan([n]): + yield line + elif hasattr(node, '__iter__'): + for n in scan(node): + yield n + + fp = file(filename) + try: + try: + return scan(parse(fp.read())) + except: + print >> sys.stderr, 'Syntax Error in file %r' % filename + finally: + fp.close() + + +def scan_glade_file(filename): + """Scan a glade file for translatable strings.""" + try: + doc = minidom.parse(filename) + except: + print >> sys.stderr, 'Syntax Error in file %r' % filename + for element in doc.getElementsByTagName('property'): + if element.getAttribute('translatable') == 'yes': + data = element.firstChild.nodeValue + if data and not data.startswith('gtk-'): + yield data + + +def scan_tree(pathname, calls=['_']): + """Scans a tree for translatable strings.""" + out = StringCollection(pathname) + for folder, _, files in os.walk(pathname): + for filename in files: + filename = os.path.join(folder, filename) + if filename.endswith('.py'): + result = scan_python_file(filename, calls) + if result is not None: + for lineno, string in result: + out.feed(filename, lineno, string) + elif filename.endswith('.glade'): + result = scan_glade_file(filename) + if result is not None: + for string in result: + out.feed(filename, None, string) + for line in out: + yield line + + +def run(): + if len(sys.argv) != 3: + print 'usage: %s ' % sys.argv[0] + sys.exit() + print PO_HEADER % { + 'time': datetime.now(), + 'filename': sys.argv[0], + 'name': sys.argv[2], + } + basepath = sys.argv[1] + for string, occurrences in scan_tree(basepath): + print + for path, lineno in occurrences: + print '#. file %r, line %s' % (path, lineno or '?') + print 'msgid %s' % quote(string) + print 'msgstr ""' + + +if __name__ == '__main__': + run() diff --git a/locale/pl.po b/locale/pl.po new file mode 100644 index 0000000..d0226dd --- /dev/null +++ b/locale/pl.po @@ -0,0 +1,243 @@ +# +# pygtktalog Language File +# +msgid "" +msgstr "" +"Project-Id-Version: pygtktalog 1\n" +"POT-Creation-Date: 2009-05-04 14:24:17.873909\n" +"Last-Translator: Roman Dobosz\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Generated-By: ./generate_pot.py\n" + +#. file 'views/glade/main.glade', line ? +msgid "pyGTKtalog" +msgstr "pyGTKtalog" + +#. file 'views/glade/main.glade', line ? +msgid "_File" +msgstr "_Plik" + +#. file 'views/glade/main.glade', line ? +msgid "Import" +msgstr "Import" + +#. file 'views/glade/main.glade', line ? +msgid "Export" +msgstr "Eksport" + +#. file 'views/glade/main.glade', line ? +msgid "Recent files" +msgstr "Ostatnio używane pliki" + +#. file 'views/glade/main.glade', line ? +#. file 'views/glade/main.glade', line ? +msgid "_Edit" +msgstr "_Edycja" + +#. file 'views/glade/main.glade', line ? +msgid "_Catalog" +msgstr "_Katalog" + +#. file 'views/glade/main.glade', line ? +msgid "Add _CD/DVD" +msgstr "Dodaj _Cd/DVD" + +#. file 'views/glade/main.glade', line ? +msgid "Add _Directory" +msgstr "Dodaj kata_log" + +#. file 'views/glade/main.glade', line ? +msgid "Deletes all images from files in current colection" +msgstr "Usuwa wszystkie obrazy z plików w bieżącej kolekcji" + +#. file 'views/glade/main.glade', line ? +msgid "Delete all images" +msgstr "Usuń wszystkie obrazy" + +#. file 'views/glade/main.glade', line ? +msgid "Delete all thumbnals" +msgstr "Usuń wszystkie miniatury" + +#. file 'views/glade/main.glade', line ? +msgid "Save all images..." +msgstr "Zapisz wszytkie obrazy..." + +#. file 'views/glade/main.glade', line ? +msgid "Catalog _statistics" +msgstr "_Statystyka katalogu" + +#. file 'views/glade/main.glade', line ? +#. file 'views/glade/main.glade', line ? +msgid "Cancel" +msgstr "Analuj" + +#. file 'views/glade/main.glade', line ? +msgid "_View" +msgstr "_Widok" + +#. file 'views/glade/main.glade', line ? +msgid "Toolbar" +msgstr "Pasek narzędzi" + +#. file 'views/glade/main.glade', line ? +msgid "Status bar" +msgstr "Pasek stanu" + +#. file 'views/glade/main.glade', line ? +msgid "_Help" +msgstr "_Pomoc" + +#. file 'views/glade/main.glade', line ? +msgid "Create new catalog" +msgstr "Utwórz nowy katalog" + +#. file 'views/glade/main.glade', line ? +msgid "Open catalog file" +msgstr "Otwórz plik katalogu" + +#. file 'views/glade/main.glade', line ? +msgid "Save catalog" +msgstr "Zapisz katalog" + +#. file 'views/glade/main.glade', line ? +msgid "Add CD/DVD to catalog" +msgstr "Dodaj CD/DVD do katalogu" + +#. file 'views/glade/main.glade', line ? +msgid "Add CD" +msgstr "Dodaj CD" + +#. file 'views/glade/main.glade', line ? +msgid "Add Dir" +msgstr "Dodaj katalog" + +#. file 'views/glade/main.glade', line ? +msgid "Find file" +msgstr "Znajdź plik" + +#. file 'views/glade/main.glade', line ? +msgid "Quit pyGTKtalog" +msgstr "Zakończ pyGTKtalog" + +#. file 'views/glade/main.glade', line ? +msgid "Discs" +msgstr "Dyski" + +#. file 'views/glade/main.glade', line ? +msgid "Tags" +msgstr "Tagi" + +#. file 'views/glade/main.glade', line ? +msgid "File info" +msgstr "Informacje o pliku" + +#. file 'views/glade/main.glade', line ? +msgid "Double click to open image" +msgstr "Dwukrotne kliknięcie otwiera obraz" + +#. file 'views/glade/main.glade', line ? +msgid "Images" +msgstr "Obrazy" + +#. file 'views/glade/main.glade', line ? +msgid "EXIF" +msgstr "EXIF" + +#. file 'views/glade/main.glade', line ? +msgid "Expand all nodes" +msgstr "Rozwiń wszystkie gałęzie" + +#. file 'views/glade/main.glade', line ? +msgid "_Expand all" +msgstr "_Rozwiń wszystko" + +#. file 'views/glade/main.glade', line ? +msgid "Collapse all nodes" +msgstr "Zwiń wszystkie gałęzie" + +#. file 'views/glade/main.glade', line ? +msgid "_Collapse all" +msgstr "_Zwiń wszystko" + +#. file 'views/glade/main.glade', line ? +msgid "_Update" +msgstr "_Uaktualnij" + +#. file 'views/glade/main.glade', line ? +#. file 'views/glade/main.glade', line ? +msgid "_Rename" +msgstr "_Zmień nazwę" + +#. file 'views/glade/main.glade', line ? +#. file 'views/glade/main.glade', line ? +msgid "_Delete" +msgstr "_Usuń" + +#. file 'views/glade/main.glade', line ? +msgid "_Statistics" +msgstr "_Statystyka" + +#. file 'views/glade/main.glade', line ? +msgid "_Add tag" +msgstr "_Dodaj tag" + +#. file 'views/glade/main.glade', line ? +msgid "Remo_ve tag" +msgstr "Usuń tag" + +#. file 'views/glade/main.glade', line ? +msgid "Add _Thumbnail" +msgstr "Dodaj miniaturę" + +#. file 'views/glade/main.glade', line ? +msgid "Re_move Thumbnail" +msgstr "Usuń miniaturę" + +#. file 'views/glade/main.glade', line ? +msgid "" +"Add images to file. If file have no thumbnail,\n" +"thumbnail from first image will be generated." +msgstr "Dodanie obrazów do pliku. Jeśli plik nie posiada miniatury,\n" +"zostanie wygenerowana miniatura z pierwszego obrazu." + +#. file 'views/glade/main.glade', line ? +msgid "Add _Images" +msgstr "Dodaj _obrazy" + +#. file 'views/glade/main.glade', line ? +msgid "Rem_ove All Images" +msgstr "Usuń wszystkie obrazy" + +#. file 'views/glade/main.glade', line ? +msgid "pyGTKtalog - Image" +msgstr "pyGTKtalog - Obraz" + +#. file 'views/glade/main.glade', line ? +msgid "_Add images" +msgstr "Dodaj obrazy" + +#. file 'views/glade/main.glade', line ? +msgid "_Delete images" +msgstr "Usuń obrazy" + +#. file 'views/glade/main.glade', line ? +msgid "Set as _thumbnail" +msgstr "Ustaw jako miniaturę" + +#. file 'views/glade/main.glade', line ? +msgid "_Save images to..." +msgstr "Zapisz obrazy do..." + +#. file 'views/glade/main.glade', line ? +msgid "_Remove Thumbnail" +msgstr "Usuń miniaturę" + +#. file 'views/glade/main.glade', line ? +msgid "Delete tag" +msgstr "Usuń tag" + +#. file 'models/main.py', line 11 +msgid "Idle" +msgstr "Bezczynny" diff --git a/pygtktalog.py b/pygtktalog.py index 282941d..f180a58 100644 --- a/pygtktalog.py +++ b/pygtktalog.py @@ -10,65 +10,49 @@ import os import locale import gettext +import __builtin__ + import gtk import pygtk pygtk.require("2.0") import gtkmvc -gtkmvc.require("1.2.2") +gtkmvc.require("1.99.0") -from src.lib.globs import TOPDIR -from src.lib.globs import APPL_SHORT_NAME -sys.path = [os.path.join(TOPDIR, "src")] + sys.path -from models.m_config import ConfigModel -from models.m_main import MainModel -from ctrls.c_main import MainController -from views.v_main import MainView -def check_requirements(): - """Checks versions and other requirements""" +# Setup i18n +# adapted from example by Armin Ronacher: +# http://lucumr.pocoo.org/2007/6/10/internationalized-pygtk-applications2 +GETTEXT_DOMAIN = 'pygtktalog' +LOCALE_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), + 'locale') - conf = ConfigModel() - conf.load() +locale.setlocale(locale.LC_ALL, '') +for module in gtk.glade, gettext: + module.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH) + module.textdomain(GETTEXT_DOMAIN) - if conf.confd['thumbs'] and conf.confd['retrive']: - try: - import Image - except ImportError: - print _("WARNING: You'll need Python Imaging Library (PIL), if " - "you want to make thumbnails!") - raise - return +# register the gettext function for the whole interpreter as "_" +__builtin__._ = gettext.gettext + + +from pygtktalog.models.main import MainModel +from pygtktalog.controllers.main import MainController +from pygtktalog.views.main import MainView def run(): """Create model, controller and view and launch it.""" - # Directory from where pygtkatalog was invoced. We need it for calculate - # path for argument (catalog file) - execution_dir = os.path.abspath(os.path.curdir) - - # Directory, where this files lies. We need it to setup private source - # paths - libraries_dir = os.path.dirname(__file__) - if libraries_dir: - os.chdir(libraries_dir) - - # Setup i18n - locale.setlocale(locale.LC_ALL, '') - gettext.install(APPL_SHORT_NAME, 'locale', unicode=True) - - check_requirements() - model = MainModel() if len(sys.argv) > 1: model.open(os.path.join(execution_dir, sys.argv[1])) - controler = MainController(model) - view = MainView(controler) + view = MainView() + controler = MainController(model, view) try: gtk.main() except KeyboardInterrupt: - model.config.save() - model.cleanup() + #model.config.save() + #model.cleanup() gtk.main_quit if __name__ == "__main__":