Reorganized source code hierarchy:
authorJoel Rosdahl <joel@rosdahl.net>
Wed, 21 Sep 2005 20:32:35 +0000 (20:32 +0000)
committerJoel Rosdahl <joel@rosdahl.net>
Wed, 21 Sep 2005 20:32:35 +0000 (20:32 +0000)
 * Renamed src/lib to src/packages.
 * Moved src/gkofoto/gkofoto to src/packages/kofoto/gkofoto.
 * Moved src/cmdline/kofoto to src/packages/kofoto/commandline/main.py
   and wrote a new, simple src/cmdline/kofoto that uses
   kofoto.commandline.main.
 * Adjusted code for new package locations.

This was mostly done for consistency reasons. It also opens the door
to using pychecker and pylint in a better way.

121 files changed:
Makefile
build-windows-py2exe-installer.py
setup.py
src/cmdline/kofoto
src/gkofoto/gkofoto/__init__.py [deleted file]
src/gkofoto/gkofoto/albumdialog.py [deleted file]
src/gkofoto/gkofoto/albummembers.py [deleted file]
src/gkofoto/gkofoto/albums.py [deleted file]
src/gkofoto/gkofoto/categories.py [deleted file]
src/gkofoto/gkofoto/categorydialog.py [deleted file]
src/gkofoto/gkofoto/clipboard.py [deleted file]
src/gkofoto/gkofoto/controller.py [deleted file]
src/gkofoto/gkofoto/crashdialog.py [deleted file]
src/gkofoto/gkofoto/duplicateandopenimagedialog.py [deleted file]
src/gkofoto/gkofoto/environment.py [deleted file]
src/gkofoto/gkofoto/generatehtmldialog.py [deleted file]
src/gkofoto/gkofoto/handleimagesdialog.py [deleted file]
src/gkofoto/gkofoto/imagepreloader.py [deleted file]
src/gkofoto/gkofoto/imageversionsdialog.py [deleted file]
src/gkofoto/gkofoto/imageversionslist.py [deleted file]
src/gkofoto/gkofoto/imageview.py [deleted file]
src/gkofoto/gkofoto/main.py [deleted file]
src/gkofoto/gkofoto/mainwindow.py [deleted file]
src/gkofoto/gkofoto/menuhandler.py [deleted file]
src/gkofoto/gkofoto/mysortedmodel.py [deleted file]
src/gkofoto/gkofoto/objectcollection.py [deleted file]
src/gkofoto/gkofoto/objectcollectionfactory.py [deleted file]
src/gkofoto/gkofoto/objectcollectionview.py [deleted file]
src/gkofoto/gkofoto/objectselection.py [deleted file]
src/gkofoto/gkofoto/persistentstate.py [deleted file]
src/gkofoto/gkofoto/registerimagesdialog.py [deleted file]
src/gkofoto/gkofoto/registerimageversionsdialog.py [deleted file]
src/gkofoto/gkofoto/searchresult.py [deleted file]
src/gkofoto/gkofoto/singleobjectview.py [deleted file]
src/gkofoto/gkofoto/sortableobjectcollection.py [deleted file]
src/gkofoto/gkofoto/tableview.py [deleted file]
src/gkofoto/gkofoto/taganddescriptiondialog.py [deleted file]
src/gkofoto/gkofoto/thumbnailview.py [deleted file]
src/gkofoto/start-in-source.py
src/gkofoto/start-installed.py
src/lib/kofoto/EXIF.py [deleted file]
src/lib/kofoto/__init__.py [deleted file]
src/lib/kofoto/alternative.py [deleted file]
src/lib/kofoto/cachedobject.py [deleted file]
src/lib/kofoto/clientenvironment.py [deleted file]
src/lib/kofoto/clientutils.py [deleted file]
src/lib/kofoto/common.py [deleted file]
src/lib/kofoto/config.py [deleted file]
src/lib/kofoto/dag.py [deleted file]
src/lib/kofoto/generate.py [deleted file]
src/lib/kofoto/imagecache.py [deleted file]
src/lib/kofoto/output/__init__.py [deleted file]
src/lib/kofoto/output/woolly.py [deleted file]
src/lib/kofoto/outputengine.py [deleted file]
src/lib/kofoto/search.py [deleted file]
src/lib/kofoto/shelf.py [deleted file]
src/lib/kofoto/shelfupgrade.py [deleted file]
src/lib/kofoto/structclass.py [deleted file]
src/lib/kofoto/timer.py [deleted file]
src/lib/kofoto/version.py [deleted file]
src/packages/kofoto/EXIF.py [new file with mode: 0644]
src/packages/kofoto/__init__.py [new file with mode: 0644]
src/packages/kofoto/alternative.py [new file with mode: 0644]
src/packages/kofoto/cachedobject.py [new file with mode: 0644]
src/packages/kofoto/clientenvironment.py [new file with mode: 0644]
src/packages/kofoto/clientutils.py [new file with mode: 0644]
src/packages/kofoto/commandline/__init__.py [new file with mode: 0644]
src/packages/kofoto/commandline/main.py [new file with mode: 0755]
src/packages/kofoto/common.py [new file with mode: 0644]
src/packages/kofoto/config.py [new file with mode: 0644]
src/packages/kofoto/dag.py [new file with mode: 0644]
src/packages/kofoto/generate.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/__init__.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/albumdialog.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/albummembers.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/albums.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/categories.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/categorydialog.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/clipboard.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/controller.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/crashdialog.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/duplicateandopenimagedialog.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/environment.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/generatehtmldialog.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/handleimagesdialog.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/imagepreloader.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/imageversionsdialog.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/imageversionslist.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/imageview.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/main.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/mainwindow.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/menuhandler.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/mysortedmodel.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/objectcollection.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/objectcollectionfactory.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/objectcollectionview.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/objectselection.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/persistentstate.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/registerimagesdialog.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/registerimageversionsdialog.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/searchresult.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/singleobjectview.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/sortableobjectcollection.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/tableview.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/taganddescriptiondialog.py [new file with mode: 0644]
src/packages/kofoto/gkofoto/thumbnailview.py [new file with mode: 0644]
src/packages/kofoto/imagecache.py [new file with mode: 0644]
src/packages/kofoto/output/__init__.py [new file with mode: 0644]
src/packages/kofoto/output/woolly.py [new file with mode: 0644]
src/packages/kofoto/outputengine.py [new file with mode: 0644]
src/packages/kofoto/search.py [new file with mode: 0644]
src/packages/kofoto/shelf.py [new file with mode: 0644]
src/packages/kofoto/shelfupgrade.py [new file with mode: 0644]
src/packages/kofoto/structclass.py [new file with mode: 0644]
src/packages/kofoto/timer.py [new file with mode: 0644]
src/packages/kofoto/version.py [new file with mode: 0644]
src/test/alltests.py
src/test/dagtests.py
src/test/searchtests.py
src/test/shelftests.py
src/web/webkofoto

index 30f585a..affef84 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 PREFIX = /usr/local
 
-VERSION = $(shell sed -n 's/^version = "\(.*\)"$$/\1/p' src/lib/kofoto/version.py)
+VERSION = $(shell sed -n 's/^version = "\(.*\)"$$/\1/p' src/packages/kofoto/version.py)
 
 help:
        @echo "Available targets:"
index 71e22a4..628f91f 100755 (executable)
@@ -54,7 +54,7 @@ for x in ["python", "gtk", "pygtk", "pil", "pysqlite"]:
     license_file.write(f.read())
 
 versionDict = {}
-execfile("src/lib/kofoto/version.py", versionDict)
+execfile("src/packages/kofoto/version.py", versionDict)
 
 print "creating kofoto.iss"
 template = \
index ca39199..1495afa 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -7,20 +7,20 @@ import shutil
 import sys
 
 package_dirs = {
-    "kofoto": "src/lib/kofoto",
-    "gkofoto": "src/gkofoto/gkofoto",
+    "kofoto": "src/packages/kofoto",
     }
 packages = [
     "kofoto",
+    "kofoto.commandline",
+    "kofoto.gkofoto",
     "kofoto.output",
-    "gkofoto",
     ]
 data_files = [
     ("share/gkofoto/glade", ["src/gkofoto/glade/gkofoto.glade"]),
     ("share/gkofoto/icons", glob.glob("src/gkofoto/icons/*.png")),
     ]
 versionDict = {}
-execfile("src/lib/kofoto/version.py", versionDict)
+execfile("src/packages/kofoto/version.py", versionDict)
 common_setup_options = {
     "name": "kofoto",
     "version": versionDict["version"],
index b88a8f3..2ef5998 100755 (executable)
@@ -1,14 +1,9 @@
 #! /usr/bin/env python
 
-import getopt
 import os
-from sets import Set
 import sys
-import time
 
-######################################################################
-
-# Find libraries if installed in ../lib (like in the source tree).
+# Find bindir when started via a symlink.
 if os.path.islink(sys.argv[0]):
     link = os.readlink(sys.argv[0])
     absloc = os.path.normpath(
@@ -16,1179 +11,10 @@ if os.path.islink(sys.argv[0]):
     bindir = os.path.dirname(absloc)
 else:
     bindir = os.path.dirname(sys.argv[0])
-sys.path.insert(0, os.path.join(bindir, "..", "lib"))
-
-from kofoto.clientenvironment import *
-from kofoto.clientutils import *
-from kofoto.common import *
-from kofoto.config import *
-from kofoto.imagecache import *
-from kofoto.search import *
-from kofoto.shelf import *
-
-######################################################################
-### Constants.
-
-PRINT_ALBUMS_INDENT = 4
-
-######################################################################
-### Exceptions.
-
-class ArgumentError:
-    pass
-
-######################################################################
-### Help text data.
-
-optionDefinitionList = [
-    ("    --configfile FILE",
-     "Use configuration file FILE instead of the default (%s)." % (
-         DEFAULT_CONFIGFILE_LOCATION)),
-    ("    --database FILE",
-     "Use the metadata database FILE instead of the default (specified in the configuration file)."),
-    ("    --gencharenc ENCODING",
-     "Generate HTML pages with character encoding ENCODING instead of the default (taken from locale settings)."),
-    ("-h, --help",
-     "Display this help."),
-    ("    --identify-by-hash",
-     "Identify image versions by hash. This is the default."),
-    ("    --identify-by-path",
-     "Identify image versions by path."),
-    ("    --ids",
-     "Print ID numbers instead of locations."),
-    ("    --include-all",
-     "Include all image versions for images matching a search expression."),
-    ("    --include-important",
-     "Include all image versions with type \"important\" for images matching a search expression."),
-    ("    --include-original",
-     "Include all image versions with type \"original\" for images matching a search expression."),
-    ("    --include-other",
-     "Include all image versions with type \"other\" for images matching a search expression."),
-    ("    --include-primary",
-     "Include all primary image versions for images matching a search expression."),
-    ("    --no-act",
-     "Do everything which is supposed to be done, but don't commit any changes to the database."),
-    ("    --position POSITION",
-     "Add/register to position POSITION. Default: last."),
-    ("-t, --type TYPE",
-     "Use album type TYPE when creating an album or output type TYPE when generating output."),
-    ("-v, --verbose",
-      "Be verbose (and slower)."),
-    ("    --version",
-     "Print version to standard output."),
-    ]
-
-albumAndImageCommandsDefinitionList = [
-    ("add-category CATEGORY OBJECT [OBJECT ...]",
-     "Add a category to the given objects."),
-    ("delete-attribute ATTRIBUTE OBJECT [OBJECT ...]",
-     "Delete an attribute from the given objects."),
-    ("get-attribute ATTRIBUTE OBJECT",
-     "Get an attribute's value for an object."),
-    ("get-attributes OBJECT",
-     "Get attributes for an object."),
-    ("get-categories OBJECT",
-     "Get categories for an object."),
-    ("remove-category CATEGORY OBJECT [OBJECT ...]",
-     "Remove a category from the given objects."),
-    ("search SEARCHEXPRESSION",
-     "Search for images matching an expression and print image version locations on standard output (or image IDs if the --ids option is given). By default, only primary image versions are printed, but other versions can be printed by supplying one or several of the options --include-all, --include-important, --include-original and --include-other. (If no --include-* option is supplied, --include-primary is assumed.)"),
-    ("set-attribute ATTRIBUTE VALUE OBJECT [OBJECT ...]",
-     "Set ATTRIBUTE to VALUE for the given objects."),
-    ]
-
-albumCommandsDefinitionList = [
-    ("add ALBUM OBJECT [OBJECT ...]",
-     "Add the given objects (albums and images) to the album ALBUM. (The objects are placed last if a position is not specified with --position.)"),
-    ("create-album TAG",
-     "Create an empty, unlinked album with tag TAG. If a type argument is not given with -t/--type, an album of type \"plain\" will be created."),
-    ("destroy-album ALBUM [ALBUM ...]",
-     "Destroy the given albums permanently. All metadata is also destroyed, but not the album's children."),
-    ("generate ROOTALBUM DIRECTORY [SUBALBUM ...]",
-     "Generate output for ROOTALBUM in the directory DIRECTORY. If subalbums are given, only generate those albums, their descendants and their immediate parents. Use -t/--type to use another output type than the default."),
-    ("print-albums [ALBUM]",
-     "Print the album graph for ALBUM (default: root). If -v/--verbose is given, also print images and attributes."),
-    ("register ALBUM PATH [PATH ...]",
-     "Register objects (i.e. directories and images) and add them to the album ALBUM. (The objects are placed last.) Directories are recursively scanned for other directories and images, which also will be registered."),
-    ("remove ALBUM POSITION [POSITION ...]",
-     "Remove the objects at the given positions from ALBUM."),
-    ("rename-album OLDTAG NEWTAG",
-     "Rename album tag."),
-    ("sort-album ALBUM [ATTRIBUTE]",
-     "Sort the contents of ALBUM by an attribute (default: captured)."),
-    ]
-
-imageCommandsDefinitionList = [
-    ("destroy-image IMAGE [IMAGE ...]",
-     "Destroy the given images permanently. All metadata and image versions are also destroyed (but not the image files on disk)."),
-    ("find-missing-imageversions",
-     "Find missing image versions and print them to standard output."),
-    ("get-imageversions IMAGE",
-     "Print image versions for an image. If -v/--verbose is given, print more information."),
-    ]
-
-imageversionCommandsDefinitionList = [
-    ("destroy-imageversion IMAGEVERSION [IMAGEVERSION ...]",
-     "Destroy the given image versions permanently. All metadata is also destroyed (but not the image files on disk)."),
-    ("inspect-path PATH [PATH ...]",
-     "Traverse the given paths and print whether each found file is a registered, modified, moved or unregistered image version or a non-image."),
-    ("make-primary IMAGEVERSION [IMAGEVERSION ...]",
-     "Make an image version the primary version."),
-    ("set-imageversion-comment VALUE IMAGEVERSION [IMAGEVERSION ...]",
-     "Set comment of the given image versions."),
-    ("set-imageversion-image IMAGE IMAGEVERSION [IMAGEVERSION ...]",
-     "Set the image to which the given image versions belong."),
-    ("set-imageversion-type IMAGEVERSIONTYPE IMAGEVERSION [IMAGEVERSION ...]",
-     "Set type of the given image versions."),
-    ("update-contents PATH [PATH ...]",
-     "Traverse the given paths recursively and remember the new contents (checksum, width and height) of found image versions."),
-    ("update-locations PATH [PATH ...]",
-     "Traverse the given paths recursively and remember the new locations of found image versions."),
-    ]
-
-categoryCommandsDefinitionList = [
-    ("connect-category PARENTCATEGORY CHILDCATEGORY",
-     "Make a category a child of another category."),
-    ("disconnect-category PARENTCATEGORY CHILDCATEGORY",
-     "Remove parent-child realationship bewteen two categories."),
-    ("create-category TAG DESCRIPTION",
-     "Create category."),
-    ("destroy-category CATEGORY [CATEGORY ...]",
-     "Destroy category permanently."),
-    ("print-categories",
-     "Print category tree."),
-    ("rename-category OLDTAG NEWTAG",
-     "Rename category tag."),
-    ("set-category-description TAG DESCRIPTION",
-     "Set category description."),
-    ]
-
-miscellaneousCommandsDefinitionList = [
-    ("clean-cache",
-     "Clean up the image cache (remove left-over generated images)."),
-    ("print-statistics",
-     "Print some statistics about the database."),
-    ]
-
-parameterSemanticsDefinitionList = [
-    ("ALBUM",
-     "An integer ID or an album tag (see TAG)."),
-    ("ATTRIBUTE",
-     "An arbitrary attribute name."),
-    ("CATEGORY",
-     "A category tag (see TAG)."),
-    ("DESCRIPTION",
-     "An arbitrary string."),
-    ("ENCODING",
-     "An encoding parsable by Python, e.g. \"utf-8\", \"latin1\" or \"iso-8859-1\"."),
-    ("FILE",
-     "A path to a file."),
-    ("IMAGE",
-     "An integer ID or a path to an image version file. If it's a path to an image version, its corresponding image is selected. If --identify-by-path is given, the path is used for identifying the image version; otherwise the file's content used for identification."),
-    ("IMAGEVERSION",
-     "An integer ID or a path to an image version file. If --identify-by-path is given, the path is used for identifying the image version; otherwise the file's contents used for identification."),
-    ("IMAGEVERSIONTYPE",
-     "\"important\", \"original\" or \"other\"."),
-    ("OBJECT",
-     "An integer ID, an album tag (see TAG) or a path to an image version file. If it's a path to an image version, its corresponding image is selected. If --identify-by-path is given, the path is used for identifying the image version; otherwise the file's contents used for identification."),
-    ("PATH",
-     "A path to a directory or a file."),
-    ("POSITION",
-     "An integer specifying an index into an album's children. 0 is the first position, 1 is the second, and so on."),
-    ("SEARCHEXPRESSION",
-     "A search expression. See http://kofoto.rosdahl.net/trac/wiki/SearchExpressions for more information."),
-    ("TAG",
-     "A text string not containing space or @ characters and not consisting solely of integers."),
-    ("TYPE",
-     "An album type (as listed below) or an HTML output type (for the moment, only \"woolly\" is allowed)."),
-    ("VALUE",
-     "An arbitrary string."),
-    ]
-
-albumTypesDefinitionList = [
-    ("orphans",
-     "All albums and images that don't exist in any plain album."),
-    ("plain",
-     "An ordinary container that holds albums and images."),
-    ("search",
-     "An album containing the albums and images that match a search string (sorted by capture timestamp). The search string is read from the album's \"query\" attribute."),
-    ]
-
-######################################################################
-### Helper functions.
-
-def printDefinitionList(
-    definitionList, outfile, basicIndentation, definitionIndentation, width):
-    from textwrap import TextWrapper
-    wrapper = TextWrapper(width=width)
-    for term, definition in definitionList:
-        wrapper.subsequent_indent = \
-            (basicIndentation + definitionIndentation) * " "
-        if len(term) < definitionIndentation - 1:
-            wrapper.initial_indent = basicIndentation * " "
-            textToWrap = "%s%s%s" % (
-                term,
-                (definitionIndentation - len(term)) * " ",
-                definition)
-        else:
-            wrapper.initial_indent = wrapper.subsequent_indent
-            outfile.write("%s%s\n" % (basicIndentation * " ", term))
-            textToWrap = definition
-        outfile.write("%s\n" % wrapper.fill(textToWrap))
-
-def displayHelp():
-    sys.stdout.write(
-        "Usage: kofoto [options] command [parameters]\n"
-        "\n"
-        "Options:\n"
-        "\n")
-    printDefinitionList(optionDefinitionList, sys.stdout, 4, 27, 79)
-    sys.stdout.write(
-        "\n"
-        "Commands:\n"
-        "\n"
-        "    For albums and images\n"
-        "    =====================\n")
-    printDefinitionList(
-        albumAndImageCommandsDefinitionList, sys.stdout, 4, 27, 79)
-    sys.stdout.write(
-        "\n"
-        "    For albums\n"
-        "    ==========\n")
-    printDefinitionList(albumCommandsDefinitionList, sys.stdout, 4, 27, 79)
-    sys.stdout.write(
-        "\n"
-        "    For images\n"
-        "    ==========\n")
-    printDefinitionList(imageCommandsDefinitionList, sys.stdout, 4, 27, 79)
-    sys.stdout.write(
-        "\n"
-        "    For image versions\n"
-        "    ==================\n")
-    printDefinitionList(
-        imageversionCommandsDefinitionList, sys.stdout, 4, 27, 79)
-    sys.stdout.write(
-        "\n"
-        "    For categories\n"
-        "    ==============\n")
-    printDefinitionList(categoryCommandsDefinitionList, sys.stdout, 4, 27, 79)
-    sys.stdout.write(
-        "\n"
-        "    Miscellaneous\n"
-        "    =============\n")
-    printDefinitionList(
-        miscellaneousCommandsDefinitionList, sys.stdout, 4, 27, 79)
-    sys.stdout.write(
-        "\n"
-        "Parameter semantics:\n"
-        "\n"
-        "    Parameter         Interpretation\n"
-        "    --------------------------------\n")
-    printDefinitionList(
-        parameterSemanticsDefinitionList, sys.stdout, 4, 18, 79)
-    sys.stdout.write(
-        "\n"
-        "Album types:\n"
-        "\n"
-        "    Type              Description\n"
-        "    -----------------------------\n")
-    printDefinitionList(
-        albumTypesDefinitionList, sys.stdout, 4, 18, 79)
-
-def printOutput(infoString):
-    sys.stdout.write(infoString)
-    sys.stdout.flush()
-
-def printNotice(noticeString):
-    sys.stderr.write("Notice: " + noticeString)
-
-def printError(errorString):
-    sys.stderr.write("Error: " + errorString)
-
-def printErrorAndExit(errorString):
-    printError(errorString)
-    sys.exit(1)
-
-def sloppyGetAlbum(env, idOrTag):
-    try:
-        return env.shelf.getAlbum(int(idOrTag))
-    except ValueError:
-        return env.shelf.getAlbumByTag(idOrTag)
-
-def sloppyGetImage(env, idOrLocation):
-    try:
-        return env.shelf.getImage(int(idOrLocation))
-    except ValueError:
-        try:
-            if env.identifyByPath:
-                imageversion = env.shelf.getImageVersionByLocation(
-                    idOrLocation)
-            else:
-                imageversion = env.shelf.getImageVersionByHash(
-                    computeImageHash(idOrLocation))
-            return imageversion.getImage()
-        except ImageVersionDoesNotExistError:
-            raise ImageDoesNotExistError, idOrLocation
-
-def sloppyGetImageVersion(env, idOrLocation):
-    try:
-        return env.shelf.getImageVersion(int(idOrLocation))
-    except ValueError:
-        if env.identifyByPath:
-            return env.shelf.getImageVersionByLocation(idOrLocation)
-        else:
-            return env.shelf.getImageVersionByHash(
-                computeImageHash(idOrLocation))
-
-def sloppyGetObject(env, idOrTagOrLocation):
-    try:
-        return sloppyGetAlbum(env, idOrTagOrLocation)
-    except AlbumDoesNotExistError:
-        try:
-            return sloppyGetImage(env, idOrTagOrLocation)
-        except (ImageDoesNotExistError, ImageVersionDoesNotExistError), x:
-            raise ObjectDoesNotExistError, x
-
-def parseAlbumType(atype):
-    try:
-        return {
-            u"plain": AlbumType.Plain,
-            u"orphans": AlbumType.Orphans,
-            u"search": AlbumType.Search,
-            }[atype]
-    except KeyError:
-        raise UnknownAlbumTypeError, atype
-
-def parseImageVersionType(ivtype):
-    try:
-        return {
-            u"important": ImageVersionType.Important,
-            u"original": ImageVersionType.Original,
-            u"other": ImageVersionType.Other,
-            }[ivtype]
-    except KeyError:
-        raise UnknownImageVersionTypeError, ivtype
-
-######################################################################
-### Helper classes.
-
-class CommandlineClientEnvironment(ClientEnvironment):
-    to_l = ClientEnvironment.unicodeToLocalizedString
-    to_u = ClientEnvironment.localizedStringToUnicode
-
-    def _writeInfo(self, info):
-        printNotice(info)
-
-######################################################################
-### Commands.
-
-def cmdAdd(env, args):
-    if len(args) < 2:
-        raise ArgumentError
-    destalbum = sloppyGetAlbum(env, args[0])
-    objects = [sloppyGetObject(env, x) for x in args[1:]]
-    addHelper(env, destalbum, objects)
-
-
-def addHelper(env, destalbum, objects):
-    oldchildren = list(destalbum.getChildren())
-    if env.position == -1:
-        pos = len(oldchildren)
-    else:
-        pos = env.position
-    destalbum.setChildren(oldchildren[:pos] + objects + oldchildren[pos:])
-
-
-def cmdAddCategory(env, args):
-    if len(args) < 2:
-        raise ArgumentError
-    category = env.shelf.getCategoryByTag(args[0])
-    for arg in args[1:]:
-        sloppyGetObject(env, arg).addCategory(category)
-
-
-def cmdCleanCache(env, args):
-    if env.noAct:
-        raise ArgumentError
-    if len(args) != 0:
-        raise ArgumentError
-    env.imageCache.cleanup()
-
-
-def cmdConnectCategory(env, args):
-    if len(args) != 2:
-        raise ArgumentError
-    parent = env.shelf.getCategoryByTag(args[0])
-    child = env.shelf.getCategoryByTag(args[1])
-    parent.connectChild(child)
-
-
-def cmdCreateAlbum(env, args):
-    if len(args) != 1:
-        raise ArgumentError
-    if env.type:
-        atype = env.type
-    else:
-        atype = u"plain"
-    env.shelf.createAlbum(args[0], parseAlbumType(atype))
-
-
-def cmdCreateCategory(env, args):
-    if len(args) != 2:
-        raise ArgumentError
-    env.shelf.createCategory(args[0], args[1])
-
-
-def cmdDeleteAttribute(env, args):
-    if len(args) < 2:
-        raise ArgumentError
-    attr = args[0]
-    for arg in args[1:]:
-        sloppyGetObject(env, arg).deleteAttribute(attr)
-
-
-def cmdDestroyAlbum(env, args):
-    if len(args) == 0:
-        raise ArgumentError
-    for arg in args:
-        env.shelf.deleteAlbum(sloppyGetAlbum(env, arg).getId())
-
-
-def cmdDestroyCategory(env, args):
-    if len(args) == 0:
-        raise ArgumentError
-    for arg in args:
-        env.shelf.deleteCategory(env.shelf.getCategoryByTag(arg).getId())
-
-
-def cmdDestroyImage(env, args):
-    if len(args) == 0:
-        raise ArgumentError
-    for arg in args:
-        env.shelf.deleteImage(sloppyGetImage(env, arg).getId())
-
-
-def cmdDestroyImageVersion(env, args):
-    if len(args) == 0:
-        raise ArgumentError
-    for arg in args:
-        env.shelf.deleteImageVersion(sloppyGetImageVersion(env, arg).getId())
-
-
-def cmdDisconnectCategory(env, args):
-    if len(args) != 2:
-        raise ArgumentError
-    parent = env.shelf.getCategoryByTag(args[0])
-    child = env.shelf.getCategoryByTag(args[1])
-    parent.disconnectChild(child)
-
-
-def cmdFindMissingImageVersions(env, args):
-    if len(args) != 0:
-        raise ArgumentError
-    badchecksums = []
-    missingfiles = []
-    for iv in env.shelf.getAllImageVersions():
-        location = env.to_l(iv.getLocation())
-        if env.verbose:
-            env.out("Checking %s ...\n" % location)
-        try:
-            realId = computeImageHash(location)
-            storedId = iv.getHash()
-            if realId != storedId:
-                badchecksums.append(location)
-        except IOError:
-            missingfiles.append(location)
-
-    env.out("Missing image versions:")
-    if badchecksums or missingfiles:
-        for path in badchecksums:
-            env.out("\n    (bad checksum) %s" % path)
-        for path in missingfiles:
-            env.out("\n    (missing) %s" % path)
-        env.out("\n")
-    else:
-        env.out(" none\n")
-
-
-def cmdGenerate(env, args):
-    if len(args) < 2:
-        raise ArgumentError
-    root = sloppyGetAlbum(env, args[0])
-    dest = args[1]
-    subalbums = [sloppyGetAlbum(env, x) for x in args[2:]]
-    if env.type:
-        otype = env.type
-    else:
-        otype = u"woolly"
-    import kofoto.generate
-    try:
-        generator = kofoto.generate.Generator(otype, env)
-        generator.generate(root, subalbums, dest, env.gencharenc)
-    except kofoto.generate.OutputTypeError, x:
-        env.errexit("No such output module: %s\n" % env.to_l(x))
-
-
-def cmdGetAttribute(env, args):
-    if len(args) != 2:
-        raise ArgumentError
-    obj = sloppyGetObject(env, args[1])
-    value = obj.getAttribute(args[0])
-    if value:
-        env.out(env.to_l(value) + "\n")
-
-
-def cmdGetAttributes(env, args):
-    if len(args) != 1:
-        raise ArgumentError
-    obj = sloppyGetObject(env, args[0])
-    for name in obj.getAttributeNames():
-        env.out("%s: %s\n" % (
-            env.to_l(name),
-            env.to_l(obj.getAttribute(name))))
-
-
-def cmdGetCategories(env, args):
-    if len(args) != 1:
-        raise ArgumentError
-    obj = sloppyGetObject(env, args[0])
-    for category in obj.getCategories():
-        env.out("%s (%s) <%s>\n" % (
-            env.to_l(category.getDescription()),
-            env.to_l(category.getTag()),
-            category.getId()))
-
-
-def cmdGetImageVersions(env, args):
-    if len(args) != 1:
-        raise ArgumentError
-    image = sloppyGetImage(env, args[0])
-    for iv in image.getImageVersions():
-        if env.printIDs:
-            env.out("%s\n" % iv.getId())
-        elif env.verbose:
-            env.out("%s\n  %s%s\n" % (
-                env.to_l(iv.getLocation()),
-                iv.isPrimary() and "Primary, " or "",
-                iv.getType()))
-            if iv.getComment():
-                env.out("  %s\n" % env.to_l(iv.getComment()))
-        else:
-            env.out("%s\n" % env.to_l(iv.getLocation()))
-
-
-def cmdInspectPath(env, args):
-    if len(args) < 1:
-        raise ArgumentError
-    import Image as PILImage
-    for filepath in walk_files(args):
-        try:
-            imageversion = env.shelf.getImageVersionByHash(
-                computeImageHash(filepath))
-            if imageversion.getLocation() == os.path.realpath(filepath):
-                env.out("[Registered]   %s\n" % env.to_l(filepath))
-            else:
-                env.out("[Moved]        %s\n" % env.to_l(filepath))
-        except ImageVersionDoesNotExistError:
-            try:
-                imageversion = env.shelf.getImageVersionByLocation(filepath)
-                env.out("[Modified]     %s\n" % env.to_l(filepath))
-            except MultipleImageVersionsAtOneLocationError:
-                env.out("[Multiple]     %s\n" % env.to_l(filepath))
-            except ImageVersionDoesNotExistError:
-                try:
-                    PILImage.open(env.to_l(filepath))
-                    env.out("[Unregistered] %s\n" % env.to_l(filepath))
-#                except IOError:
-                except: # Work-around for buggy PIL.
-                    env.out("[Non-image]    %s\n" % env.to_l(filepath))
-
-
-def cmdMakePrimary(env, args):
-    if len(args) == 0:
-        raise ArgumentError
-    for iv in args:
-        sloppyGetImageVersion(env, iv).makePrimary()
-
-
-def cmdPrintAlbums(env, args):
-    if len(args) > 0:
-        root = sloppyGetAlbum(env, args[0])
-    else:
-        root = env.shelf.getRootAlbum()
-    printAlbumsHelper(env, root, 0, 0, [])
-
-
-def printAlbumsHelper(env, object, position, level, visited):
-    imgtmpl = "%(indent)s[I] {%(position)s} <%(id)s>\n"
-    imgvertmpl = "%(indent)s[V] %(location)s {%(primary)s%(type)s} <%(id)s>\n"
-    if env.verbose:
-        albtmpl = "%(indent)s[A] %(name)s {%(position)s} <%(id)s> (%(type)s)\n"
-    else:
-        albtmpl = "%(indent)s[A] %(name)s <%(id)s> (%(type)s)\n"
-    indentspaces = PRINT_ALBUMS_INDENT * " "
-    if object.isAlbum():
-        tag = object.getTag()
-        env.out(albtmpl % {
-            "indent": level * indentspaces,
-            "name": env.to_l(tag),
-            "position": position,
-            "id": object.getId(),
-            "type": env.to_l(object.getType()),
-            })
-        if tag in visited:
-            env.out("%s[...]\n" % ((level + 1) * indentspaces))
-        else:
-            pos = 0
-            if env.verbose:
-                children = object.getChildren()
-            else:
-                children = object.getAlbumChildren()
-            for child in children:
-                printAlbumsHelper(
-                    env,
-                    child,
-                    pos,
-                    level + 1,
-                    visited + [tag])
-                pos += 1
-    else:
-        env.out(imgtmpl % {
-            "indent": level * indentspaces,
-            "position": position,
-            "id": object.getId(),
-            })
-        for iv in object.getImageVersions():
-            env.out(imgvertmpl % {
-                "indent": (level + 1) * indentspaces,
-                "id": iv.getId(),
-                "location": env.to_l(iv.getLocation()),
-                "primary": iv.isPrimary() and "Primary " or "",
-                "type": iv.getType(),
-                })
-    if env.verbose:
-        attrtmpl = "%(indent)s%(key)s: %(value)s\n"
-        names = object.getAttributeNames()
-        for name in names:
-            env.out(attrtmpl % {
-                "indent": (level + 1) * indentspaces,
-                "key": env.to_l(name),
-                "value": env.to_l(object.getAttribute(name)),
-                })
-
-
-def cmdPrintCategories(env, args):
-    if len(args) != 0:
-        raise ArgumentError
-    for category in env.shelf.getRootCategories():
-        printCategoriesHelper(env, category, 0)
-
-
-def printCategoriesHelper(env, category, level):
-    indentspaces = PRINT_ALBUMS_INDENT * " "
-    env.out("%s%s (%s) <%s>\n" % (
-        level * indentspaces,
-        env.to_l(category.getDescription()),
-        env.to_l(category.getTag()),
-        category.getId()))
-    for child in category.getChildren():
-        printCategoriesHelper(env, child, level + 1)
-
-
-def cmdPrintStatistics(env, args):
-    stats = env.shelf.getStatistics()
-    env.out("Number of albums: %d\n" % stats["nalbums"])
-    env.out("Number of images: %d\n" % stats["nimages"])
-    env.out("Number of image versions: %d\n" % stats["nimageversions"])
-
-
-def cmdRegister(env, args):
-    if len(args) < 2:
-        raise ArgumentError
-    destalbum = sloppyGetAlbum(env, args[0])
-    registrationTimeString = unicode(time.strftime("%Y-%m-%d %H:%M:%S"))
-    registerHelper(
-        env,
-        destalbum,
-        registrationTimeString,
-        [env.to_l(x) for x in args[1:]])
-
-
-def registerHelper(env, destalbum, registrationTimeString, paths):
-    paths.sort()
-    newchildren = []
-    for path in paths:
-        if env.verbose:
-            env.out("Processing %s ...\n" % path)
-        if os.path.isdir(path):
-            tag = os.path.basename(path)
-            if tag in DIRECTORIES_TO_IGNORE:
-                if env.verbose:
-                    env.out("Ignoring.\n")
-                continue
-            tag = makeValidTag(tag)
-            while True:
-                try:
-                    album = env.shelf.createAlbum(env.to_u(tag))
-                    break
-                except AlbumExistsError:
-                    tag += "_"
-            newchildren.append(album)
-            env.out("Registered directory %s as an album with tag %s\n" % (
-                path,
-                tag))
-            registerHelper(
-                env,
-                album,
-                registrationTimeString,
-                [os.path.join(path, x) for x in os.listdir(path)])
-        elif os.path.isfile(path):
-            try:
-                image = env.shelf.createImage()
-                imageversion = env.shelf.createImageVersion(
-                    image, env.to_u(path), ImageVersionType.Original)
-                image.setAttribute(u"registered", registrationTimeString)
-                newchildren.append(image)
-                if env.verbose:
-                    env.out("Registered image: %s\n" % path)
-            except NotAnImageFileError, x:
-                env.out("Ignoring non-image file: %s\n" % path)
-            except ImageVersionExistsError, x:
-                env.err("Ignoring already registered image version: %s\n" % path)
-        else:
-            env.err("No such file or directory (ignored): %s\n" % path)
-    addHelper(env, destalbum, newchildren)
-
-
-def cmdRemove(env, args):
-    if len(args) < 2:
-        raise ArgumentError
-    album = sloppyGetAlbum(env, args[0])
-    positions = []
-    for pos in args[1:]:
-        try:
-            positions.append(int(pos))
-        except ValueError:
-            env.errexit("Bad position: %s.\n" % env.to_l(pos))
-    positions.sort()
-    positions.reverse()
-    children = list(album.getChildren())
-    if not (0 <= positions[0] < len(children)):
-        env.errexit("Bad position: %d.\n" % positions[0])
-    for pos in positions:
-        del children[pos]
-    album.setChildren(children)
-
-
-def cmdRemoveCategory(env, args):
-    if len(args) < 2:
-        raise ArgumentError
-    category = env.shelf.getCategoryByTag(args[0])
-    for arg in args[1:]:
-        sloppyGetObject(env, arg).removeCategory(category)
-
-
-def cmdRenameAlbum(env, args):
-    if len(args) != 2:
-        raise ArgumentError
-    sloppyGetAlbum(env, args[0]).setTag(args[1])
-
-
-def cmdRenameCategory(env, args):
-    if len(args) != 2:
-        raise ArgumentError
-    env.shelf.getCategoryByTag(args[0]).setTag(args[1])
-
-
-def cmdSearch(env, args):
-    if len(args) != 1:
-        raise ArgumentError
-    parser = Parser(env.shelf)
-    objects = env.shelf.search(parser.parse(args[0]))
-    objects = [x for x in objects if not x.isAlbum()]
-    ivs = []
-    for o in objects:
-        for iv in o.getImageVersions():
-            ivs.append(iv)
-    if env.printIDs:
-        ids = Set()
-        for iv in ivs:
-            ids.add(iv.getImage().getId())
-        output = [str(x) for x in ids]
-    else:
-        output = []
-        for iv in ivs:
-            t = iv.getType()
-            if (env.includeAll or
-                (env.includeImportant and t == ImageVersionType.Important) or
-                (env.includeOriginal and t == ImageVersionType.Original) or
-                (env.includeOther and t == ImageVersionType.Other) or
-                (env.includePrimary and iv.isPrimary())):
-                output.append(env.to_l(iv.getLocation()))
-        output.sort()
-    if output:
-        env.out("%s\n" % "\n".join(output))
-
-
-def cmdSetAttribute(env, args):
-    if len(args) < 3:
-        raise ArgumentError
-    attr = args[0]
-    value = args[1]
-    for arg in args[2:]:
-        sloppyGetObject(env, arg).setAttribute(attr, value)
-
-
-def cmdSetCategoryDescription(env, args):
-    if len(args) != 2:
-        raise ArgumentError
-    env.shelf.getCategoryByTag(args[0]).setDescription(args[1])
-
-
-def cmdSetImageVersionComment(env, args):
-    if len(args) < 2:
-        raise ArgumentError
-    comment = args[0]
-    for iv in args[1:]:
-        sloppyGetImageVersion(env, iv).setComment(comment)
-
-
-def cmdSetImageVersionImage(env, args):
-    if len(args) < 2:
-        raise ArgumentError
-    image = sloppyGetImage(env, args[0])
-    for iv in args[1:]:
-        sloppyGetImageVersion(env, iv).setImage(image)
-
-
-def cmdSetImageVersionType(env, args):
-    if len(args) < 2:
-        raise ArgumentError
-    ivtype = parseImageVersionType(args[0])
-    for iv in args[1:]:
-        sloppyGetImageVersion(env, iv).setType(ivtype)
-
-
-def cmdSortAlbum(env, args):
-    if not 1 <= len(args) <= 2:
-        raise ArgumentError
-    if len(args) == 2:
-        attr = args[1]
-    else:
-        attr = u"captured"
-    album = sloppyGetAlbum(env, args[0])
-    children = list(album.getChildren())
-    children.sort(
-        lambda x, y: cmp(x.getAttribute(attr), y.getAttribute(attr)))
-    album.setChildren(children)
-
-
-def cmdUpdateContents(env, args):
-    if len(args) < 1:
-        raise ArgumentError
-    for filepath in walk_files(args):
-        try:
-            imageversion = env.shelf.getImageVersionByLocation(filepath)
-            oldhash = imageversion.getHash()
-            imageversion.contentChanged()
-            if imageversion.getHash() != oldhash:
-                env.out("New checksum: %s\n" % env.to_l(filepath))
-            else:
-                if env.verbose:
-                    env.out("Same checksum as before: %s\n" %
-                            env.to_l(filepath))
-        except ImageVersionDoesNotExistError:
-            if env.verbose:
-                env.out("Unregistered image/file: %s\n" % env.to_l(filepath))
-        except MultipleImageVersionsAtOneLocationError:
-            env.errexit(
-                "Multiple known image versions at this location: %s\n" %
-                env.to_l(filepath))
-
-
-def cmdUpdateLocations(env, args):
-    if len(args) < 1:
-        raise ArgumentError
-    for filepath in walk_files(args):
-        try:
-            imageversion = env.shelf.getImageVersionByHash(
-                computeImageHash(filepath))
-            oldlocation = imageversion.getLocation()
-            if oldlocation != os.path.realpath(filepath):
-                imageversion.locationChanged(filepath)
-                env.out("New location: %s --> %s\n" % (
-                    env.to_l(oldlocation),
-                    env.to_l(imageversion.getLocation())))
-            else:
-                if env.verbose:
-                    env.out("Same location as before: %s\n" %
-                            env.to_l(filepath))
-        except IOError, x:
-            if env.verbose:
-                env.out("Failed to read: %s\n" % env.to_l(filepath))
-        except ImageVersionDoesNotExistError:
-            if env.verbose:
-                env.out("Unregistered image/file: %s\n" % env.to_l(filepath))
-
-
-commandTable = {
-    "add": cmdAdd,
-    "add-category": cmdAddCategory,
-    "clean-cache": cmdCleanCache,
-    "connect-category": cmdConnectCategory,
-    "create-album": cmdCreateAlbum,
-    "create-category": cmdCreateCategory,
-    "delete-attribute": cmdDeleteAttribute,
-    "destroy-album": cmdDestroyAlbum,
-    "destroy-category": cmdDestroyCategory,
-    "destroy-image": cmdDestroyImage,
-    "destroy-imageversion": cmdDestroyImageVersion,
-    "disconnect-category": cmdDisconnectCategory,
-    "find-missing-imageversions": cmdFindMissingImageVersions,
-    "generate": cmdGenerate,
-    "get-attribute": cmdGetAttribute,
-    "get-attributes": cmdGetAttributes,
-    "get-categories": cmdGetCategories,
-    "get-imageversions": cmdGetImageVersions,
-    "inspect-path": cmdInspectPath,
-    "make-primary": cmdMakePrimary,
-    "print-albums": cmdPrintAlbums,
-    "print-categories": cmdPrintCategories,
-    "print-statistics": cmdPrintStatistics,
-    "register": cmdRegister,
-    "remove": cmdRemove,
-    "remove-category": cmdRemoveCategory,
-    "rename-album": cmdRenameAlbum,
-    "rename-category": cmdRenameCategory,
-    "search": cmdSearch,
-    "set-attribute": cmdSetAttribute,
-    "set-category-description": cmdSetCategoryDescription,
-    "set-imageversion-comment": cmdSetImageVersionComment,
-    "set-imageversion-image": cmdSetImageVersionImage,
-    "set-imageversion-type": cmdSetImageVersionType,
-    "sort-album": cmdSortAlbum,
-    "update-contents": cmdUpdateContents,
-    "update-locations": cmdUpdateLocations,
-}
-
-######################################################################
-### Main
-
-def main(argv):
-    env = CommandlineClientEnvironment()
-
-    argv = [env.to_u(x) for x in argv]
-    try:
-        optlist, args = getopt.gnu_getopt(
-            argv[1:],
-            "ht:v",
-            ["configfile=",
-             "database=",
-             "gencharenc=",
-             "help",
-             "identify-by-hash",
-             "identify-by-path",
-             "ids",
-             "include-all",
-             "include-important",
-             "include-original",
-             "include-other",
-             "include-primary",
-             "no-act",
-             "position=",
-             "type=",
-             "verbose",
-             "version"])
-    except getopt.GetoptError:
-        printErrorAndExit("Unknown option. See \"kofoto --help\" for help.\n")
-
-    # Defaults in env:
-    env.identifyByPath = False
-    env.includeAll = False
-    env.includeImportant = False
-    env.includeOriginal = False
-    env.includeOther = False
-    env.includePrimary = False
-    env.noAct = False
-    env.position = -1
-    env.printIDs = False
-    env.type = None
-    env.verbose = False
-
-    # Other defaults:
-    shelfLocation = None
-    configFileLocation = None
-    genCharEnc = env.codeset
-
-    for opt, optarg in optlist:
-        if opt == "--configfile":
-            configFileLocation = os.path.expanduser(env.to_l(optarg))
-        elif opt == "--database":
-            shelfLocation = env.to_l(optarg)
-        elif opt == "--gencharenc":
-            genCharEnc = str(optarg)
-        elif opt in ("-h", "--help"):
-            displayHelp()
-            sys.exit(0)
-        elif opt == "--identify-by-hash":
-            env.identifyByPath = False
-        elif opt == "--identify-by-path":
-            env.identifyByPath = True
-        elif opt == "--ids":
-            env.printIDs = True
-        elif opt == "--include-all":
-            env.includeAll = True
-        elif opt == "--include-important":
-            env.includeImportant = True
-        elif opt == "--include-original":
-            env.includeOriginal = True
-        elif opt == "--include-other":
-            env.includeOther = True
-        elif opt == "--include-primary":
-            env.includePrimary = True
-        elif opt == "--no-act":
-            printNotice(
-                "no-act: No changes will be commited to the database!\n")
-            env.noAct = True
-        elif opt == "--position":
-            if optarg == "last":
-                env.position = -1
-            else:
-                try:
-                    env.position = int(optarg)
-                except ValueError:
-                    printErrorAndExit(
-                        "Invalid position: \"%s\"\n" % env.to_l(optarg))
-        elif opt in ("-t", "--type"):
-            env.type = optarg
-        elif opt in ("-v", "--verbose"):
-            env.verbose = True
-        elif opt == "--version":
-            sys.stdout.write("%s\n" % env.version)
-            sys.exit(0)
-
-    if not (env.includeAll or env.includeImportant or env.includeOriginal
-            or env.includeOther or env.includePrimary):
-        env.includePrimary = True
-
-    if len(args) == 0:
-        printErrorAndExit(
-            "No command given. See \"kofoto --help\" for help.\n")
-
-    try:
-        env.setup(configFileLocation, shelfLocation)
-    except ClientEnvironmentError, e:
-        printErrorAndExit(e[0])
-
-    if not commandTable.has_key(args[0]):
-        printErrorAndExit(
-            "Unknown command \"%s\". See \"kofoto --help\" for help.\n" %
-            env.to_l(args[0]))
-
-    try:
-        if env.shelf.isUpgradable():
-            printNotice(
-                "Upgrading %s to new database format...\n" % env.shelfLocation)
-            if not env.shelf.tryUpgrade():
-                printErrorAndExit("Failed to upgrade metadata database format.\n")
-        env.shelf.begin()
-    except ShelfNotFoundError, x:
-        printErrorAndExit("Could not open metadata database \"%s\".\n" % (
-            env.shelfLocation))
-    except ShelfLockedError, x:
-        printErrorAndExit(
-            "Could not open metadata database \"%s\".\n" % env.shelfLocation +
-            "Another process is locking it.\n")
-    except UnsupportedShelfError, filename:
-        printErrorAndExit(
-            "Could not read metadata database file %s (too new database format?).\n" %
-            filename)
-    try:
-        env.gencharenc = genCharEnc
-        env.out = printOutput
-        env.err = printError
-        env.errexit = printErrorAndExit
-        env.thumbnailsizelimit = env.config.getcoordlist(
-            "album generation", "thumbnail_size_limit")[0]
-        env.defaultsizelimit = env.config.getcoordlist(
-            "album generation", "default_image_size_limit")[0]
-
-        imgsizesval = env.config.getcoordlist(
-            "album generation", "other_image_size_limits")
-        imgsizesset = Set(imgsizesval) # Get rid of duplicates.
-        defaultlimit = env.config.getcoordlist(
-            "album generation", "default_image_size_limit")[0]
-        imgsizesset.add(defaultlimit)
-        imgsizes = list(imgsizesset)
-        imgsizes.sort(lambda x, y: cmp(x[0] * x[1], y[0] * y[1]))
-        env.imagesizelimits = imgsizes
-
-        commandTable[args[0]](env, args[1:])
-        if env.noAct:
-            env.shelf.rollback()
-            printOutput("no-act: All changes to the database have been revoked!\n")
-        else:
-            env.shelf.commit()
-        sys.exit(0)
-    except ArgumentError:
-        printErrorAndExit(
-            "Bad arguments to command. See \"kofoto --help\" for help.\n")
-    except UndeletableAlbumError, x:
-        printError("Undeletable album: \"%s\".\n" % env.to_l(x.args[0]))
-    except BadAlbumTagError, x:
-        printError("Bad album tag: \"%s\".\n" % env.to_l(x.args[0]))
-    except AlbumExistsError, x:
-        printError("Album already exists: \"%s\".\n" % env.to_l(x.args[0]))
-    except ImageDoesNotExistError, x:
-        printError("Image does not exist: \"%s\".\n" % env.to_l(x.args[0]))
-    except AlbumDoesNotExistError, x:
-        printError("Album does not exist: \"%s\".\n" % env.to_l(x.args[0]))
-    except ObjectDoesNotExistError, x:
-        printError("Object does not exist: \"%s\".\n" % env.to_l(x.args[0]))
-    except UnknownAlbumTypeError, x:
-        printError("Unknown album type: \"%s\".\n" % env.to_l(x.args[0]))
-    except UnsettableChildrenError, x:
-        printError("Cannot modify children of \"%s\" (children are created virtually).\n" % env.to_l(x.args[0]))
-    except CategoryExistsError, x:
-        printError("Category already exists: \"%s\".\n" % env.to_l(x.args[0]))
-    except CategoryDoesNotExistError, x:
-        printError("Category does not exist: \"%s\".\n" % env.to_l(x.args[0]))
-    except BadCategoryTagError, x:
-        printError("Bad category tag: %s.\n" % env.to_l(x.args[0]))
-    except CategoryPresentError, x:
-        printError("Object %s is already associated with category %s.\n" % (
-            env.to_l(x.args[0]), env.to_l(x.args[1])))
-    except CategoriesAlreadyConnectedError, x:
-        printError("Categories %s and %s are already connected.\n" % (
-            env.to_l(x.args[0]), env.to_l(x.args[1])))
-    except CategoryLoopError, x:
-        printError("Connecting %s to %s would make a loop in the categories.\n" % (
-            env.to_l(x.args[0]), env.to_l(x.args[1])))
-    except ParseError, x:
-        printError("While parsing search expression: %s.\n" % env.to_l(x.args[0]))
-    except UnterminatedStringError, x:
-        printError("While scanning search expression: unterminated string starting at character %d.\n" % (
-            env.to_l(x.args[0])))
-    except BadTokenError, x:
-        printError("While scanning search expression: bad token starting at character %d.\n" % (
-            env.to_l(x.args[0])))
-    except UnknownImageVersionTypeError, x:
-        printError("Unknown image version type: \"%s\".\n" % (
-            env.to_l(x.args[0])))
-    except KeyboardInterrupt:
-        printOutput("Interrupted.\n")
-    except IOError, e:
-        if e.filename:
-            errstr = "%s: \"%s\"" % (e.strerror, e.filename)
-        else:
-            errstr = e.strerror
-        printError("%s.\n" % errstr)
-    env.shelf.rollback()
-    sys.exit(1)
 
+# Find libraries if run from the source tree.
+sys.path.insert(0, os.path.join(bindir, "..", "packages"))
 
-######################################################################
-### Main.
+from kofoto.commandline.main import main
+main(sys.argv)
 
-if __name__ == "__main__":
-    main(sys.argv)
diff --git a/src/gkofoto/gkofoto/__init__.py b/src/gkofoto/gkofoto/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/src/gkofoto/gkofoto/albumdialog.py b/src/gkofoto/gkofoto/albumdialog.py
deleted file mode 100644 (file)
index d89bc46..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-import gtk
-import string
-import re
-from environment import env
-from gkofoto.taganddescriptiondialog import *
-
-class AlbumDialog(TagAndDescriptionDialog):
-    def __init__(self, title, albumId=None):
-        if albumId is not None:
-            self._album = env.shelf.getAlbum(albumId)
-            tagText = self._album.getTag()
-            descText = self._album.getAttribute(u"title")
-            if descText == None:
-                descText = u""
-        else:
-            self._album = None
-            tagText = u""
-            descText = u""
-        TagAndDescriptionDialog.__init__(self, title, tagText, descText)
-        label = self._widgets.get_widget("titleLabel")
-        label.set_label(u"Title:")
-
-    def _isTagOkay(self, tagString):
-        try:
-           # Check that the tag name is valid.
-           verifyValidAlbumTag(tagString)
-        except BadAlbumTagError:
-            return False
-        try:
-            album = env.shelf.getAlbumByTag(tagString)
-            if album == self._album:
-                # The tag exists, but is same as before.
-                return True
-            else:
-                # The tag is taken by another album.
-                return False
-        except AlbumDoesNotExistError:
-            # The tag didn't exist.
-            return True
diff --git a/src/gkofoto/gkofoto/albummembers.py b/src/gkofoto/gkofoto/albummembers.py
deleted file mode 100644 (file)
index 6490560..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-from objectcollection import *
-from environment import env
-
-class AlbumMembers(ObjectCollection):
-
-######################################################################
-### Public functions and constants
-
-    def __init__(self):
-        env.debug("Init AlbumMembers")
-        ObjectCollection.__init__(self)
-        self.__album = None
-
-    def loadAlbum(self, album):
-        env.debug("Loading album: " + album.getTag())
-        self.__album = album
-        self._loadObjectList(album.getChildren())
-
-    def isReorderable(self):
-        return self.__album and self.__album.isMutable()
-
-    def isMutable(self):
-        return (self.__album and
-                self.__album.isMutable() and
-                not self.isLoading())
-
-    def getContainer(self):
-        return self.__album
-
-    def cut(self, *foo):
-        self.copy()
-        self.delete()
-
-    def paste(self, *foo):
-        # This method assumes that self.getModel() returns an unsorted
-        # and mutable model.
-        self._freezeViews()
-        locations = list(self.getObjectSelection())
-        newObjects = list(env.clipboard)
-        albumCopied = False
-        for obj in newObjects:
-            if obj.isAlbum():
-                albumCopied = True
-                break
-        currentChildren = list(self.__album.getChildren())
-        if len(locations) > 0:
-            locations.sort()
-            insertLocation = locations[0]
-        else:
-            # Insert last.
-            insertLocation = len(currentChildren)
-        self.__album.setChildren(currentChildren[:insertLocation] +
-                                 newObjects +
-                                 currentChildren[insertLocation:])
-        self._insertObjectList(newObjects, insertLocation)
-        if albumCopied:
-            # TODO: Don't reload the whole tree.
-            env.mainwindow.reloadAlbumTree()
-        self.getObjectSelection().unselectAll()
-        self._thawViews()
-
-    def delete(self, *foo):
-        # This method assumes that self.getModel() returns an unsorted
-        # and mutable model
-        model = self.getModel()
-        self._freezeViews()
-        albumMembers = list(self.__album.getChildren())
-        locations = list(self.getObjectSelection())
-        locations.sort()
-        locations.reverse()
-        albumDeleted = False
-        for loc in locations:
-            if albumMembers[loc].isAlbum():
-                albumDeleted = True
-            albumMembers.pop(loc)
-            del model[loc]
-        self.__album.setChildren(albumMembers)
-        self.getObjectSelection().unselectAll()
-        if albumDeleted:
-            # TODO: Don't reload the whole tree.
-            env.mainwindow.reloadAlbumTree()
-        self._thawViews()
-
-######################################################################
-### Private functions
diff --git a/src/gkofoto/gkofoto/albums.py b/src/gkofoto/gkofoto/albums.py
deleted file mode 100644 (file)
index f893f3a..0000000
+++ /dev/null
@@ -1,253 +0,0 @@
-import gtk
-import gobject
-import gtk
-from environment import env
-from albumdialog import AlbumDialog
-from menuhandler import *
-from registerimagesdialog import RegisterImagesDialog
-
-class Albums:
-
-###############################################################################
-### Public
-
-    __createAlbumLabel = "Create subalbum..."
-    __registerImagesLabel = "Register and add images..."
-    __generateHtmlLabel = "Generate HTML..."
-    __destroyAlbumLabel = "Destroy album..."
-    __editAlbumLabel = "Album properties..."
-
-    # TODO This class should probably be splited in a model and a view when/if
-    #      a multiple windows feature is introduced.
-
-    def __init__(self, mainWindow):
-        self._connectedOids = []
-        self.__albumModel = gtk.TreeStore(gobject.TYPE_INT,      # ALBUM_ID
-                                          gobject.TYPE_STRING,   # TAG
-                                          gobject.TYPE_STRING,   # TEXT
-                                          gobject.TYPE_STRING,   # TYPE
-                                          gobject.TYPE_BOOLEAN)  # SELECTABLE
-        self.__mainWindow = mainWindow
-        self.__albumView = env.widgets["albumView"]
-        self.__albumView.set_model(self.__albumModel)
-        self.__albumView.connect("focus-in-event", self._treeViewFocusInEvent)
-        self.__albumView.connect("focus-out-event", self._treeViewFocusOutEvent)
-        renderer = gtk.CellRendererText()
-        column = gtk.TreeViewColumn("Albums", renderer, text=self.__COLUMN_TEXT)
-        column.set_clickable(True)
-        self.__albumView.append_column(column)
-        albumSelection = self.__albumView.get_selection()
-        albumSelection.connect("changed", self._albumSelectionUpdated)
-        albumSelection.set_select_function(self._isSelectable, self.__albumModel)
-        self.__contextMenu = self.__createContextMenu()
-        self.__albumView.connect("button_press_event", self._button_pressed)
-        self.loadAlbumTree()
-        iterator = self.__albumModel.get_iter_first()
-        albumSelection.select_iter(iterator)
-
-    def loadAlbumTree(self):
-        env.shelf.flushObjectCache()
-        self.__albumModel.clear()
-        self.__loadAlbumTreeHelper()
-        env.widgets["albumView"].expand_row(0, False) # Expand root album
-
-    def unselect(self):
-        self.__albumView.get_selection().unselect_all()
-
-###############################################################################
-### Callback functions registered by this class but invoked from other classes.
-
-    def _isSelectable(self, path, model):
-        return model[path][self.__COLUMN_SELECTABLE]
-
-    def _albumSelectionUpdated(self, selection=None, load=True):
-        # The focus grab below is made to compensate for what could be
-        # some GTK bug. Without the call, the focus-out-event signal
-        # sometimes isn't emitted for the view widget in the table
-        # view, which messes up the menubar callback registrations.
-        self.__albumView.grab_focus()
-
-        if not selection:
-            selection = self.__albumView.get_selection()
-        albumModel, iterator =  self.__albumView.get_selection().get_selected()
-        createMenuItem = self.__menuGroup[self.__createAlbumLabel]
-        registerMenuItem = self.__menuGroup[self.__registerImagesLabel]
-        generateHtmlMenuItem = self.__menuGroup[self.__generateHtmlLabel]
-        destroyMenuItem = self.__menuGroup[self.__destroyAlbumLabel]
-        editMenuItem = self.__menuGroup[self.__editAlbumLabel]
-        if iterator:
-            albumTag = albumModel.get_value(iterator, self.__COLUMN_TAG)
-            if load:
-                self.__mainWindow.loadQuery("/" + albumTag.decode("utf-8"))
-            album = env.shelf.getAlbum(
-                albumModel.get_value(iterator, self.__COLUMN_ALBUM_ID))
-            createMenuItem.set_sensitive(album.isMutable())
-            env.widgets["menubarCreateAlbumChild"].set_sensitive(album.isMutable())
-            registerMenuItem.set_sensitive(album.isMutable())
-            env.widgets["menubarRegisterAndAddImages"].set_sensitive(album.isMutable())
-            generateHtmlMenuItem.set_sensitive(True)
-            env.widgets["menubarGenerateHtml"].set_sensitive(True)
-            destroyMenuItem.set_sensitive(album != env.shelf.getRootAlbum())
-            env.widgets["menubarDestroy"].set_sensitive(album != env.shelf.getRootAlbum())
-            editMenuItem.set_sensitive(True)
-            env.widgets["menubarProperties"].set_sensitive(True)
-        else:
-            createMenuItem.set_sensitive(False)
-            registerMenuItem.set_sensitive(False)
-            generateHtmlMenuItem.set_sensitive(False)
-            destroyMenuItem.set_sensitive(False)
-            editMenuItem.set_sensitive(False)
-            env.widgets["menubarCreateAlbumChild"].set_sensitive(False)
-            env.widgets["menubarRegisterAndAddImages"].set_sensitive(False)
-            env.widgets["menubarGenerateHtml"].set_sensitive(False)
-            env.widgets["menubarDestroy"].set_sensitive(False)
-            env.widgets["menubarProperties"].set_sensitive(False)
-
-    def _createChildAlbum(self, *dummies):
-        dialog = AlbumDialog("Create album")
-        dialog.run(self._createAlbumHelper)
-
-    def _registerImages(self, *dummies):
-        albumModel, iterator =  self.__albumView.get_selection().get_selected()
-        selectedAlbumId = albumModel.get_value(iterator, self.__COLUMN_ALBUM_ID)
-        selectedAlbum = env.shelf.getAlbum(selectedAlbumId)
-        dialog = RegisterImagesDialog(selectedAlbum)
-        if dialog.run() == gtk.RESPONSE_OK:
-            self.__mainWindow.reload() # TODO: don't reload everything.
-        dialog.destroy()
-
-    def _generateHtml(self, *dummies):
-        albumModel, iterator =  self.__albumView.get_selection().get_selected()
-        selectedAlbumId = albumModel.get_value(iterator, self.__COLUMN_ALBUM_ID)
-        selectedAlbum = env.shelf.getAlbum(selectedAlbumId)
-        self.__mainWindow.generateHtml(selectedAlbum)
-
-    def _createAlbumHelper(self, tag, desc):
-        newAlbum = env.shelf.createAlbum(tag)
-        if len(desc) > 0:
-            newAlbum.setAttribute(u"title", desc)
-        albumModel, iterator =  self.__albumView.get_selection().get_selected()
-        if iterator is None:
-            selectedAlbum = env.shelf.getRootAlbum()
-        else:
-            selectedAlbumId = albumModel.get_value(iterator, self.__COLUMN_ALBUM_ID)
-            selectedAlbum = env.shelf.getAlbum(selectedAlbumId)
-        children = list(selectedAlbum.getChildren())
-        children.append(newAlbum)
-        selectedAlbum.setChildren(children)
-        # TODO The whole tree should not be reloaded
-        self.loadAlbumTree()
-        # TODO update objectCollection?
-
-    def _destroyAlbum(self, *dummies):
-        dialogId = "destroyAlbumsDialog"
-        widgets = gtk.glade.XML(env.gladeFile, dialogId)
-        dialog = widgets.get_widget(dialogId)
-        result = dialog.run()
-        if result == gtk.RESPONSE_OK:
-            albumModel, iterator =  self.__albumView.get_selection().get_selected()
-            selectedAlbumId = albumModel.get_value(iterator, self.__COLUMN_ALBUM_ID)
-            env.shelf.deleteAlbum(selectedAlbumId)
-            # TODO The whole tree should not be reloaded
-            self.loadAlbumTree()
-            # TODO update objectCollection?
-        dialog.destroy()
-
-    def _editAlbum(self, *dummies):
-        albumModel, iterator =  self.__albumView.get_selection().get_selected()
-        selectedAlbumId = albumModel.get_value(iterator, self.__COLUMN_ALBUM_ID)
-        dialog = AlbumDialog("Edit album", selectedAlbumId)
-        dialog.run(self._editAlbumHelper)
-
-    def _editAlbumHelper(self, tag, desc):
-        albumModel, iterator =  self.__albumView.get_selection().get_selected()
-        selectedAlbumId = albumModel.get_value(iterator, self.__COLUMN_ALBUM_ID)
-        selectedAlbum = env.shelf.getAlbum(selectedAlbumId)
-        selectedAlbum.setTag(tag)
-        if len(desc) > 0:
-            selectedAlbum.setAttribute(u"title", desc)
-        else:
-            selectedAlbum.deleteAttribute(u"title")
-        # TODO The whole tree should not be reloaded
-        self.loadAlbumTree()
-        # TODO update objectCollection?
-
-    def _button_pressed(self, treeView, event):
-        if event.button == 3:
-            self.__contextMenu.popup(None,None,None,event.button,event.time)
-            return True
-        else:
-            return False
-
-    def _treeViewFocusInEvent(self, widget, event):
-        self._albumSelectionUpdated(None, load=False)
-        for widgetName, function in [
-                ("menubarCreateAlbumChild", self._createChildAlbum),
-                ("menubarRegisterAndAddImages", self._registerImages),
-                ("menubarGenerateHtml", self._generateHtml),
-                ("menubarProperties", self._editAlbum),
-                ("menubarDestroy", self._destroyAlbum),
-                ]:
-            w = env.widgets[widgetName]
-            oid = w.connect("activate", function, None)
-            self._connectedOids.append((w, oid))
-
-    def _treeViewFocusOutEvent(self, widget, event):
-        for (widget, oid) in self._connectedOids:
-            widget.disconnect(oid)
-        self._connectedOids = []
-        for widgetName in [
-                "menubarCreateAlbumChild",
-                "menubarRegisterAndAddImages",
-                "menubarGenerateHtml",
-                "menubarProperties",
-                "menubarDestroy",
-                ]:
-            env.widgets[widgetName].set_sensitive(False)
-
-###############################################################################
-### Private
-
-    __COLUMN_ALBUM_ID   = 0
-    __COLUMN_TAG        = 1
-    __COLUMN_TEXT       = 2
-    __COLUMN_TYPE       = 3
-    __COLUMN_SELECTABLE = 4
-
-    def __loadAlbumTreeHelper(self, parentAlbum=None, album=None, visited=[]):
-        if not album:
-            album = env.shelf.getRootAlbum()
-        iterator = self.__albumModel.append(parentAlbum)
-        # TODO Do we have to use iterators here or can we use pygtks simplified syntax?
-        self.__albumModel.set_value(iterator, self.__COLUMN_ALBUM_ID, album.getId())
-        self.__albumModel.set_value(iterator, self.__COLUMN_TYPE, album.getType())
-        self.__albumModel.set_value(iterator, self.__COLUMN_TAG, album.getTag())
-        self.__albumModel.set_value(iterator, self.__COLUMN_SELECTABLE, True)
-        albumTitle = album.getAttribute(u"title")
-        if albumTitle == None or len(albumTitle) < 1:
-            self.__albumModel.set_value(iterator, self.__COLUMN_TEXT, album.getTag())
-        else:
-            self.__albumModel.set_value(iterator, self.__COLUMN_TEXT, albumTitle)
-        if album.getId() not in visited:
-            for child in album.getAlbumChildren():
-                self.__loadAlbumTreeHelper(iterator, child, visited + [album.getId()])
-        else:
-            iterator = self.__albumModel.insert_before(iterator, None)
-            self.__albumModel.set_value(iterator, self.__COLUMN_TEXT, "[...]")
-            self.__albumModel.set_value(iterator, self.__COLUMN_SELECTABLE, False)
-
-    def __createContextMenu(self):
-        self.__menuGroup = MenuGroup()
-        self.__menuGroup.addMenuItem(
-            self.__createAlbumLabel, self._createChildAlbum)
-        self.__menuGroup.addMenuItem(
-            self.__registerImagesLabel, self._registerImages)
-        self.__menuGroup.addMenuItem(
-            self.__generateHtmlLabel, self._generateHtml)
-        self.__menuGroup.addMenuItem(
-            self.__destroyAlbumLabel, self._destroyAlbum)
-        self.__menuGroup.addStockImageMenuItem(
-            self.__editAlbumLabel,
-            gtk.STOCK_PROPERTIES,
-            self._editAlbum)
-        return self.__menuGroup.createGroupMenu()
diff --git a/src/gkofoto/gkofoto/categories.py b/src/gkofoto/gkofoto/categories.py
deleted file mode 100644 (file)
index 18891c9..0000000
+++ /dev/null
@@ -1,692 +0,0 @@
-import gobject
-import gtk
-import re
-
-from environment import env
-from categorydialog import CategoryDialog
-from menuhandler import *
-from kofoto.search import *
-from kofoto.shelf import *
-
-class Categories:
-
-######################################################################
-### Public
-
-    def __init__(self, mainWindow):
-        self.__mainWindow = mainWindow
-        self.__toggleColumn = None
-        self.__objectCollection = None
-        self.__ignoreSelectEvent = False
-        self.__selectedCategoriesIds  = {}
-        self.__categoryModel = gtk.TreeStore(gobject.TYPE_INT,      # CATEGORY_ID
-                                             gobject.TYPE_STRING,   # DESCRIPTION
-                                             gobject.TYPE_BOOLEAN,  # CONNECTED
-                                             gobject.TYPE_BOOLEAN)  # INCONSISTENT
-
-        #
-        # Category tree view
-        #
-        self.__categoryView = env.widgets["categoryView"]
-        self.__categoryView.realize()
-        self.__categoryView.set_model(self.__categoryModel)
-        self.__categoryView.connect("focus-in-event", self._categoryViewFocusInEvent)
-        self.__categoryView.connect("focus-out-event", self._categoryViewFocusOutEvent)
-
-        # Create toggle column
-        toggleRenderer = gtk.CellRendererToggle()
-        toggleRenderer.connect("toggled", self._connectionToggled)
-        self.__toggleColumn = gtk.TreeViewColumn("",
-                                                 toggleRenderer,
-                                                 active=self.__COLUMN_CONNECTED,
-                                                 inconsistent=self.__COLUMN_INCONSISTENT)
-        self.__categoryView.append_column(self.__toggleColumn)
-
-        # Create text column
-        textRenderer = gtk.CellRendererText()
-        textColumn = gtk.TreeViewColumn("Category", textRenderer, text=self.__COLUMN_DESCRIPTION)
-        self.__categoryView.append_column(textColumn)
-        self.__categoryView.set_expander_column(textColumn)
-
-        #
-        # Category quick select view
-        #
-        self.__categoryQSModel = gtk.ListStore(
-            gobject.TYPE_INT,      # CATEGORY_ID
-            gobject.TYPE_STRING,   # DESCRIPTION
-            gobject.TYPE_BOOLEAN,  # CONNECTED
-            gobject.TYPE_BOOLEAN)  # INCONSISTENT
-        self.__categoryQSView = env.widgets["categoryQuickSelectView"]
-        self.__categoryQSView.connect(
-            "focus-in-event", self._categoryQSViewFocusInEvent)
-        self.__categoryQSEntry = env.widgets["categoryQuickSelectEntry"]
-        self.__categoryQSEntry.connect(
-            "activate", self._categoryQSEntryActivateEvent)
-        self.__categoryQSEntry.connect(
-            "changed", self._categoryQSEntryChangedEvent)
-        self.__categoryQSButton = env.widgets["categoryQuickSelectButton"]
-        self.__categoryQSButton.connect(
-            "clicked", self._categoryQSEntryActivateEvent)
-        self.__categoryQSView.realize()
-        self.__categoryQSView.set_model(self.__categoryQSModel)
-        self.__categoryQSFreeze = False
-
-        # Create toggle column
-        toggleRenderer = gtk.CellRendererToggle()
-        toggleRenderer.connect("toggled", self._qsConnectionToggled)
-        self.__toggleQSColumn = gtk.TreeViewColumn(
-            "",
-            toggleRenderer,
-            active=self.__COLUMN_CONNECTED,
-            inconsistent=self.__COLUMN_INCONSISTENT)
-        self.__qsToggleColumn = gtk.TreeViewColumn(
-            "",
-            toggleRenderer,
-            active=self.__COLUMN_CONNECTED,
-            inconsistent=self.__COLUMN_INCONSISTENT)
-        self.__categoryQSView.append_column(self.__qsToggleColumn)
-
-        # Create text column
-        textRenderer = gtk.CellRendererText()
-        textColumn = gtk.TreeViewColumn(
-            "Category", textRenderer, text=self.__COLUMN_DESCRIPTION)
-        self.__categoryQSView.append_column(textColumn)
-        self.__categoryQSView.set_expander_column(textColumn)
-
-        # Create context menu
-        # TODO Is it possible to load a menu from a glade file instead?
-        #      If not, create some helper functions to construct the menu...
-        self._contextMenu = gtk.Menu()
-
-        self._contextMenuGroup = MenuGroup()
-        self._contextMenuGroup.addStockImageMenuItem(
-            self.__cutCategoryLabel,
-            gtk.STOCK_CUT,
-            self._cutCategory)
-        self._contextMenuGroup.addStockImageMenuItem(
-            self.__copyCategoryLabel,
-            gtk.STOCK_COPY,
-            self._copyCategory)
-        self._contextMenuGroup.addStockImageMenuItem(
-            self.__pasteCategoryLabel,
-            gtk.STOCK_PASTE,
-            self._pasteCategory)
-        self._contextMenuGroup.addStockImageMenuItem(
-            self.__destroyCategoryLabel,
-            gtk.STOCK_DELETE,
-            self._deleteCategories)
-        self._contextMenuGroup.addMenuItem(
-            self.__disconnectCategoryLabel,
-            self._disconnectCategory)
-        self._contextMenuGroup.addMenuItem(
-            self.__createChildCategoryLabel,
-            self._createChildCategory)
-        self._contextMenuGroup.addMenuItem(
-            self.__createRootCategoryLabel,
-            self._createRootCategory)
-        self._contextMenuGroup.addStockImageMenuItem(
-            self.__propertiesLabel,
-            gtk.STOCK_PROPERTIES,
-            self._editProperties)
-
-        for item in self._contextMenuGroup:
-            self._contextMenu.append(item)
-
-        env.widgets["categorySearchButton"].set_sensitive(False)
-
-        # Init menubar items.
-        env.widgets["menubarDisconnectFromParent"].connect(
-            "activate", self._disconnectCategory, None)
-        env.widgets["menubarCreateChild"].connect(
-            "activate", self._createChildCategory, None)
-        env.widgets["menubarCreateRoot"].connect(
-            "activate", self._createRootCategory, None)
-
-        # Init selection functions
-        categorySelection = self.__categoryView.get_selection()
-        categorySelection.set_mode(gtk.SELECTION_MULTIPLE)
-        categorySelection.set_select_function(self._selectionFunction, None)
-        categorySelection.connect("changed", self._categorySelectionChanged)
-        categoryQSSelection = self.__categoryQSView.get_selection()
-        categoryQSSelection.set_mode(gtk.SELECTION_NONE)
-
-        # Connect the rest of the UI events
-        self.__categoryView.connect("button_press_event", self._button_pressed)
-        self.__categoryView.connect("button_release_event", self._button_released)
-        self.__categoryView.connect("row-activated", self._rowActivated)
-        env.widgets["categorySearchButton"].connect('clicked', self._executeQuery)
-
-        self.loadCategoryTree()
-
-
-    def loadCategoryTree(self):
-        self.__categoryModel.clear()
-        env.shelf.flushCategoryCache()
-        for category in self.__sortCategories(env.shelf.getRootCategories()):
-            self.__loadCategorySubTree(None, category)
-        if self.__objectCollection is not None:
-            self.objectSelectionChanged()
-
-    def setCollection(self, objectCollection):
-        if self.__objectCollection is not None:
-            self.__objectCollection.getObjectSelection().removeChangedCallback(self.objectSelectionChanged)
-        self.__objectCollection = objectCollection
-        self.__objectCollection.getObjectSelection().addChangedCallback(self.objectSelectionChanged)
-        self.objectSelectionChanged()
-
-    def objectSelectionChanged(self, objectSelection=None):
-        self.__updateToggleColumn()
-        self.__updateQSToggleColumn()
-        self.__updateContextMenu()
-        self.__expandAndCollapseRows(env.widgets["autoExpand"].get_active(),
-                                     env.widgets["autoCollapse"].get_active())
-
-
-###############################################################################
-### Callback functions registered by this class but invoked from other classes.
-
-    def _executeQuery(self, *foo):
-        query = self.__buildQueryFromSelection()
-        if query:
-            self.__mainWindow.loadQuery(query)
-
-    def _categoryViewFocusInEvent(self, widget, event):
-        self._menubarOids = []
-        for widgetName, function in [
-                ("menubarCut", lambda *x: self._cutCategory(None, None)),
-                ("menubarCopy", lambda *x: self._copyCategory(None, None)),
-                ("menubarPaste", lambda *x: self._pasteCategory(None, None)),
-                ("menubarDestroy", lambda *x: self._deleteCategories(None, None)),
-                ("menubarClear", lambda *x: widget.get_selection().unselect_all()),
-                ("menubarSelectAll", lambda *x: widget.get_selection().select_all()),
-                ("menubarProperties", lambda *x: self._editProperties(None, None)),
-                ]:
-            w = env.widgets[widgetName]
-            oid = w.connect("activate", function)
-            self._menubarOids.append((w, oid))
-        self.__updateContextMenu()
-
-    def _categoryViewFocusOutEvent(self, widget, event):
-        for (widget, oid) in self._menubarOids:
-            widget.disconnect(oid)
-
-    def _categorySelectionChanged(self, selection):
-        selectedCategoryRows = []
-        selection = self.__categoryView.get_selection()
-        # TODO replace with "get_selected_rows()" when it is introduced in Pygtk 2.2 API
-        selection.selected_foreach(lambda model,
-                                   path,
-                                   iter:
-                                   selectedCategoryRows.append(model[path]))
-        self.__selectedCategoriesIds  = {}
-
-        for categoryRow in selectedCategoryRows:
-            cid = categoryRow[self.__COLUMN_CATEGORY_ID]
-            # row.parent method gives assertion failed, dont know why. Using workaround instead.
-            parentPath = categoryRow.path[:-1]
-            if parentPath:
-                parentId = categoryRow.model[parentPath][self.__COLUMN_CATEGORY_ID]
-            else:
-                parentId = None
-            try:
-                 self.__selectedCategoriesIds[cid].append(parentId)
-            except KeyError:
-                 self.__selectedCategoriesIds[cid] = [parentId]
-        self.__updateContextMenu()
-        env.widgets["categorySearchButton"].set_sensitive(
-            len(selectedCategoryRows) > 0)
-
-    def _connectionToggled(self, renderer, path):
-        categoryRow = self.__categoryModel[path]
-        category = env.shelf.getCategory(categoryRow[self.__COLUMN_CATEGORY_ID])
-        if categoryRow[self.__COLUMN_INCONSISTENT] \
-               or not categoryRow[self.__COLUMN_CONNECTED]:
-            for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
-                try:
-                    obj.addCategory(category)
-                except CategoryPresentError:
-                    # The object was already connected to the category
-                    pass
-            categoryRow[self.__COLUMN_INCONSISTENT] = False
-            categoryRow[self.__COLUMN_CONNECTED] = True
-        else:
-            for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
-                obj.removeCategory(category)
-            categoryRow[self.__COLUMN_CONNECTED] = False
-            categoryRow[self.__COLUMN_INCONSISTENT] = False
-        self.__updateToggleColumn()
-        self.__updateQSToggleColumn()
-
-    def _button_pressed(self, treeView, event):
-        if event.button == 3:
-            self._contextMenu.popup(None,None,None,event.button,event.time)
-            return True
-        rec = self.__categoryView.get_cell_area(0, self.__toggleColumn)
-        if event.x <= (rec.x + rec.width):
-            # Ignore selection event since the user clicked on the toggleColumn.
-            self.__ignoreSelectEvent = True
-        return False
-
-    def _button_released(self, treeView, event):
-        self.__ignoreSelectEvent = False
-        return False
-
-    def _rowActivated(self, a, b, c):
-        # TODO What should happen if the user dubble-click on a category?
-        pass
-
-    def _copyCategory(self, item, data):
-        cc = ClipboardCategories()
-        cc.type = cc.COPY
-        cc.categories = self.__selectedCategoriesIds
-        env.clipboard.setCategories(cc)
-
-    def _cutCategory(self, item, data):
-        cc = ClipboardCategories()
-        cc.type = cc.CUT
-        cc.categories = self.__selectedCategoriesIds
-        env.clipboard.setCategories(cc)
-
-    def _pasteCategory(self, item, data):
-        assert env.clipboard.hasCategories()
-        clipboardCategories = env.clipboard[0]
-        env.clipboard.clear()
-        try:
-            for (categoryId, previousParentIds) in clipboardCategories.categories.items():
-                for newParentId in self.__selectedCategoriesIds:
-                    if clipboardCategories.type == ClipboardCategories.COPY:
-                        self.__connectChildToCategory(categoryId, newParentId)
-                        for parentId in previousParentIds:
-                            if parentId is None:
-                                self.__disconnectChildHelper(categoryId, None,
-                                                             None, self.__categoryModel)
-                    else:
-                        if newParentId in previousParentIds:
-                            previousParentIds.remove(newParentId)
-                        else:
-                            self.__connectChildToCategory(categoryId, newParentId)
-                        for parentId in previousParentIds:
-                            if parentId is None:
-                                self.__disconnectChildHelper(categoryId, None,
-                                                             None, self.__categoryModel)
-                            else:
-                                self.__disconnectChild(categoryId, parentId)
-        except CategoryLoopError:
-            dialog = gtk.MessageDialog(
-                type=gtk.MESSAGE_ERROR,
-                buttons=gtk.BUTTONS_OK,
-                message_format="Category loop detected.")
-            dialog.run()
-            dialog.destroy()
-        self.__updateToggleColumn()
-        self.__expandAndCollapseRows(False, False)
-
-    def _createRootCategory(self, item, data):
-        dialog = CategoryDialog("Create top-level category")
-        dialog.run(self._createRootCategoryHelper)
-
-    def _createRootCategoryHelper(self, tag, desc):
-        category = env.shelf.createCategory(tag, desc)
-        self.__loadCategorySubTree(None, category)
-
-    def _createChildCategory(self, item, data):
-        dialog = CategoryDialog("Create subcategory")
-        dialog.run(self._createChildCategoryHelper)
-
-    def _createChildCategoryHelper(self, tag, desc):
-        newCategory = env.shelf.createCategory(tag, desc)
-        for selectedCategoryId in self.__selectedCategoriesIds:
-            self.__connectChildToCategory(newCategory.getId(), selectedCategoryId)
-        self.__expandAndCollapseRows(False, False)
-
-    def _deleteCategories(self, item, data):
-        dialogId = "destroyCategoriesDialog"
-        widgets = gtk.glade.XML(env.gladeFile, dialogId)
-        dialog = widgets.get_widget(dialogId)
-        result = dialog.run()
-        if result == gtk.RESPONSE_OK:
-            for categoryId in self.__selectedCategoriesIds:
-                category = env.shelf.getCategory(categoryId)
-                for child in list(category.getChildren()):
-                    # The backend automatically disconnects childs
-                    # when a category is deleted, but we do it ourself
-                    # to make sure that the treeview widget is
-                    # updated.
-                    self.__disconnectChild(child.getId(), categoryId)
-                env.shelf.deleteCategory(categoryId)
-                env.shelf.flushCategoryCache()
-                self.__forEachCategoryRow(
-                    self.__deleteCategoriesHelper, categoryId)
-        dialog.destroy()
-
-    def __deleteCategoriesHelper(self, categoryRow, categoryIdToDelete):
-        if categoryRow[self.__COLUMN_CATEGORY_ID] == categoryIdToDelete:
-            self.__categoryModel.remove(categoryRow.iter)
-
-    def _disconnectCategory(self, item, data):
-        for (categoryId, parentIds) in self.__selectedCategoriesIds.items():
-            for parentId in parentIds:
-                if not parentId == None: # Not possible to disconnect root categories
-                    self.__disconnectChild(categoryId, parentId)
-
-    def _editProperties(self, item, data):
-        for categoryId in self.__selectedCategoriesIds:
-            dialog = CategoryDialog("Change properties", categoryId)
-            dialog.run(self._editPropertiesHelper, data=categoryId)
-
-    def _editPropertiesHelper(self, tag, desc, categoryId):
-         category = env.shelf.getCategory(categoryId)
-         category.setTag(tag)
-         category.setDescription(desc)
-         env.shelf.flushCategoryCache()
-         self.__forEachCategoryRow(self.__updatePropertiesFromShelf, categoryId)
-
-    def _selectionFunction(self, path, b):
-        return not self.__ignoreSelectEvent
-
-    def _categoryQSViewFocusInEvent(self, widget, event):
-        self.__categoryQSEntry.grab_focus()
-
-    def _categoryQSEntryActivateEvent(self, entry):
-        if not self.__qsSelectedPath:
-            return
-        self._qsConnectionToggled(None, self.__qsSelectedPath)
-        self.__categoryQSFreeze = True
-        self.__categoryQSEntry.set_text("")
-        self.__categoryQSFreeze = False
-        self.__qsSelectedPath = None
-
-    def _categoryQSEntryChangedEvent(self, entry):
-        if self.__categoryQSFreeze:
-            return
-        self.__categoryQSModel.clear()
-        self.__qsSelectedPath = None
-        self.__categoryQSButton.set_sensitive(False)
-        text = entry.get_text().decode("utf-8")
-        if text == "":
-            return
-
-        regexp = re.compile(".*%s.*" % re.escape(text.lower()))
-        categories = list(env.shelf.getMatchingCategories(regexp))
-        categories.sort(self.__compareCategories)
-        exactMatches = []
-        for category in categories:
-            iterator = self.__categoryQSModel.append()
-            if (category.getTag().lower() == text.lower() or
-                category.getDescription().lower() == text.lower()):
-                exactMatches.append(self.__categoryQSModel.get_path(iterator))
-            self.__categoryQSModel.set_value(
-                iterator, self.__COLUMN_CATEGORY_ID, category.getId())
-            self.__categoryQSModel.set_value(
-                iterator,
-                self.__COLUMN_DESCRIPTION,
-                "%s [%s]" % (category.getDescription(), category.getTag()))
-        if len(categories) == 1:
-            self.__qsSelectedPath = (0,)
-            self.__categoryQSButton.set_sensitive(True)
-        elif len(exactMatches) == 1:
-            self.__qsSelectedPath = exactMatches[0]
-            self.__categoryQSButton.set_sensitive(True)
-        self.__updateQSToggleColumn()
-
-    def _qsConnectionToggled(self, renderer, path):
-        categoryRow = self.__categoryQSModel[path]
-        category = env.shelf.getCategory(
-            categoryRow[self.__COLUMN_CATEGORY_ID])
-        if categoryRow[self.__COLUMN_INCONSISTENT] \
-               or not categoryRow[self.__COLUMN_CONNECTED]:
-            for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
-                try:
-                    obj.addCategory(category)
-                except CategoryPresentError:
-                    # The object was already connected to the category
-                    pass
-            categoryRow[self.__COLUMN_INCONSISTENT] = False
-            categoryRow[self.__COLUMN_CONNECTED] = True
-        else:
-            for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
-                obj.removeCategory(category)
-            categoryRow[self.__COLUMN_CONNECTED] = False
-            categoryRow[self.__COLUMN_INCONSISTENT] = False
-        self.__updateToggleColumn()
-        self.__expandAndCollapseRows(
-            env.widgets["autoExpand"].get_active(),
-            env.widgets["autoCollapse"].get_active())
-
-######################################################################
-### Private
-
-    __cutCategoryLabel = "Cut"
-    __copyCategoryLabel = "Copy"
-    __pasteCategoryLabel = "Paste as child(ren)"
-    __destroyCategoryLabel = "Destroy..."
-    __disconnectCategoryLabel = "Disconnect from parent"
-    __createChildCategoryLabel = "Create subcategory..."
-    __createRootCategoryLabel = "Create top-level category..."
-    __propertiesLabel = "Properties"
-
-    __COLUMN_CATEGORY_ID  = 0
-    __COLUMN_DESCRIPTION  = 1
-    __COLUMN_CONNECTED    = 2
-    __COLUMN_INCONSISTENT = 3
-
-    def __loadCategorySubTree(self, parent, category):
-        # TODO Do we have to use iterators here or can we use pygtks simplified syntax?
-        iterator = self.__categoryModel.iter_children(parent)
-        while (iterator != None and
-               self.__categoryModel.get_value(iterator, self.__COLUMN_DESCRIPTION) <
-                   category.getDescription()):
-            iterator = self.__categoryModel.iter_next(iterator)
-        iterator = self.__categoryModel.insert_before(parent, iterator)
-        self.__categoryModel.set_value(iterator, self.__COLUMN_CATEGORY_ID, category.getId())
-        self.__categoryModel.set_value(iterator, self.__COLUMN_DESCRIPTION, category.getDescription())
-        self.__categoryModel.set_value(iterator, self.__COLUMN_CONNECTED, False)
-        self.__categoryModel.set_value(iterator, self.__COLUMN_INCONSISTENT, False)
-        for child in self.__sortCategories(category.getChildren()):
-            self.__loadCategorySubTree(iterator, child)
-
-    def __buildQueryFromSelection(self):
-        if env.widgets["categoriesOr"].get_active():
-            operator = " or "
-        else:
-            operator = " and "
-        return operator.join([env.shelf.getCategory(x).getTag()
-                              for x in self.__selectedCategoriesIds])
-
-    def __updateContextMenu(self):
-        # TODO Create helper functions to use from this method
-        menubarWidgetNames = [
-                "menubarCut",
-                "menubarCopy",
-                "menubarPaste",
-                "menubarDestroy",
-                "menubarProperties",
-                "menubarDisconnectFromParent",
-                "menubarCreateChild",
-                "menubarCreateRoot",
-                ]
-        if len(self.__selectedCategoriesIds) == 0:
-            self._contextMenuGroup.disable()
-            for widgetName in menubarWidgetNames:
-                env.widgets[widgetName].set_sensitive(False)
-            self._contextMenuGroup[
-                self.__createRootCategoryLabel].set_sensitive(True)
-            env.widgets["menubarCreateRoot"].set_sensitive(True)
-        else:
-            self._contextMenuGroup.enable()
-            for widgetName in menubarWidgetNames:
-                env.widgets[widgetName].set_sensitive(True)
-            if not env.clipboard.hasCategories():
-                self._contextMenuGroup[
-                    self.__pasteCategoryLabel].set_sensitive(False)
-                env.widgets["menubarPaste"].set_sensitive(False)
-        propertiesItem = self._contextMenuGroup[self.__propertiesLabel]
-        propertiesItemSensitive = len(self.__selectedCategoriesIds) == 1
-        propertiesItem.set_sensitive(propertiesItemSensitive)
-        env.widgets["menubarProperties"].set_sensitive(propertiesItemSensitive)
-
-    def __updateToggleColumn(self):
-        # find out which categories are connected, not connected or
-        # partitionally connected to selected objects
-        nrSelectedObjectsInCategory = {}
-        nrSelectedObjects = 0
-        for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
-            nrSelectedObjects += 1
-            for category in obj.getCategories():
-                categoryId = category.getId()
-                try:
-                    nrSelectedObjectsInCategory[categoryId] += 1
-                except KeyError:
-                        nrSelectedObjectsInCategory[categoryId] = 1
-        self.__forEachCategoryRow(self.__updateToggleColumnHelper,
-                                  (nrSelectedObjects, nrSelectedObjectsInCategory))
-
-    def __updateQSToggleColumn(self):
-        selectedObjects = \
-            self.__objectCollection.getObjectSelection().getSelectedObjects()
-        nrSelectedObjectsInCategory = {}
-        nrSelectedObjects = 0
-        for obj in selectedObjects:
-            nrSelectedObjects += 1
-            for category in obj.getCategories():
-                catid = category.getId()
-                nrSelectedObjectsInCategory.setdefault(catid, 0)
-                nrSelectedObjectsInCategory[catid] += 1
-        self.__forEachCategoryRow(
-            self.__updateToggleColumnHelper,
-            (nrSelectedObjects, nrSelectedObjectsInCategory),
-            self.__categoryQSModel)
-
-    def __updateToggleColumnHelper(self,
-                                   categoryRow,
-                                   (nrSelectedObjects, nrSelectedObjectsInCategory)):
-        categoryId = categoryRow[self.__COLUMN_CATEGORY_ID]
-        if categoryId in nrSelectedObjectsInCategory:
-            if nrSelectedObjectsInCategory[categoryId] < nrSelectedObjects:
-                # Some of the selected objects are connected to the category
-                categoryRow[self.__COLUMN_CONNECTED] = False
-                categoryRow[self.__COLUMN_INCONSISTENT] = True
-            else:
-                # All of the selected objects are connected to the category
-                categoryRow[self.__COLUMN_CONNECTED] = True
-                categoryRow[self.__COLUMN_INCONSISTENT] = False
-        else:
-            # None of the selected objects are connected to the category
-            categoryRow[self.__COLUMN_CONNECTED] = False
-            categoryRow[self.__COLUMN_INCONSISTENT] = False
-
-    def __forEachCategoryRow(self, function, data=None, categoryRows=None):
-        # We can't use gtk.TreeModel.foreach() since it does not pass a row
-        # to the callback function.
-        if not categoryRows:
-            categoryRows=self.__categoryModel
-        for categoryRow in categoryRows:
-            function(categoryRow, data)
-            self.__forEachCategoryRow(function, data, categoryRow.iterchildren())
-
-    def __expandAndCollapseRows(self, autoExpand, autoCollapse, categoryRows=None):
-        if categoryRows is None:
-            categoryRows=self.__categoryModel
-        someRowsExpanded = False
-        for categoryRow in categoryRows:
-            expandThisRow = False
-            # Expand all rows that are selected or has expanded childs
-            childRowsExpanded = self.__expandAndCollapseRows(autoExpand,
-                                                            autoCollapse,
-                                                            categoryRow.iterchildren())
-            if (childRowsExpanded
-                or self.__categoryView.get_selection().path_is_selected(categoryRow.path)):
-                expandThisRow = True
-            # Auto expand all rows that has a checked toggle
-            if autoExpand:
-                if (categoryRow[self.__COLUMN_CONNECTED]
-                    or categoryRow[self.__COLUMN_INCONSISTENT]):
-                    expandThisRow = True
-            if expandThisRow:
-                for a in range(len(categoryRow.path)):
-                    self.__categoryView.expand_row(categoryRow.path[:a+1], False)
-                someRowsExpanded = True
-            # Auto collapse?
-            elif autoCollapse:
-                self.__categoryView.collapse_row(categoryRow.path)
-        return someRowsExpanded
-
-    def __connectChildToCategory(self, childId, parentId):
-        try:
-            # Update shelf
-            childCategory = env.shelf.getCategory(childId)
-            parentCategory = env.shelf.getCategory(parentId)
-            parentCategory.connectChild(childCategory)
-            env.shelf.flushCategoryCache()
-            # Update widget modell
-            # If we reload the whole category tree from the shelf, we would lose
-            # the widgets information about current selected categories,
-            # expanded categories and the widget's scroll position. Hence,
-            # we update our previously loaded model instead.
-            self.__connectChildToCategoryHelper(parentId,
-                                                childCategory,
-                                                self.__categoryModel)
-        except CategoriesAlreadyConnectedError:
-            # This is okay.
-            pass
-
-    def __connectChildToCategoryHelper(self, parentId, childCategory, categoryRows):
-        for categoryRow in categoryRows:
-            if categoryRow[self.__COLUMN_CATEGORY_ID] == parentId:
-                self.__loadCategorySubTree(categoryRow.iter, childCategory)
-            else:
-                self.__connectChildToCategoryHelper(parentId, childCategory, categoryRow.iterchildren())
-
-    def __disconnectChild(self, childId, parentId):
-        # Update shelf
-        childCategory = env.shelf.getCategory(childId)
-        parentCategory = env.shelf.getCategory(parentId)
-        if childCategory in env.shelf.getRootCategories():
-            alreadyWasRootCategory = True
-        else:
-            alreadyWasRootCategory = False
-        parentCategory.disconnectChild(childCategory)
-        env.shelf.flushCategoryCache()
-        # Update widget modell.
-        # If we reload the whole category tree from the shelf, we would lose
-        # the widgets information about current selected categories,
-        # expanded categories and the widget's scroll position. Hence,
-        # we update our previously loaded model instead.
-        self.__disconnectChildHelper(childId,
-                                    parentId,
-                                    None,
-                                    self.__categoryModel)
-        if not alreadyWasRootCategory:
-            for c in env.shelf.getRootCategories():
-                if c.getId() == childCategory.getId():
-                    self.__loadCategorySubTree(None, childCategory)
-                    break
-
-    def __disconnectChildHelper(self, wantedChildId, wantedParentId,
-                                parentId, categoryRows):
-        for categoryRow in categoryRows:
-            cid = categoryRow[self.__COLUMN_CATEGORY_ID]
-            if cid == wantedChildId and parentId == wantedParentId:
-                self.__categoryModel.remove(categoryRow.iter)
-            self.__disconnectChildHelper(wantedChildId, wantedParentId, cid, categoryRow.iterchildren())
-
-    def __updatePropertiesFromShelf(self, categoryRow, categoryId):
-        if categoryRow[self.__COLUMN_CATEGORY_ID] == categoryId:
-            category = env.shelf.getCategory(categoryId)
-            categoryRow[self.__COLUMN_DESCRIPTION] = category.getDescription()
-
-    def __sortCategories(self, categoryIter):
-        categories = list(categoryIter)
-        categories.sort(self.__compareCategories)
-        return categories
-
-    def __compareCategories(self, x, y):
-        return cmp(
-            (x.getDescription(), x.getTag()),
-            (y.getDescription(), y.getTag()))
-
-class ClipboardCategories:
-    COPY = 1
-    CUT = 2
-    categories = None
-    type = None
diff --git a/src/gkofoto/gkofoto/categorydialog.py b/src/gkofoto/gkofoto/categorydialog.py
deleted file mode 100644 (file)
index 7d305a3..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-import gtk
-import string
-import re
-from environment import env
-from gkofoto.taganddescriptiondialog import *
-
-class CategoryDialog(TagAndDescriptionDialog):
-    def __init__(self, title, categoryId=None):
-        if categoryId:
-            self._category = env.shelf.getCategory(categoryId)
-            tagText = self._category.getTag()
-            descText = self._category.getDescription()
-        else:
-            self._category = None
-            tagText = u""
-            descText = u""
-        TagAndDescriptionDialog.__init__(self, title, tagText, descText)
-
-    def _isTagOkay(self, tagString):
-        try:
-           # Check that the tag name is valid.
-           verifyValidCategoryTag(tagString)
-        except BadCategoryTagError:
-            return False
-        try:
-            category = env.shelf.getCategoryByTag(tagString)
-            if category == self._category:
-                # The tag exists, but is same as before.
-                return True
-            else:
-                # The tag is taken by another category.
-                return False
-        except CategoryDoesNotExistError:
-            # The tag didn't exist.
-            return True
diff --git a/src/gkofoto/gkofoto/clipboard.py b/src/gkofoto/gkofoto/clipboard.py
deleted file mode 100644 (file)
index d364377..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-from sets import Set
-from environment import env
-from kofoto.shelf import Image
-from kofoto.shelf import Album
-from categories import ClipboardCategories
-
-class Clipboard:
-
-    # TYPES
-    OBJECTS    = 0 # shelf.Album and shelf.Image
-    CATEGORIES = 1 # shelf.Category
-
-    def __init__(self):
-        self.__changedCallbacks = Set()
-        self.clear()
-
-    def addChangedCallback(self, callback):
-        self.__changedCallbacks.add(callback)
-
-    def removeChangedCallback(self, callback):
-        self.__changedCallbacks.remove(callback)
-
-    def setObjects(self, iter):
-        self.__objects = []
-        self.__types = Clipboard.OBJECTS
-        for object in iter:
-            if (isinstance(object, Image) or isinstance(object, Album)):
-                self.__objects.append(object)
-            else:
-                self.clear()
-                raise "Object is not an Image nor an Album" # TODO
-        self.__invokeChangedCallbacks()
-
-    def setCategories(self, clipboardCategories):
-        self.__objects = []
-        if isinstance(clipboardCategories, ClipboardCategories):
-            self.__objects.append(clipboardCategories)
-        else:
-            self.clear()
-            raise "Object is not a ClipboardCategories" # TODO
-        self.__types = Clipboard.CATEGORIES
-        self.__invokeChangedCallbacks()
-
-    def clear(self):
-        self.__objects = []
-        self.__types = None
-        self.__invokeChangedCallbacks()
-
-    def removeObjects(self, object):
-        self.__objects = [x for x in self.__objects if x != object]
-        self.__invokeChangedCallbacks()
-
-    def hasCategories(self):
-        return (self.__types == Clipboard.CATEGORIES and len(self.__objects) > 0)
-
-    def hasObjects(self):
-        return (self.__types == Clipboard.OBJECTS and len(self.__objects) > 0)
-
-    def __len__(self):
-        return len(self.__objects)
-
-    def __iter__(self):
-        return self.__objects.__iter__()
-
-    def __getitem__(self, index):
-        return self.__objects.__getitem__(index)
-
-    def __invokeChangedCallbacks(self):
-        for callback in self.__changedCallbacks:
-            callback(self)
diff --git a/src/gkofoto/gkofoto/controller.py b/src/gkofoto/gkofoto/controller.py
deleted file mode 100644 (file)
index c165722..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-import sys
-import gtk
-from kofoto.shelf import ShelfLockedError, UnsupportedShelfError
-from gkofoto.mainwindow import MainWindow
-from gkofoto.environment import env
-
-class Controller:
-    def __init__(self):
-        self.__clipboard = None
-
-    def start(self, setupOk):
-        if setupOk:
-            try:
-                if env.shelf.isUpgradable():
-                    dialog = gtk.MessageDialog(
-                        type=gtk.MESSAGE_INFO,
-                        buttons=gtk.BUTTONS_OK_CANCEL,
-                        message_format=
-                            "You have a metadata database from an older"
-                            " Kofoto version. It needs to be upgraded"
-                            " before you can continue.\n\n"
-                            "Press OK to upgrade the database"
-                            " automatically. A backup copy of the old"
-                            " database will be made before upgrading.")
-                    dialog.set_default_response(gtk.RESPONSE_OK)
-                    result = dialog.run()
-                    if result == gtk.RESPONSE_CANCEL:
-                        return
-                    dialog.set_response_sensitive(gtk.RESPONSE_CANCEL, False)
-                    dialog.set_response_sensitive(gtk.RESPONSE_OK, False)
-                    dialog.label.set_text("Upgrading database. Please wait...")
-                    while gtk.events_pending():
-                        gtk.main_iteration()
-                    success = env.shelf.tryUpgrade()
-                    dialog.destroy()
-                    if not success:
-                        dialog = gtk.MessageDialog(
-                            type=gtk.MESSAGE_ERROR,
-                            buttons=gtk.BUTTONS_OK,
-                            message_format="Failed to upgrade metadata database format.\n")
-                        dialog.run()
-                        dialog.destroy()
-                        return
-                env.shelf.begin()
-            except ShelfLockedError, e:
-                env.startupNotices += [
-                    "Error: Could not open metadata database \"%s\"." % e +
-                    " Another process is locking it.\n"]
-                setupOk = False
-            except UnsupportedShelfError, e:
-                env.startupNotices += [
-                    "Error: Too new format for metadata database \"%s\"." % e]
-                setupOk = False
-        if env.startupNotices:
-            if setupOk:
-                dialogtype = gtk.MESSAGE_INFO
-            else:
-                dialogtype = gtk.MESSAGE_ERROR
-            dialog = gtk.MessageDialog(
-                type=dialogtype,
-                buttons=gtk.BUTTONS_OK,
-                message_format="".join(env.startupNotices))
-            if setupOk:
-                # Doesn't work with x[0].destroy(). Don't know why.
-                dialog.connect("response", lambda *x: x[0].hide())
-            dialog.run()
-        if setupOk:
-            self.__mainWindow = MainWindow()
-            env.widgets["mainWindow"].connect("destroy", self.quit, False)
-            env.widgets["mainWindow"].show()
-            gtk.main()
-
-    def quit(self, app, cancelButton=True):
-        if env.shelf.isModified():
-            widgets = gtk.glade.XML(env.gladeFile, "quitDialog")
-            quitDialog = widgets.get_widget("quitDialog")
-            if not cancelButton:
-                widgets.get_widget("cancel").set_sensitive(False)
-            result = quitDialog.run()
-            if result == 0:
-                env.shelf.commit()
-                self._doQuit()
-            elif result == 1:
-                env.shelf.rollback()
-                self._doQuit()
-            else:
-                quitDialog.destroy()
-                return
-        else:
-            env.shelf.rollback()
-            self._doQuit()
-
-    def save(self, app):
-        env.shelf.commit()
-        env.shelf.begin()
-
-    def revert(self, app):
-        dialog = gtk.MessageDialog(
-            type=gtk.MESSAGE_QUESTION,
-            buttons=gtk.BUTTONS_YES_NO,
-            message_format="Revert to the previously saved state and lose all changes?")
-        if dialog.run() == gtk.RESPONSE_YES:
-            env.shelf.rollback()
-            env.shelf.begin()
-            self.__mainWindow.reload()
-        dialog.destroy()
-
-    def _doQuit(self):
-        self.__mainWindow.saveState()
-        gtk.main_quit()
diff --git a/src/gkofoto/gkofoto/crashdialog.py b/src/gkofoto/gkofoto/crashdialog.py
deleted file mode 100644 (file)
index 4995c4b..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-import pygtk
-import gtk
-import pango
-import linecache
-import os
-import traceback
-import re
-
-class CrashDialog(gtk.Dialog):
-    def __init__(self, exctype, value, tb, parent=None):
-        gtk.Dialog.__init__(
-            self, "GKofoto crash", parent,
-            gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR,
-            (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
-        self._exctype = exctype
-        self._value = value
-        self._tb = tb
-
-        self.set_default_size(
-            gtk.gdk.screen_width() / 2,
-            gtk.gdk.screen_height() / 2)
-        self.set_border_width(10)
-
-        self.vbox.set_spacing(5)
-
-        button = gtk.Button(stock=gtk.STOCK_SAVE)
-        button.show()
-        button.connect("clicked", self._save)
-        self.action_area.pack_start(button)
-        self.action_area.reorder_child(button, 0)
-
-        label = gtk.Label()
-        label.set_markup(
-            "<big><b>A programming error has been detected during the "
-            "execution of this program.</b></big>\n\n"
-            "Please report this problem at "
-            "<span font_family='monospace' foreground='blue'>"
-            "http://kofoto.rosdahl.net</span> "
-            "or <span font_family='monospace' foreground='blue'>"
-            "kofoto@rosdahl.net</span>\n"
-            "and include the information below along with a description of "
-            "what you did before the crash.")
-        label.set_selectable(True)
-        label.show()
-        self.vbox.pack_start(label, False, False)
-
-        sw = gtk.ScrolledWindow()
-        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
-        sw.set_shadow_type(gtk.SHADOW_IN)
-        sw.show()
-        self.vbox.pack_start(sw)
-
-        self._textbuffer = gtk.TextBuffer()
-        self._textbuffer.create_tag("filename", style=pango.STYLE_ITALIC)
-        self._textbuffer.create_tag("name", weight=pango.WEIGHT_BOLD)
-        self._textbuffer.create_tag("lineno", weight=pango.WEIGHT_BOLD)
-        self._textbuffer.create_tag("exception", weight=pango.WEIGHT_BOLD)
-        self._textbuffer.create_tag("source", family="monospace")
-
-        self._formatTraceback(self._textbuffer, exctype, value, tb)
-
-        textview = gtk.TextView(self._textbuffer)
-        textview.set_editable(False)
-        textview.set_cursor_visible(False)
-        sw.add(textview)
-        textview.show()
-
-    def _save(self, widget):
-        filechooser = gtk.FileChooserDialog(
-            "Save crash log",
-            self,
-            gtk.FILE_CHOOSER_ACTION_SAVE,
-            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
-             gtk.STOCK_OK, gtk.RESPONSE_OK))
-        if filechooser.run() == gtk.RESPONSE_OK:
-            try:
-                f = open(filechooser.get_filename(), "w")
-                traceback.print_exception(
-                    self._exctype, self._value, self._tb, None, f)
-                f.close()
-            except (OSError, IOError):
-                dialog = gtk.MessageDialog(
-                    self,
-                    gtk.DIALOG_MODAL,
-                    gtk.MESSAGE_ERROR,
-                    gtk.BUTTONS_OK,
-                    "Could not write to selected file.")
-                dialog.run()
-                dialog.destroy()
-        filechooser.destroy()
-
-    def _formatTraceback(self, textbuffer, exctype, value, tb):
-        def add(line, *tags):
-            textbuffer.insert_with_tags_by_name(
-                textbuffer.get_end_iter(),
-                line,
-                *tags)
-        def addFile(filename, lineno, name=None):
-            add("  File ")
-            add(filename, "filename")
-            add(", line ")
-            add(str(lineno), "lineno")
-            if name:
-                add(", in ")
-                add(name, "name")
-            add("\n")
-        def addSource(line):
-            add("  %s" % line.lstrip(), "source")
-
-        cwd = os.getcwd()
-        add("Traceback (most recent call last):\n")
-        while tb is not None:
-            lineno = tb.tb_lineno
-            filename = tb.tb_frame.f_code.co_filename
-            name = tb.tb_frame.f_code.co_name
-            if filename.startswith(cwd):
-                filename = filename[len(cwd) + 1:]
-            addFile(filename, lineno, name)
-            line = linecache.getline(filename, lineno)
-            if line:
-                addSource(line)
-            tb = tb.tb_next
-        lines = traceback.format_exception_only(exctype, value)
-        for line in lines[:-1]:
-            m = re.match("^  File \"([^\"]+)\", line (\d+)", line)
-            if m:
-                addFile(m.group(1), m.group(2))
-            else:
-                addSource(line)
-        a = lines[-1].split(":")
-        if len(a) == 1:
-            add(a[0], "exception")
-        else:
-            add(a[0], "exception")
-            add(":" + ":".join(a[1:]))
-
-def show(exctype, value, tb):
-    if exctype != KeyboardInterrupt:
-        window = CrashDialog(exctype, value, tb)
-        window.run()
-        window.destroy()
-    raise SystemExit
diff --git a/src/gkofoto/gkofoto/duplicateandopenimagedialog.py b/src/gkofoto/gkofoto/duplicateandopenimagedialog.py
deleted file mode 100644 (file)
index 2957c9b..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-import glob
-import os
-import shutil
-
-import gtk
-
-from environment import env
-
-class DuplicateAndOpenImageDialog:
-    def __init__(self):
-        self._widgets = gtk.glade.XML(
-            env.gladeFile, "duplicateAndOpenImageDialog")
-        self._dialog = self._widgets.get_widget("duplicateAndOpenImageDialog")
-        self._cancelButton = self._widgets.get_widget("cancelButton")
-        self._okButton = self._widgets.get_widget("okButton")
-        self._browseButton = self._widgets.get_widget("browseButton")
-        self._fileEntry = self._widgets.get_widget("fileEntry")
-
-        self._cancelButton.connect("clicked", self._onCancel)
-        self._okButton.connect("clicked", self._onOk)
-        self._browseButton.connect("clicked", self._onBrowse)
-
-    def run(self, imageversion):
-        self._imageversion = imageversion
-        location = imageversion.getLocation()
-        prefix, suffix = os.path.splitext(location)
-        duplicateLocation = "%s+fix%s" % (prefix, suffix)
-        self.__setFile(duplicateLocation)
-        self._fileEntry.select_region(len(prefix) + 1, len(prefix + "fix") + 1)
-        self._dialog.run()
-
-    def _onCancel(self, *unused):
-        self._dialog.destroy()
-
-    def _onOk(self, *unused):
-        duplicateLocation = self._fileEntry.get_text()
-        if os.path.exists(duplicateLocation.encode(env.codeset)):
-            dialog = gtk.MessageDialog(
-                self._dialog,
-                gtk.DIALOG_MODAL,
-                gtk.MESSAGE_ERROR,
-                gtk.BUTTONS_OK,
-                "File already exists: %s" % duplicateLocation)
-            dialog.run()
-            dialog.destroy()
-        else:
-            shutil.copyfile(
-                self._imageversion.getLocation().encode(env.codeset),
-                duplicateLocation.encode(env.codeset))
-            command = env.openCommand % {"locations": duplicateLocation}
-            result = os.system(command.encode(env.codeset) + " &")
-            if result != 0:
-                dialog = gtk.MessageDialog(
-                    self._dialog,
-                    gtk.DIALOG_MODAL,
-                    gtk.MESSAGE_ERROR,
-                    gtk.BUTTONS_OK,
-                    "Failed to execute command: \"%s\"" % command)
-                dialog.run()
-                dialog.destroy()
-        self._dialog.destroy()
-
-    def _onBrowse(self, *unused):
-        dialog = gtk.FileChooserDialog(
-            "Choose name of duplicate image",
-            self._dialog,
-            gtk.FILE_CHOOSER_ACTION_SAVE,
-            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
-             gtk.STOCK_OK, gtk.RESPONSE_OK))
-        if dialog.run() == gtk.RESPONSE_OK:
-            self.__setFile(dialog.get_filename())
-        dialog.destroy()
-
-    def __setFile(self, location):
-        self._fileEntry.set_text(location)
-        self._fileEntry.set_position(-1)  # Last.
diff --git a/src/gkofoto/gkofoto/environment.py b/src/gkofoto/gkofoto/environment.py
deleted file mode 100644 (file)
index 62871e5..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-import sys
-import os
-import getopt
-import locale
-import re
-
-import pygtk
-if sys.platform != "win32":
-    pygtk.require('2.0')
-import gtk
-import gobject
-import gtk.gdk
-import gtk.glade
-import gkofoto.crashdialog
-sys.excepthook = gkofoto.crashdialog.show
-
-from kofoto.clientenvironment import *
-from kofoto.common import *
-from kofoto.shelf import *
-from kofoto.config import *
-from kofoto.imagecache import *
-
-class WidgetsWrapper:
-    def __init__(self):
-        self.widgets = gtk.glade.XML(env.gladeFile, "mainWindow")
-
-    def __getitem__(self, key):
-        return self.widgets.get_widget(key)
-
-class Environment(ClientEnvironment):
-    def __init__(self):
-        ClientEnvironment.__init__(self)
-        self.startupNotices = []
-
-    def setup(self, bindir, isDebug=False, configFileLocation=None,
-              shelfLocation=None):
-        try:
-            ClientEnvironment.setup(self, configFileLocation, shelfLocation)
-        except ClientEnvironmentError, e:
-            self.startupNotices += [e[0]]
-            return False
-
-        self.isDebug = isDebug
-        self.thumbnailSize = self.config.getcoordlist(
-            "gkofoto", "thumbnail_size_limit")[0]
-        self.defaultTableViewColumns = re.findall(
-            "\S+",
-            self.config.get("gkofoto", "default_table_columns"))
-        if not "versions" in self.defaultTableViewColumns:
-            # Ugly, temporary hack:
-            self.defaultTableViewColumns.insert(1, "versions")
-        self.defaultSortColumn = self.config.get(
-            "gkofoto", "default_sort_column")
-        self.openCommand = self.config.get(
-            "gkofoto", "open_command", True)
-        self.rotateRightCommand = self.config.get(
-            "gkofoto", "rotate_right_command", True)
-        self.rotateLeftCommand = self.config.get(
-            "gkofoto", "rotate_left_command", True)
-
-        # Case 1: Running from normal UNIX or Windows non-py2exe installation.
-        dataDir = os.path.join(bindir, "..", "share", "gkofoto")
-        if not os.path.isdir(dataDir):
-            # Case 2: Running from Windows py2exe installation.
-            dataDir = os.path.join(bindir, "share", "gkofoto")
-            if not os.path.isdir(dataDir):
-                # Case 3: Running from source code.
-                dataDir = bindir
-        self.iconDir = os.path.join(dataDir, "icons")
-        self.gladeFile = os.path.join(dataDir, "glade", "gkofoto.glade")
-        self.albumIconFileName = os.path.join(self.iconDir, "album.png")
-        self.albumIconPixbuf = gtk.gdk.pixbuf_new_from_file(self.albumIconFileName)
-        self.loadingPixbuf = self.albumIconPixbuf # TODO: create another icon with a hour-glass or something
-        self.unknownImageIconFileName = os.path.join(self.iconDir, "unknownimage.png")
-        self.unknownImageIconPixbuf = gtk.gdk.pixbuf_new_from_file(self.unknownImageIconFileName)
-        from clipboard import Clipboard
-        self.clipboard = Clipboard()
-
-        self.widgets = WidgetsWrapper()
-
-        return True
-
-    def _writeInfo(self, infoString):
-        self.startupNotices += [infoString]
-
-    def debug(self, msg):
-        if self.isDebug:
-            print msg
-
-    def enter(self, method):
-        if self.isDebug:
-            print "-->", method
-
-    def exit(self, method):
-        if self.isDebug:
-            print "<--", method
-
-    def assertUnicode(self, obj):
-        assert isinstance(obj, unicode), \
-               "%s is not a unicode object: \"%s\"" % (type(obj), obj)
-
-env = Environment()
diff --git a/src/gkofoto/gkofoto/generatehtmldialog.py b/src/gkofoto/gkofoto/generatehtmldialog.py
deleted file mode 100644 (file)
index 8fd5c64..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-import gtk
-import os
-import re
-from sets import Set
-from environment import env
-import kofoto.generate
-
-class GenerateHTMLDialog:
-    def __init__(self, album):
-        self.album = album
-        self.widgets = gtk.glade.XML(
-            env.gladeFile, "generateHtmlDialog")
-        self.dialog = self.widgets.get_widget("generateHtmlDialog")
-        self.browseButton = self.widgets.get_widget("browseButton")
-        self.cancelButton = self.widgets.get_widget("cancelButton")
-        self.directoryTextEntry = self.widgets.get_widget("directoryTextEntry")
-        self.generateButton = self.widgets.get_widget("generateButton")
-
-        self.browseButton.connect("clicked", self._onBrowse)
-        self.cancelButton.connect("clicked", self._onCancel)
-        self.generateButton.connect("clicked", self._onGenerate)
-
-        self.directoryTextEntry.connect(
-            "changed", self._onDirectoryTextEntryModified)
-
-        self.generateButton.set_sensitive(False)
-
-    def run(self):
-        self.dialog.show()
-
-    def _onDirectoryTextEntryModified(self, *unused):
-        self.generateButton.set_sensitive(
-            os.path.isdir(self.directoryTextEntry.get_text()))
-
-    def _onBrowse(self, *unused):
-        directorySelectedInDirList = False
-        dirDialog = gtk.FileChooserDialog(
-            title="Choose directory",
-            action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
-            buttons=(
-                gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
-                gtk.STOCK_OK, gtk.RESPONSE_OK))
-        if dirDialog.run() == gtk.RESPONSE_OK:
-            self.directoryTextEntry.set_text(dirDialog.get_filename())
-        dirDialog.destroy()
-
-    def _onCancel(self, *unused):
-        self.dialog.destroy()
-
-    def _onGenerate(self, *unused):
-        for widget in [self.directoryTextEntry, self.browseButton,
-                       self.cancelButton, self.generateButton]:
-            widget.set_sensitive(False)
-        self._generate(self.directoryTextEntry.get_text())
-        self.dialog.destroy()
-
-    def _generate(self, directoryName):
-        # TODO: Rewrite this gross hack.
-
-        def outputParser(string):
-            m = re.match(
-                r"Creating album (\S+) \((\d+) of (\d+)\)",
-                string)
-            if m:
-                progressBar.set_text(m.group(1).decode(env.codeset))
-                progressBar.set_fraction(
-                    (int(m.group(2)) - 1) / float(m.group(3)))
-                while gtk.events_pending():
-                    gtk.main_iteration()
-
-        progressBar = self.widgets.get_widget("progressBar")
-
-        env.out = outputParser
-        env.verbose = True
-        env.thumbnailsizelimit = env.config.getcoordlist(
-            "album generation", "thumbnail_size_limit")[0]
-        env.defaultsizelimit = env.config.getcoordlist(
-            "album generation", "default_image_size_limit")[0]
-
-        imgsizesval = env.config.getcoordlist(
-            "album generation", "other_image_size_limits")
-        imgsizesset = Set(imgsizesval) # Get rid of duplicates.
-        defaultlimit = env.config.getcoordlist(
-            "album generation", "default_image_size_limit")[0]
-        imgsizesset.add(defaultlimit)
-        imgsizes = list(imgsizesset)
-        imgsizes.sort(lambda x, y: cmp(x[0] * x[1], y[0] * y[1]))
-        env.imagesizelimits = imgsizes
-
-        try:
-            generator = kofoto.generate.Generator(u"woolly", env)
-            generator.generate(self.album, None, directoryName, "utf-8")
-            progressBar.set_fraction(1)
-            while gtk.events_pending():
-                gtk.main_iteration()
-        except (IOError, kofoto.shelf.KofotoError), e:
-            dialog = gtk.MessageDialog(
-                type=gtk.MESSAGE_ERROR,
-                buttons=gtk.BUTTONS_OK,
-                message_format="Error: \"%s\"" % e)
-            dialog.run()
-            dialog.destroy()
diff --git a/src/gkofoto/gkofoto/handleimagesdialog.py b/src/gkofoto/gkofoto/handleimagesdialog.py
deleted file mode 100644 (file)
index 6b8097a..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-import gtk
-import gobject
-import os
-from environment import env
-from kofoto.shelf import \
-     ImageVersionDoesNotExistError, ImageVersionExistsError, \
-     MultipleImageVersionsAtOneLocationError, \
-     makeValidTag, computeImageHash
-from kofoto.clientutils import walk_files
-
-class HandleImagesDialog(gtk.FileChooserDialog):
-    def __init__(self):
-        gtk.FileChooserDialog.__init__(
-            self,
-            title="Handle images",
-            action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
-            buttons=(
-                gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
-                gtk.STOCK_OK, gtk.RESPONSE_OK))
-        self.connect("response", self._response)
-
-    def _response(self, widget, responseId):
-        if responseId != gtk.RESPONSE_OK:
-            return
-        widgets = gtk.glade.XML(env.gladeFile, "handleImagesProgressDialog")
-        handleImagesProgressDialog = widgets.get_widget(
-            "handleImagesProgressDialog")
-        knownUnchangedImagesCount = widgets.get_widget(
-            "knownUnchangedImagesCount")
-        knownMovedImagesCount = widgets.get_widget(
-            "knownMovedImagesCount")
-        unknownModifiedImagesCount = widgets.get_widget(
-            "unknownModifiedImagesCount")
-        unknownFilesCount = widgets.get_widget(
-            "unknownFilesCount")
-        investigatedFilesCount = widgets.get_widget(
-            "investigatedFilesCount")
-        okButton = widgets.get_widget("okButton")
-        okButton.set_sensitive(False)
-
-        handleImagesProgressDialog.show()
-
-        knownUnchangedImages = 0
-        knownMovedImages = 0
-        unknownModifiedImages = 0
-        unknownFiles = 0
-        investigatedFiles = 0
-        modifiedImages = []
-        movedImages = []
-        for filepath in walk_files([self.get_filename()]):
-            try:
-                filepath = filepath.decode(env.codeset)
-            except UnicodeDecodeError:
-                filepath = filepath.decode("latin1")
-            try:
-                imageversion = env.shelf.getImageVersionByHash(
-                    computeImageHash(filepath))
-                if imageversion.getLocation() == os.path.realpath(filepath):
-                    # Registered.
-                    knownUnchangedImages += 1
-                    knownUnchangedImagesCount.set_text(
-                        str(knownUnchangedImages))
-                else:
-                    # Moved.
-                    knownMovedImages += 1
-                    knownMovedImagesCount.set_text(str(knownMovedImages))
-                    movedImages.append(filepath)
-            except ImageVersionDoesNotExistError:
-                try:
-                    env.shelf.getImageVersionByLocation(filepath)
-                    # Modified.
-                    unknownModifiedImages += 1
-                    unknownModifiedImagesCount.set_text(
-                        str(unknownModifiedImages))
-                    modifiedImages.append(filepath)
-                except MultipleImageVersionsAtOneLocationError:
-                    # Multiple images at one location.
-                    # TODO: Handle this error.
-                    pass
-                except ImageVersionDoesNotExistError:
-                    # Unregistered.
-                    unknownFiles += 1
-                    unknownFilesCount.set_text(str(unknownFiles))
-            investigatedFiles += 1
-            investigatedFilesCount.set_text(str(investigatedFiles))
-            while gtk.events_pending():
-                gtk.main_iteration()
-
-        okButton.set_sensitive(True)
-        handleImagesProgressDialog.run()
-        handleImagesProgressDialog.destroy()
-
-        if modifiedImages or movedImages:
-            if modifiedImages:
-                self._dialogHelper(
-                    "Update modified images",
-                    "The above image files have been modified. Press OK to"
-                    " make Kofoto recognize the new contents.",
-                    modifiedImages,
-                    self._updateModifiedImages)
-            if movedImages:
-                self._dialogHelper(
-                    "Update moved or renamed images",
-                    "The above image files have been moved or renamed. Press OK to"
-                    " make Kofoto recognize the new locations.",
-                    movedImages,
-                    self._updateMovedImages)
-        else:
-            dialog = gtk.MessageDialog(
-                type=gtk.MESSAGE_INFO,
-                buttons=gtk.BUTTONS_OK,
-                message_format="No modified, renamed or moved images found.")
-            dialog.run()
-            dialog.destroy()
-
-    def _dialogHelper(self, title, text, filepaths, handlerFunction):
-        widgets = gtk.glade.XML(env.gladeFile, "updateImagesDialog")
-        dialog = widgets.get_widget("updateImagesDialog")
-        dialog.set_title(title)
-        filenameList = widgets.get_widget("filenameList")
-        renderer = gtk.CellRendererText()
-        column = gtk.TreeViewColumn("Image filename", renderer, text=0)
-        filenameList.append_column(column)
-        dialogText = widgets.get_widget("dialogText")
-        dialogText.set_text(text)
-        model = gtk.ListStore(gobject.TYPE_STRING)
-        for filepath in filepaths:
-            model.append([filepath])
-        filenameList.set_model(model)
-        if dialog.run() == gtk.RESPONSE_OK:
-            handlerFunction(filepaths)
-        dialog.destroy()
-
-    def _error(self, errorText):
-        dialog = gtk.MessageDialog(
-            type=gtk.MESSAGE_ERROR,
-            buttons=gtk.BUTTONS_OK,
-            message_format=errorText)
-        dialog.run()
-        dialog.destroy()
-
-    def _updateModifiedImages(self, filepaths):
-        for filepath in filepaths:
-            try:
-                imageversion = env.shelf.getImageVersionByLocation(filepath)
-                imageversion.contentChanged()
-            except ImageVersionDoesNotExistError:
-                self._error("Image does not exist: %s" % filepath)
-            except MultipleImageVersionsAtOneLocationError:
-                # TODO: Handle this.
-                pass
-            except IOError, x:
-                self._error("Error while reading %s: %s" % (
-                    filepath, x))
-
-    def _updateMovedImages(self, filepaths):
-        for filepath in filepaths:
-            try:
-                imageversion = env.shelf.getImageVersionByHash(
-                    computeImageHash(filepath))
-                imageversion.locationChanged(filepath)
-            except ImageVersionDoesNotExistError:
-                self._error("Image does not exist: %s" % filepath)
-            except MultipleImageVersionsAtOneLocationError:
-                # TODO: Handle this.
-                pass
-            except IOError, x:
-                self._error("Error while reading %s: %s" % (
-                    filepath, x))
diff --git a/src/gkofoto/gkofoto/imagepreloader.py b/src/gkofoto/gkofoto/imagepreloader.py
deleted file mode 100644 (file)
index d499d06..0000000
+++ /dev/null
@@ -1,208 +0,0 @@
-import gobject
-import gtk
-from kofoto.timer import Timer
-from kofoto.common import calculateDownscaledDimensions
-
-class _MyPixbufLoader(gtk.gdk.PixbufLoader):
-    def __init__(self, *args, **kwargs):
-        gtk.gdk.PixbufLoader.__init__(self, *args, **kwargs)
-        self.__closed = False
-
-    def close(self):
-        if not self.__closed:
-            gtk.gdk.PixbufLoader.close(self)
-            self.__closed = True
-
-class _PreloadState:
-    def __init__(self, filename, fileSystemCodeset):
-        self.fullsizePixbuf = None
-        self.pixbufLoader = _MyPixbufLoader()
-        self.loadFinished = False # Whether loading of fullsizePixbuf is ready.
-        self.scaledPixbuf = None
-        try:
-            self.fp = open(filename.encode(fileSystemCodeset), "rb")
-        except (IOError, OSError):
-            self.loadFinished = True
-            self.pixbufLoader = None
-
-    def __del__(self):
-        if self.pixbufLoader:
-            self.pixbufLoader.close()
-
-class ImagePreloader(object):
-    def __init__(self, fileSystemCodeset, debugPrintFunction=None):
-        self._fileSystemCodeset = fileSystemCodeset
-        if debugPrintFunction:
-            self._debugPrint = debugPrintFunction
-        else:
-            self._debugPrint = lambda x: None
-        self.__delayTimerTag = None
-        self.__idleTimerTag = None
-        # filename --> _PreloadState
-        self.__preloadStates = {}
-
-    def preloadImages(self, filenames, scaledMaxWidth, scaledMaxHeight):
-        """Preload images.
-
-        The images are loaded and stored both in a fullsize version
-        and a scaled-down version.
-
-        Note that this method discards previously preloaded images,
-        except those present in the filenames argument.
-
-        filenames -- Iterable of filenames of images to preload.
-        scaledMaxWidth -- Wanted maximum width of the scaled image.
-        scaledMaxHeight -- Wanted maximum height of the scaled image.
-        """
-        if self.__delayTimerTag != None:
-            gobject.source_remove(self.__delayTimerTag)
-        if self.__idleTimerTag != None:
-            gobject.source_remove(self.__idleTimerTag)
-
-        # Delay preloading somewhat to make display of the current
-        # image faster. Not sure whether it helps, though...
-        self.__delayTimerTag = gobject.timeout_add(
-            500,
-            self._beginPreloading,
-            filenames,
-            scaledMaxWidth,
-            scaledMaxHeight)
-
-    def clearCache(self):
-        for ps in self.__preloadStates.values():
-            if ps.pixbufLoader:
-                # Set loadFinished to avoid an extra
-                # pixbufLoader.close() by the loop in
-                # _preloadImagesWorker.
-                ps.loadFinished = True
-        self.__preloadStates = {}
-
-    def getPixbuf(self, filename, maxWidth=None, maxHeight=None):
-        """Get a pixbuf.
-
-        If maxWidth and maxHeight are None, the fullsize version is
-        returned, otherwise a scaled version no larger than maxWidth
-        and maxHeight is returned.
-
-        The pixbuf may be None if the image was unloadable.
-        """
-        pixbuf = None
-
-        if not self.__preloadStates.has_key(filename):
-            self.__preloadStates[filename] = _PreloadState(
-                filename, self._fileSystemCodeset)
-        ps = self.__preloadStates[filename]
-        if not ps.loadFinished:
-            try:
-                ps.pixbufLoader.write(ps.fp.read())
-                ps.pixbufLoader.close()
-                ps.fullsizePixbuf = ps.pixbufLoader.get_pixbuf()
-            except (gobject.GError, OSError):
-                ps.fullsizePixbuf = None
-            ps.pixbufLoader = None
-            ps.loadFinished = True
-        if (ps.fullsizePixbuf == None or
-            (maxWidth == None and maxHeight == None) or
-            (ps.fullsizePixbuf.get_width() <= maxWidth and
-             ps.fullsizePixbuf.get_height() <= maxHeight)):
-            # Requested fullsize pixbuf or scaled pixbuf larger than
-            # fullsize.
-            return ps.fullsizePixbuf
-        else:
-            # Requested scaled pixbuf.
-            ps.scaledPixbuf = self._maybeScalePixbuf(
-                ps.fullsizePixbuf,
-                ps.scaledPixbuf,
-                maxWidth,
-                maxHeight,
-                filename)
-            return ps.scaledPixbuf
-
-    def _beginPreloading(self, filenames, scaledMaxWidth, scaledMaxHeight):
-        self.__idleTimerTag = gobject.idle_add(
-            self._preloadImagesWorker(
-                filenames, scaledMaxWidth, scaledMaxHeight).next)
-        return False
-
-    def _preloadImagesWorker(self, filenames, scaledMaxWidth, scaledMaxHeight):
-        filenames = list(filenames)
-        self._debugPrint("Preloading images %s" % str(filenames))
-
-        # Discard old preloaded images.
-        for filename in self.__preloadStates.keys():
-            if not filename in filenames:
-                pixbufLoader = self.__preloadStates[filename].pixbufLoader
-                del self.__preloadStates[filename]
-
-        # Preload the new images.
-        for filename in filenames:
-            if not self.__preloadStates.has_key(filename):
-                self.__preloadStates[filename] = _PreloadState(
-                    filename, self._fileSystemCodeset)
-            ps = self.__preloadStates[filename]
-            try:
-                self._debugPrint("Preloading %s" % filename)
-                timer = Timer()
-                while not ps.loadFinished: # could be set by getPixbuf
-                    data = ps.fp.read(32768)
-                    if not data:
-                        ps.pixbufLoader.close()
-                        ps.fullsizePixbuf = ps.pixbufLoader.get_pixbuf()
-                        break
-                    ps.pixbufLoader.write(data)
-                    yield True
-                self._debugPrint("Preload of %s took %.2f seconds" % (
-                    filename, timer.get()))
-            except (gobject.GError, OSError):
-                pass
-            ps.pixbufLoader = None
-            ps.loadFinished = True
-
-            ps.scaledPixbuf = self._maybeScalePixbuf(
-                ps.fullsizePixbuf,
-                ps.scaledPixbuf,
-                scaledMaxWidth,
-                scaledMaxHeight,
-                filename)
-            yield True
-
-        # We're finished.
-        self.__idleTimerTag = None
-        yield False
-
-    def _maybeScalePixbuf(self, fullsizePixbuf, scaledPixbuf,
-                          maxWidth, maxHeight, filename):
-        if not fullsizePixbuf:
-            return None
-        elif (fullsizePixbuf.get_width() <= maxWidth and
-              fullsizePixbuf.get_height() <= maxHeight):
-            return fullsizePixbuf
-        elif not (scaledPixbuf and
-                  scaledPixbuf.get_width() <= maxWidth and
-                  scaledPixbuf.get_height() <= maxHeight and
-                  (scaledPixbuf.get_width() == maxWidth or
-                   scaledPixbuf.get_height() == maxHeight)):
-            scaledWidth, scaledHeight = calculateDownscaledDimensions(
-                fullsizePixbuf.get_width(),
-                fullsizePixbuf.get_height(),
-                maxWidth,
-                maxHeight)
-            self._debugPrint("Scaling %s to %dx%d" % (
-                filename, scaledWidth, scaledHeight))
-            if scaledPixbuf:
-                self._debugPrint("old size: %dx%d" % (
-                    scaledPixbuf.get_width(),
-                    scaledPixbuf.get_height()))
-                self._debugPrint("new size: %dx%d" % (
-                    scaledWidth,
-                    scaledHeight))
-            timer = Timer()
-            scaledPixbuf = fullsizePixbuf.scale_simple(
-                scaledWidth,
-                scaledHeight,
-                gtk.gdk.INTERP_BILINEAR) # TODO: Make configurable.
-            self._debugPrint("Scaling of %s to %dx%d took %.2f seconds" % (
-                filename, scaledWidth, scaledHeight, timer.get()))
-            return scaledPixbuf
-        else: # Appropriately sized scaled pixbuf.
-            return scaledPixbuf
diff --git a/src/gkofoto/gkofoto/imageversionsdialog.py b/src/gkofoto/gkofoto/imageversionsdialog.py
deleted file mode 100644 (file)
index 776ee79..0000000
+++ /dev/null
@@ -1,222 +0,0 @@
-import gtk
-import os
-import re
-from environment import env
-from kofoto.structclass import makeStructClass
-from kofoto.shelf import CategoryPresentError, ImageVersionType
-from sets import Set
-
-RowDataStruct = makeStructClass(
-    "imageVersion",
-    "commentTextBuffer",
-    "primaryButton",
-    "importantButton",
-    "originalButton",
-    "otherButton")
-
-class ImageVersionsDialog:
-    tableWidth = 3
-
-    def __init__(self, model):
-        self._model = model
-        self._versionDataList = []
-        self._widgets = gtk.glade.XML(
-            env.gladeFile, "imageVersionsDialog")
-        self._dialog = self._widgets.get_widget("imageVersionsDialog")
-        self._cancelButton = self._widgets.get_widget("cancelButton")
-        self._okButton = self._widgets.get_widget("okButton")
-
-        self._cancelButton.connect("clicked", self._onCancel)
-        self._okButton.connect("clicked", self._onOk)
-
-        scrolledWindow = self._widgets.get_widget("scrolledWindow")
-        self._table = gtk.Table(1, ImageVersionsDialog.tableWidth)
-        self._table.set_border_width(5)
-        self._table.set_row_spacings(5)
-        self._table.set_col_spacings(10)
-        scrolledWindow.add_with_viewport(self._table)
-        self.__primaryRadioButtonGroup = None
-
-    def runViewImageVersions(self, image):
-        self._isMerge = False
-        self._run([image])
-
-    def runMergeImages(self, images):
-        assert len(images) > 1
-        self._isMerge = True
-        self._mergeImages = images
-        self._run(images)
-
-    def _run(self, images):
-        for image in images:
-            for imageVersion in image.getImageVersions():
-                self._addRow(imageVersion)
-        self._table.show_all()
-        x, y = self._dialog.get_position()
-        width, height = self._dialog.get_size()
-        hackyConstant = 89 # TODO: How to calculate this properly?
-        newheight = min(800, self._table.size_request()[1] + hackyConstant)
-        self._dialog.move(x, max(0, y - ((newheight - height) / 2)))
-        self._dialog.resize(450, newheight)
-        self._dialog.run()
-
-    def _onCancel(self, *unused):
-        self._dialog.destroy()
-
-    def _onOk(self, *unused):
-        t = self._table
-        for data in self._versionDataList:
-            tb = data.commentTextBuffer
-            comment = tb.get_text(tb.get_start_iter(), tb.get_end_iter())
-            data.imageVersion.setComment(comment.decode("utf-8"))
-            if data.primaryButton.get_active():
-                data.imageVersion.makePrimary()
-            if data.importantButton.get_active():
-                data.imageVersion.setType(ImageVersionType.Important)
-            elif data.originalButton.get_active():
-                data.imageVersion.setType(ImageVersionType.Original)
-            elif data.otherButton.get_active():
-                data.imageVersion.setType(ImageVersionType.Other)
-            else:
-                assert False
-
-        proceed = True
-        if self._isMerge:
-            #
-            # The mother image below is the image of the primary
-            # version, i.e. the image that will adopt all image
-            # versions.
-            #
-
-            motherImage = None
-            for data in self._versionDataList:
-                if data.primaryButton.get_active():
-                    motherImage = data.imageVersion.getImage()
-                    break
-            assert motherImage
-
-            for image in self._mergeImages:
-                if image != motherImage:
-                    for key, value in image.getAttributeMap().items():
-                        motherImage.setAttribute(key, value, overwrite=False)
-
-            for data in self._versionDataList:
-                data.imageVersion.setImage(motherImage)
-
-            descriptionTexts = []
-            for image in self._mergeImages:
-                description = image.getAttribute(u"description")
-                if description:
-                    descriptionTexts.append(description)
-                if image == motherImage:
-                    continue
-                for category in image.getCategories():
-                    try:
-                        motherImage.addCategory(category)
-                    except CategoryPresentError:
-                        pass
-                for album in image.getParents():
-                    children = list(album.getChildren())
-                    if motherImage in children:
-                        # Both motherImage and image are present in
-                        # children, so just remove image to avoid
-                        # duplicates in album.
-                        pass
-                    else:
-                        # Replace image with motherImage.
-                        for i, child in enumerate(children):
-                            if child == image:
-                                children[i] = motherImage
-                        album.setChildren(children)
-                env.shelf.deleteImage(image.getId())
-            description = "\n\n".join(descriptionTexts)
-            if len(descriptionTexts) > 1:
-                widgets = gtk.glade.XML(
-                    env.gladeFile, "editMergedDescriptionDialog")
-                dialog = widgets.get_widget("editMergedDescriptionDialog")
-                textBuffer = widgets.get_widget("descriptionText").get_buffer()
-                textBuffer.set_text(description)
-                dialog.run()
-                dialog.destroy()
-                motherImage.setAttribute(
-                    u"description",
-                    textBuffer.get_text(
-                        textBuffer.get_start_iter(),
-                        textBuffer.get_end_iter()).decode("utf-8"))
-            env.mainwindow.reloadObjectList()
-        else:
-            self._model.reloadSelectedRows()
-        self._dialog.destroy()
-
-    def _addRow(self, imageVersion):
-        table = self._table
-        data = RowDataStruct()
-        self._versionDataList.append(data)
-        data.imageVersion = imageVersion
-        number = len(self._versionDataList) - 1
-        table.resize(number + 1, ImageVersionsDialog.tableWidth)
-
-        #
-        # Column one.
-        #
-        image = gtk.Image()
-        try:
-            thumbnailLocation, w, h = env.imageCache.get(
-                imageVersion.getLocation().encode(env.codeset), 128, 128)
-            image.set_from_file(thumbnailLocation.decode(env.codeset))
-        except OSError:
-            image.set_from_pixbuf(env.unknownImageIconPixbuf)
-        table.attach(
-            image, 0, 1, number, number + 1, gtk.SHRINK, gtk.SHRINK)
-
-        #
-        # Column two.
-        #
-        buttonBox = gtk.VBox()
-        table.attach(
-            buttonBox, 1, 2, number, number + 1, gtk.SHRINK, gtk.FILL)
-        primaryButton = gtk.RadioButton(self.__primaryRadioButtonGroup, "Primary")
-        buttonBox.pack_start(primaryButton, False, False)
-        data.primaryButton = primaryButton
-        if not self.__primaryRadioButtonGroup:
-            self.__primaryRadioButtonGroup = primaryButton
-        primaryButton.set_active(imageVersion.isPrimary())
-
-        typeFrame = gtk.Frame("Type")
-        buttonBox.pack_start(typeFrame, False, False)
-
-        typeButtonBox = gtk.VBox()
-        typeFrame.add(typeButtonBox)
-
-        importantButton = gtk.RadioButton(None, "Important")
-        typeButtonBox.add(importantButton)
-        data.importantButton = importantButton
-        importantButton.set_active(
-            imageVersion.getType() == ImageVersionType.Important)
-
-        originalButton = gtk.RadioButton(importantButton, "Original")
-        typeButtonBox.add(originalButton)
-        data.originalButton = originalButton
-        originalButton.set_active(
-            imageVersion.getType() == ImageVersionType.Original)
-
-        otherButton = gtk.RadioButton(importantButton, "Other")
-        typeButtonBox.add(otherButton)
-        data.otherButton = otherButton
-        otherButton.set_active(
-            imageVersion.getType() == ImageVersionType.Other)
-
-        #
-        # Column three.
-        #
-        scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
-        scrolledWindow.set_shadow_type(gtk.SHADOW_IN)
-        textView = gtk.TextView()
-        scrolledWindow.add(textView)
-        data.commentTextBuffer = textView.get_buffer()
-        textView.set_wrap_mode(gtk.WRAP_WORD)
-        textView.get_buffer().set_text(imageVersion.getComment())
-        table.attach(
-            scrolledWindow, 2, 3, number, number + 1,
-            gtk.FILL|gtk.EXPAND, gtk.FILL)
diff --git a/src/gkofoto/gkofoto/imageversionslist.py b/src/gkofoto/gkofoto/imageversionslist.py
deleted file mode 100644 (file)
index 76d0582..0000000
+++ /dev/null
@@ -1,357 +0,0 @@
-import os
-import gtk
-
-from environment import env
-from kofoto.shelf import ImageVersionType
-from gkofoto.menuhandler import MenuGroup
-from imageversionsdialog import ImageVersionsDialog
-from sets import Set as set
-from kofoto.alternative import Alternative
-from duplicateandopenimagedialog import DuplicateAndOpenImageDialog
-
-_imageVersionTypeToStringMap = {
-    ImageVersionType.Important: "Important",
-    ImageVersionType.Original: "Original",
-    ImageVersionType.Other: "Other",
-}
-
-_rotationDirection = Alternative("Left", "Right")
-
-class ImageVersionsList(gtk.ScrolledWindow):
-    def __init__(self, singleObjectView, imageView):
-        gtk.ScrolledWindow.__init__(self)
-        self.__singleObjectView = singleObjectView
-        self.__imageView = imageView
-        self.__vbox = gtk.VBox()
-        self.__vbox.set_border_width(5)
-        self.__vbox.set_spacing(10)
-        self.__vbox.show()
-        self.add_with_viewport(self.__vbox)
-        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
-        self.__image = None
-        self.__tooltips = gtk.Tooltips()
-        self.__recentlySelectedImageWidget = None
-        self.connect("focus-in-event", self.__focusInEventHandler)
-        self.connect("focus-out-event", self.__focusOutEventHandler)
-        self.__contextMenu = self.__createContextMenu()
-        self.clear()
-
-        callbacks = [
-            ("menubarViewImageVersion", self.__view),
-            ("menubarCopyImageVersionLocations", self.__copyImageLocation),
-            ("menubarOpenImageVersions", self.__open),
-            ("menubarDuplicateAndOpenImageVersion", self.__duplicateAndOpen),
-            ("menubarRotateImageVersionLeft", self.__rotateLeft),
-            ("menubarRotateImageVersionRight", self.__rotateRight),
-            ("menubarSplitToIndependentImages", self.__split),
-            ("menubarDestroyImageVersion", self.__destroy),
-            ("menubarEditImageVersionProperties", self.__editProperties),
-            ]
-        for widgetName, callback in callbacks:
-            env.widgets[widgetName].connect("activate", callback)
-
-    def clear(self):
-        for widget in self.__vbox.get_children():
-            self.__vbox.remove(widget)
-        self.__imageWidgetList = []
-        self.__imageWidgetToImageVersion = {}
-        self.__selectedImageWidgets = set()
-        self.__updateMenus()
-
-    def loadImage(self, image):
-        self.clear()
-        self.__image = image
-        self.__tooltips.enable()
-        for iv in image.getImageVersions():
-            vbox = gtk.VBox()
-            vbox.set_border_width(3)
-            self.__vbox.pack_start(vbox, expand=False, fill=False)
-            thumbnail = gtk.Image()
-            try:
-                thumbnailLocation = env.imageCache.get(iv, 128, 128)[0]
-                thumbnail.set_from_file(thumbnailLocation.encode(env.codeset))
-            except OSError:
-                thumbnail.set_from_pixbuf(env.unknownImageIconPixbuf)
-            alignment = gtk.Alignment(0.5, 0.5, 0.5, 0.5)
-            alignment.add(thumbnail)
-            alignment.set_padding(5, 5, 5, 5)
-            eventbox = gtk.EventBox()
-            eventbox.add(alignment)
-            eventbox.connect(
-                "button-press-event", self.__mouseButtonPressed)
-            eventbox.connect_after(
-                "expose-event", self.__imageWidgetExposed)
-            tooltipText = "Location: " + iv.getLocation()
-            if iv.getComment():
-                tooltipText += "\nComment: " + iv.getComment()
-            self.__tooltips.set_tip(eventbox, tooltipText)
-            vbox.add(eventbox)
-            if iv.isPrimary():
-                vbox.add(gtk.Label("Primary"))
-            vbox.add(gtk.Label(_imageVersionTypeToStringMap[iv.getType()]))
-            self.__imageWidgetList.append(eventbox)
-            self.__imageWidgetToImageVersion[eventbox] = iv
-        self.__vbox.show_all()
-
-    def reload(self):
-        self.loadImage(self.__image)
-
-    def __createContextMenu(self):
-        menu = gtk.Menu()
-        menugroup = MenuGroup()
-        menugroup.addMenuItem(
-            "View",
-            self.__view)
-        menugroup.addMenuItem(
-            "Copy image version location(s)",
-            self.__copyImageLocation)
-        menugroup.addStockImageMenuItem(
-            "Open image version(s) in external program...",
-            gtk.STOCK_OPEN,
-            self.__open)
-        menugroup.addStockImageMenuItem(
-            "Duplicate and open image version(s) in external program...",
-            gtk.STOCK_OPEN,
-            self.__duplicateAndOpen)
-        menugroup.addImageMenuItem(
-            "Rotate left",
-            os.path.join(env.iconDir, "rotateleft.png"),
-            self.__rotateLeft)
-        menugroup.addImageMenuItem(
-            "Rotate right",
-            os.path.join(env.iconDir, "rotateright.png"),
-            self.__rotateRight)
-        menugroup.addSeparator()
-        menugroup.addMenuItem(
-            "Split to independent image(s)",
-            self.__split)
-        menugroup.addSeparator()
-        menugroup.addStockImageMenuItem(
-            "Destroy...",
-            gtk.STOCK_DELETE,
-            self.__destroy)
-        menugroup.addStockImageMenuItem(
-            "Edit properties...",
-            gtk.STOCK_PROPERTIES,
-            self.__editProperties)
-        for item in menugroup:
-            menu.add(item)
-        self.__menuGroup = menugroup
-        return menu
-
-    def __updateMenus(self):
-        zeroSelected = len(self.__selectedImageWidgets) == 0
-        oneSelected = len(self.__selectedImageWidgets) == 1
-        allSelected = (
-            len(self.__selectedImageWidgets) == len(self.__imageWidgetList))
-
-        env.widgets["menubarViewImageVersion"].set_sensitive(
-            oneSelected)
-        env.widgets["menubarCopyImageVersionLocations"].set_sensitive(
-            not zeroSelected)
-        env.widgets["menubarOpenImageVersions"].set_sensitive(
-            not zeroSelected)
-        env.widgets["menubarDuplicateAndOpenImageVersion"].set_sensitive(
-            oneSelected)
-        env.widgets["menubarRotateImageVersionLeft"].set_sensitive(
-            not zeroSelected)
-        env.widgets["menubarRotateImageVersionRight"].set_sensitive(
-            not zeroSelected)
-        env.widgets["menubarSplitToIndependentImages"].set_sensitive(
-            not zeroSelected and not allSelected)
-        env.widgets["menubarDestroyImageVersion"].set_sensitive(
-            not zeroSelected)
-        env.widgets["menubarEditImageVersionProperties"].set_sensitive(
-            not zeroSelected)
-
-        if zeroSelected:
-            self.__menuGroup.disable()
-        else:
-            self.__menuGroup.enable()
-            if not oneSelected:
-                self.__menuGroup["View"].set_sensitive(False)
-                self.__menuGroup[
-                    ("Duplicate and open image version(s) in external"
-                     " program...")
-                    ].set_sensitive(False)
-            if allSelected:
-                self.__menuGroup["Split to independent image(s)"].set_sensitive(False)
-
-    def __mouseButtonPressed(self, widget, event):
-        def selectWidget(widget):
-            widget.set_state(gtk.STATE_SELECTED)
-            self.__selectedImageWidgets.add(widget)
-        def unselectWidget(widget):
-            widget.set_state(gtk.STATE_NORMAL)
-            self.__selectedImageWidgets.remove(widget)
-        def selectOnlyThis():
-            for x in list(self.__selectedImageWidgets):
-                unselectWidget(x)
-            selectWidget(widget)
-        def selectThisToo():
-            selectWidget(widget)
-        def flipThis():
-            if widget in self.__selectedImageWidgets:
-                unselectWidget(widget)
-            else:
-                selectWidget(widget)
-        def extendSelection():
-            otherIndex = self.__imageWidgetList.index(
-                self.__recentlySelectedImageWidget)
-            thisIndex = self.__imageWidgetList.index(widget)
-            for x in xrange(
-                min(otherIndex, thisIndex), max(otherIndex, thisIndex) + 1):
-                selectWidget(self.__imageWidgetList[x])
-
-        self.grab_focus()
-        if event.button == 1:
-            if event.type == gtk.gdk.BUTTON_PRESS:
-                if event.state & gtk.gdk.CONTROL_MASK:
-                    flipThis()
-                elif event.state & gtk.gdk.SHIFT_MASK:
-                    extendSelection()
-                else:
-                    selectOnlyThis()
-            elif event.type == gtk.gdk._2BUTTON_PRESS:
-                if (event.state & (
-                        gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK) == 0):
-                    self.__view()
-        elif event.button == 3:
-            if widget in self.__selectedImageWidgets:
-                selectThisToo()
-            else:
-                selectOnlyThis()
-            self.__contextMenu.popup(
-                None, None, None, event.button, event.time)
-        if self.__recentlySelectedImageWidget:
-            self.__recentlySelectedImageWidget.queue_draw()
-        self.__recentlySelectedImageWidget = widget
-        self.__updateMenus()
-
-    def __imageWidgetExposed(self, widget, event):
-        if widget == self.__recentlySelectedImageWidget:
-            state = gtk.STATE_SELECTED
-            allocation = widget.get_allocation()
-            widget.style.paint_focus(
-                widget.window, state, None, widget, "",
-                2, 2, allocation.width - 4, allocation.height - 4)
-        else:
-            state = gtk.STATE_NORMAL
-
-    def __view(self, *args):
-        assert len(self.__selectedImageWidgets) == 1
-        widget = list(self.__selectedImageWidgets)[0]
-        imageVersion = self.__imageWidgetToImageVersion[widget]
-        self.__imageView.loadFile(imageVersion.getLocation())
-
-    def __copyImageLocation(self, widget, param):
-        assert len(self.__selectedImageWidgets) > 0
-        clipboard = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)
-        primary = gtk.clipboard_get(gtk.gdk.SELECTION_PRIMARY)
-        location = "\n".join(
-            [self.__imageWidgetToImageVersion[x].getLocation()
-             for x in self.__selectedImageWidgets])
-        clipboard.set_text(location)
-        primary.set_text(location)
-
-    def __open(self, *args):
-        assert len(self.__selectedImageWidgets) > 0
-        locations = [
-            self.__imageWidgetToImageVersion[x].getLocation()
-            for x in self.__selectedImageWidgets]
-        command = env.openCommand % {"locations": " ".join(locations)}
-        result = os.system(command.encode(env.codeset) + " &")
-        if result != 0:
-            dialog = gtk.MessageDialog(
-                type=gtk.MESSAGE_ERROR,
-                buttons=gtk.BUTTONS_OK,
-                message_format="Failed to execute command: \"%s\"" % command)
-            dialog.run()
-            dialog.destroy()
-
-    def __duplicateAndOpen(self, *args):
-        assert len(self.__selectedImageWidgets) == 1
-        imageWidget = list(self.__selectedImageWidgets)[0]
-        imageVersion = self.__imageWidgetToImageVersion[imageWidget]
-        dialog = DuplicateAndOpenImageDialog()
-        dialog.run(imageVersion)
-
-    def __split(self, *args):
-        assert len(self.__selectedImageWidgets) > 0
-        assert len(self.__selectedImageWidgets) < len(self.__imageWidgetList)
-        for widget in self.__selectedImageWidgets:
-            imageVersion = self.__imageWidgetToImageVersion[widget]
-            image = env.shelf.createImage()
-            imageVersion.setImage(image)
-            for key, value in self.__image.getAttributeMap().items():
-                image.setAttribute(key, value)
-            for category in  self.__image.getCategories():
-                image.addCategory(category)
-        self.__singleObjectView.reload()
-
-    def __rotateLeft(self, *args):
-        assert len(self.__selectedImageWidgets) > 0
-        self.__rotate(_rotationDirection.Left)
-
-    def __rotateRight(self, *args):
-        assert len(self.__selectedImageWidgets) > 0
-        self.__rotate(_rotationDirection.Right)
-
-    def __destroy(self, *args):
-        assert len(self.__selectedImageWidgets) > 0
-        widgets = gtk.glade.XML(env.gladeFile, "destroyImageVersionsDialog")
-        dialog = widgets.get_widget("destroyImageVersionsDialog")
-        result = dialog.run()
-        if result == gtk.RESPONSE_OK:
-            checkbutton = widgets.get_widget("deleteImageFilesCheckbutton")
-            deleteFiles = checkbutton.get_active()
-            for widget in self.__selectedImageWidgets:
-                imageVersion = self.__imageWidgetToImageVersion[widget]
-                if deleteFiles:
-                    try:
-                        os.remove(
-                            imageVersion.getLocation().encode(env.codeset))
-                        # TODO: Delete from image cache too?
-                    except OSError:
-                        pass
-                env.shelf.deleteImageVersion(imageVersion.getId())
-            self.__singleObjectView.reload()
-        dialog.destroy()
-
-    def __editProperties(self, *args):
-        assert len(self.__selectedImageWidgets) > 0
-        dialog = ImageVersionsDialog(self.__singleObjectView._objectCollection)
-        dialog.runViewImageVersions(self.__image)
-        self.__singleObjectView.reload()
-
-    def __focusInEventHandler(self, widget, event):
-        for x in self.__selectedImageWidgets:
-            x.set_state(gtk.STATE_SELECTED)
-
-    def __focusOutEventHandler(self, widget, event):
-        for x in self.__selectedImageWidgets:
-            x.set_state(gtk.STATE_ACTIVE)
-
-    def __rotate(self, rotationDirection):
-        for widget in self.__selectedImageWidgets:
-            imageVersion = self.__imageWidgetToImageVersion[widget]
-            if rotationDirection == _rotationDirection.Left:
-                rotateCommand = env.rotateLeftCommand
-            elif rotationDirection == _rotationDirection.Right:
-                rotateCommand = env.rotateRightCommand
-            else:
-                # Can't happen.
-                assert True
-            command = rotateCommand % {"location": imageVersion.getLocation()}
-            result = os.system(command.encode(env.codeset))
-            if result == 0:
-                imageVersion.contentChanged()
-            else:
-                dialog = gtk.MessageDialog(
-                    type=gtk.MESSAGE_ERROR,
-                    buttons=gtk.BUTTONS_OK,
-                    message_format="Failed to execute command: \"%s\"" % command)
-                dialog.run()
-                dialog.destroy()
-        env.mainwindow.getImagePreloader().clearCache()
-        self.__singleObjectView.reload()
diff --git a/src/gkofoto/gkofoto/imageview.py b/src/gkofoto/gkofoto/imageview.py
deleted file mode 100644 (file)
index e481541..0000000
+++ /dev/null
@@ -1,177 +0,0 @@
-import gtk
-import gtk.gdk
-import math
-import gobject
-import gc
-from environment import env
-from kofoto.common import calculateDownscaledDimensions
-
-class ImageView(gtk.ScrolledWindow):
-    # TODO: Read from configuration file?
-    _INTERPOLATION_TYPE = gtk.gdk.INTERP_BILINEAR
-    # gtk.gdk.INTERP_HYPER is slower but gives better quality.
-    _MAX_IMAGE_SIZE = 2000
-    _MIN_IMAGE_SIZE = 10 # Work-around for bug in GTK. (pixbuf.scale_iter(1, 1) crashes.)
-    _MIN_ZOOM = -100
-    _MAX_ZOOM = 1
-    _ZOOMFACTOR = 1.2
-
-    def __init__(self):
-        self._image = gtk.Image()
-        gtk.ScrolledWindow.__init__(self)
-        self.__loadedFileName = None
-        self.__pixBuf = None
-        self.__currentZoom = None
-        self.__wantedZoom = None
-        self.__fitToWindowMode = True
-        self.__previousWidgetWidth = 0
-        self.__previousWidgetHeight = 0
-
-        # Don't know why the EventBox is needed, but if it is removed,
-        # a size_allocate signal will trigger self.resizeEventHandler,
-        # which will resize the image, which will trigger
-        # size_allocate again, and so on.
-        eventBox = gtk.EventBox()
-        eventBox.add(self._image)
-
-        self.add_with_viewport(eventBox)
-        self.add_events(gtk.gdk.ALL_EVENTS_MASK)
-        self.connect_after("size-allocate", self.resizeEventHandler)
-        self.connect("scroll-event", self.scrollEventHandler)
-        self.connect("focus-in-event", self.focusInEventHandler)
-        self.connect("focus-out-event", self.focusOutEventHandler)
-
-    def focusInEventHandler(self, widget, event):
-        pass
-
-    def focusOutEventHandler(self, widget, event):
-        pass
-
-    def loadFile(self, fileName, reload=True):
-        if (not reload) and self.__loadedFileName == fileName:
-            return
-        self.clear()
-        env.debug("ImageView is loading image from file: " + fileName)
-        self.__pixBuf = env.mainwindow.getImagePreloader().getPixbuf(fileName)
-        if self.__pixBuf:
-            self.__loadedFileName = fileName
-        else:
-            dialog = gtk.MessageDialog(
-                type=gtk.MESSAGE_ERROR,
-                buttons=gtk.BUTTONS_OK,
-                message_format="Could not load image: %s" % fileName)
-            dialog.run()
-            dialog.destroy()
-            self.__pixBuf = env.unknownImageIconPixbuf
-            self.__loadedFileName = None
-        self._newImageLoaded = True
-        self._image.show()
-        self.fitToWindow()
-
-    def clear(self):
-        self._image.hide()
-        self._image.set_from_file(None)
-        self.__pixBuf = None
-        self.__loadedFileName = None
-        gc.collect()
-        env.debug("ImageView is cleared.")
-
-    def reload(self):
-        self.loadFile(self.__loadedFileName)
-
-    def renderImage(self):
-        # TODO: Scaling should be asyncronous to avoid freezing the gtk-main loop
-        if self.__pixBuf == None:
-            # No image loaded
-            self._image.hide()
-            return
-        if self.__currentZoom == self.__wantedZoom and not self._newImageLoaded:
-            return
-        if self.__wantedZoom == 0:
-            pixBufResized = self.__pixBuf
-        else:
-            if self.__fitToWindowMode:
-                maxWidth, maxHeight = tuple(self.get_allocation())[2:4]
-                wantedWidth, wantedHeight = calculateDownscaledDimensions(
-                    self.__pixBuf.get_width(),
-                    self.__pixBuf.get_height(),
-                    maxWidth,
-                    maxHeight)
-            else:
-                zoomMultiplicator = pow(self._ZOOMFACTOR, self.__wantedZoom)
-                wantedWidth = int(self.__pixBuf.get_width() * zoomMultiplicator)
-                wantedHeight = int(self.__pixBuf.get_height() * zoomMultiplicator)
-            if min(wantedWidth, wantedHeight) < self._MIN_IMAGE_SIZE:
-                # Too small image size
-                return
-            if max(wantedWidth, wantedHeight) > self._MAX_IMAGE_SIZE:
-                # Too large image size
-                return
-            pixBufResized = env.mainwindow.getImagePreloader().getPixbuf(
-                self.__loadedFileName,
-                wantedWidth,
-                wantedHeight)
-            if not pixBufResized:
-                pixBufResized = env.unknownImageIconPixbuf
-        pixMap, mask = pixBufResized.render_pixmap_and_mask()
-        self._image.set_from_pixmap(pixMap, mask)
-        self._newImageLoaded = False
-        self.__currentZoom = self.__wantedZoom
-        gc.collect()
-
-    def resizeEventHandler(self, widget, gdkEvent):
-        if self.__fitToWindowMode:
-            x, y, width, height = self.get_allocation()
-            if height != self.__previousWidgetHeight or width != self.__previousWidgetWidth:
-                self.fitToWindow()
-        return False
-
-    def fitToWindow(self, *foo):
-        self.__fitToWindowMode = True
-        self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
-        y, x, widgetWidth, widgetHeight = self.get_allocation()
-        if self.__pixBuf != None:
-            self.__previousWidgetWidth = widgetWidth
-            self.__previousWidgetHeight = widgetHeight
-            a = min(float(widgetWidth) / self.__pixBuf.get_width(),
-                    float(widgetHeight) / self.__pixBuf.get_height())
-            self.__wantedZoom = self._log(self._ZOOMFACTOR, a)
-            self.__wantedZoom = min(self.__wantedZoom, 0)
-            self.__wantedZoom = max(self.__wantedZoom, self._MIN_ZOOM)
-            self.renderImage()
-
-    def getAvailableSpace(self):
-        return tuple(self.get_allocation())[2:4]
-
-    def _log(self, base, value):
-        return math.log(value) / math.log(base)
-
-    def zoomIn(self, *foo):
-        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
-        self.__fitToWindowMode = False
-        if self.__wantedZoom <= self._MAX_ZOOM:
-            self.__wantedZoom = math.floor(self.__wantedZoom + 1)
-            self.renderImage()
-
-    def zoomOut(self, *foo):
-        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
-        self.__fitToWindowMode = False
-        if self.__wantedZoom >= self._MIN_ZOOM:
-            self.__wantedZoom = math.ceil(self.__wantedZoom - 1)
-            self.renderImage()
-
-    def zoom100(self, *foo):
-        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
-        self.__fitToWindowMode = False
-        self.__wantedZoom = 0
-        self.renderImage()
-
-    def scrollEventHandler(self, widget, gdkEvent):
-        if gdkEvent.type == gtk.gdk.SCROLL:
-            if gdkEvent.direction == gtk.gdk.SCROLL_UP:
-                self.zoomOut()
-            elif gdkEvent.direction == gtk.gdk.SCROLL_DOWN:
-                self.zoomIn()
-            return True
-        else:
-            return False
diff --git a/src/gkofoto/gkofoto/main.py b/src/gkofoto/gkofoto/main.py
deleted file mode 100644 (file)
index cde59d9..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-import os
-import sys
-from kofoto.clientenvironment import DEFAULT_CONFIGFILE_LOCATION
-from gkofoto.environment import env
-from gkofoto.controller import Controller
-from optparse import OptionParser
-
-def setupWindowsEnvironment(bindir):
-    # Allow (default) datafile location to be determined under Windows 98.
-    if os.path.expanduser("~") == "~":
-        # Probably running under Windows 98 or similar OS where the
-        # environment variables HOMEPATH and HOMEDRIVE (and HOME) are not
-        # set. We have to fake it instead.
-        try:
-            import _winreg
-
-            # Look up where "My Documents" lives.
-            key = _winreg.OpenKey(
-                _winreg.HKEY_CURRENT_USER,
-                "Software\\Microsoft\\Windows\\CurrentVersion"
-                "\\Explorer\\Shell Folders")
-            home, dummy = _winreg.QueryValueEx(key, "Personal")
-            # At this point home is _probably_ a Unicode string.
-        except EnvironmentError:
-            home = None
-
-        if home == None:
-            # Unable to look up the location so just make one up, however
-            # nasty that location may be. We do output where the data
-            # location is on gkofoto startup.
-            home = "C:\\"
-
-        os.environ["HOME"] = home
-        # Note: Use os.environ as os.putenv() at least in Windows 98 only
-        # changes variable for sub processes; this process would not see
-        # the change.
-
-def main(bindir, argv):
-    parser = OptionParser(version=env.version)
-    parser.add_option(
-        "--configfile",
-        type="string",
-        dest="configfile",
-        help="use configuration file CONFIGFILE instead of the default (%s)" % (
-            DEFAULT_CONFIGFILE_LOCATION),
-        default=None)
-    parser.add_option(
-        "--database",
-        type="string",
-        dest="database",
-        help="use metadata database DATABASE instead of the default (specified in the configuration file)",
-        default=None)
-    parser.add_option(
-        "--debug",
-        action="store_true",
-        help="print debug messages to stdout",
-        default=False)
-    options, args = parser.parse_args(argv[1:])
-
-    if len(args) != 0:
-        parser.error("incorrect number of arguments")
-
-    if sys.platform == "win32":
-        setupWindowsEnvironment(bindir)
-
-    setupOk = env.setup(
-        bindir, options.debug, options.configfile, options.database)
-    env.controller = Controller()
-    env.controller.start(setupOk)
diff --git a/src/gkofoto/gkofoto/mainwindow.py b/src/gkofoto/gkofoto/mainwindow.py
deleted file mode 100644 (file)
index 6b21569..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-import gtk
-import gtk.gdk
-import os
-
-from gkofoto.categories import *
-from gkofoto.albums import *
-from environment import env
-from gkofoto.tableview import *
-from gkofoto.thumbnailview import *
-from gkofoto.singleobjectview import *
-from gkofoto.objectcollectionfactory import *
-from gkofoto.objectcollection import *
-from gkofoto.registerimagesdialog import RegisterImagesDialog
-from gkofoto.handleimagesdialog import HandleImagesDialog
-from gkofoto.generatehtmldialog import GenerateHTMLDialog
-from gkofoto.persistentstate import PersistentState
-from gkofoto.imagepreloader import ImagePreloader
-
-class MainWindow(gtk.Window):
-    def __init__(self):
-        env.mainwindow = self
-        self._toggleLock = False
-        self.__currentObjectCollection = None
-        self._currentView = None
-        self.__persistentState = PersistentState()
-        self.__imagePreloader = ImagePreloader(env.codeset, env.debug)
-        self.__sourceEntry = env.widgets["sourceEntry"]
-        self.__filterEntry = env.widgets["filterEntry"]
-        self.__filterEntry.set_text(self.__persistentState.filterText)
-        self.__isFilterEnabledCheckbox = env.widgets["isFilterEnabledCheckbox"]
-        env.widgets["menubarViewTreePane"].connect("toggled", self._toggleTree)
-        env.widgets["menubarViewDetailsPane"].connect("toggled", self._toggleDetails)
-#        env.widgets["thumbnailsViewToggleButton"].connect("clicked", self._toggleThumbnailsView)
-        env.widgets["thumbnailsViewToggleButton"].hide()
-        env.widgets["thumbnailsViewToggleButton"].set_sensitive(False)
-        env.widgets["thumbnailsViewToggleButton"].get_child().add(self.getIconImage("thumbnailsview.png"))
-        env.widgets["objectViewToggleButton"].connect("clicked", self._toggleObjectView)
-        env.widgets["objectViewToggleButton"].get_child().add(self.getIconImage("objectview.png"))
-        env.widgets["menubarObjectView"].connect("activate", self._toggleObjectView)
-        env.widgets["tableViewToggleButton"].connect("clicked", self._toggleTableView)
-        env.widgets["tableViewToggleButton"].get_child().add(self.getIconImage("tableview.png"))
-        env.widgets["menubarTableView"].connect("activate", self._toggleTableView)
-        env.widgets["previousButton"].set_sensitive(False)
-        env.widgets["nextButton"].set_sensitive(False)
-        env.widgets["zoom100"].set_sensitive(False)
-        env.widgets["zoomToFit"].set_sensitive(False)
-        env.widgets["zoomIn"].set_sensitive(False)
-        env.widgets["zoomOut"].set_sensitive(False)
-
-        env.widgets["menubarSave"].connect("activate", env.controller.save)
-        env.widgets["menubarSave"].set_sensitive(False)
-        env.widgets["menubarRevert"].connect("activate", env.controller.revert)
-        env.widgets["menubarRevert"].set_sensitive(False)
-        env.widgets["menubarQuit"].connect("activate", env.controller.quit)
-
-        env.widgets["menubarThumbnailsView"].hide()
-
-        env.widgets["menubarNextImage"].set_sensitive(False)
-        env.widgets["menubarPreviousImage"].set_sensitive(False)
-        env.widgets["menubarZoom"].set_sensitive(False)
-
-        env.widgets["menubarRegisterImages"].connect("activate", self.registerImages, None)
-        env.widgets["menubarHandleModifiedOrRenamedImages"].connect(
-            "activate", self.handleModifiedOrRenamedImages, None)
-
-        env.widgets["menubarRotateLeft"].get_children()[1].set_from_pixbuf(
-            gtk.gdk.pixbuf_new_from_file(os.path.join(env.iconDir, "rotateleft.png")))
-        env.widgets["menubarRotateRight"].get_children()[1].set_from_pixbuf(
-            gtk.gdk.pixbuf_new_from_file(os.path.join(env.iconDir, "rotateright.png")))
-        env.widgets["menubarRotateImageVersionLeft"].get_children()[1].set_from_pixbuf(
-            gtk.gdk.pixbuf_new_from_file(os.path.join(env.iconDir, "rotateleft.png")))
-        env.widgets["menubarRotateImageVersionRight"].get_children()[1].set_from_pixbuf(
-            gtk.gdk.pixbuf_new_from_file(os.path.join(env.iconDir, "rotateright.png")))
-        env.widgets["menubarAbout"].get_children()[1].set_from_pixbuf(
-            gtk.gdk.pixbuf_new_from_file(os.path.join(env.iconDir, "about-icon.png")))
-
-        env.widgets["menubarAbout"].connect("activate", self.showAboutBox)
-
-        self.__sourceEntry.connect("activate", self._queryChanged)
-        self.__filterEntry.connect("activate", self._queryChanged)
-        self.__isFilterEnabledCheckbox.connect("toggled", self._queryChanged)
-
-        env.shelf.registerModificationCallback(self._shelfModificationChangedCallback)
-
-        self.__factory = ObjectCollectionFactory()
-        self.__categories = Categories(self)
-        self.__albums = Albums(self)
-        self.__thumbnailView = ThumbnailView()
-        self.__tableView = TableView()
-        self.__singleObjectView = SingleObjectView()
-        self.__showTableView()
-
-    def saveState(self):
-        self.__persistentState.save()
-
-    def _queryChanged(self, *foo):
-        query = self.__sourceEntry.get_text().decode("utf-8")
-        self.loadQuery(query)
-        self.__sourceEntry.grab_remove()
-        self.__persistentState.filterText = self.__filterEntry.get_text()
-
-    def loadQuery(self, query):
-        self.__query = query
-        self.__sourceEntry.set_text(query)
-        useFilter = self.__isFilterEnabledCheckbox.get_active()
-        self.__filterEntry.set_sensitive(useFilter)
-        if useFilter:
-            filterText = self.__filterEntry.get_text().decode("utf-8")
-        else:
-            filterText = ""
-        self.__setObjectCollection(
-            self.__factory.getObjectCollection(query, filterText))
-
-    def reload(self):
-        self.__albums.loadAlbumTree()
-        self.__categories.loadCategoryTree()
-        self.loadQuery(self.__query)
-
-    def reloadObjectList(self):
-        self.loadQuery(self.__query)
-
-    def reloadAlbumTree(self):
-        self.__albums.loadAlbumTree()
-
-    def unselectAlbumTree(self):
-        self.__albums.unselect()
-
-    def registerImages(self, widget, data):
-        dialog = RegisterImagesDialog()
-        if dialog.run() == gtk.RESPONSE_OK:
-            self.reload() # TODO: don't reload everything.
-        dialog.destroy()
-
-    def handleModifiedOrRenamedImages(self, widget, data):
-        dialog = HandleImagesDialog()
-        dialog.run()
-        dialog.destroy()
-
-    def showAboutBox(self, *unused):
-        widgets = gtk.glade.XML(env.gladeFile, "aboutDialog")
-        aboutDialog = widgets.get_widget("aboutDialog")
-        nameAndVersionLabel = widgets.get_widget("nameAndVersionLabel")
-        nameAndVersionLabel.set_text("Kofoto %s" % env.version)
-        aboutDialog.run()
-        aboutDialog.destroy()
-
-    def generateHtml(self, album):
-        dialog = GenerateHTMLDialog(album)
-        dialog.run()
-
-    def getIconImage(self, name):
-        pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(env.iconDir, name))
-        image = gtk.Image()
-        image.set_from_pixbuf(pixbuf)
-        image.show()
-        return image
-
-    def getImagePreloader(self):
-        return self.__imagePreloader
-
-    def _viewChanged(self):
-        for hiddenView in self._hiddenViews:
-            hiddenView.hide()
-        self._currentView.show(self.__currentObjectCollection)
-
-    def __showTableView(self):
-        self._currentView = self.__tableView
-        self._hiddenViews = [self.__thumbnailView, self.__singleObjectView]
-        self._viewChanged()
-
-    def __showThumbnailView(self):
-        self._currentView = self.__thumbnailView
-        self._hiddenViews = [self.__tableView, self.__singleObjectView]
-        self._viewChanged()
-
-    def __showSingleObjectView(self):
-        self._currentView = self.__singleObjectView
-        self._hiddenViews = [self.__tableView, self.__thumbnailView]
-        self._viewChanged()
-
-    def _toggleTree(self, button):
-        if button.get_active():
-            env.widgets["sourceNotebook"].show()
-        else:
-            env.widgets["sourceNotebook"].hide()
-
-    def _toggleDetails(self, button):
-        if button.get_active():
-            self.__singleObjectView.showDetailsPane()
-        else:
-            self.__singleObjectView.hideDetailsPane()
-
-    def _toggleThumbnailsView(self, button):
-        if not self._toggleLock:
-            self._toggleLock = True
-            button.set_active(True)
-            env.widgets["thumbnailsViewToggleButton"].set_active(True)
-            env.widgets["objectViewToggleButton"].set_active(False)
-            env.widgets["tableViewToggleButton"].set_active(False)
-            env.widgets["menubarThumbnailsView"].set_active(True)
-            env.widgets["menubarObjectView"].set_active(False)
-            env.widgets["menubarTableView"].set_active(False)
-            self.__showThumbnailView()
-            self._toggleLock = False
-
-    def _toggleObjectView(self, button):
-        if not self._toggleLock:
-            self._toggleLock = True
-            button.set_active(True)
-            env.widgets["thumbnailsViewToggleButton"].set_active(False)
-            env.widgets["objectViewToggleButton"].set_active(True)
-            env.widgets["tableViewToggleButton"].set_active(False)
-            env.widgets["menubarThumbnailsView"].set_active(False)
-            env.widgets["menubarObjectView"].set_active(True)
-            env.widgets["menubarTableView"].set_active(False)
-            self.__showSingleObjectView()
-            self._toggleLock = False
-
-    def _toggleTableView(self, button):
-        if not self._toggleLock:
-            self._toggleLock = True
-            button.set_active(True)
-            env.widgets["thumbnailsViewToggleButton"].set_active(False)
-            env.widgets["objectViewToggleButton"].set_active(False)
-            env.widgets["tableViewToggleButton"].set_active(True)
-            env.widgets["menubarThumbnailsView"].set_active(False)
-            env.widgets["menubarObjectView"].set_active(False)
-            env.widgets["menubarTableView"].set_active(True)
-            self.__showTableView()
-            self._toggleLock = False
-
-    def _shelfModificationChangedCallback(self, modified):
-        env.widgets["menubarRevert"].set_sensitive(modified)
-        env.widgets["menubarSave"].set_sensitive(modified)
-        env.widgets["statusbarModified"].pop(1)
-        if modified:
-            env.widgets["statusbarModified"].push(1, "Modified")
-
-    def __setObjectCollection(self, objectCollection):
-        if self.__currentObjectCollection != objectCollection:
-            env.debug("MainWindow is propagating a new ObjectCollection")
-            self.__currentObjectCollection = objectCollection
-            self.__categories.setCollection(objectCollection)
-            if self._currentView is not None:
-                self._currentView.setObjectCollection(objectCollection)
diff --git a/src/gkofoto/gkofoto/menuhandler.py b/src/gkofoto/gkofoto/menuhandler.py
deleted file mode 100644 (file)
index b471d3a..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-import gtk
-
-class MenuGroup:
-    def __init__(self, label=""):
-        self.__label = label
-        self.__childItems = []
-        self.__childItemsMap = {}
-        self.__radioGroup = None
-
-    def addMenuItem(self, label, callback, callbackData=None):
-        item = gtk.MenuItem(label)
-        self.__addItem(item, label, callback, callbackData)
-
-    def addStockImageMenuItem(self, label, stockId, callback,
-                              callbackData=None):
-        item = gtk.ImageMenuItem(label)
-        image = gtk.Image()
-        image.set_from_stock(stockId, gtk.ICON_SIZE_MENU)
-        item.set_image(image)
-        self.__addItem(item, label, callback, callbackData)
-
-    def addImageMenuItem(self, label, imageFilename, callback,
-                         callbackData=None):
-        item = gtk.ImageMenuItem(label)
-        image = gtk.Image()
-        image.set_from_file(imageFilename)
-        item.set_image(image)
-        self.__addItem(item, label, callback, callbackData)
-
-    def addCheckedMenuItem(self, label, callback, callbackData=None):
-        item = gtk.CheckMenuItem(label)
-        self.__addItem(item, label, callback, callbackData)
-
-    def addRadioMenuItem(self, label, callback, callbackData=None):
-        item = gtk.RadioMenuItem(self.__radioGroup, label)
-        self.__addItem(item, label, callback, callbackData)
-        self.__radioGroup = item
-
-    def addSeparator(self):
-        separator = gtk.SeparatorMenuItem()
-        self.__childItems.append(separator)
-        separator.show()
-        self.__radioGroup = None
-
-    def __getitem__(self, key):
-        return self.__childItemsMap[key]
-
-    def createGroupMenu(self):
-        menu = gtk.Menu()
-        for item in self:
-            menu.append(item)
-        menu.show()
-        return menu
-
-    def createGroupMenuItem(self):
-        menuItem = gtk.MenuItem(self.__label)
-        subMenu = self.createGroupMenu()
-        if len(self) > 0:
-            menuItem.set_submenu(subMenu)
-        else:
-            menuItem.set_sensitive(False)
-        menuItem.show()
-        return menuItem
-
-    def __len__(self):
-        return len(self.__childItems)
-
-    def __iter__(self):
-        for child in self.__childItems:
-            yield child
-
-    def enable(self):
-        for child in self.__childItems:
-            child.set_sensitive(True)
-
-    def disable(self):
-        for child in self.__childItems:
-            child.set_sensitive(False)
-
-    def __addItem(self, item, label, callback, callbackData=None):
-        if callbackData == None:
-            key = label
-        else:
-            key = callbackData
-        self.__childItemsMap[key] = item
-        self.__childItems.append(item)
-        item.connect("activate", callback, callbackData)
-        item.show()
diff --git a/src/gkofoto/gkofoto/mysortedmodel.py b/src/gkofoto/gkofoto/mysortedmodel.py
deleted file mode 100644 (file)
index 8a6585d..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-import gtk
-
-class MySortedModel(gtk.TreeModelSort):
-
-    def __init__(self, model):
-        gtk.TreeModelSort.__init__(self, model)
-        self._model = model
-
-    def __getitem__(self, path):
-        child_path = self.convert_path_to_child_path(path)
-        if child_path:
-            return self._model[child_path]
-        else:
-            raise IndexError
-
-    def __delitem__(self, path):
-        child_path = self.convert_path_to_child_path(path)
-        if child_path:
-            del self._model[child_path]
-        else:
-            raise IndexError
-
-    def set_value(self, iter, column, value):
-        childIter = self._model.get_iter_first()
-        self.convert_iter_to_child_iter(childIter, iter)
-        self._model.set_value(childIter, column, value)
-
-    # Workaround until http://bugzilla.gnome.org/show_bug.cgi?id=121633 is solved.
-    def get_iter_first(self):
-        if len(self) > 0:
-            return gtk.TreeModelSort.get_iter_first(self)
-        else:
-            return None
-
-    # Workaround until http://bugzilla.gnome.org/show_bug.cgi?id=121633 is solved.
-    def __iter__(self):
-        if len(self._model) > 0:
-            return gtk.TreeModelSort.__iter__(self)
-        else:
-            return self._model.__iter__()
diff --git a/src/gkofoto/gkofoto/objectcollection.py b/src/gkofoto/gkofoto/objectcollection.py
deleted file mode 100644 (file)
index b6081be..0000000
+++ /dev/null
@@ -1,596 +0,0 @@
-import os
-import gtk
-import gobject
-import gc
-from sets import *
-from kofoto.shelf import *
-from menuhandler import *
-from environment import env
-from objectselection import *
-from albumdialog import AlbumDialog
-from registerimagesdialog import RegisterImagesDialog
-from imageversionsdialog import ImageVersionsDialog
-from registerimageversionsdialog import RegisterImageVersionsDialog
-from duplicateandopenimagedialog import DuplicateAndOpenImageDialog
-
-class ObjectCollection(object):
-
-######################################################################
-### Public
-
-    def __init__(self):
-        env.debug("Init ObjectCollection")
-        self.__objectSelection = ObjectSelection(self)
-        self.__insertionWorkerTag = None
-        self.__registeredViews = []
-        self.__disabledFields = Set()
-        self.__rowInsertedCallbacks = []
-        self.__columnsType = [ gobject.TYPE_BOOLEAN,  # COLUMN_VALID_LOCATION
-                               gobject.TYPE_BOOLEAN,  # COLUMN_VALID_CHECKSUM
-                               gobject.TYPE_BOOLEAN,  # COLUMN_ROW_EDITABLE
-                               gobject.TYPE_BOOLEAN,  # COLUMN_IS_ALBUM
-                               gobject.TYPE_INT,      # COLUMN_OBJECT_ID
-                               gobject.TYPE_STRING,   # COLUMN_LOCATION
-                               gtk.gdk.Pixbuf,        # COLUMN_THUMBNAIL
-                               gobject.TYPE_STRING,   # COLUMN_IMAGE_VERSIONS
-                               gobject.TYPE_STRING ]  # COLUMN_ALBUM_TAG
-        self.__objectMetadataMap = {
-            u"id"       :(gobject.TYPE_INT,    self.COLUMN_OBJECT_ID, None,                 None),
-            u"location" :(gobject.TYPE_STRING, self.COLUMN_LOCATION,  None,                 None),
-            u"thumbnail":(gtk.gdk.Pixbuf,      self.COLUMN_THUMBNAIL, None,                 None),
-            u"albumtag" :(gobject.TYPE_STRING, self.COLUMN_ALBUM_TAG, self._albumTagEdited, self.COLUMN_ALBUM_TAG),
-            u"versions" :(gobject.TYPE_STRING, self.COLUMN_IMAGE_VERSIONS, None,            None),
-            }
-        for name in env.shelf.getAllAttributeNames():
-            self.__addAttribute(name)
-        self.__treeModel = gtk.ListStore(*self.__columnsType)
-        self.__frozen = False
-
-    # Return true if the objects has a defined order and may
-    # be reordered. An object that is reorderable is not
-    # allowed to also be sortable.
-    def isReorderable(self):
-        return False
-
-    # Return true if the objects may be sorted.
-    def isSortable(self):
-        return False
-
-    # Return true if objects may be added and removed from the collection.
-    def isMutable(self):
-        return not self.isLoading()
-
-    # Return true if object collection has not finished loading.
-    def isLoading(self):
-        return self.__insertionWorkerTag != None
-
-    def getCutLabel(self):
-        return "Cut reference"
-
-    def getCopyLabel(self):
-        return "Copy reference"
-
-    def getPasteLabel(self):
-        return "Paste reference"
-
-    def getDeleteLabel(self):
-        return "Delete reference"
-
-    def getDestroyLabel(self):
-        return "Destroy..."
-
-    def getCreateAlbumChildLabel(self):
-        return "Create album child..."
-
-    def getRegisterImagesLabel(self):
-        return "Register and add images..."
-
-    def getGenerateHtmlLabel(self):
-        return "Generate HTML..."
-
-    def getAlbumPropertiesLabel(self):
-        return "Album properties..."
-
-    def getOpenImageLabel(self):
-        return "Open image in external program..."
-
-    def getDuplicateAndOpenImageLabel(self):
-        return "Duplicate and open image in external program..."
-
-    def getRotateImageLeftLabel(self):
-        return "Rotate image left"
-
-    def getRotateImageRightLabel(self):
-        return "Rotate image right"
-
-    def getImageVersionsLabel(self):
-        return "Edit image versions..."
-
-    def getRegisterImageVersionsLabel(self):
-        return "Register image versions..."
-
-    def getMergeImagesLabel(self):
-        return "Merge images..."
-
-    def getObjectMetadataMap(self):
-        return self.__objectMetadataMap
-
-    def getModel(self):
-        return self.__treeModel
-
-    def getUnsortedModel(self):
-        return self.__treeModel
-
-    def addInsertedRowCallback(self, callback, data=None):
-        self.__rowInsertedCallbacks.append((callback, data))
-
-    def removeInsertedRowCallback(self, callback, data=None):
-        self.__rowInsertedCallbacks.remove((callback, data))
-
-    def signalRowInserted(self):
-        for callback, data in self.__rowInsertedCallbacks:
-            callback(data)
-
-    def convertToUnsortedRowNr(self, rowNr):
-        return rowNr
-
-    def convertFromUnsortedRowNr(self, unsortedRowNr):
-        return unsortedRowNr
-
-    def getObjectSelection(self):
-        return self.__objectSelection
-
-    def getDisabledFields(self):
-        return self.__disabledFields
-
-    def registerView(self, view):
-        env.debug("Register view to object collection")
-        self.__registeredViews.append(view)
-
-    def unRegisterView(self, view):
-        env.debug("Unregister view from object collection")
-        self.__registeredViews.remove(view)
-
-    def reloadSingleObjectView(self):
-        for view in self.__registeredViews:
-            view._reloadSingleObjectView()
-
-    def clear(self, freeze=True):
-        env.debug("Clearing object collection")
-        if freeze:
-            self._freezeViews()
-        self.__stopInsertionWorker()
-        self.__treeModel.clear()
-        gc.collect()
-        self.__nrOfAlbums = 0
-        self.__nrOfImages = 0
-        self._handleNrOfObjectsUpdate()
-        self.__objectSelection.unselectAll()
-        if freeze:
-            self._thawViews()
-
-    def cut(self, *foo):
-        raise Exception("Error. Not allowed to cut objects into objectCollection.") # TODO
-
-    def copy(self, *foo):
-        env.clipboard.setObjects(self.__objectSelection.getSelectedObjects())
-
-    def paste(self, *foo):
-        raise Exception("Error. Not allowed to paste objects into objectCollection.") # TODO
-
-    def delete(self, *foo):
-        raise Exception("Error. Not allowed to delete objects from objectCollection.") # TODO
-
-    def destroy(self, *foo):
-        model = self.getModel()
-
-        albumsSelected = False
-        imagesSelected = False
-        for position in self.__objectSelection:
-            iterator = model.get_iter(position)
-            isAlbum = model.get_value(
-                iterator, self.COLUMN_IS_ALBUM)
-            if isAlbum:
-                albumsSelected = True
-            else:
-                imagesSelected = True
-
-        assert albumsSelected ^ imagesSelected
-
-        self._freezeViews()
-        if albumsSelected:
-            dialogId = "destroyAlbumsDialog"
-        else:
-            dialogId = "destroyImagesDialog"
-        widgets = gtk.glade.XML(env.gladeFile, dialogId)
-        dialog = widgets.get_widget(dialogId)
-        result = dialog.run()
-        albumDestroyed = False
-        if result == gtk.RESPONSE_OK:
-            if albumsSelected:
-                deleteFiles = False
-            else:
-                checkbutton = widgets.get_widget("deleteImageFilesCheckbutton")
-                deleteFiles = checkbutton.get_active()
-            objectIds = Set()
-            # Create a Set to avoid duplicated objects.
-            for obj in Set(self.__objectSelection.getSelectedObjects()):
-                if deleteFiles and not obj.isAlbum():
-                    for iv in obj.getImageVersions():
-                        try:
-                            os.remove(iv.getLocation().encode(env.codeset))
-                            # TODO: Delete from image cache too?
-                        except OSError:
-                            pass
-                env.clipboard.removeObjects(obj)
-                env.shelf.deleteObject(obj.getId())
-                objectIds.add(obj.getId())
-                if obj.isAlbum():
-                    albumDestroyed = True
-            self.getObjectSelection().unselectAll()
-            unsortedModel = self.getUnsortedModel()
-            locations = [row.path for row in unsortedModel
-                         if row[ObjectCollection.COLUMN_OBJECT_ID] in objectIds]
-            locations.sort()
-            locations.reverse()
-            for loc in locations:
-                del unsortedModel[loc]
-        dialog.destroy()
-        if albumDestroyed:
-            env.mainwindow.reloadAlbumTree()
-        self._thawViews()
-
-    COLUMN_VALID_LOCATION = 0
-    COLUMN_VALID_CHECKSUM = 1
-    COLUMN_ROW_EDITABLE   = 2
-    COLUMN_IS_ALBUM       = 3
-
-    # Columns visible to user
-    COLUMN_OBJECT_ID      = 4
-    COLUMN_LOCATION       = 5
-    COLUMN_THUMBNAIL      = 6
-    COLUMN_IMAGE_VERSIONS = 7
-    COLUMN_ALBUM_TAG      = 8
-
-    # Content in objectMetadata fields
-    TYPE                 = 0
-    COLUMN_NR            = 1
-    EDITED_CALLBACK      = 2
-    EDITED_CALLBACK_DATA = 3
-
-
-
-######################################################################
-### Only for subbclasses
-
-    def _getRegisteredViews(self):
-        return self.__registeredViews
-
-    def _loadObjectList(self, objectList):
-        env.enter("Object collection loading objects.")
-        self._freezeViews()
-        self.clear(False)
-        self._insertObjectList(objectList)
-        self._thawViews()
-        env.exit("Object collection loading objects. (albums=" + str(self.__nrOfAlbums) + " images=" + str(self.__nrOfImages) + ")")
-
-    def _insertObjectList(self, objectList, location=None):
-        # location = None means insert last, otherwise insert before
-        # location.
-        #
-        # Note that this method does NOT update objectSelection.
-
-        if location == None:
-            location = len(self.__treeModel)
-        self.__insertionWorkerTag = gobject.idle_add(
-            self.__insertionWorker(objectList, location).next)
-
-    def __insertionWorker(self, objectList, location):
-        for obj in objectList:
-            self._freezeViews()
-
-#            self.__treeModel.insert(location)
-# Work-around for bug 171027 in PyGTK 2.6.1:
-            if location >= len(self.__treeModel):
-                iterator = self.__treeModel.append()
-            else:
-                iterator = self.__treeModel.insert_before(
-                    self.__treeModel[location].iter)
-# End work-around.
-
-            self.__treeModel.set_value(iterator, self.COLUMN_OBJECT_ID, obj.getId())
-            if obj.isAlbum():
-                self.__treeModel.set_value(iterator, self.COLUMN_IS_ALBUM, True)
-                self.__treeModel.set_value(iterator, self.COLUMN_ALBUM_TAG, obj.getTag())
-                self.__treeModel.set_value(iterator, self.COLUMN_LOCATION, None)
-                self.__treeModel.set_value(iterator, self.COLUMN_IMAGE_VERSIONS, "")
-                self.__nrOfAlbums += 1
-            else:
-                if obj.getPrimaryVersion():
-                    ivlocation = obj.getPrimaryVersion().getLocation()
-                else:
-                    ivlocation = None
-                imageVersions = list(obj.getImageVersions())
-                if len(imageVersions) > 1:
-                    imageVersionsText = str(len(imageVersions))
-                else:
-                    imageVersionsText = ""
-                self.__treeModel.set_value(iterator, self.COLUMN_IS_ALBUM, False)
-                self.__treeModel.set_value(iterator, self.COLUMN_ALBUM_TAG, None)
-                self.__treeModel.set_value(iterator, self.COLUMN_LOCATION, ivlocation)
-                self.__treeModel.set_value(iterator, self.COLUMN_IMAGE_VERSIONS, imageVersionsText)
-                self.__nrOfImages += 1
-                # TODO Set COLUMN_VALID_LOCATION and COLUMN_VALID_CHECKSUM
-            for attribute, value in obj.getAttributeMap().items():
-                if "@" + attribute in self.__objectMetadataMap:
-                    column = self.__objectMetadataMap["@" + attribute][self.COLUMN_NR]
-                    self.__treeModel.set_value(iterator, column, value)
-            self.__treeModel.set_value(iterator, self.COLUMN_ROW_EDITABLE, True)
-            self._thawViews()
-            self.signalRowInserted()
-            self.__loadThumbnail(self.__treeModel, iterator)
-            location += 1
-            self.__updateObjectCount(True)
-            yield True
-
-        self._handleNrOfObjectsUpdate()
-        self.__insertionWorkerFinished()
-        yield False
-
-    def __stopInsertionWorker(self):
-        if self.__insertionWorkerTag:
-            gobject.source_remove(self.__insertionWorkerTag)
-            self.__insertionWorkerFinished()
-
-    def __insertionWorkerFinished(self):
-        self.__insertionWorkerTag = None
-        self.__updateObjectCount(False)
-        for view in self.__registeredViews:
-            view.loadingFinished()
-
-    def __updateObjectCount(self, loadingInProgress):
-        env.widgets["statusbarLoadedObjects"].pop(1)
-        if loadingInProgress:
-            text = "%d objects (and counting...)" % len(self.__treeModel)
-        else:
-            text = "%d objects" % len(self.__treeModel)
-        env.widgets["statusbarLoadedObjects"].push(1, text)
-
-    def _handleNrOfObjectsUpdate(self):
-        updatedDisabledFields = Set()
-        if self.__nrOfAlbums == 0:
-            updatedDisabledFields.add(u"albumtag")
-        if self.__nrOfImages == 0:
-            updatedDisabledFields.add(u"location")
-        for view in self.__registeredViews:
-            view.fieldsDisabled(updatedDisabledFields - self.__disabledFields)
-            view.fieldsEnabled(self.__disabledFields - updatedDisabledFields)
-        self.__disabledFields = updatedDisabledFields
-        env.debug("The following fields are disabled: " + str(self.__disabledFields))
-
-    def _getTreeModel(self):
-        return self.__treeModel
-
-    def _freezeViews(self):
-        if self.__frozen:
-            return
-        for view in self.__registeredViews:
-            view.freeze()
-        self.__frozen = True
-
-    def _thawViews(self):
-        if not self.__frozen:
-            return
-        for view in self.__registeredViews:
-            view.thaw()
-        self.__frozen = False
-
-
-###############################################################################
-### Callback functions
-
-    def _attributeEdited(self, renderer, path, value, column, attributeName):
-        model = self.getModel()
-        columnNumber = self.__objectMetadataMap["@" + attributeName][self.COLUMN_NR]
-        iterator = model.get_iter(path)
-        oldValue = model.get_value(iterator, columnNumber)
-        if not oldValue:
-            oldValue = u""
-        value = unicode(value, "utf-8")
-        if oldValue != value:
-            # TODO Show dialog and ask for confirmation?
-            objectId = model.get_value(iterator, self.COLUMN_OBJECT_ID)
-            obj = env.shelf.getObject(objectId)
-            obj.setAttribute(attributeName, value)
-            model.set_value(iterator, columnNumber, value)
-            env.debug("Object attribute edited")
-
-    def _albumTagEdited(self, renderer, path, value, column, columnNumber):
-        model = self.getModel()
-        iterator = model.get_iter(path)
-        assert model.get_value(iterator, self.COLUMN_IS_ALBUM)
-        oldValue = model.get_value(iterator, columnNumber)
-        if not oldValue:
-            oldValue = u""
-        value = unicode(value, "utf-8")
-        if oldValue != value:
-            # TODO Show dialog and ask for confirmation?
-            objectId = model.get_value(iterator, self.COLUMN_OBJECT_ID)
-            obj = env.shelf.getAlbum(objectId)
-            obj.setTag(value)
-            # TODO Handle invalid album tag?
-            model.set_value(iterator, columnNumber, value)
-            # TODO Update the album tree widget.
-            env.debug("Album tag edited")
-
-    def reloadSelectedRows(self):
-        model = self.getUnsortedModel()
-        for (rowNr, obj) in self.__objectSelection.getMap().items():
-            if not obj.isAlbum():
-                self.__loadThumbnail(model, model.get_iter(rowNr))
-                imageVersions = list(obj.getImageVersions())
-                if len(imageVersions) > 1:
-                    imageVersionsText = str(len(imageVersions))
-                else:
-                    imageVersionsText = ""
-                model.set_value(model.get_iter(rowNr), self.COLUMN_IMAGE_VERSIONS, imageVersionsText)
-
-    def createAlbumChild(self, *unused):
-        dialog = AlbumDialog("Create album")
-        dialog.run(self._createAlbumChildHelper)
-
-    def _createAlbumChildHelper(self, tag, desc):
-        newAlbum = env.shelf.createAlbum(tag)
-        if len(desc) > 0:
-            newAlbum.setAttribute(u"title", desc)
-        selectedObjects = self.__objectSelection.getSelectedObjects()
-        selectedAlbum = selectedObjects[0]
-        children = list(selectedAlbum.getChildren())
-        children.append(newAlbum)
-        selectedAlbum.setChildren(children)
-        env.mainwindow.reloadAlbumTree()
-
-    def registerAndAddImages(self, *unused):
-        selectedObjects = self.__objectSelection.getSelectedObjects()
-        assert len(selectedObjects) == 1 and selectedObjects[0].isAlbum()
-        selectedAlbum = selectedObjects[0]
-        dialog = RegisterImagesDialog(selectedAlbum)
-        if dialog.run() == gtk.RESPONSE_OK:
-            env.mainwindow.reload() # TODO: Don't reload everything.
-        dialog.destroy()
-
-    def generateHtml(self, *unused):
-        selectedObjects = self.__objectSelection.getSelectedObjects()
-        assert len(selectedObjects) == 1 and selectedObjects[0].isAlbum()
-        selectedAlbum = selectedObjects[0]
-        env.mainwindow.generateHtml(selectedAlbum)
-
-    def albumProperties(self, *unused):
-        selectedObjects = self.__objectSelection.getSelectedObjects()
-        assert len(selectedObjects) == 1 and selectedObjects[0].isAlbum()
-        selectedAlbumId = selectedObjects[0].getId()
-        dialog = AlbumDialog("Edit album", selectedAlbumId)
-        dialog.run(self._albumPropertiesHelper)
-
-    def _albumPropertiesHelper(self, tag, desc):
-        selectedObjects = self.__objectSelection.getSelectedObjects()
-        selectedAlbum = selectedObjects[0]
-        selectedAlbum.setTag(tag)
-        if len(desc) > 0:
-            selectedAlbum.setAttribute(u"title", desc)
-        else:
-            selectedAlbum.deleteAttribute(u"title")
-        env.mainwindow.reloadAlbumTree()
-        # TODO: Update objectCollection.
-
-    def imageVersions(self, widget, *unused):
-        selectedObjects = self.__objectSelection.getSelectedObjects()
-        assert len(selectedObjects) == 1
-        dialog = ImageVersionsDialog(self)
-        dialog.runViewImageVersions(selectedObjects[0])
-        self.reloadSingleObjectView()
-
-    def registerImageVersions(self, widget, *unused):
-        selectedObjects = self.__objectSelection.getSelectedObjects()
-        assert len(selectedObjects) == 1
-        dialog = RegisterImageVersionsDialog(self)
-        dialog.run(selectedObjects[0])
-        self.reloadSingleObjectView()
-
-    def mergeImages(self, widget, *unused):
-        selectedObjects = self.__objectSelection.getSelectedObjects()
-        assert len(selectedObjects) > 1
-        dialog = ImageVersionsDialog(self)
-        dialog.runMergeImages(selectedObjects)
-
-    def rotateImage(self, widget, angle):
-        env.mainwindow.getImagePreloader().clearCache()
-        for (rowNr, obj) in self.__objectSelection.getMap().items():
-            if not obj.isAlbum():
-                imageversion = obj.getPrimaryVersion()
-                if not imageversion:
-                    # Image has no versions. Skip it for now.
-                    continue
-                location = imageversion.getLocation()
-                if angle == 90:
-                    commandString = env.rotateRightCommand
-                else:
-                    commandString = env.rotateLeftCommand
-                command = commandString % { "location":location }
-                result = os.system(command.encode(env.codeset))
-                if result == 0:
-                    imageversion.contentChanged()
-                    model = self.getUnsortedModel()
-                    self.__loadThumbnail(model, model.get_iter(rowNr))
-                    env.mainwindow.getImagePreloader().clearCache()
-                else:
-                    dialog = gtk.MessageDialog(
-                        type=gtk.MESSAGE_ERROR,
-                        buttons=gtk.BUTTONS_OK,
-                        message_format="Failed to execute command: \"%s\"" % command)
-                    dialog.run()
-                    dialog.destroy()
-        self.reloadSingleObjectView()
-
-    def rotateImageLeft(self, widget, *unused):
-        self.rotateImage(widget, 270)
-
-    def rotateImageRight(self, widget, *unused):
-        self.rotateImage(widget, 90)
-
-    def openImage(self, widget, *unused):
-        locations = ""
-        for obj in self.__objectSelection.getSelectedObjects():
-            if not obj.isAlbum():
-                imageversion = obj.getPrimaryVersion()
-                if not imageversion:
-                    # Image has no versions. Skip it for now.
-                    continue
-                location = imageversion.getLocation()
-                locations += location + " "
-        if locations != "":
-            command = env.openCommand % { "locations":locations }
-            result = os.system(command.encode(env.codeset) + " &")
-            if result != 0:
-                dialog = gtk.MessageDialog(
-                    type=gtk.MESSAGE_ERROR,
-                    buttons=gtk.BUTTONS_OK,
-                    message_format="Failed to execute command: \"%s\"" % command)
-                dialog.run()
-                dialog.destroy()
-
-    def duplicateAndOpenImage(self, widget, *unused):
-        selectedObjects = self.__objectSelection.getSelectedObjects()
-        assert len(selectedObjects) == 1
-        assert not selectedObjects[0].isAlbum()
-        dialog = DuplicateAndOpenImageDialog()
-        dialog.run(selectedObjects[0].getPrimaryVersion())
-
-######################################################################
-### Private
-
-    def __addAttribute(self, name):
-        self.__objectMetadataMap["@" + name] = (gobject.TYPE_STRING,
-                                                len(self.__columnsType),
-                                                self._attributeEdited,
-                                                name)
-        self.__columnsType.append(gobject.TYPE_STRING)
-
-    def __loadThumbnail(self, model, iterator):
-        objectId = model.get_value(iterator, self.COLUMN_OBJECT_ID)
-        obj = env.shelf.getObject(objectId)
-        if obj.isAlbum():
-            pixbuf = env.albumIconPixbuf
-        elif not obj.getPrimaryVersion():
-            pixbuf = env.unknownImageIconPixbuf
-        else:
-            try:
-                thumbnailLocation = env.imageCache.get(
-                    obj.getPrimaryVersion(),
-                    env.thumbnailSize[0],
-                    env.thumbnailSize[1])[0]
-                pixbuf = gtk.gdk.pixbuf_new_from_file(thumbnailLocation.encode(env.codeset))
-                # TODO Set and use COLUMN_VALID_LOCATION and COLUMN_VALID_CHECKSUM
-            except IOError:
-                pixbuf = env.unknownImageIconPixbuf
-        model.set_value(iterator, self.COLUMN_THUMBNAIL, pixbuf)
diff --git a/src/gkofoto/gkofoto/objectcollectionfactory.py b/src/gkofoto/gkofoto/objectcollectionfactory.py
deleted file mode 100644 (file)
index 40248a5..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-import string
-from searchresult import *
-from albummembers import *
-from environment import env
-from kofoto.search import *
-from kofoto.shelf import *
-
-class ObjectCollectionFactory:
-
-######################################################################
-### Public functions and constants
-
-    def __init__(self):
-        env.debug("Init ObjectCollectionFactory")
-        self.__searchResult = SearchResult()
-        self.__albumMembers = AlbumMembers()
-
-    def getObjectCollection(self, query, filterText=""):
-        env.debug("Object collection factory loading query: " + query);
-        self.__clear()
-        validAlbumTag = False
-        if query and query[0] == "/":
-            try:
-                verifyValidAlbumTag(query[1:])
-                validAlbumTag = True
-            except BadAlbumTagError:
-                pass
-        try:
-            if validAlbumTag and not filterText:
-                self.__albumMembers.loadAlbum(env.shelf.getAlbumByTag(query[1:]))
-                return self.__albumMembers
-            else:
-                if filterText:
-                    queryWithFilter = "(%s) and (%s)" % (query, filterText)
-                else:
-                    queryWithFilter = query
-                self.__searchResult.loadQuery(queryWithFilter)
-                if not validAlbumTag:
-                    env.mainwindow.unselectAlbumTree()
-                return self.__searchResult
-        except AlbumDoesNotExistError, tag:
-            errorText = "No such album tag: \"%s\"." % tag
-        except CategoryDoesNotExistError, tag:
-            errorText = "No such category tag: \"%s\"." % tag
-        except BadTokenError, pos:
-            errorText = "Error parsing query: bad token starting at position %s: \"%s\"." % (
-                pos,
-                query[pos[0]:])
-        except UnterminatedStringError, e:
-            errorText = "Error parsing query: unterminated string starting at position %s: \"%s\"." % (
-                e.args[0],
-                query[e.args[0]:])
-        except ParseError, text:
-            errorText = "Error parsing query: %s." % text
-        dialog = gtk.MessageDialog(
-            type=gtk.MESSAGE_ERROR,
-            buttons=gtk.BUTTONS_OK,
-            message_format=errorText)
-        dialog.run()
-        dialog.destroy()
-        self.__searchResult = SearchResult()
-        return self.__searchResult
-
-
-######################################################################
-### Private functions
-
-    def __clear(self):
-        self.__searchResult.clear()
-        self.__albumMembers.clear()
diff --git a/src/gkofoto/gkofoto/objectcollectionview.py b/src/gkofoto/gkofoto/objectcollectionview.py
deleted file mode 100644 (file)
index f87d9eb..0000000
+++ /dev/null
@@ -1,409 +0,0 @@
-import gtk
-from environment import env
-from menuhandler import *
-from objectcollection import *
-
-class ObjectCollectionView:
-
-###############################################################################
-### Public
-
-    def __init__(self, view):
-        self._viewWidget = view
-        self._objectCollection = None
-        self._contextMenu = None
-        self.__objectCollectionLoaded = False
-        self.__hidden = True
-        self.__connections = []
-
-    def show(self, objectCollection):
-        if self.__hidden:
-            self.__hidden = False
-            self.__connectObjectCollection(objectCollection)
-            self._showHelper()
-            self._updateMenubarSortMenu()
-        else:
-            self.setObjectCollection(objectCollection)
-
-    def _connectMenubarImageItems(self):
-        self._connect(
-            env.widgets["menubarOpenImage"],
-            "activate",
-            self._objectCollection.openImage)
-        self._connect(
-            env.widgets["menubarDuplicateAndOpenImage"],
-            "activate",
-            self._objectCollection.duplicateAndOpenImage)
-        self._connect(
-            env.widgets["menubarRotateLeft"],
-            "activate",
-            self._objectCollection.rotateImage,
-            270)
-        self._connect(
-            env.widgets["menubarRotateRight"],
-            "activate",
-            self._objectCollection.rotateImage,
-            90)
-        self._connect(
-            env.widgets["menubarImageVersions"],
-            "activate",
-            self._objectCollection.imageVersions)
-        self._connect(
-            env.widgets["menubarRegisterImageVersions"],
-            "activate",
-            self._objectCollection.registerImageVersions)
-        self._connect(
-            env.widgets["menubarMergeImages"],
-            "activate",
-            self._objectCollection.mergeImages)
-
-    def _updateMenubarSortMenu(self):
-        sortMenuGroup = self.__createSortMenuGroup(self._objectCollection)
-        sortByItem = env.widgets["menubarSortBy"]
-        if self._objectCollection.isSortable():
-            sortByItem.set_sensitive(True)
-            sortByItem.set_submenu(sortMenuGroup.createGroupMenu())
-        else:
-            sortByItem.remove_submenu()
-            sortByItem.set_sensitive(False)
-
-    def hide(self):
-        if not self.__hidden:
-            self.__hidden = True
-            self._hideHelper()
-            self._clearAllConnections()
-            self.__disconnectObjectCollection()
-
-    def setObjectCollection(self, objectCollection):
-        if not self.__hidden:
-            env.debug("ObjectCollectionView sets object collection")
-            self.__connectObjectCollection(objectCollection)
-
-    def freeze(self):
-        self._freezeHelper()
-        self._objectCollection.getObjectSelection().removeChangedCallback(self.importSelection)
-        env.clipboard.removeChangedCallback(self._updateContextMenu)
-
-    def thaw(self):
-        self._thawHelper()
-        self._objectCollection.getObjectSelection().addChangedCallback(self.importSelection)
-        env.clipboard.addChangedCallback(self._updateContextMenu)
-        self.importSelection(self._objectCollection.getObjectSelection())
-        # importSelection makes an implicit _updateContextMenu()
-
-    def sortOrderChanged(self, sortOrder):
-        env.debug("Sort order is " + str(sortOrder))
-        self.__sortMenuGroup[sortOrder].activate()
-
-    def sortColumnChanged(self, sortColumn):
-        env.debug("Sort column is " + str(sortColumn))
-        self.__sortMenuGroup[sortColumn].activate()
-
-    def fieldsDisabled(self, fields):
-        pass
-
-    def fieldsEnabled(self, fields):
-        pass
-
-    def loadingFinished(self):
-        self._updateContextMenu()
-
-    def _mouse_button_pressed(self, widget, event):
-        widget.grab_focus()
-        if event.button == 3:
-            self._contextMenu.popup(None, None, None, event.button, event.time)
-            return True
-        else:
-            return False
-
-##############################################################################
-### Methods used by and overloaded by subbclasses
-
-    def _connect(self, obj, signal, function, data=None):
-        oid = obj.connect(signal, function, data)
-        self.__connections.append((obj, oid))
-        return oid
-
-    def _disconnect(self, obj, oid):
-        obj.disconnect(oid)
-        self.__connections.remove((obj, oid))
-
-    def _clearAllConnections(self):
-        for (obj, oid) in self.__connections:
-            obj.disconnect(oid)
-        self.__connections = []
-
-    def _createContextMenu(self, objectCollection):
-        env.debug("Creating view context menu")
-        self._contextMenu = gtk.Menu()
-        self.__clipboardMenuGroup = self.__createClipboardMenuGroup(objectCollection)
-        for item in self.__clipboardMenuGroup:
-            self._contextMenu.add(item)
-        self.__objectMenuGroup = self.__createObjectMenuGroup(objectCollection)
-        for item in self.__objectMenuGroup:
-            self._contextMenu.add(item)
-        self.__albumMenuGroup = self.__createAlbumMenuGroup(objectCollection)
-        for item in self.__albumMenuGroup:
-            self._contextMenu.add(item)
-        self.__imageMenuGroup = self.__createImageMenuGroup(objectCollection)
-        for item in self.__imageMenuGroup:
-            self._contextMenu.add(item)
-        self.__singleImageMenuGroup = self.__createSingleImageMenuGroup(objectCollection)
-        for item in self.__singleImageMenuGroup:
-            self._contextMenu.add(item)
-        self.__multipleImagesMenuGroup = self.__createMultipleImagesMenuGroup(objectCollection)
-        for item in self.__multipleImagesMenuGroup:
-            self._contextMenu.add(item)
-        self.__sortMenuGroup = self.__createSortMenuGroup(objectCollection)
-        self._contextMenu.add(self.__sortMenuGroup.createGroupMenuItem())
-
-    def _clearContextMenu(self):
-        env.debug("Clearing view context menu")
-        self._contextMenu = None
-        self.__clipboardMenuGroup = None
-        self.__objectMenuGroup = None
-        self.__albumMenuGroup = None
-        self.__imageMenuGroup = None
-        self.__singleImageMenuGroup = None
-        self.__multipleImagesMenuGroup = None
-        self.__sortMenuGroup = None
-
-    def _updateContextMenu(self, *foo):
-        if not self._hasFocus():
-            return
-        env.debug("Updating context menu")
-        self.__objectMenuGroup[self._objectCollection.getDestroyLabel()].set_sensitive(False)
-        env.widgets["menubarDestroy"].set_sensitive(False)
-        mutable = self._objectCollection.isMutable()
-        loading = self._objectCollection.isLoading()
-        objectSelection = self._objectCollection.getObjectSelection()
-        if objectSelection:
-            model = self._objectCollection.getModel()
-            rootAlbumId = env.shelf.getRootAlbum().getId()
-
-            albumsSelected = 0
-            imagesSelected = 0
-            rootAlbumSelected = False
-            for position in objectSelection:
-                iterator = model.get_iter(position)
-                isAlbum = model.get_value(
-                    iterator, self._objectCollection.COLUMN_IS_ALBUM)
-                if isAlbum:
-                    albumsSelected += 1
-                    if rootAlbumId == model.get_value(
-                        iterator, self._objectCollection.COLUMN_OBJECT_ID):
-                        rootAlbumSelected = True
-                else:
-                    imagesSelected += 1
-
-            modifiable = mutable and not loading
-            self.__clipboardMenuGroup[self._objectCollection.getCutLabel()].set_sensitive(mutable and not loading)
-            env.widgets["menubarCut"].set_sensitive(mutable and not loading)
-            self.__clipboardMenuGroup[self._objectCollection.getCopyLabel()].set_sensitive(True)
-            env.widgets["menubarCopy"].set_sensitive(True)
-            self.__clipboardMenuGroup[self._objectCollection.getDeleteLabel()].set_sensitive(mutable and not loading)
-            env.widgets["menubarDelete"].set_sensitive(mutable and not loading)
-            destroyActive = (imagesSelected == 0) ^ (albumsSelected == 0) and not rootAlbumSelected and not loading
-            self.__objectMenuGroup[self._objectCollection.getDestroyLabel()].set_sensitive(destroyActive)
-            env.widgets["menubarDestroy"].set_sensitive(destroyActive)
-            if albumsSelected == 1 and imagesSelected == 0:
-                selectedAlbumId = model.get_value(
-                    iterator, self._objectCollection.COLUMN_OBJECT_ID)
-                selectedAlbum = env.shelf.getAlbum(selectedAlbumId)
-                if selectedAlbum.isMutable():
-                    self.__albumMenuGroup.enable()
-                    env.widgets["menubarCreateAlbumChild"].set_sensitive(True)
-                    env.widgets["menubarRegisterAndAddImages"].set_sensitive(True)
-                    env.widgets["menubarGenerateHtml"].set_sensitive(True)
-                    env.widgets["menubarProperties"].set_sensitive(True)
-                else:
-                    self.__albumMenuGroup.disable()
-                    self.__albumMenuGroup[
-                        self._objectCollection.getAlbumPropertiesLabel()
-                        ].set_sensitive(True)
-                    env.widgets["menubarCreateAlbumChild"].set_sensitive(False)
-                    env.widgets["menubarRegisterAndAddImages"].set_sensitive(False)
-                    env.widgets["menubarGenerateHtml"].set_sensitive(True)
-                    env.widgets["menubarProperties"].set_sensitive(True)
-            else:
-                self.__albumMenuGroup.disable()
-                env.widgets["menubarCreateAlbumChild"].set_sensitive(False)
-                env.widgets["menubarRegisterAndAddImages"].set_sensitive(False)
-                env.widgets["menubarGenerateHtml"].set_sensitive(False)
-                env.widgets["menubarProperties"].set_sensitive(False)
-            if albumsSelected == 0 and imagesSelected > 0:
-                self.__imageMenuGroup.enable()
-                env.widgets["menubarOpenImage"].set_sensitive(True)
-                env.widgets["menubarRotateLeft"].set_sensitive(True)
-                env.widgets["menubarRotateRight"].set_sensitive(True)
-                if imagesSelected == 1:
-                    env.widgets["menubarDuplicateAndOpenImage"].set_sensitive(True)
-                    self.__singleImageMenuGroup.enable()
-                    env.widgets["menubarImageVersions"].set_sensitive(True)
-                    env.widgets["menubarRegisterImageVersions"].set_sensitive(True)
-                    self.__multipleImagesMenuGroup.disable()
-                    env.widgets["menubarMergeImages"].set_sensitive(False)
-                else:
-                    self.__imageMenuGroup[
-                        self._objectCollection.getDuplicateAndOpenImageLabel()
-                        ].set_sensitive(False)
-                    env.widgets["menubarDuplicateAndOpenImage"].set_sensitive(False)
-                    self.__singleImageMenuGroup.disable()
-                    env.widgets["menubarImageVersions"].set_sensitive(False)
-                    env.widgets["menubarRegisterImageVersions"].set_sensitive(False)
-                    self.__multipleImagesMenuGroup.enable()
-                    env.widgets["menubarMergeImages"].set_sensitive(True)
-            else:
-                self.__imageMenuGroup.disable()
-                self.__singleImageMenuGroup.disable()
-                env.widgets["menubarOpenImage"].set_sensitive(False)
-                env.widgets["menubarDuplicateAndOpenImage"].set_sensitive(False)
-                env.widgets["menubarRegisterImageVersions"].set_sensitive(False)
-                env.widgets["menubarRotateLeft"].set_sensitive(False)
-                env.widgets["menubarRotateRight"].set_sensitive(False)
-        else:
-            self.__clipboardMenuGroup.disable()
-            env.widgets["menubarCut"].set_sensitive(False)
-            env.widgets["menubarCopy"].set_sensitive(False)
-            env.widgets["menubarDelete"].set_sensitive(False)
-
-            self.__objectMenuGroup.disable()
-            env.widgets["menubarDestroy"].set_sensitive(False)
-
-            self.__albumMenuGroup.disable()
-            env.widgets["menubarCreateAlbumChild"].set_sensitive(False)
-            env.widgets["menubarRegisterAndAddImages"].set_sensitive(False)
-            env.widgets["menubarGenerateHtml"].set_sensitive(False)
-            env.widgets["menubarProperties"].set_sensitive(False)
-
-            self.__imageMenuGroup.disable()
-            env.widgets["menubarOpenImage"].set_sensitive(False)
-            env.widgets["menubarDuplicateAndOpenImage"].set_sensitive(False)
-            env.widgets["menubarRotateLeft"].set_sensitive(False)
-            env.widgets["menubarRotateRight"].set_sensitive(False)
-            self.__singleImageMenuGroup.disable()
-            env.widgets["menubarImageVersions"].set_sensitive(False)
-            env.widgets["menubarRegisterImageVersions"].set_sensitive(False)
-            self.__multipleImagesMenuGroup.disable()
-            env.widgets["menubarMergeImages"].set_sensitive(False)
-
-        if env.clipboard.hasObjects():
-            self.__clipboardMenuGroup[
-                self._objectCollection.getPasteLabel()].set_sensitive(mutable)
-            env.widgets["menubarPaste"].set_sensitive(mutable)
-        else:
-            self.__clipboardMenuGroup[
-                self._objectCollection.getPasteLabel()].set_sensitive(False)
-            env.widgets["menubarPaste"].set_sensitive(False)
-
-
-###############################################################################
-### Private
-
-    def __connectObjectCollection(self, objectCollection):
-        if self._objectCollection != None:
-            self.__disconnectObjectCollection()
-        self._objectCollection = objectCollection
-        self._createContextMenu(objectCollection)
-        self._connectObjectCollectionHelper()
-        self.thaw()
-        self._objectCollection.registerView(self)
-
-    def __disconnectObjectCollection(self):
-        if self._objectCollection is not None:
-            self._objectCollection.unRegisterView(self)
-            self.freeze()
-            self._disconnectObjectCollectionHelper()
-            self._clearContextMenu()
-            self._objectCollection = None
-
-    def __createSortMenuGroup(self, objectCollection):
-        menuGroup = MenuGroup("Sort by")
-        if objectCollection.isSortable():
-            env.debug("Creating sort menu group for sortable log collection")
-            menuGroup.addRadioMenuItem("Ascending",
-                                       objectCollection.setSortOrder,
-                                       gtk.SORT_ASCENDING)
-            menuGroup.addRadioMenuItem("Descending",
-                                       objectCollection.setSortOrder,
-                                       gtk.SORT_DESCENDING)
-            menuGroup.addSeparator()
-            objectMetadataMap = objectCollection.getObjectMetadataMap()
-            columnNames = list(objectMetadataMap.keys())
-            columnNames.sort()
-            for columnName in columnNames:
-                if objectMetadataMap[columnName][ObjectCollection.TYPE] != gtk.gdk.Pixbuf:
-                    menuGroup.addRadioMenuItem(columnName,
-                                               objectCollection.setSortColumnName,
-                                               columnName)
-        return menuGroup
-
-    def __createClipboardMenuGroup(self, oc):
-        menuGroup = MenuGroup()
-        env.debug("Creating clipboard menu")
-        menuGroup.addStockImageMenuItem(
-            oc.getCutLabel(), gtk.STOCK_CUT, oc.cut)
-        menuGroup.addStockImageMenuItem(
-            oc.getCopyLabel(), gtk.STOCK_COPY, oc.copy)
-        menuGroup.addStockImageMenuItem(
-            oc.getPasteLabel(), gtk.STOCK_PASTE, oc.paste)
-        menuGroup.addStockImageMenuItem(
-            oc.getDeleteLabel(), gtk.STOCK_DELETE, oc.delete)
-        menuGroup.addSeparator()
-        return menuGroup
-
-    def __createObjectMenuGroup(self, oc):
-        menuGroup = MenuGroup()
-        menuGroup.addStockImageMenuItem(
-            oc.getDestroyLabel(), gtk.STOCK_DELETE, oc.destroy)
-        menuGroup.addSeparator()
-        return menuGroup
-
-    def __createAlbumMenuGroup(self, oc):
-        menuGroup = MenuGroup()
-        menuGroup.addMenuItem(
-            oc.getCreateAlbumChildLabel(), oc.createAlbumChild)
-        menuGroup.addMenuItem(
-            oc.getRegisterImagesLabel(), oc.registerAndAddImages)
-        menuGroup.addMenuItem(
-            oc.getGenerateHtmlLabel(), oc.generateHtml)
-        menuGroup.addStockImageMenuItem(
-            oc.getAlbumPropertiesLabel(),
-            gtk.STOCK_PROPERTIES,
-            oc.albumProperties)
-        menuGroup.addSeparator()
-        return menuGroup
-
-    def __createImageMenuGroup(self, oc):
-        menuGroup = MenuGroup()
-        menuGroup.addStockImageMenuItem(
-            oc.getOpenImageLabel(),
-            gtk.STOCK_OPEN,
-            oc.openImage)
-        menuGroup.addStockImageMenuItem(
-            oc.getDuplicateAndOpenImageLabel(),
-            gtk.STOCK_OPEN,
-            oc.duplicateAndOpenImage)
-        menuGroup.addImageMenuItem(
-            oc.getRotateImageLeftLabel(),
-            os.path.join(env.iconDir, "rotateleft.png"),
-            oc.rotateImage, 270)
-        menuGroup.addImageMenuItem(
-            oc.getRotateImageRightLabel(),
-            os.path.join(env.iconDir, "rotateright.png"),
-            oc.rotateImage, 90)
-        menuGroup.addSeparator()
-        return menuGroup
-
-    def __createSingleImageMenuGroup(self, oc):
-        menuGroup = MenuGroup()
-        menuGroup.addMenuItem(oc.getImageVersionsLabel(), oc.imageVersions)
-        menuGroup.addMenuItem(
-            oc.getRegisterImageVersionsLabel(), oc.registerImageVersions)
-        return menuGroup
-
-    def __createMultipleImagesMenuGroup(self, oc):
-        menuGroup = MenuGroup()
-        menuGroup.addMenuItem(oc.getMergeImagesLabel(), oc.mergeImages)
-        menuGroup.addSeparator()
-        return menuGroup
diff --git a/src/gkofoto/gkofoto/objectselection.py b/src/gkofoto/gkofoto/objectselection.py
deleted file mode 100644 (file)
index 6478c08..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-from environment import env
-from sets import Set
-
-class ObjectSelection:
-    def __init__(self, objectCollection):
-        # Don't forget to update this class when the model is reordered or
-        # when rows are removed or added.
-        self.__selectedObjects = {}
-        # When objects are stored in self.__selectedObjects, the key MUST be
-        # the location in the UNSORTED model since this class is not
-        # notified when/if the model is re-sorted.
-        #
-        # This class must know about each object's row to be able to distinguish
-        # individual objects in an album that contains multiple instances
-        # of the same image or album.
-        self.__changedCallbacks = Set()
-        self.__objectCollection = objectCollection
-        self.addChangedCallback(self._nrOfSelectedObjectsChanged)
-
-    def addChangedCallback(self, callback):
-        self.__changedCallbacks.add(callback)
-
-    def removeChangedCallback(self, callback):
-        self.__changedCallbacks.remove(callback)
-
-    def unselectAll(self, notify=True):
-        self.__selectedObjects.clear()
-        if notify:
-            self.__invokeChangedCallbacks()
-
-    def setSelection(self, rowNrs, notify=True):
-        self.__selectedObjects.clear()
-        for rowNr in rowNrs:
-            self.addSelection(rowNr, False)
-        if notify:
-            self.__invokeChangedCallbacks()
-
-    def addSelection(self, rowNr, notify=True):
-        unsortedRowNr = self.__objectCollection.convertToUnsortedRowNr(rowNr)
-        self.__selectedObjects[unsortedRowNr] = self.__getObject(unsortedRowNr)
-        if notify:
-            self.__invokeChangedCallbacks()
-
-    def removeSelection(self, rowNr, notify=True):
-        unsortedRowNr = self.__objectCollection.convertToUnsortedRowNr(rowNr)
-        del self.__selectedObjects[unsortedRowNr]
-        if notify:
-            self.__invokeChangedCallbacks()
-
-    def getSelectedObjects(self):
-        conv = self.__objectCollection.convertFromUnsortedRowNr
-        items = self.__selectedObjects.items()
-        items.sort(lambda x, y: cmp(conv(x[0]), conv(y[0])))
-        return [x[1] for x in items]
-
-    def getLowestSelectedRowNr(self):
-        rowNrs = list(self)
-        if (len(rowNrs) > 0):
-            rowNrs.sort()
-            return rowNrs[0]
-        else:
-            return None
-
-    def getMap(self):
-        return self.__selectedObjects
-
-    def getImageFilenamesToPreload(self):
-        oc = self.__objectCollection
-        model = oc.getModel()
-        if len(self) == 0:
-            rowNr = 0
-        else:
-            rowNumbers = list(self)
-            rowNumbers.sort()
-            rowNr = rowNumbers[0]
-        filenames = []
-        for x in [rowNr, rowNr + 1, rowNr - 1, rowNr + 2]: # TODO: Make configurable.
-            if 0 <= x < len(model):
-                ux = oc.convertToUnsortedRowNr(x)
-                obj = self.__getObject(ux)
-                if not obj.isAlbum():
-                    imageversion = obj.getPrimaryVersion()
-                    if imageversion:
-                        filenames.append(imageversion.getLocation())
-        env.debug("filenames to preload: %s" % str(filenames))
-        return filenames
-
-    def _nrOfSelectedObjectsChanged(self, objectSelection):
-        env.widgets["statusbarSelectedObjects"].pop(1)
-        env.widgets["statusbarSelectedObjects"].push(
-            1, "%d selected" % len(objectSelection))
-
-    def __contains__(self, rowNr):
-        unsortedRowNr = self.__objectCollection.convertToUnsortedRowNr(rowNr)
-        return unsortedRowNr in self.__selectedObjects.keys()
-
-    def __len__(self):
-        return len(self.__selectedObjects)
-
-    def __iter__(self):
-        for unsortedRowNr in self.__selectedObjects.keys():
-            rowNr = self.__objectCollection.convertFromUnsortedRowNr(unsortedRowNr)
-            yield rowNr
-
-    def __getitem__(self, rowNr):
-        unsortedRowNr = self.__objectCollection.convertToUnsortedRowNr(rowNr)
-        return self.__selectedObjects[unsortedRowNr]
-
-    def __invokeChangedCallbacks(self):
-        env.debug("Invoking selection changed callbacks: " + str(self.__selectedObjects.keys()))
-        for callback in self.__changedCallbacks:
-            callback(self)
-
-    def __getObject(self, unsortedRowNr):
-        objectId = self.__objectCollection.getUnsortedModel()[unsortedRowNr][self.__objectCollection.COLUMN_OBJECT_ID]
-        return env.shelf.getObject(objectId)
diff --git a/src/gkofoto/gkofoto/persistentstate.py b/src/gkofoto/gkofoto/persistentstate.py
deleted file mode 100644 (file)
index 1217b91..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-import ConfigParser
-import os
-import sys
-
-class PersistentState(object):
-    def __init__(self):
-        self.__configParser = ConfigParser.ConfigParser()
-        cp = self.__configParser
-
-        # Defaults:
-        cp.add_section("state")
-        cp.set("state", "filter-text", "")
-
-        home = os.path.expanduser("~")
-        if sys.platform.startswith("win"):
-            self.__stateFile = os.path.join(
-                home, "KofotoData", "state", "gkofoto.ini")
-        else:
-            self.__stateFile = os.path.join(
-                home, ".kofoto", "state", "gkofoto")
-        if not os.path.isdir(os.path.dirname(self.__stateFile)):
-            os.mkdir(os.path.dirname(self.__stateFile))
-        if os.path.isfile(self.__stateFile):
-            self.__configParser.read(self.__stateFile)
-
-    def save(self):
-        self.__configParser.write(open(self.__stateFile, "w"))
-
-    def getFilterText(self):
-        return self.__configParser.get("state", "filter-text")
-
-    def setFilterText(self, text):
-        self.__configParser.set("state", "filter-text", text)
-
-    filterText = property(getFilterText, setFilterText)
diff --git a/src/gkofoto/gkofoto/registerimagesdialog.py b/src/gkofoto/gkofoto/registerimagesdialog.py
deleted file mode 100644 (file)
index 0f33d50..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-import gtk
-import os
-import time
-from environment import env
-from kofoto.shelf import \
-     ImageVersionExistsError, ImageVersionType, NotAnImageFileError, \
-     makeValidTag
-from kofoto.clientutils import walk_files
-
-class RegisterImagesDialog(gtk.FileChooserDialog):
-    def __init__(self, albumToAddTo=None):
-        gtk.FileChooserDialog.__init__(
-            self,
-            title="Register images",
-            action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
-            buttons=(
-                gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
-                gtk.STOCK_OK, gtk.RESPONSE_OK))
-        self.__albumToAddTo = albumToAddTo
-        self.connect("response", self._response)
-
-    def _response(self, widget, responseId):
-        if responseId != gtk.RESPONSE_OK:
-            return
-        widgets = gtk.glade.XML(env.gladeFile, "registrationProgressDialog")
-        registrationProgressDialog = widgets.get_widget(
-            "registrationProgressDialog")
-        newImagesCount = widgets.get_widget(
-            "newImagesCount")
-        alreadyRegisteredImagesCount = widgets.get_widget(
-            "alreadyRegisteredImagesCount")
-        nonImagesCount = widgets.get_widget(
-            "nonImagesCount")
-        filesInvestigatedCount = widgets.get_widget(
-            "filesInvestigatedCount")
-        okButton = widgets.get_widget("okButton")
-        okButton.set_sensitive(False)
-
-        registrationProgressDialog.show()
-
-        newImages = 0
-        alreadyRegisteredImages = 0
-        nonImages = 0
-        filesInvestigated = 0
-        images = []
-        registrationTimeString = unicode(time.strftime("%Y-%m-%d %H:%M:%S"))
-        for filepath in walk_files([self.get_filename()]):
-            try:
-                try:
-                    filepath = filepath.decode("utf-8")
-                except UnicodeDecodeError:
-                    filepath = filepath.decode("latin1")
-                image = env.shelf.createImage()
-                env.shelf.createImageVersion(
-                    image, filepath, ImageVersionType.Original)
-                image.setAttribute(u"registered", registrationTimeString)
-                images.append(image)
-                newImages += 1
-                newImagesCount.set_text(str(newImages))
-            except ImageVersionExistsError:
-                alreadyRegisteredImages += 1
-                alreadyRegisteredImagesCount.set_text(str(alreadyRegisteredImages))
-                env.shelf.deleteImage(image.getId())
-            except NotAnImageFileError:
-                nonImages += 1
-                nonImagesCount.set_text(str(nonImages))
-                env.shelf.deleteImage(image.getId())
-            filesInvestigated += 1
-            filesInvestigatedCount.set_text(str(filesInvestigated))
-            while gtk.events_pending():
-                gtk.main_iteration()
-        if self.__albumToAddTo:
-            children = list(self.__albumToAddTo.getChildren())
-            self.__albumToAddTo.setChildren(children + images)
-
-        okButton.set_sensitive(True)
-        registrationProgressDialog.run()
-        registrationProgressDialog.destroy()
diff --git a/src/gkofoto/gkofoto/registerimageversionsdialog.py b/src/gkofoto/gkofoto/registerimageversionsdialog.py
deleted file mode 100644 (file)
index 753e86c..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-import os
-import re
-import sets
-
-import gtk
-
-from environment import env
-from kofoto.shelf import \
-    ImageVersionDoesNotExistError, \
-    ImageVersionExistsError, \
-    ImageVersionType, \
-    NotAnImageFileError
-
-class RegisterImageVersionsDialog:
-    def __init__(self, model):
-        self._model = model
-        self._widgets = gtk.glade.XML(
-            env.gladeFile, "registerImageVersionsDialog")
-        self._dialog = self._widgets.get_widget("registerImageVersionsDialog")
-        self._cancelButton = self._widgets.get_widget("cancelButton")
-        self._okButton = self._widgets.get_widget("okButton")
-        self._browseButton = self._widgets.get_widget("browseButton")
-        self._fileListView = self._widgets.get_widget("fileList")
-
-        self._cancelButton.connect("clicked", self._onCancel)
-        self._okButton.connect("clicked", self._onOk)
-        self._browseButton.connect("clicked", self._onBrowse)
-
-        renderer = gtk.CellRendererText()
-        column = gtk.TreeViewColumn("Image filename", renderer, text=0)
-        self._fileListView.append_column(column)
-        self._fileListView.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
-
-        self._fileListStore = gtk.ListStore(str)
-        self._fileListView.set_model(self._fileListStore)
-
-    def run(self, image):
-        self._image = image
-        files = sets.Set()
-        for imageversion in image.getImageVersions():
-            base, filename = os.path.split(imageversion.getLocation())
-            prefix, suffix = os.path.splitext(filename)
-            for candidateFilename in os.listdir(base):
-                if (re.match("%s[^a-zA-Z0-9].*" % prefix, candidateFilename)):
-                    candidatePath = os.path.join(base, candidateFilename)
-                    if os.path.isfile(candidatePath):
-                        try:
-                            env.shelf.getImageVersionByLocation(candidatePath)
-                        except ImageVersionDoesNotExistError:
-                            files.add(candidatePath)
-        self.__setFiles(files)
-        self._dialog.run()
-
-    def _onCancel(self, *unused):
-        self._dialog.destroy()
-
-    def _onOk(self, *unused):
-        selection = self._fileListView.get_selection()
-        model, selectedRows = selection.get_selected_rows()
-        changed = False
-        for path in selectedRows:
-            treeiter = self._fileListStore.get_iter(path)
-            location = self._fileListStore.get_value(treeiter, 0)
-            try:
-                imageVersion = env.shelf.createImageVersion(
-                    self._image, location, ImageVersionType.Other)
-                changed = True
-            except NotAnImageFileError:
-                dialog = gtk.MessageDialog(
-                    self._dialog,
-                    gtk.DIALOG_MODAL,
-                    gtk.MESSAGE_ERROR,
-                    gtk.BUTTONS_OK,
-                    "Not an image: %s" % location)
-                dialog.run()
-                dialog.destroy()
-            except ImageVersionExistsError:
-                dialog = gtk.MessageDialog(
-                    self._dialog,
-                    gtk.DIALOG_MODAL,
-                    gtk.MESSAGE_ERROR,
-                    gtk.BUTTONS_OK,
-                    "Already registered: %s" % location)
-                dialog.run()
-                dialog.destroy()
-        if changed:
-            imageVersion.makePrimary()
-            self._model.reloadSelectedRows()
-        self._dialog.destroy()
-
-    def _onBrowse(self, *unused):
-        dialog = gtk.FileChooserDialog(
-            "Choose image version files",
-            self._dialog,
-            gtk.FILE_CHOOSER_ACTION_OPEN,
-            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
-             gtk.STOCK_OK, gtk.RESPONSE_OK))
-        dialog.set_select_multiple(True)
-        if dialog.run() == gtk.RESPONSE_OK:
-            self.__setFiles(dialog.get_filenames())
-        dialog.destroy()
-
-    def __setFiles(self, filenames):
-        self._fileListStore.clear()
-        for filename in filenames:
-            self._fileListStore.append([filename])
-        self._fileListView.get_selection().select_all()
diff --git a/src/gkofoto/gkofoto/searchresult.py b/src/gkofoto/gkofoto/searchresult.py
deleted file mode 100644 (file)
index 71c0e85..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-from kofoto.shelf import *
-from kofoto.search import *
-from sortableobjectcollection import *
-from environment import env
-
-class SearchResult(SortableObjectCollection):
-
-######################################################################
-### Public functions and constants
-
-    def __init__(self):
-        SortableObjectCollection.__init__(self)
-
-    def isMutable(self):
-        return False
-
-    def loadQuery(self, query):
-        parser = Parser(env.shelf)
-        self._loadObjectList(env.shelf.search(parser.parse(query)))
-
-######################################################################
-### Private functions and datastructures
diff --git a/src/gkofoto/gkofoto/singleobjectview.py b/src/gkofoto/gkofoto/singleobjectview.py
deleted file mode 100644 (file)
index 34e0ca7..0000000
+++ /dev/null
@@ -1,208 +0,0 @@
-import gtk
-import sys
-from environment import env
-from gkofoto.imageview import *
-from gkofoto.objectcollectionview import *
-from gkofoto.imageversionslist import ImageVersionsList
-
-class SingleObjectView(ObjectCollectionView, gtk.HPaned):
-
-###############################################################################
-### Public
-
-    def __init__(self):
-        env.debug("Init SingleObjectView")
-        ObjectCollectionView.__init__(self, env.widgets["objectView"])
-        gtk.HPaned.__init__(self)
-        self._viewWidget.add(self)
-        self.__imageView = ImageView()
-        self.__imageView.connect("button_press_event", self._mouse_button_pressed)
-        self.pack1(self.__imageView, resize=True)
-        self.__imageVersionsFrame = gtk.Frame("Image versions")
-        self.__imageVersionsFrame.set_size_request(162, -1)
-        self.__imageVersionsWindow = gtk.ScrolledWindow()
-        self.__imageVersionsList = ImageVersionsList(self, self.__imageView)
-        self.__imageVersionsFrame.add(self.__imageVersionsList)
-        self.pack2(self.__imageVersionsFrame, resize=False)
-        self.show_all()
-        env.widgets["nextButton"].connect("clicked", self._goto, 1)
-        env.widgets["menubarNextImage"].connect("activate", self._goto, 1)
-        env.widgets["previousButton"].connect("clicked", self._goto, -1)
-        env.widgets["menubarPreviousImage"].connect("activate", self._goto, -1)
-        env.widgets["zoomToFit"].connect("clicked", self.__imageView.fitToWindow)
-        env.widgets["menubarZoomToFit"].connect("activate", self.__imageView.fitToWindow)
-        env.widgets["zoom100"].connect("clicked", self.__imageView.zoom100)
-        env.widgets["menubarActualSize"].connect("activate", self.__imageView.zoom100)
-        env.widgets["zoomIn"].connect("clicked", self.__imageView.zoomIn)
-        env.widgets["menubarZoomIn"].connect("activate", self.__imageView.zoomIn)
-        env.widgets["zoomOut"].connect("clicked", self.__imageView.zoomOut)
-        env.widgets["menubarZoomOut"].connect("activate", self.__imageView.zoomOut)
-        env.widgets["mainWindow"].connect("key_press_event", self._key_pressed)
-        env.widgets["menubarViewDetailsPane"].set_sensitive(True)
-        self.__selectionLocked = False
-
-    def showDetailsPane(self):
-        self.__imageVersionsFrame.show()
-
-    def hideDetailsPane(self):
-        self.__imageVersionsFrame.hide()
-
-    def importSelection(self, objectSelection):
-        if not self.__selectionLocked:
-            env.debug("SingleImageView is importing selection")
-            self.__selectionLocked = True
-            model = self._objectCollection.getModel()
-            self.__loadedObject = None
-            if len(model) == 0:
-                # Model is empty. No rows can be selected.
-                self.__selectedRowNr = -1
-                self.__imageView.clear()
-            else:
-                if len(objectSelection) == 0:
-                    # No objects is selected -> select first object
-                    self.__selectedRowNr = 0
-                    objectSelection.setSelection([self.__selectedRowNr])
-                elif len(objectSelection) > 1:
-                    # More than one object selected -> select first object
-                    self.__selectedRowNr = objectSelection.getLowestSelectedRowNr()
-                    objectSelection.setSelection([self.__selectedRowNr])
-                else:
-                    # Exactly one object selected
-                    self.__selectedRowNr = objectSelection.getLowestSelectedRowNr()
-                self.__loadedObject = objectSelection[self.__selectedRowNr]
-            self.__loadObject(self.__loadedObject)
-            enablePreviousButton = (self.__selectedRowNr > 0)
-            env.widgets["previousButton"].set_sensitive(enablePreviousButton)
-            env.widgets["menubarPreviousImage"].set_sensitive(enablePreviousButton)
-            enableNextButton = (self.__selectedRowNr != -1 and
-                                self.__selectedRowNr < len(model) - 1)
-            env.widgets["nextButton"].set_sensitive(enableNextButton)
-            env.widgets["menubarNextImage"].set_sensitive(enableNextButton)
-            self._preloadImages()
-            self.__selectionLocked = False
-        self._updateContextMenu()
-
-        # Override sensitiveness set in _updateContextMenu.
-        for widgetName in [
-                "menubarCut",
-                "menubarCopy",
-                "menubarDelete",
-                "menubarDestroy",
-                "menubarProperties",
-                "menubarCreateAlbumChild",
-                "menubarRegisterAndAddImages",
-                "menubarGenerateHtml",
-                ]:
-            env.widgets[widgetName].set_sensitive(False)
-
-    def __loadObject(self, obj):
-        self.__imageVersionsList.clear()
-        if obj == None:
-            filename = env.unknownImageIconFileName
-        elif obj.isAlbum():
-            filename = env.albumIconFileName
-        elif obj.getPrimaryVersion():
-            filename = obj.getPrimaryVersion().getLocation()
-            self.__imageVersionsList.loadImage(obj)
-        else:
-            filename = env.unknownImageIconFileName
-        self.__imageView.loadFile(filename)
-
-    def _reloadSingleObjectView(self):
-        self.reload()
-
-    def reload(self):
-        self.__loadObject(self.__loadedObject)
-        self.__imageVersionsList.reload()
-        self._objectCollection.reloadSelectedRows()
-
-    def _showHelper(self):
-        env.enter("SingleObjectView.showHelper()")
-        env.widgets["objectView"].show()
-        env.widgets["objectView"].grab_focus()
-        self._connectMenubarImageItems() # Grossest hack of the month. Sigh.
-        for widgetName in [
-                "zoom100",
-                "zoomToFit",
-                "zoomIn",
-                "zoomOut",
-                "menubarZoom",
-                ]:
-            env.widgets[widgetName].set_sensitive(True)
-        env.exit("SingleObjectView.showHelper()")
-
-    def _hideHelper(self):
-        env.enter("SingleObjectView.hideHelper()")
-        env.widgets["objectView"].hide()
-        for widgetName in [
-                "previousButton",
-                "nextButton",
-                "menubarPreviousImage",
-                "menubarNextImage",
-                "zoom100",
-                "zoomToFit",
-                "zoomIn",
-                "zoomOut",
-                "menubarZoom",
-                ]:
-            env.widgets[widgetName].set_sensitive(False)
-        env.exit("SingleObjectView.hideHelper()")
-
-    def _connectObjectCollectionHelper(self):
-        env.enter("Connecting SingleObjectView to object collection")
-        env.exit("Connecting SingleObjectView to object collection")
-
-    def _disconnectObjectCollectionHelper(self):
-        env.enter("Disconnecting SingleObjectView from object collection")
-        env.exit("Disconnecting SingleObjectView from object collection")
-
-    def _freezeHelper(self):
-        env.enter("SingleObjectView.freezeHelper()")
-        self._clearAllConnections()
-        self.__imageView.clear()
-        self._objectCollection.removeInsertedRowCallback(self._modelUpdated)
-        env.exit("SingleObjectView.freezeHelper()")
-
-    def _thawHelper(self):
-        env.enter("SingleObjectView.thawHelper()")
-        model = self._objectCollection.getModel()
-        # The following events are needed to update the previous and
-        # next navigation buttons.
-        self._connect(model, "rows_reordered", self._modelUpdated)
-        self._connect(model, "row_deleted", self._modelUpdated)
-        self._objectCollection.addInsertedRowCallback(self._modelUpdated)
-        env.exit("SingleObjectView.thawHelper()")
-
-    def _modelUpdated(self, *foo):
-        env.debug("SingleObjectView is handling model update")
-        self.importSelection(self._objectCollection.getObjectSelection())
-
-    def _goto(self, button, direction):
-        objectSelection = self._objectCollection.getObjectSelection()
-        objectSelection.setSelection([self.__selectedRowNr + direction])
-
-    def _preloadImages(self):
-        objectSelection = self._objectCollection.getObjectSelection()
-        filenames = objectSelection.getImageFilenamesToPreload()
-        maxWidth, maxHeight = self.__imageView.getAvailableSpace()
-
-        # Work-around for bug in GTK. (pixbuf.scale_iter(1, 1) crashes.)
-        if maxWidth < 10 and maxHeight < 10:
-            return
-
-        env.mainwindow.getImagePreloader().preloadImages(
-            filenames, maxWidth, maxHeight)
-
-    def _key_pressed(self, widget, event):
-        # TODO use UiManager instead of this...
-        if event.state & gtk.gdk.CONTROL_MASK:
-            if (event.keyval == gtk.gdk.keyval_from_name("space") and
-                env.widgets["nextButton"].flags() & gtk.SENSITIVE):
-                self._goto(None, 1)
-            elif (event.keyval == gtk.gdk.keyval_from_name("BackSpace") and
-                  env.widgets["previousButton"].flags() & gtk.SENSITIVE):
-                self._goto(None, -1)
-        return False
-
-    def _hasFocus(self):
-        return True
diff --git a/src/gkofoto/gkofoto/sortableobjectcollection.py b/src/gkofoto/gkofoto/sortableobjectcollection.py
deleted file mode 100644 (file)
index 18bf3e3..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-import gtk
-from environment import env
-from mysortedmodel import *
-from objectcollection import *
-
-def attributeSortFunc(model, iterA, iterB, column):
-    valueA = model.get_value(iterA, column)
-    valueB = model.get_value(iterB, column)
-    try:
-        result = cmp(float(valueA), float(valueB))
-    except (ValueError, TypeError):
-        result = cmp(valueA, valueB)
-    if result == 0:
-        result = cmp(model.get_value(iterA, ObjectCollection.COLUMN_OBJECT_ID),
-                     model.get_value(iterB, ObjectCollection.COLUMN_OBJECT_ID))
-    return result
-
-class SortableObjectCollection(ObjectCollection):
-
-######################################################################
-### Public
-
-    def __init__(self):
-        ObjectCollection.__init__(self)
-        self.__sortOrder = None
-        self.__sortColumnName = None
-        self.__sortedTreeModel = MySortedModel(self.getUnsortedModel())
-        self.setSortOrder(order=gtk.SORT_ASCENDING)
-        self.setSortColumnName(columnName=env.defaultSortColumn)
-
-    def isSortable(self):
-        return True
-
-    def isReorderable(self):
-        return False
-
-    def getModel(self):
-        return self.__sortedTreeModel
-
-    def getUnsortedModel(self):
-        return ObjectCollection.getModel(self)
-
-    def convertToUnsortedRowNr(self, rowNr):
-        return self.__sortedTreeModel.convert_path_to_child_path(rowNr)[0]
-
-    def convertFromUnsortedRowNr(self, unsortedRowNr):
-        return self.__sortedTreeModel. convert_child_path_to_path(unsortedRowNr)[0]
-
-    def getSortOrder(self):
-        return self.__sortOrder
-
-    def getSortColumnName(self):
-        return self.__sortColumnName
-
-    def setSortOrder(self, widget=None, order=None):
-        if widget != None and not widget.get_active():
-            # ignore the callback when the radio menu item is unselected
-            return
-        if self.__sortOrder != order:
-            env.debug("Setting sort order to: " + str(order))
-            self.__sortOrder = order
-            self.__configureSortedModel(self.__sortColumnName, self.__sortOrder)
-            self.__emitSortOrderChanged()
-
-    def setSortColumnName(self, widget=None, columnName=None):
-        if widget != None and not widget.get_active():
-            # ignore the callback when the radio menu item is unselected
-            return
-        if self.__sortColumnName != columnName:
-            if not columnName in self.getObjectMetadataMap():
-                columnName = "id"
-            env.debug("Setting sort column to: " + columnName)
-            self.__sortColumnName = columnName
-            self.__configureSortedModel(self.__sortColumnName, self.__sortOrder)
-            self.__emitSortColumnChanged()
-
-    def registerView(self, view):
-        ObjectCollection.registerView(self, view)
-        self.__emitSortOrderChanged()
-        self.__emitSortColumnChanged()
-
-    def __emitSortOrderChanged(self):
-        for view in self._getRegisteredViews():
-            view.sortOrderChanged(self.__sortOrder)
-
-    def __emitSortColumnChanged(self):
-        for view in self._getRegisteredViews():
-            view.sortColumnChanged(self.__sortColumnName)
-
-    def __configureSortedModel(self, sortColumnName, sortOrder):
-        if (sortOrder != None and sortColumnName != None):
-            metaDataMap = self.getObjectMetadataMap()
-            if not metaDataMap.has_key(sortColumnName):
-                sortColumnName = u"id"
-            sortColumnNr = metaDataMap[sortColumnName][self.COLUMN_NR]
-            model = self.getModel()
-            model.set_sort_column_id(sortColumnNr, self.__sortOrder)
-            # It is important that the attributeSortFunc is not an class member method,
-            # otherwise we are leaking memmory.
-            model.set_sort_func(sortColumnNr,
-                                attributeSortFunc,
-                                sortColumnNr)
diff --git a/src/gkofoto/gkofoto/tableview.py b/src/gkofoto/gkofoto/tableview.py
deleted file mode 100644 (file)
index d060643..0000000
+++ /dev/null
@@ -1,394 +0,0 @@
-import gtk
-from environment import env
-from sets import Set
-from gkofoto.objectcollectionview import *
-from sets import Set
-from objectcollection import *
-from menuhandler import *
-
-class TableView(ObjectCollectionView):
-
-###############################################################################
-### Public
-
-    def __init__(self):
-        env.debug("Init TableView")
-        ObjectCollectionView.__init__(self, env.widgets["tableView"])
-        selection = self._viewWidget.get_selection()
-        selection.set_mode(gtk.SELECTION_MULTIPLE)
-        self.__selectionLocked = False
-        self._viewWidget.connect("drag_data_received", self._onDragDataReceived)
-        self._viewWidget.connect("drag-data-get", self._onDragDataGet)
-        self.__userChosenColumns = {}
-        self.__createdColumns = {}
-        self.__editedCallbacks = {}
-        self._connectedOids = []
-        self.__hasFocus = False
-        # Import the users setting in the configuration file for
-        # which columns that shall be shown.
-        columnLocation = 0
-        for columnName in env.defaultTableViewColumns:
-            self.__userChosenColumns[columnName] = columnLocation
-            columnLocation += 1
-        env.widgets["tableView"].connect("button_press_event", self._mouse_button_pressed)
-        env.widgets["menubarViewDetailsPane"].set_sensitive(False)
-
-    def importSelection(self, objectSelection):
-        if not self.__selectionLocked:
-            env.debug("TableView is importing selection")
-            self.__selectionLocked = True
-            selection = self._viewWidget.get_selection()
-            selection.unselect_all()
-            for rowNr in objectSelection:
-                selection.select_path(rowNr)
-            rowNr = self._objectCollection.getObjectSelection().getLowestSelectedRowNr()
-            if rowNr is not None:
-                # Scroll to first selected object in view
-                self._viewWidget.scroll_to_cell(rowNr, None, False, 0, 0)
-            self.__selectionLocked = False
-        self._updateContextMenu()
-        self._updateMenubarSortMenu()
-
-    def fieldsDisabled(self, fields):
-        env.debug("Table view disable fields: " + str(fields))
-        self.__removeColumnsAndUpdateLocation(fields)
-        for columnName in fields:
-            self.__viewGroup[columnName].set_sensitive(False)
-
-    def fieldsEnabled(self, fields):
-        env.debug("Table view enable fields: " + str(fields))
-        objectMetadataMap = self._objectCollection.getObjectMetadataMap()
-        for columnName in fields:
-            self.__viewGroup[columnName].set_sensitive(True)
-            if columnName not in self.__createdColumns:
-                if columnName in self.__userChosenColumns:
-                    self.__createColumn(columnName, objectMetadataMap, self.__userChosenColumns[columnName])
-
-    def _showHelper(self):
-        env.enter("TableView.showHelper()")
-        env.widgets["tableViewScroll"].show()
-        self._viewWidget.grab_focus()
-        env.exit("TableView.showHelper()")
-
-    def _hideHelper(self):
-        env.enter("TableView.hideHelper()")
-        env.widgets["tableViewScroll"].hide()
-        env.exit("TableView.hideHelper()")
-
-    def _connectObjectCollectionHelper(self):
-        env.enter("Connecting TableView to object collection")
-        # Set model
-        self._viewWidget.set_model(self._objectCollection.getModel())
-        # Create columns
-        objectMetadataMap = self._objectCollection.getObjectMetadataMap()
-        disabledFields = self._objectCollection.getDisabledFields()
-        columnLocationList = self.__userChosenColumns.items()
-        columnLocationList.sort(lambda x, y: cmp(x[1], y[1]))
-        env.debug("Column locations: " + str(columnLocationList))
-        for (columnName, columnLocation) in columnLocationList:
-            if (columnName in objectMetadataMap and
-                columnName not in disabledFields):
-                self.__createColumn(columnName, objectMetadataMap)
-                self.__viewGroup[columnName].activate()
-        self.fieldsDisabled(self._objectCollection.getDisabledFields())
-        env.exit("Connecting TableView to object collection")
-
-    def _initDragAndDrop(self):
-        # Init drag & drop
-        if self._objectCollection.isReorderable() and not self._objectCollection.isSortable():
-            targetEntries = [("STRING", gtk.TARGET_SAME_WIDGET, 0)]
-            self._viewWidget.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
-                                                      targetEntries,
-                                                      gtk.gdk.ACTION_MOVE)
-            self._viewWidget.enable_model_drag_dest(targetEntries, gtk.gdk.ACTION_COPY)
-        else:
-            self._viewWidget.unset_rows_drag_source()
-            self._viewWidget.unset_rows_drag_dest()
-
-    def _disconnectObjectCollectionHelper(self):
-        env.enter("Disconnecting TableView from object collection")
-        self.__removeColumnsAndUpdateLocation()
-        self._viewWidget.set_model(None)
-        env.exit("Disconnecting TableView from object collection")
-
-    def _freezeHelper(self):
-        env.enter("TableView.freezeHelper()")
-        self._clearAllConnections()
-        env.exit("TableView.freezeHelper()")
-
-    def _thawHelper(self):
-        env.enter("TableView.thawHelper()")
-        self._initDragAndDrop()
-        self._connect(
-            self._viewWidget, "focus-in-event", self._treeViewFocusInEvent)
-        self._connect(
-            self._viewWidget, "focus-out-event", self._treeViewFocusOutEvent)
-        self._connect(
-            self._viewWidget.get_selection(), "changed", self._widgetSelectionChanged)
-        env.exit("TableView.thawHelper()")
-
-    def _createContextMenu(self, objectCollection):
-        ObjectCollectionView._createContextMenu(self, objectCollection)
-        self.__viewGroup = self.__createTableColumnsMenuGroup(objectCollection)
-        self._contextMenu.add(self.__viewGroup.createGroupMenuItem())
-
-    def __createTableColumnsMenuGroup(self, objectCollection):
-        menuGroup = MenuGroup("View columns")
-        columnNames = objectCollection.getObjectMetadataMap().keys()
-        columnNames.sort()
-        for columnName in columnNames:
-            menuGroup.addCheckedMenuItem(
-                columnName,
-                self._viewColumnToggled,
-                columnName)
-        return menuGroup
-
-    def _clearContextMenu(self):
-        ObjectCollectionView._clearContextMenu(self)
-        self.__viewGroup = None
-
-    def _hasFocus(self):
-        return self.__hasFocus
-
-###############################################################################
-### Callback functions registered by this class but invoked from other classes.
-
-    def _treeViewFocusInEvent(self, widget, event, data):
-        if self.__hasFocus:
-            # Work-around for some bug that makes the focus-out signal
-            # disappear.
-            return
-        self.__hasFocus = True
-        oc = self._objectCollection
-        for widgetName, function in [
-                ("menubarCut", self._objectCollection.cut),
-                ("menubarCopy", self._objectCollection.copy),
-                ("menubarPaste", self._objectCollection.paste),
-                ("menubarDelete", self._objectCollection.delete),
-                ("menubarDestroy", oc.destroy),
-                ("menubarClear", lambda x: widget.get_selection().unselect_all()),
-                ("menubarSelectAll", lambda x: widget.get_selection().select_all()),
-                ("menubarProperties", oc.albumProperties),
-                ("menubarCreateAlbumChild", oc.createAlbumChild),
-                ("menubarRegisterAndAddImages", oc.registerAndAddImages),
-                ("menubarGenerateHtml", oc.generateHtml),
-                ("menubarOpenImage", oc.openImage),
-                ("menubarDuplicateAndOpenImage", oc.duplicateAndOpenImage),
-                ("menubarRotateLeft", oc.rotateImageLeft),
-                ("menubarRotateRight", oc.rotateImageRight),
-                ("menubarImageVersions", oc.imageVersions),
-                ("menubarRegisterImageVersions", oc.registerImageVersions),
-                ("menubarMergeImages", oc.mergeImages),
-                ]:
-            w = env.widgets[widgetName]
-            oid = w.connect("activate", function)
-            self._connectedOids.append((w, oid))
-
-        self._updateContextMenu()
-
-        for widgetName in [
-                "menubarClear",
-                "menubarSelectAll"
-                ]:
-            env.widgets[widgetName].set_sensitive(True)
-
-    def _treeViewFocusOutEvent(self, widget, event, data):
-        self.__hasFocus = False
-        for (widget, oid) in self._connectedOids:
-            widget.disconnect(oid)
-        self._connectedOids = []
-        for widgetName in [
-                "menubarCut",
-                "menubarCopy",
-                "menubarPaste",
-                "menubarDelete",
-                "menubarDestroy",
-                "menubarClear",
-                "menubarSelectAll",
-                "menubarProperties",
-                "menubarCreateAlbumChild",
-                "menubarRegisterAndAddImages",
-                "menubarGenerateHtml",
-                "menubarOpenImage",
-                "menubarDuplicateAndOpenImage",
-                "menubarRotateLeft",
-                "menubarRotateRight",
-                "menubarImageVersions",
-                "menubarRegisterImageVersions",
-                "menubarMergeImages",
-                ]:
-            env.widgets[widgetName].set_sensitive(False)
-
-    def _widgetSelectionChanged(self, selection, data):
-        if not self.__selectionLocked:
-            env.enter("TableView selection changed")
-            self.__selectionLocked = True
-            rowNrs = []
-            selection.selected_foreach(lambda model,
-                                       path,
-                                       iter:
-                                       rowNrs.append(path[0]))
-            self._objectCollection.getObjectSelection().setSelection(rowNrs)
-            self.__selectionLocked = False
-            env.exit("TableView selection changed")
-
-    def _onDragDataGet(self, widget, dragContext, selection, info, timestamp):
-        selectedRows = []
-        # TODO replace with "get_selected_rows()" when it is introduced in Pygtk 2.2 API
-        self._viewWidget.get_selection().selected_foreach(lambda model,
-                                                          path,
-                                                          iter:
-                                                          selectedRows.append(model[path]))
-        if len(selectedRows) == 1:
-            # Ignore drag & drop if zero or more then one row is selected
-            # Drag & drop of multiple rows will probably come in gtk 2.4.
-            # http://mail.gnome.org/archives/gtk-devel-list/2003-December/msg00160.html
-            sourceRowNumber = str(selectedRows[0].path[0])
-            selection.set_text(sourceRowNumber, len(sourceRowNumber))
-        else:
-            env.debug("Ignoring drag&drop when only one row is selected")
-
-
-    def _onDragDataReceived(self, treeview, dragContext, x, y, selection, info, eventtime):
-        targetData = treeview.get_dest_row_at_pos(x, y)
-        if selection.get_text() == None:
-            dragContext.finish(False, False, eventtime)
-        else:
-            model = self._objectCollection.getModel()
-            if targetData == None:
-                targetPath = (len(model) - 1,)
-                dropPosition = gtk.TREE_VIEW_DROP_AFTER
-            else:
-                targetPath, dropPosition = targetData
-            sourceRowNumber = int(selection.get_text())
-            if sourceRowNumber == targetPath[0]:
-                # dropped on itself
-                dragContext.finish(False, False, eventtime)
-            else:
-                # The continer must have a getChildren() and a setChildren()
-                # method as for example the album class has.
-                container = self._objectCollection.getContainer()
-                children = list(container.getChildren())
-                sourceRow = model[sourceRowNumber]
-                targetIter = model.get_iter(targetPath)
-                objectSelection = self._objectCollection.getObjectSelection()
-                if (dropPosition == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE
-                    or dropPosition == gtk.TREE_VIEW_DROP_BEFORE):
-                    container.setChildren(self.__moveListItem(children,
-                                                              sourceRowNumber,
-                                                              targetPath[0]))
-                    model.insert_before(sibling=targetIter, row=sourceRow)
-                    self._objectCollection.signalRowInserted()
-                    model.remove(sourceRow.iter)
-                    # TODO update the album tree widget?
-                elif (dropPosition == gtk.TREE_VIEW_DROP_INTO_OR_AFTER
-                      or dropPosition == gtk.TREE_VIEW_DROP_AFTER):
-                    container.setChildren(self.__moveListItem(children,
-                                                              sourceRowNumber,
-                                                              targetPath[0] + 1))
-                    model.insert_after(sibling=targetIter, row=sourceRow)
-                    model.remove(sourceRow.iter)
-                    # TODO update the album tree widget?
-                objectSelection.setSelection([targetPath[0]])
-                # I've experienced that the drag-data-delete signal isn't
-                # always emitted when I drag & drop rapidly in the TreeView.
-                # And when it is missing the source row is not removed as is
-                # should. It is probably an bug in gtk+ (or maybe in pygtk).
-                # It only happens sometimes and I have not managed to reproduce
-                # it with a simpler example. Hence we remove the row ourself
-                # and are not relying on the drag-data-delete-signal.
-                # http://bugzilla.gnome.org/show_bug.cgi?id=134997
-                removeSourceRowAutomatically = False
-                dragContext.finish(True, removeSourceRowAutomatically, eventtime)
-
-    def _viewColumnToggled(self, checkMenuItem, columnName):
-        if checkMenuItem.get_active():
-            if columnName not in self.__createdColumns:
-                self.__createColumn(columnName,
-                                    self._objectCollection.getObjectMetadataMap())
-                # The correct columnLocation is stored when the column is removed
-                # there is no need to store the location when it is created
-                # since the column order may be reordered later before it is removed.
-        else:
-            # Since the column has been removed explicitly by the user
-            # we dont store the column's relative location.
-            try:
-                del self.__userChosenColumns[columnName]
-            except KeyError:
-                pass
-            if columnName in self.__createdColumns:
-                self.__removeColumn(columnName)
-
-    def _reloadSingleObjectView(self):
-        pass
-
-###############################################################################
-### Private
-
-    def __createColumn(self, columnName, objectMetadataMap, location=-1):
-        (objtype, column, editedCallback, editedCallbackData) = objectMetadataMap[columnName]
-        if objtype == gtk.gdk.Pixbuf:
-            renderer = gtk.CellRendererPixbuf()
-            column = gtk.TreeViewColumn(columnName, renderer, pixbuf=column)
-            env.debug("Created a PixBuf column for " + columnName)
-        elif objtype == gobject.TYPE_STRING or objtype == gobject.TYPE_INT:
-            renderer = gtk.CellRendererText()
-            column = gtk.TreeViewColumn(columnName,
-                                        renderer,
-                                        text=column,
-                                        editable=ObjectCollection.COLUMN_ROW_EDITABLE)
-            column.set_resizable(True)
-            if editedCallback:
-                cid = renderer.connect("edited",
-                                       editedCallback,
-                                       column,
-                                       editedCallbackData)
-                self.__editedCallbacks[columnName] = (cid, renderer)
-                env.debug("Created a Text column with editing callback for " + columnName)
-            else:
-                env.debug("Created a Text column without editing callback for " + columnName)
-        else:
-            print "Warning, unsupported type for column ", columnName
-            return
-        column.set_reorderable(True)
-        self._viewWidget.insert_column(column, location)
-        self.__createdColumns[columnName] = column
-        return column
-
-    def __removeColumn(self, columnName):
-        column = self.__createdColumns[columnName]
-        self._viewWidget.remove_column(column)
-        if columnName in self.__editedCallbacks:
-            (cid, renderer) = self.__editedCallbacks[columnName]
-            renderer.disconnect(cid)
-            del self.__editedCallbacks[columnName]
-        del self.__createdColumns[columnName]
-        column.destroy()
-        env.debug("Removed column " + columnName)
-
-    def __removeColumnsAndUpdateLocation(self, columnNames=None):
-       # Remove columns and store their relative locations for next time
-       # they are re-created.
-       columnLocation = 0
-       for column in self._viewWidget.get_columns():
-           columnName = column.get_title()
-           # TODO Store the column width and reuse it when the column is
-           #      recreated. I don't know how to store the width since
-           #      column.get_width() return correct values for columns
-           #      containing a gtk.CellRendererPixbuf but only 0 for all
-           #      columns containing a gtk.CellRendererText. It is probably
-           #      a bug in gtk och pygtk. I have not yet reported the bug.
-           if columnNames is None or columnName in columnNames:
-               if columnName in self.__createdColumns:
-                   self.__removeColumn(columnName)
-                   self.__userChosenColumns[columnName] = columnLocation
-           columnLocation += 1
-
-    def __moveListItem(self, list, currentIndex, newIndex):
-        if currentIndex == newIndex:
-            return list
-        if currentIndex < newIndex:
-            newIndex -= 1
-        movingChild = list[currentIndex]
-        del list[currentIndex]
-        return list[:newIndex] + [movingChild] + list[newIndex:]
diff --git a/src/gkofoto/gkofoto/taganddescriptiondialog.py b/src/gkofoto/gkofoto/taganddescriptiondialog.py
deleted file mode 100644 (file)
index 6b69cdd..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-import gtk
-import string
-import re
-from environment import env
-from kofoto.shelf import *
-
-class TagAndDescriptionDialog:
-    def __init__(self, title, tagText=u"", descText=u""):
-        env.assertUnicode(tagText)
-        env.assertUnicode(descText)
-        self._widgets = gtk.glade.XML(env.gladeFile, "tagAndDescriptionDialog")
-        self._dialog = self._widgets.get_widget("tagAndDescriptionDialog")
-        self._dialog.set_title(title)
-        self._tagWidget = self._widgets.get_widget("tag")
-        self._tagWidget.set_text(tagText)
-        self._descWidget = self._widgets.get_widget("description")
-        self._descWidget.set_text(descText)
-        self._descWidget.connect("changed", self._descriptionChanged, self._tagWidget)
-        okbutton = self._widgets.get_widget("okbutton")
-        self._tagWidget.connect("changed", self._tagChanged, okbutton)
-        self.__descText = descText
-        okbutton.set_sensitive(self._isTagOkay(tagText))
-
-    def run(self, ok=None, data=None):
-        result = self._dialog.run()
-        tag = self._tagWidget.get_text().decode("utf-8")
-        desc = self._descWidget.get_text().decode("utf-8")
-        self._dialog.destroy()
-        if result == gtk.RESPONSE_OK:
-            if ok == None:
-                return None
-            else:
-                if data:
-                    return ok(tag, desc, data)
-                else:
-                    return ok(tag, desc)
-        else:
-            return None
-
-    def __generateTagName(self, descText):
-        env.assertUnicode(descText)
-        return re.sub(r"(?u)\W", "", descText).lower()
-
-    def __generateTagNameDeprecated1(self, descText):
-        # An algoritm for generating tag names used in previous gkofoto
-        # versions (2004-04-26 -- 2004-05-15). This algoritm
-        # must always remove all swedish characters, regardles of LOCAL
-        # or UNICODE setting, to be backward compatible with the old version.
-        env.assertUnicode(descText)
-        return re.sub("\W", "", descText)
-
-    def __generateTagNameDeprecated2(self, descText):
-        # An algoritm for generating tag names used in previous gkofoto
-        # versions (< 2004-04-26)
-        env.assertUnicode(descText)
-        return string.translate(descText.encode(env.codeset),
-                                string.maketrans("", ""),
-                                string.whitespace)
-
-    def _descriptionChanged(self, description, tag):
-        newDescText = description.get_text().decode("utf-8")
-        currentTagText = self._tagWidget.get_text()
-        if (currentTagText == self.__generateTagName(self.__descText) or
-            currentTagText == self.__generateTagNameDeprecated1(self.__descText) or
-            currentTagText == self.__generateTagNameDeprecated2(self.__descText)):
-            tag.set_text(self.__generateTagName(newDescText))
-        self.__descText = newDescText
-
-    def _tagChanged(self, tag, button):
-        tagString = tag.get_text().decode("utf-8")
-        button.set_sensitive(self._isTagOkay(tagString))
diff --git a/src/gkofoto/gkofoto/thumbnailview.py b/src/gkofoto/gkofoto/thumbnailview.py
deleted file mode 100644 (file)
index 16f7105..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-import gtk
-from gkofoto.objectcollectionview import *
-from gkofoto.objectcollection import *
-from environment import env
-
-class ThumbnailView(ObjectCollectionView):
-
-###############################################################################
-### Public
-
-    def __init__(self):
-        env.debug("Init ThumbnailView")
-##        ObjectCollectionView.__init__(self,
-##                                      env.widgets["thumbnailList"])
-        ObjectCollectionView.__init__(self,
-                                      env.widgets["thumbnailView"])
-        self.__currentMaxWidth = env.thumbnailSize[0]
-        self.__selectionLocked = False
-        return
-        self._viewWidget.connect("select_icon", self._widgetIconSelected)
-        self._viewWidget.connect("unselect_icon", self._widgetIconUnselected)
-
-    def importSelection(self, objectSelection):
-        if not self.__selectionLocked:
-            env.debug("ThumbnailView is importing selection.")
-            self.__selectionLocked = True
-            self._viewWidget.unselect_all()
-            for rowNr in objectSelection:
-                self._viewWidget.select_icon(rowNr)
-            self.__selectionLocked = False
-        self._updateContextMenu()
-
-    def _showHelper(self):
-        env.enter("ThumbnailView.showHelper()")
-        env.widgets["thumbnailView"].show()
-        self._viewWidget.grab_focus()
-        self.__scrollToFirstSelectedObject()
-        env.exit("ThumbnailView.showHelper()")
-
-    def _hideHelper(self):
-        env.enter("ThumbnailView.hideHelper()")
-        env.widgets["thumbnailView"].hide()
-        env.exit("ThumbnailView.hideHelper()")
-
-    def _connectObjectCollectionHelper(self):
-        env.enter("Connecting ThumbnailView to object collection")
-        # The model is loaded in thawHelper instead.
-        env.exit("Connecting ThumbnailView to object collection")
-
-    def _disconnectObjectCollectionHelper(self):
-        env.enter("Disconnecting ThumbnailView from object collection")
-        # The model is unloaded in freezeHelper instead.
-        env.exit("Disconnecting ThumbnailView from object collection")
-
-    def _freezeHelper(self):
-        env.enter("ThumbnailView.freezeHelper()")
-        self._clearAllConnections()
-        self._viewWidget.clear()
-        env.exit("ThumbnailView.freezeHelper()")
-
-    def _thawHelper(self):
-        env.enter("ThumbnailView.thawHelper()")
-        model = self._objectCollection.getModel()
-        for row in model:
-            self.__loadRow(row)
-        self._connect(model, "row_inserted",   self._rowInserted)
-        self._connect(model, "row_deleted",    self._rowDeleted)
-        self._connect(model, "rows_reordered", self._rowsReordered)
-        self._connect(model, "row_changed",    self._rowChanged)
-        env.exit("ThumbnailView.thawHelper()")
-
-###############################################################################
-### Callback functions registered by this class but invoked from other classes.
-
-    def _rowChanged(self, model, path, iterator):
-        env.debug("ThumbnailView row changed.")
-        self.__selectionLocked = True
-        self._viewWidget.remove(path[0])
-        # For some reason that I don't understand model[path].path != path
-        # Hence we pass by path as the location where the icon shall be
-        # inserted.
-        self.__loadRow(model[path[0]], path[0])
-        if path[0] in self._objectCollection.getObjectSelection():
-            self._viewWidget.select_icon(path[0])
-        self.__selectionLocked = False
-
-    def _rowInserted(self, model, path, iterator):
-        env.debug("ThumbnailView row inserted.")
-        self.__loadRow(model[path])
-
-    def _rowsReordered(self, model, b, c, d):
-        env.debug("ThumbnailView rows reordered.")
-        # TODO I Don't know how to parse which rows that has
-        #      been reordered. Hence I must reload all rows.
-        self._viewWidget.clear()
-        for row in self._objectCollection.getModel():
-            self.__loadRow(row)
-        self.importSelection(self._objectCollection.getObjectSelection())
-
-    def _rowDeleted(self, model, path):
-        env.debug("ThumbnailView row deleted.")
-        self._viewWidget.remove(path[0])
-
-    def _widgetIconSelected(self, widget, index, event):
-        if not self.__selectionLocked:
-            env.enter("ThumbnailView selection changed")
-            self.__selectionLocked = True
-            self._objectCollection.getObjectSelection().addSelection(index)
-            self.__selectionLocked = False
-
-    def _widgetIconUnselected(self, widget, index, event):
-        if not self.__selectionLocked:
-            env.enter("ThumbnailView selection changed")
-            self.__selectionLocked = True
-            self._objectCollection.getObjectSelection().removeSelection(index)
-            self.__selectionLocked = False
-
-###############################################################################
-### Private
-
-    def __loadRow(self, row, location=None):
-        if location is None:
-            location = row.path[0]
-        if row[ObjectCollection.COLUMN_IS_ALBUM]:
-            text = row[ObjectCollection.COLUMN_ALBUM_TAG]
-        else:
-            # TODO Let configuration decide what to show...
-            text = row[ObjectCollection.COLUMN_OBJECT_ID]
-        pixbuf = row[ObjectCollection.COLUMN_THUMBNAIL]
-        if pixbuf == None:
-            # It is possible that we get the row inserted event before
-            # the thumbnail is loaded. The temporary icon will be removed
-            # when we receive the row changed event.
-            pixbuf = env.loadingPixbuf
-        self._viewWidget.insert_pixbuf(location, pixbuf, "", str(text))
-        self.__currentMaxWidth = max(self.__currentMaxWidth, pixbuf.get_width())
-        self._viewWidget.set_icon_width(self.__currentMaxWidth)
-
-    def __scrollToFirstSelectedObject(self):
-        numberOfIcons = self._viewWidget.get_num_icons()
-        if numberOfIcons > 1:
-            # First check that the widget contains icons because I don't know
-            # how icon_is_visible() is handled if the view is empty.
-            if (self._viewWidget.icon_is_visible(0) == gtk.VISIBILITY_FULL
-                and self._viewWidget.icon_is_visible(numberOfIcons - 1) == gtk.VISIBILITY_FULL):
-                # All icons already visible. No need to scroll widget.
-                pass
-            else:
-                # Scroll widget to first selected icon
-                rowNr = self._objectCollection.getObjectSelection().getLowestSelectedRowNr()
-                if rowNr is not None:
-                    self._viewWidget.moveto(rowNr, 0.4)
index ef2de91..7e5e2d8 100755 (executable)
@@ -12,8 +12,8 @@ if os.path.islink(sys.argv[0]):
 else:
     bindir = os.path.dirname(sys.argv[0])
 
-# Find kofoto libraries (../lib) in the source tree.
-sys.path.insert(0, os.path.join(bindir, "..", "lib"))
+# Find libraries if run from the source tree.
+sys.path.insert(0, os.path.join(bindir, "..", "packages"))
 
-from gkofoto.main import main
+from kofoto.gkofoto.main import main
 main(bindir, sys.argv)
index 8527580..6e6c62b 100755 (executable)
@@ -12,5 +12,5 @@ if os.path.islink(sys.argv[0]):
 else:
     bindir = os.path.dirname(sys.argv[0])
 
-from gkofoto.main import main
+from kofoto.gkofoto.main import main
 main(bindir, sys.argv)
diff --git a/src/lib/kofoto/EXIF.py b/src/lib/kofoto/EXIF.py
deleted file mode 100644 (file)
index f9d26c8..0000000
+++ /dev/null
@@ -1,1080 +0,0 @@
-# Library to extract EXIF information in digital camera image files
-#
-# Contains code from "exifdump.py" originally written by Thierry Bousch
-# <bousch@topo.math.u-psud.fr> and released into the public domain.
-#
-# Updated and turned into general-purpose library by Gene Cash
-# <email gcash at cfl.rr.com>
-#
-# This copyright license is intended to be similar to the FreeBSD license. 
-#
-# Copyright 2002 Gene Cash All rights reserved. 
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#    1. Redistributions of source code must retain the above copyright
-#       notice, this list of conditions and the following disclaimer.
-#    2. Redistributions in binary form must reproduce the above copyright
-#       notice, this list of conditions and the following disclaimer in the
-#       documentation and/or other materials provided with the
-#       distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY GENE CASH ``AS IS'' AND ANY EXPRESS OR
-# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
-# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# This means you may do anything you want with this code, except claim you
-# wrote it. Also, if it breaks you get to keep both pieces.
-#
-# 21-AUG-99 TB  Last update by Thierry Bousch to his code.
-# 17-JAN-02 CEC Discovered code on web.
-#               Commented everything.
-#               Made small code improvements.
-#               Reformatted for readability.
-# 19-JAN-02 CEC Added ability to read TIFFs and JFIF-format JPEGs.
-#               Added ability to extract JPEG formatted thumbnail.
-#               Added ability to read GPS IFD (not tested).
-#               Converted IFD data structure to dictionaries indexed by
-#               tag name.
-#               Factored into library returning dictionary of IFDs plus
-#               thumbnail, if any.
-# 20-JAN-02 CEC Added MakerNote processing logic.
-#               Added Olympus MakerNote.
-#               Converted data structure to single-level dictionary, avoiding
-#               tag name collisions by prefixing with IFD name.  This makes
-#               it much easier to use.
-# 23-JAN-02 CEC Trimmed nulls from end of string values.
-# 25-JAN-02 CEC Discovered JPEG thumbnail in Olympus TIFF MakerNote.
-# 26-JAN-02 CEC Added ability to extract TIFF thumbnails.
-#               Added Nikon, Fujifilm, Casio MakerNotes.
-# 30-NOV-03 CEC Fixed problem with canon_decode_tag() not creating an
-#               IFD_Tag() object.
-# 15-FEB-04 CEC Finally fixed bit shift warning by converting Y to 0L.
-#
-# To do:
-# * Better printing of ratios
-
-import string
-import struct
-
-# field type descriptions as (length, abbreviation, full name) tuples
-FIELD_TYPES=(
-    (0, 'X',  'Proprietary'), # no such type
-    (1, 'B',  'Byte'),
-    (1, 'A',  'ASCII'),
-    (2, 'S',  'Short'),
-    (4, 'L',  'Long'),
-    (8, 'R',  'Ratio'),
-    (1, 'SB', 'Signed Byte'),
-    (1, 'U',  'Undefined'),
-    (2, 'SS', 'Signed Short'),
-    (4, 'SL', 'Signed Long'),
-    (8, 'SR', 'Signed Ratio')
-    )
-
-# dictionary of main EXIF tag names
-# first element of tuple is tag name, optional second element is
-# another dictionary giving names to values
-EXIF_TAGS={
-    0x0100: ('ImageWidth', ),
-    0x0101: ('ImageLength', ),
-    0x0102: ('BitsPerSample', ),
-    0x0103: ('Compression',
-             {1: 'Uncompressed TIFF',
-              6: 'JPEG Compressed'}),
-    0x0106: ('PhotometricInterpretation', ),
-    0x010A: ('FillOrder', ),
-    0x010D: ('DocumentName', ),
-    0x010E: ('ImageDescription', ),
-    0x010F: ('Make', ),
-    0x0110: ('Model', ),
-    0x0111: ('StripOffsets', ),
-    0x0112: ('Orientation', ),
-    0x0115: ('SamplesPerPixel', ),
-    0x0116: ('RowsPerStrip', ),
-    0x0117: ('StripByteCounts', ),
-    0x011A: ('XResolution', ),
-    0x011B: ('YResolution', ),
-    0x011C: ('PlanarConfiguration', ),
-    0x0128: ('ResolutionUnit',
-             {1: 'Not Absolute',
-              2: 'Pixels/Inch',
-              3: 'Pixels/Centimeter'}),
-    0x012D: ('TransferFunction', ),
-    0x0131: ('Software', ),
-    0x0132: ('DateTime', ),
-    0x013B: ('Artist', ),
-    0x013E: ('WhitePoint', ),
-    0x013F: ('PrimaryChromaticities', ),
-    0x0156: ('TransferRange', ),
-    0x0200: ('JPEGProc', ),
-    0x0201: ('JPEGInterchangeFormat', ),
-    0x0202: ('JPEGInterchangeFormatLength', ),
-    0x0211: ('YCbCrCoefficients', ),
-    0x0212: ('YCbCrSubSampling', ),
-    0x0213: ('YCbCrPositioning', ),
-    0x0214: ('ReferenceBlackWhite', ),
-    0x828D: ('CFARepeatPatternDim', ),
-    0x828E: ('CFAPattern', ),
-    0x828F: ('BatteryLevel', ),
-    0x8298: ('Copyright', ),
-    0x829A: ('ExposureTime', ),
-    0x829D: ('FNumber', ),
-    0x83BB: ('IPTC/NAA', ),
-    0x8769: ('ExifOffset', ),
-    0x8773: ('InterColorProfile', ),
-    0x8822: ('ExposureProgram',
-             {0: 'Unidentified',
-              1: 'Manual',
-              2: 'Program Normal',
-              3: 'Aperture Priority',
-              4: 'Shutter Priority',
-              5: 'Program Creative',
-              6: 'Program Action',
-              7: 'Portrait Mode',
-              8: 'Landscape Mode'}),
-    0x8824: ('SpectralSensitivity', ),
-    0x8825: ('GPSInfo', ),
-    0x8827: ('ISOSpeedRatings', ),
-    0x8828: ('OECF', ),
-    # print as string
-    0x9000: ('ExifVersion', lambda x: ''.join(map(chr, x))),
-    0x9003: ('DateTimeOriginal', ),
-    0x9004: ('DateTimeDigitized', ),
-    0x9101: ('ComponentsConfiguration',
-             {0: '',
-              1: 'Y',
-              2: 'Cb',
-              3: 'Cr',
-              4: 'Red',
-              5: 'Green',
-              6: 'Blue'}),
-    0x9102: ('CompressedBitsPerPixel', ),
-    0x9201: ('ShutterSpeedValue', ),
-    0x9202: ('ApertureValue', ),
-    0x9203: ('BrightnessValue', ),
-    0x9204: ('ExposureBiasValue', ),
-    0x9205: ('MaxApertureValue', ),
-    0x9206: ('SubjectDistance', ),
-    0x9207: ('MeteringMode',
-             {0: 'Unidentified',
-              1: 'Average',
-              2: 'CenterWeightedAverage',
-              3: 'Spot',
-              4: 'MultiSpot'}),
-    0x9208: ('LightSource',
-             {0:   'Unknown',
-              1:   'Daylight',
-              2:   'Fluorescent',
-              3:   'Tungsten',
-              10:  'Flash',
-              17:  'Standard Light A',
-              18:  'Standard Light B',
-              19:  'Standard Light C',
-              20:  'D55',
-              21:  'D65',
-              22:  'D75',
-              255: 'Other'}),
-    0x9209: ('Flash', {0:  'No',
-                       1:  'Fired',
-                       5:  'Fired (?)', # no return sensed
-                       7:  'Fired (!)', # return sensed
-                       9:  'Fill Fired',
-                       13: 'Fill Fired (?)',
-                       15: 'Fill Fired (!)',
-                       16: 'Off',
-                       24: 'Auto Off',
-                       25: 'Auto Fired',
-                       29: 'Auto Fired (?)',
-                       31: 'Auto Fired (!)',
-                       32: 'Not Available'}),
-    0x920A: ('FocalLength', ),
-    0x927C: ('MakerNote', ),
-    # print as string
-    0x9286: ('UserComment', lambda x: ''.join(map(chr, x))),
-    0x9290: ('SubSecTime', ),
-    0x9291: ('SubSecTimeOriginal', ),
-    0x9292: ('SubSecTimeDigitized', ),
-    # print as string
-    0xA000: ('FlashPixVersion', lambda x: ''.join(map(chr, x))),
-    0xA001: ('ColorSpace', ),
-    0xA002: ('ExifImageWidth', ),
-    0xA003: ('ExifImageLength', ),
-    0xA005: ('InteroperabilityOffset', ),
-    0xA20B: ('FlashEnergy', ),               # 0x920B in TIFF/EP
-    0xA20C: ('SpatialFrequencyResponse', ),  # 0x920C    -  -
-    0xA20E: ('FocalPlaneXResolution', ),     # 0x920E    -  -
-    0xA20F: ('FocalPlaneYResolution', ),     # 0x920F    -  -
-    0xA210: ('FocalPlaneResolutionUnit', ),  # 0x9210    -  -
-    0xA214: ('SubjectLocation', ),           # 0x9214    -  -
-    0xA215: ('ExposureIndex', ),             # 0x9215    -  -
-    0xA217: ('SensingMethod', ),             # 0x9217    -  -
-    0xA300: ('FileSource',
-             {3: 'Digital Camera'}),
-    0xA301: ('SceneType',
-             {1: 'Directly Photographed'}),
-    }
-
-# interoperability tags
-INTR_TAGS={
-    0x0001: ('InteroperabilityIndex', ),
-    0x0002: ('InteroperabilityVersion', ),
-    0x1000: ('RelatedImageFileFormat', ),
-    0x1001: ('RelatedImageWidth', ),
-    0x1002: ('RelatedImageLength', ),
-    }
-
-# GPS tags (not used yet, haven't seen camera with GPS)
-GPS_TAGS={
-    0x0000: ('GPSVersionID', ),
-    0x0001: ('GPSLatitudeRef', ),
-    0x0002: ('GPSLatitude', ),
-    0x0003: ('GPSLongitudeRef', ),
-    0x0004: ('GPSLongitude', ),
-    0x0005: ('GPSAltitudeRef', ),
-    0x0006: ('GPSAltitude', ),
-    0x0007: ('GPSTimeStamp', ),
-    0x0008: ('GPSSatellites', ),
-    0x0009: ('GPSStatus', ),
-    0x000A: ('GPSMeasureMode', ),
-    0x000B: ('GPSDOP', ),
-    0x000C: ('GPSSpeedRef', ),
-    0x000D: ('GPSSpeed', ),
-    0x000E: ('GPSTrackRef', ),
-    0x000F: ('GPSTrack', ),
-    0x0010: ('GPSImgDirectionRef', ),
-    0x0011: ('GPSImgDirection', ),
-    0x0012: ('GPSMapDatum', ),
-    0x0013: ('GPSDestLatitudeRef', ),
-    0x0014: ('GPSDestLatitude', ),
-    0x0015: ('GPSDestLongitudeRef', ),
-    0x0016: ('GPSDestLongitude', ),
-    0x0017: ('GPSDestBearingRef', ),
-    0x0018: ('GPSDestBearing', ),
-    0x0019: ('GPSDestDistanceRef', ),
-    0x001A: ('GPSDestDistance', )
-    }
-
-# Nikon E99x MakerNote Tags
-# http://members.tripod.com/~tawba/990exif.htm
-MAKERNOTE_NIKON_NEWER_TAGS={
-    0x0002: ('ISOSetting', ),
-    0x0003: ('ColorMode', ),
-    0x0004: ('Quality', ),
-    0x0005: ('Whitebalance', ),
-    0x0006: ('ImageSharpening', ),
-    0x0007: ('FocusMode', ),
-    0x0008: ('FlashSetting', ),
-    0x000F: ('ISOSelection', ),
-    0x0080: ('ImageAdjustment', ),
-    0x0082: ('AuxiliaryLens', ),
-    0x0085: ('ManualFocusDistance', ),
-    0x0086: ('DigitalZoomFactor', ),
-    0x0088: ('AFFocusPosition',
-             {0x0000: 'Center',
-              0x0100: 'Top',
-              0x0200: 'Bottom',
-              0x0300: 'Left',
-              0x0400: 'Right'}),
-    0x0094: ('Saturation',
-             {-3: 'B&W',
-              -2: '-2',
-              -1: '-1',
-              0:  '0',
-              1:  '1',
-              2:  '2'}),
-    0x0095: ('NoiseReduction', ),
-    0x0010: ('DataDump', )
-    }
-
-MAKERNOTE_NIKON_OLDER_TAGS={
-    0x0003: ('Quality',
-             {1: 'VGA Basic',
-              2: 'VGA Normal',
-              3: 'VGA Fine',
-              4: 'SXGA Basic',
-              5: 'SXGA Normal',
-              6: 'SXGA Fine'}),
-    0x0004: ('ColorMode',
-             {1: 'Color',
-              2: 'Monochrome'}),
-    0x0005: ('ImageAdjustment',
-             {0: 'Normal',
-              1: 'Bright+',
-              2: 'Bright-',
-              3: 'Contrast+',
-              4: 'Contrast-'}),
-    0x0006: ('CCDSpeed',
-             {0: 'ISO 80',
-              2: 'ISO 160',
-              4: 'ISO 320',
-              5: 'ISO 100'}),
-    0x0007: ('WhiteBalance',
-             {0: 'Auto',
-              1: 'Preset',
-              2: 'Daylight',
-              3: 'Incandescent',
-              4: 'Fluorescent',
-              5: 'Cloudy',
-              6: 'Speed Light'})
-    }
-
-# decode Olympus SpecialMode tag in MakerNote
-def olympus_special_mode(v):
-    a={
-        0: 'Normal',
-        1: 'Unknown',
-        2: 'Fast',
-        3: 'Panorama'}
-    b={
-        0: 'Non-panoramic',
-        1: 'Left to right',
-        2: 'Right to left',
-        3: 'Bottom to top',
-        4: 'Top to bottom'}
-    # handle broken Olympus MakerNote
-    try:
-        return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
-    except KeyError:
-        return ''
-
-MAKERNOTE_OLYMPUS_TAGS={
-    # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
-    # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
-    0x0100: ('JPEGThumbnail', ),
-    0x0200: ('SpecialMode', olympus_special_mode),
-    0x0201: ('JPEGQual',
-             {1: 'SQ',
-              2: 'HQ',
-              3: 'SHQ'}),
-    0x0202: ('Macro',
-             {0: 'Normal',
-              1: 'Macro'}),
-    0x0204: ('DigitalZoom', ),
-    0x0207: ('SoftwareRelease',  ),
-    0x0208: ('PictureInfo',  ),
-    # print as string
-    0x0209: ('CameraID', lambda x: ''.join(map(chr, x))), 
-    0x0F00: ('DataDump',  )
-    }
-
-MAKERNOTE_CASIO_TAGS={
-    0x0001: ('RecordingMode',
-             {1: 'Single Shutter',
-              2: 'Panorama',
-              3: 'Night Scene',
-              4: 'Portrait',
-              5: 'Landscape'}),
-    0x0002: ('Quality',
-             {1: 'Economy',
-              2: 'Normal',
-              3: 'Fine'}),
-    0x0003: ('FocusingMode',
-             {2: 'Macro',
-              3: 'Auto Focus',
-              4: 'Manual Focus',
-              5: 'Infinity'}),
-    0x0004: ('FlashMode',
-             {1: 'Auto',
-              2: 'On',
-              3: 'Off',
-              4: 'Red Eye Reduction'}),
-    0x0005: ('FlashIntensity',
-             {11: 'Weak',
-              13: 'Normal',
-              15: 'Strong'}),
-    0x0006: ('Object Distance', ),
-    0x0007: ('WhiteBalance',
-             {1:   'Auto',
-              2:   'Tungsten',
-              3:   'Daylight',
-              4:   'Fluorescent',
-              5:   'Shade',
-              129: 'Manual'}),
-    0x000B: ('Sharpness',
-             {0: 'Normal',
-              1: 'Soft',
-              2: 'Hard'}),
-    0x000C: ('Contrast',
-             {0: 'Normal',
-              1: 'Low',
-              2: 'High'}),
-    0x000D: ('Saturation',
-             {0: 'Normal',
-              1: 'Low',
-              2: 'High'}),
-    0x0014: ('CCDSpeed',
-             {64:  'Normal',
-              80:  'Normal',
-              100: 'High',
-              125: '+1.0',
-              244: '+3.0',
-              250: '+2.0',})
-    }
-
-MAKERNOTE_FUJIFILM_TAGS={
-    0x0000: ('NoteVersion', lambda x: ''.join(map(chr, x))),
-    0x1000: ('Quality', ),
-    0x1001: ('Sharpness',
-             {1: 'Soft',
-              2: 'Soft',
-              3: 'Normal',
-              4: 'Hard',
-              5: 'Hard'}),
-    0x1002: ('WhiteBalance',
-             {0:    'Auto',
-              256:  'Daylight',
-              512:  'Cloudy',
-              768:  'DaylightColor-Fluorescent',
-              769:  'DaywhiteColor-Fluorescent',
-              770:  'White-Fluorescent',
-              1024: 'Incandescent',
-              3840: 'Custom'}),
-    0x1003: ('Color',
-             {0:   'Normal',
-              256: 'High',
-              512: 'Low'}),
-    0x1004: ('Tone',
-             {0:   'Normal',
-              256: 'High',
-              512: 'Low'}),
-    0x1010: ('FlashMode',
-             {0: 'Auto',
-              1: 'On',
-              2: 'Off',
-              3: 'Red Eye Reduction'}),
-    0x1011: ('FlashStrength', ),
-    0x1020: ('Macro',
-             {0: 'Off',
-              1: 'On'}),
-    0x1021: ('FocusMode',
-             {0: 'Auto',
-              1: 'Manual'}),
-    0x1030: ('SlowSync',
-             {0: 'Off',
-              1: 'On'}),
-    0x1031: ('PictureMode',
-             {0:   'Auto',
-              1:   'Portrait',
-              2:   'Landscape',
-              4:   'Sports',
-              5:   'Night',
-              6:   'Program AE',
-              256: 'Aperture Priority AE',
-              512: 'Shutter Priority AE',
-              768: 'Manual Exposure'}),
-    0x1100: ('MotorOrBracket',
-             {0: 'Off',
-              1: 'On'}),
-    0x1300: ('BlurWarning',
-             {0: 'Off',
-              1: 'On'}),
-    0x1301: ('FocusWarning',
-             {0: 'Off',
-              1: 'On'}),
-    0x1302: ('AEWarning',
-             {0: 'Off',
-              1: 'On'})
-    }
-
-MAKERNOTE_CANON_TAGS={
-    0x0006: ('ImageType', ),
-    0x0007: ('FirmwareVersion', ),
-    0x0008: ('ImageNumber', ),
-    0x0009: ('OwnerName', )
-    }
-
-# see http://www.burren.cx/david/canon.html by David Burren
-# this is in element offset, name, optional value dictionary format
-MAKERNOTE_CANON_TAG_0x001={
-    1: ('Macromode',
-        {1: 'Macro',
-         2: 'Normal'}),
-    2: ('SelfTimer', ),
-    3: ('Quality',
-        {2: 'Normal',
-         3: 'Fine',
-         5: 'Superfine'}),
-    4: ('FlashMode',
-        {0: 'Flash Not Fired',
-         1: 'Auto',
-         2: 'On',
-         3: 'Red-Eye Reduction',
-         4: 'Slow Synchro',
-         5: 'Auto + Red-Eye Reduction',
-         6: 'On + Red-Eye Reduction',
-         16: 'external flash'}),
-    5: ('ContinuousDriveMode',
-        {0: 'Single Or Timer',
-         1: 'Continuous'}),
-    7: ('FocusMode',
-        {0: 'One-Shot',
-         1: 'AI Servo',
-         2: 'AI Focus',
-         3: 'MF',
-         4: 'Single',
-         5: 'Continuous',
-         6: 'MF'}),
-    10: ('ImageSize',
-         {0: 'Large',
-          1: 'Medium',
-          2: 'Small'}),
-    11: ('EasyShootingMode',
-         {0: 'Full Auto',
-          1: 'Manual',
-          2: 'Landscape',
-          3: 'Fast Shutter',
-