diff --git a/convert_1.x_to_2.x.py b/convert_1.x_to_2.x.py deleted file mode 100755 index db8248d..0000000 --- a/convert_1.x_to_2.x.py +++ /dev/null @@ -1,299 +0,0 @@ -#!/usr/bin/env python -""" - Project: pyGTKtalog - Description: convert db created with v.1.x into v.2.x - Type: tool - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2009-08-14 -""" -from datetime import datetime -from sqlite3 import dbapi2 as sqlite -from sqlite3 import OperationalError -from tempfile import mkstemp -import bz2 -import errno -import os -import shutil -import sys - -from sqlalchemy.dialects.sqlite import DATETIME - -from pygtktalog.misc import mk_paths, calculate_image_path - -PATH1 = os.path.expanduser("~/.pygtktalog/images") -PATH2 = os.path.expanduser("~/.pygtktalog/imgs2") - - -def mkdir_p(path): - """Make directories recurively, like 'mkdir -p' command""" - try: - os.makedirs(path) - except OSError as exc: - if exc.errno == errno.EEXIST and os.path.isdir(path): - pass - else: - raise - return path - -def get_images_path(cur): - """ - Calculate the data dir in order: - - config table - - old default path - - new default path - return first, which contain provided image filename - """ - - image = cur.execute("select filename from images limit 1").fetchone() - if image and image[0]: - image = image[0] - - try: - result = cur.execute("select value from config where " - "key='image_path'").fetchone() - if (result and result[0] and - os.path.exists(os.path.join(result[0].encode("utf-8"), - image.encode("utf-8")))): - return result[0] - except OperationalError: - # no such table like config. proceed. - pass - - for path in (PATH1, PATH2): - if os.path.exists(os.path.join(path, image)): - return path - return None - -def get_path(cur, image): - """ - Calculate the data dir in order: - - config table - - old default path - - new default path - return first, which contain provided image filename - """ - try: - result = cur.execute("select value from config where " - "key='image_path'").fetchone() - if (result and result[0] and - os.path.exists(os.path.join(result[0].encode("utf-8"), - image.encode("utf-8")))): - return result[0] - except OperationalError: - pass - - for path in (PATH1, PATH2): - if os.path.exists(os.path.join(path, image)): - return path - return None - -def old_style_image_handle(fname, source_dir, dest_dir): - """ - Deal with old-style images in DB. There is a flat list under - ~/.pygtktalog/images/ directory, which should be converted to nested - structure. - """ - - partial_path = mk_paths(os.path.join(source_dir, fname), dest_dir) - - dest_file = os.path.join(dest_dir, *partial_path) - dest_thumb = os.path.join(dest_dir, *partial_path) + "_t" - - shutil.copy(os.path.join(source_dir, fname), dest_file) - shutil.copy(os.path.join(source_dir, fname + "_t"), dest_thumb) - with open("log.txt", "a") as fobj: - fobj.write(os.path.join(fname) + "\n") - fobj.write(os.path.join(fname + "_t\n")) - - return os.path.join(*partial_path), os.path.join(*partial_path) + "_t" - - -def new_style_image_handle(partial_path, source_dir, dest_dir): - """ - Deal with old-style images in DB. In the early version directory was - hardcoded to ~/.pygtktalog/imgs2/, and all the needed files (with the - paths) should be copied to the new place. - params: - partial_path: string holding the relative path to file, for example - `de/ad/be/ef.jpg' - source_dir: path, where at the moment image file resides. Might be the - full path, like `/home/user/.pygtktalog/imgs2` - dest_dir: path (might be relative or absolute), where we want to put - the images (i.e. `../foo-images') - """ - dest_dir = mkdir_p(os.path.join(dest_dir, os.path.dirname(partial_path))) - base, ext = os.path.splitext(partial_path) - thumb = os.path.join(source_dir, "".join([base, "_t", ext])) - filename = os.path.join(source_dir, partial_path) - - shutil.copy(filename, dest_dir) - shutil.copy(thumb, dest_dir) - - -def copy_images_to_destination(cursor, image_path, dest): - """Copy images to dest directory and correct the db entry, if needed""" - - sql = "select id, filename from images" - update = "update images set filename=? where id=?" - t_select = "select id from thumbnails where filename=?" - t_update = "update thumbnails set filename=? where id=?" - - count = -1 - for count, (id_, filename) in enumerate(cursor.execute(sql).fetchall()): - if not image_path: - image_path = get_path(cursor, filename) - if not image_path: - raise OSError("Image file '%s' not found under data " - "directory, aborting" % filename) - - if image_path == PATH1: - # old style filenames. Flat list. - fname, tname = old_style_image_handle(filename, image_path, dest) - cursor.execute(update, (fname, id_)) - for (thumb_id,) in cursor.execute(t_select, - (filename,)).fetchall(): - cursor.execute(t_update, (tname, thumb_id)) - else: - # new style filenames. nested dirs - new_style_image_handle(filename, image_path, dest) - - if count > 0: - print "copied %d files" % (count + 1) - -def create_temporary_db_file(): - """create temporary db file""" - file_descriptor, fname = mkstemp() - os.close(file_descriptor) - return fname - -def connect_to_db(filename): - """initialize db connection and store it in class attributes""" - db_connection = sqlite.connect(filename, detect_types= - sqlite.PARSE_DECLTYPES | - sqlite.PARSE_COLNAMES) - db_cursor = db_connection.cursor() - return db_connection, db_cursor - -def opendb(filename=None): - """try to open db file""" - db_tmp_path = create_temporary_db_file() - - try: - test_file = open(filename).read(15) - except IOError: - os.unlink(db_tmp_path) - return False - - if test_file == "SQLite format 3": - db_tmp = open(db_tmp_path, "wb") - db_tmp.write(open(filename).read()) - db_tmp.close() - elif test_file[0:10] == "BZh91AY&SY": - open_file = bz2.BZ2File(filename) - try: - curdb = open(db_tmp_path, "w") - curdb.write(open_file.read()) - curdb.close() - open_file.close() - except IOError: - # file is not bz2 - os.unlink(db_tmp_path) - return False - else: - os.unlink(db_tmp_path) - return False - - return connect_to_db(db_tmp_path), db_tmp_path - -def _update_dates(cursor, select_sql, update_sql): - """update date format - worker function""" - for id_, date in cursor.execute(select_sql).fetchall(): - try: - date = int(date) - except ValueError: - # most probably there is no need for updating this record. - continue - except TypeError: - date = 0 - - if date > 0: - val = DATETIME().bind_processor(None)(datetime.fromtimestamp(date)) - else: - val = None - cursor.execute(update_sql, (val, id_)) - -def update_dates(cursor): - """Update date format from plain int to datetime object""" - - _update_dates(cursor, - "select id, date from files", - "update files set date=? where id=?") - _update_dates(cursor, - "select id, date from gthumb", - "update gthumb set date=? where id=?") - - -def main(): - """Main logic""" - if len(sys.argv) not in (4, 3): - print("usage: %s source_dbfile destination_dbfile [image_dir]\n" - "where image dir is a name where to put images. same name with" - "'_images' suffix by default" - % sys.argv[0]) - exit() - - if len(sys.argv) == 4: - source_dbfile, destination_dbfile, image_dir = sys.argv[1:] - else: - source_dbfile, destination_dbfile = sys.argv[1:] - image_dir = ":same_as_db:" - - result = opendb(source_dbfile) - if not result: - print("unable to open src db file") - exit() - - (connection, cursor), temporary_database_filename = result - - cursor.close() - connection.close() - shutil.copy(temporary_database_filename, destination_dbfile) - os.unlink(temporary_database_filename) - - connection = sqlite.connect(destination_dbfile) - cursor = connection.cursor() - - if cursor.execute("select name from sqlite_master where type='table' " - "and name='table_name'").fetchone() is None: - cursor.execute("CREATE TABLE 'config' (\n\t'id'\tINTEGER NOT NULL,\n" - "\t'key'\tTEXT,\n\t'value'\tTEXT,\n\tPRIMARY " - "KEY(id)\n)") - - if cursor.execute("select value from config where " - "key='image_path'").fetchone() is None: - cursor.execute("insert into config(key, value) " - "values('image_path', ?)", (image_dir,)) - else: - cursor.execute("update config set value=? where key='image_path'", - (image_dir,)) - - if image_dir == ":same_as_db:": - db_fname = os.path.basename(destination_dbfile) - base, dummy = os.path.splitext(db_fname) - image_dir_path = os.path.join(os.path.dirname(destination_dbfile), - base + "_images") - else: - image_dir_path = image_dir - - calculate_image_path(image_dir_path, True) - - update_dates(cursor) - old_image_path = get_images_path(cursor) - copy_images_to_destination(cursor, old_image_path, image_dir_path) - - connection.commit() - cursor.close() - connection.close() - -if __name__ == "__main__": - main() diff --git a/locale/pl.po b/locale/pl.po deleted file mode 100644 index fc19af5..0000000 --- a/locale/pl.po +++ /dev/null @@ -1,375 +0,0 @@ -# -# pygtktalog Language File -# -msgid "" -msgstr "" -"Project-Id-Version: pygtktalog\n" -"POT-Creation-Date: 2009-08-26 22:10:33.892815\n" -"Last-Translator: Roman Dobosz\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: utf-8\n" -"Generated-By: slightly modified generate_pot.py\n" - -#: pygtktalog/views/glade/main.glade.h:1 -msgid "Add CD" -msgstr "Dodaj CD" - -#: pygtktalog/views/glade/main.glade.h:2 -msgid "Add CD/DVD to catalog" -msgstr "Dodaj CD/DVD do katalogu" - -#: pygtktalog/views/glade/main.glade.h:3 -msgid "Add Dir" -msgstr "Dodaj katalog" - -#: pygtktalog/views/glade/main.glade.h:4 -msgid "Add _CD/DVD" -msgstr "Dodaj _Cd/DVD" - -#: pygtktalog/views/glade/main.glade.h:5 -msgid "Add _Directory" -msgstr "Dodaj kata_log" - -#: pygtktalog/views/glade/main.glade.h:6 -#: pygtktalog/views/glade/files.glade.h:1 -msgid "Add _Images" -msgstr "Dodaj _obrazy" - -#: pygtktalog/views/glade/main.glade.h:7 -#: pygtktalog/views/glade/files.glade.h:2 -msgid "Add _Thumbnail" -msgstr "Dodaj miniaturę" - -#: pygtktalog/views/glade/main.glade.h:8 -#: pygtktalog/views/glade/files.glade.h:3 -msgid "" -"Add images to file. If file have no thumbnail,\n" -"thumbnail from first image will be generated." -msgstr "" -"Dodanie obrazów do pliku. Jeśli plik nie posiada miniatury,\n" -"zostanie wygenerowana miniatura z pierwszego obrazu." - -#: pygtktalog/views/glade/main.glade.h:10 -#: pygtktalog/views/glade/main.glade.h:6 -msgid "Cancel" -msgstr "Analuj" - -#: pygtktalog/views/glade/main.glade.h:11 -#: pygtktalog/views/glade/main.glade.h:7 -msgid "Catalog _statistics" -msgstr "_Statystyka katalogu" - -#: pygtktalog/views/glade/main.glade.h:12 -#: pygtktalog/views/glade/discs.glade.h:1 -msgid "Collapse all nodes" -msgstr "Zwiń wszystkie gałęzie" - -#: pygtktalog/views/glade/main.glade.h:13 -#: pygtktalog/views/glade/main.glade.h:8 -msgid "Create new catalog" -msgstr "Utwórz nowy katalog" - -#: pygtktalog/views/glade/main.glade.h:14 -#: pygtktalog/views/glade/main.glade.h:9 -msgid "Delete all images" -msgstr "Usuń wszystkie obrazy" - -#: pygtktalog/views/glade/main.glade.h:15 -#: pygtktalog/views/glade/main.glade.h:10 -msgid "Delete all thumbnals" -msgstr "Usuń wszystkie miniatury" - -#: pygtktalog/views/glade/main.glade.h:16 -#: pygtktalog/views/glade/main.glade.h:11 -msgid "Delete tag" -msgstr "Usuń tag" - -#: pygtktalog/views/glade/main.glade.h:17 -#: pygtktalog/views/glade/main.glade.h:12 -msgid "Deletes all images from files in current colection" -msgstr "Usuwa wszystkie obrazy z plików w bieżącej kolekcji" - -#: pygtktalog/views/glade/main.glade.h:18 -#: pygtktalog/views/glade/main.glade.h:13 -msgid "Discs" -msgstr "Dyski" - -#: pygtktalog/views/glade/main.glade.h:19 -#: pygtktalog/views/glade/details.glade.h:1 -msgid "Double click to open image" -msgstr "Dwukrotne kliknięcie otwiera obraz" - -#: pygtktalog/views/glade/main.glade.h:20 -#: pygtktalog/views/glade/details.glade.h:2 -msgid "EXIF" -msgstr "EXIF" - -#: pygtktalog/views/glade/main.glade.h:21 -#: pygtktalog/views/glade/discs.glade.h:2 -msgid "Expand all nodes" -msgstr "Rozwiń wszystkie gałęzie" - -#: pygtktalog/views/glade/main.glade.h:22 -#: pygtktalog/views/glade/main.glade.h:14 -msgid "Export" -msgstr "Eksport" - -#: pygtktalog/views/glade/main.glade.h:23 -#: pygtktalog/views/glade/details.glade.h:3 -msgid "File info" -msgstr "Informacje o pliku" - -#: pygtktalog/views/glade/main.glade.h:24 -#: pygtktalog/views/glade/main.glade.h:15 -msgid "Find file" -msgstr "Znajdź plik" - -#: pygtktalog/views/glade/main.glade.h:25 -#: pygtktalog/views/glade/details.glade.h:4 -msgid "Images" -msgstr "Obrazy" - -#: pygtktalog/views/glade/main.glade.h:26 -#: pygtktalog/views/glade/main.glade.h:16 -msgid "Import" -msgstr "Import" - -#: pygtktalog/views/glade/main.glade.h:27 -#: pygtktalog/views/glade/main.glade.h:17 -msgid "Open catalog file" -msgstr "Otwórz plik katalogu" - -#: pygtktalog/views/glade/main.glade.h:28 -#: pygtktalog/views/glade/main.glade.h:18 -msgid "Quit pyGTKtalog" -msgstr "Zakończ pyGTKtalog" - -#: pygtktalog/views/glade/main.glade.h:29 -#: pygtktalog/views/glade/files.glade.h:5 -msgid "Re_move Thumbnail" -msgstr "Usuń miniaturę" - -#: pygtktalog/views/glade/main.glade.h:30 -#: pygtktalog/views/glade/main.glade.h:19 -msgid "Recent files" -msgstr "Ostatnio używane pliki" - -#: pygtktalog/views/glade/main.glade.h:31 -#: pygtktalog/views/glade/files.glade.h:6 -msgid "Rem_ove All Images" -msgstr "Usuń wszystkie obrazy" - -#: pygtktalog/views/glade/main.glade.h:32 -#: pygtktalog/views/glade/files.glade.h:7 -msgid "Remo_ve tag" -msgstr "Usuń tag" - -#: pygtktalog/views/glade/main.glade.h:33 -#: pygtktalog/views/glade/main.glade.h:20 -msgid "Save all images..." -msgstr "Zapisz wszytkie obrazy..." - -#: pygtktalog/views/glade/main.glade.h:34 -#: pygtktalog/views/glade/main.glade.h:21 -msgid "Save catalog" -msgstr "Zapisz katalog" - -#: pygtktalog/views/glade/main.glade.h:35 -#: pygtktalog/views/glade/main.glade.h:22 -msgid "Set as _thumbnail" -msgstr "Ustaw jako miniaturę" - -#: pygtktalog/views/glade/main.glade.h:36 -#: pygtktalog/views/glade/main.glade.h:23 -msgid "Status bar" -msgstr "Pasek stanu" - -#: pygtktalog/views/glade/main.glade.h:37 -#: pygtktalog/views/glade/main.glade.h:24 -msgid "Tags" -msgstr "Tagi" - -#: pygtktalog/views/glade/main.glade.h:38 -#: pygtktalog/views/glade/main.glade.h:25 -msgid "Toolbar" -msgstr "Pasek narzędzi" - -#: pygtktalog/views/glade/main.glade.h:39 -#: pygtktalog/views/glade/main.glade.h:26 -msgid "_Add images" -msgstr "Dodaj obrazy" - -#: pygtktalog/views/glade/main.glade.h:40 -#: pygtktalog/views/glade/files.glade.h:8 -msgid "_Add tag" -msgstr "_Dodaj tag" - -#: pygtktalog/views/glade/main.glade.h:41 -#: pygtktalog/views/glade/main.glade.h:27 -msgid "_Catalog" -msgstr "_Katalog" - -#: pygtktalog/views/glade/main.glade.h:42 -#: pygtktalog/views/glade/discs.glade.h:3 -msgid "_Collapse all" -msgstr "_Zwiń wszystko" - -#: pygtktalog/views/glade/main.glade.h:43 -#: pygtktalog/views/glade/discs.glade.h:4 -#: pygtktalog/views/glade/files.glade.h:9 -msgid "_Delete" -msgstr "_Usuń" - -#: pygtktalog/views/glade/main.glade.h:44 -#: pygtktalog/views/glade/main.glade.h:28 -msgid "_Delete images" -msgstr "Usuń obrazy" - -#: pygtktalog/views/glade/main.glade.h:45 -#: pygtktalog/views/glade/main.glade.h:29 -#: pygtktalog/views/glade/files.glade.h:10 -#: pygtktalog/views/glade/test.glade.h:1 -msgid "_Edit" -msgstr "_Edycja" - -#: pygtktalog/views/glade/main.glade.h:46 -#: pygtktalog/views/glade/discs.glade.h:5 -msgid "_Expand all" -msgstr "_Rozwiń wszystko" - -#: pygtktalog/views/glade/main.glade.h:47 pygtktalog/views/main_menu.py:19 -#: pygtktalog/views/glade/main.glade.h:30 -#: pygtktalog/views/glade/test.glade.h:2 -msgid "_File" -msgstr "_Plik" - -#: pygtktalog/views/glade/main.glade.h:48 -#: pygtktalog/views/glade/main.glade.h:31 -#: pygtktalog/views/glade/test.glade.h:3 -msgid "_Help" -msgstr "_Pomoc" - -#: pygtktalog/views/glade/main.glade.h:49 -#: pygtktalog/views/glade/main.glade.h:32 -msgid "_Remove Thumbnail" -msgstr "Usuń miniaturę" - -#: pygtktalog/views/glade/main.glade.h:50 -#: pygtktalog/views/glade/discs.glade.h:6 -#: pygtktalog/views/glade/files.glade.h:11 -msgid "_Rename" -msgstr "_Zmień nazwę" - -#: pygtktalog/views/glade/main.glade.h:51 -#: pygtktalog/views/glade/main.glade.h:33 -msgid "_Save images to..." -msgstr "Zapisz obrazy do..." - -#: pygtktalog/views/glade/main.glade.h:52 -#: pygtktalog/views/glade/discs.glade.h:7 -msgid "_Statistics" -msgstr "_Statystyka" - -#: pygtktalog/views/glade/main.glade.h:53 -#: pygtktalog/views/glade/discs.glade.h:8 -msgid "_Update" -msgstr "_Uaktualnij" - -#: pygtktalog/views/glade/main.glade.h:54 -#: pygtktalog/views/glade/main.glade.h:34 -#: pygtktalog/views/glade/test.glade.h:4 -msgid "_View" -msgstr "_Widok" - -#: pygtktalog/views/glade/main.glade.h:55 -msgid "gtk-clear" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:56 -#: pygtktalog/views/glade/main.glade.h:35 -msgid "pyGTKtalog" -msgstr "pyGTKtalog" - -#: pygtktalog/views/glade/main.glade.h:57 -#: pygtktalog/views/glade/main.glade.h:36 -msgid "pyGTKtalog - Image" -msgstr "pyGTKtalog - Obraz" - -#: pygtktalog/controllers/main.py:47 -msgid "Do you really want to quit?" -msgstr "Czy na pewno chcesz zakończyć?" - -#: pygtktalog/controllers/main.py:48 -msgid "Current database is not saved, any changes will be lost." -msgstr "Bieżący katalog nie jest zapisany, wszelkie zmiany zostaną utracone." - -#: pygtktalog/controllers/main.py:48 pygtktalog/controllers/main.py:49 -msgid "Quit application" -msgstr "Zakończ aplikację" - -#: pygtktalog/models/main.py:16 pygtktalog/models/main.py:31 -msgid "Idle" -msgstr "Bezczynny" - -#: pygtktalog/controllers/files.py:33 -#, fuzzy -msgid "Disc" -msgstr "Dyski" - -#: pygtktalog/controllers/files.py:39 -#, fuzzy -msgid "Filename" -msgstr "_Zmień nazwę" - -#: pygtktalog/controllers/files.py:50 -msgid "Path" -msgstr "" - -#: pygtktalog/controllers/files.py:56 -msgid "Size" -msgstr "" - -#: pygtktalog/controllers/files.py:61 -msgid "Date" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:5 -msgid "gtk-about" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:6 -msgid "gtk-copy" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:7 -msgid "gtk-cut" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:8 -msgid "gtk-delete" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:9 -msgid "gtk-new" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:10 -msgid "gtk-open" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:11 -msgid "gtk-paste" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:12 -msgid "gtk-quit" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:13 -msgid "gtk-save" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:14 -msgid "gtk-save-as" -msgstr "" diff --git a/locale/pygtktalog.pot b/locale/pygtktalog.pot deleted file mode 100644 index 5d4dd4e..0000000 --- a/locale/pygtktalog.pot +++ /dev/null @@ -1,370 +0,0 @@ -# -# pygtktalog Language File -# -msgid "" -msgstr "" -"Project-Id-Version: pygtktalog\n" -"POT-Creation-Date: 2009-08-26 22:10:33.892815\n" -"Last-Translator: Roman Dobosz\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: utf-8\n" - -#: pygtktalog/views/glade/main.glade.h:1 -msgid "Add CD" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:2 -msgid "Add CD/DVD to catalog" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:3 -msgid "Add Dir" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:4 -msgid "Add _CD/DVD" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:5 -msgid "Add _Directory" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:6 -#: pygtktalog/views/glade/files.glade.h:1 -msgid "Add _Images" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:7 -#: pygtktalog/views/glade/files.glade.h:2 -msgid "Add _Thumbnail" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:8 -#: pygtktalog/views/glade/files.glade.h:3 -msgid "" -"Add images to file. If file have no thumbnail,\n" -"thumbnail from first image will be generated." -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:10 -#: pygtktalog/views/glade/main.glade.h:6 -msgid "Cancel" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:11 -#: pygtktalog/views/glade/main.glade.h:7 -msgid "Catalog _statistics" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:12 -#: pygtktalog/views/glade/discs.glade.h:1 -msgid "Collapse all nodes" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:13 -#: pygtktalog/views/glade/main.glade.h:8 -msgid "Create new catalog" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:14 -#: pygtktalog/views/glade/main.glade.h:9 -msgid "Delete all images" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:15 -#: pygtktalog/views/glade/main.glade.h:10 -msgid "Delete all thumbnals" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:16 -#: pygtktalog/views/glade/main.glade.h:11 -msgid "Delete tag" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:17 -#: pygtktalog/views/glade/main.glade.h:12 -msgid "Deletes all images from files in current colection" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:18 -#: pygtktalog/views/glade/main.glade.h:13 -msgid "Discs" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:19 -#: pygtktalog/views/glade/details.glade.h:1 -msgid "Double click to open image" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:20 -#: pygtktalog/views/glade/details.glade.h:2 -msgid "EXIF" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:21 -#: pygtktalog/views/glade/discs.glade.h:2 -msgid "Expand all nodes" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:22 -#: pygtktalog/views/glade/main.glade.h:14 -msgid "Export" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:23 -#: pygtktalog/views/glade/details.glade.h:3 -msgid "File info" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:24 -#: pygtktalog/views/glade/main.glade.h:15 -msgid "Find file" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:25 -#: pygtktalog/views/glade/details.glade.h:4 -msgid "Images" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:26 -#: pygtktalog/views/glade/main.glade.h:16 -msgid "Import" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:27 -#: pygtktalog/views/glade/main.glade.h:17 -msgid "Open catalog file" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:28 -#: pygtktalog/views/glade/main.glade.h:18 -msgid "Quit pyGTKtalog" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:29 -#: pygtktalog/views/glade/files.glade.h:5 -msgid "Re_move Thumbnail" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:30 -#: pygtktalog/views/glade/main.glade.h:19 -msgid "Recent files" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:31 -#: pygtktalog/views/glade/files.glade.h:6 -msgid "Rem_ove All Images" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:32 -#: pygtktalog/views/glade/files.glade.h:7 -msgid "Remo_ve tag" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:33 -#: pygtktalog/views/glade/main.glade.h:20 -msgid "Save all images..." -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:34 -#: pygtktalog/views/glade/main.glade.h:21 -msgid "Save catalog" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:35 -#: pygtktalog/views/glade/main.glade.h:22 -msgid "Set as _thumbnail" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:36 -#: pygtktalog/views/glade/main.glade.h:23 -msgid "Status bar" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:37 -#: pygtktalog/views/glade/main.glade.h:24 -msgid "Tags" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:38 -#: pygtktalog/views/glade/main.glade.h:25 -msgid "Toolbar" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:39 -#: pygtktalog/views/glade/main.glade.h:26 -msgid "_Add images" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:40 -#: pygtktalog/views/glade/files.glade.h:8 -msgid "_Add tag" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:41 -#: pygtktalog/views/glade/main.glade.h:27 -msgid "_Catalog" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:42 -#: pygtktalog/views/glade/discs.glade.h:3 -msgid "_Collapse all" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:43 -#: pygtktalog/views/glade/discs.glade.h:4 -#: pygtktalog/views/glade/files.glade.h:9 -msgid "_Delete" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:44 -#: pygtktalog/views/glade/main.glade.h:28 -msgid "_Delete images" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:45 -#: pygtktalog/views/glade/main.glade.h:29 -#: pygtktalog/views/glade/files.glade.h:10 -#: pygtktalog/views/glade/test.glade.h:1 -msgid "_Edit" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:46 -#: pygtktalog/views/glade/discs.glade.h:5 -msgid "_Expand all" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:47 pygtktalog/views/main_menu.py:19 -#: pygtktalog/views/glade/main.glade.h:30 -#: pygtktalog/views/glade/test.glade.h:2 -msgid "_File" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:48 -#: pygtktalog/views/glade/main.glade.h:31 -#: pygtktalog/views/glade/test.glade.h:3 -msgid "_Help" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:49 -#: pygtktalog/views/glade/main.glade.h:32 -msgid "_Remove Thumbnail" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:50 -#: pygtktalog/views/glade/discs.glade.h:6 -#: pygtktalog/views/glade/files.glade.h:11 -msgid "_Rename" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:51 -#: pygtktalog/views/glade/main.glade.h:33 -msgid "_Save images to..." -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:52 -#: pygtktalog/views/glade/discs.glade.h:7 -msgid "_Statistics" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:53 -#: pygtktalog/views/glade/discs.glade.h:8 -msgid "_Update" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:54 -#: pygtktalog/views/glade/main.glade.h:34 -#: pygtktalog/views/glade/test.glade.h:4 -msgid "_View" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:55 -msgid "gtk-clear" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:56 -#: pygtktalog/views/glade/main.glade.h:35 -msgid "pyGTKtalog" -msgstr "" - -#: pygtktalog/views/glade/main.glade.h:57 -#: pygtktalog/views/glade/main.glade.h:36 -msgid "pyGTKtalog - Image" -msgstr "" - -#: pygtktalog/controllers/main.py:47 -msgid "Do you really want to quit?" -msgstr "" - -#: pygtktalog/controllers/main.py:48 -msgid "Current database is not saved, any changes will be lost." -msgstr "" - -#: pygtktalog/controllers/main.py:48 pygtktalog/controllers/main.py:49 -msgid "Quit application" -msgstr "" - -#: pygtktalog/models/main.py:16 pygtktalog/models/main.py:31 -msgid "Idle" -msgstr "" - -#: pygtktalog/controllers/files.py:33 -msgid "Disc" -msgstr "" - -#: pygtktalog/controllers/files.py:39 -msgid "Filename" -msgstr "" - -#: pygtktalog/controllers/files.py:50 -msgid "Path" -msgstr "" - -#: pygtktalog/controllers/files.py:56 -msgid "Size" -msgstr "" - -#: pygtktalog/controllers/files.py:61 -msgid "Date" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:5 -msgid "gtk-about" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:6 -msgid "gtk-copy" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:7 -msgid "gtk-cut" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:8 -msgid "gtk-delete" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:9 -msgid "gtk-new" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:10 -msgid "gtk-open" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:11 -msgid "gtk-paste" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:12 -msgid "gtk-quit" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:13 -msgid "gtk-save" -msgstr "" - -#: pygtktalog/views/glade/test.glade.h:14 -msgid "gtk-save-as" -msgstr "" diff --git a/pavement.py b/pavement.py index 0b1b841..3bc8da4 100644 --- a/pavement.py +++ b/pavement.py @@ -121,7 +121,7 @@ def distclean(): shutil.rmtree(dirname, ignore_errors=True) print "removed directory", dirname - for filename in ['paver-minilib.zip', 'setup.py', 'test/.coverage']: + for filename in ['paver-minilib.zip', 'setup.py', 'tests/.coverage']: if os.path.exists(filename): os.unlink(filename) print "deleted", filename @@ -203,7 +203,7 @@ if HAVE_LINT: @cmdopts([('coverage', 'c', 'display coverage information')]) def test(options): """run unit tests""" - cmd = "PYTHONPATH=%s:$PYTHONPATH nosetests -w test" % _setup_env() + cmd = "PYTHONPATH=%s:$PYTHONPATH nosetests -w tests" % _setup_env() if hasattr(options.test, 'coverage'): cmd += " --with-coverage --cover-package pygtktalog" os.system(cmd) diff --git a/pixmaps/mainicon.png b/pixmaps/mainicon.png deleted file mode 100644 index 55a22b1..0000000 Binary files a/pixmaps/mainicon.png and /dev/null differ diff --git a/pixmaps/mainicon.xcf b/pixmaps/mainicon.xcf deleted file mode 100644 index 06a6164..0000000 Binary files a/pixmaps/mainicon.xcf and /dev/null differ diff --git a/pixmaps/mainicon2.xcf b/pixmaps/mainicon2.xcf deleted file mode 100644 index 7e9949d..0000000 Binary files a/pixmaps/mainicon2.xcf and /dev/null differ diff --git a/pygtktalog/controllers/details.py b/pygtktalog/controllers/details.py deleted file mode 100644 index dd628c6..0000000 --- a/pygtktalog/controllers/details.py +++ /dev/null @@ -1,18 +0,0 @@ -""" - Project: pyGTKtalog - Description: Controller for Details NoteBook - Type: core - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2009-08-30 -""" -from gtkmvc import Controller - - -class DetailsController(Controller): - """ - Controller for details NoteBook. - """ - - def register_view(self, view): - """Default view registration stuff""" - pass diff --git a/pygtktalog/controllers/discs.py b/pygtktalog/controllers/discs.py deleted file mode 100644 index 41be3bf..0000000 --- a/pygtktalog/controllers/discs.py +++ /dev/null @@ -1,214 +0,0 @@ -""" - Project: pyGTKtalog - Description: Controller for Discs TreeView - Type: core - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2009-08-30 -""" -import gtk - -from gtkmvc import Controller - -from pygtktalog.logger import get_logger - -LOG = get_logger("discs ctrl") - - -class DiscsController(Controller): - """ - Controller for discs TreeView - """ - - def __init__(self, model, view): - """ - Initialize DiscsController - """ - LOG.debug(self.__init__.__doc__.strip()) - Controller.__init__(self, model, view) - self.discs_model = self.model.discs.discs - - def register_view(self, view): - """ - Do DiscTree registration - """ - LOG.debug(self.register_view.__doc__.strip()) - view['discs'].set_model(self.discs_model) - - # register observers - self.model.discs.register_observer(self) - - # connect signals to popup menu - framework somehow omits automatic - # signal connection for subviews which are not included to widgets - # tree - sigs = {'expand_all': ('activate', self.on_expand_all_activate), - 'collapse_all': ('activate', self.on_collapse_all_activate), - 'update': ('activate', self.on_update_activate), - 'rename': ('activate', self.on_rename_activate), - 'delete': ('activate', self.on_delete_activate), - 'statistics': ('activate', self.on_statistics_activate)} - for signal in sigs: - view.menu[signal].connect(sigs[signal][0], sigs[signal][1]) - - col = gtk.TreeViewColumn('discs_column') - - cellpb = gtk.CellRendererPixbuf() - cell = gtk.CellRendererText() - - # make cell text editabe - cell.set_property('editable', True) - cell.connect('edited', self.on_editing_done, self.discs_model) - # TODO: find a way how to disable default return keypress on editable - # fields - - col.pack_start(cellpb, False) - col.pack_start(cell, True) - - col.set_attributes(cellpb, stock_id=2) - col.set_attributes(cell, text=1) - - view['discs'].append_column(col) - view['discs'].show() - - # treeview signals - def on_discs_button_press_event(self, treeview, event): - """ - Handle right click on discs treeview - show popup menu. - """ - LOG.debug(self.on_discs_button_press_event.__doc__.strip()) - pathinfo = treeview.get_path_at_pos(int(event.x), int(event.y)) - - if event.button == 3: - if pathinfo: - path = pathinfo[0] - - # Make sure, that there is selected row - sel = treeview.get_selection() - sel.unselect_all() - sel.select_path(path) - - self._popup_menu(sel, event, event.button) - else: - self._popup_menu(None, event, event.button) - return True - - def on_discs_cursor_changed(self, treeview): - """ - Show files on right treeview, after clicking the left disc treeview. - """ - LOG.debug(self.on_discs_cursor_changed.__doc__.strip()) - selection = treeview.get_selection() - path = selection.get_selected_rows()[1][0] - self.model.files.refresh(self.discs_model.get_value(\ - self.discs_model.get_iter(path), 0)) - - def on_discs_key_release_event(self, treeview, event): - """ - Watch for specific keys - """ - LOG.debug(self.on_discs_key_release_event.__doc__.strip()) - if gtk.gdk.keyval_name(event.keyval) == 'Menu': - LOG.debug('Menu key pressed') - self._popup_menu(treeview.get_selection(), event, 0) - return True - return False - - def on_discs_row_activated(self, treeview, path, treecolumn): - """ - If possible, expand or collapse branch of discs tree - """ - LOG.debug(self.on_discs_row_activated.__doc__.strip()) - if treeview.row_expanded(path): - treeview.collapse_row(path) - else: - treeview.expand_row(path, False) - - def on_editing_done(self, cell, path, new_text, model): - """ - Store changed filename text - """ - LOG.debug(self.on_editing_done.__doc__.strip()) - model[path][0].filename = new_text - model[path][1] = new_text - return - - # popup menu signals - def on_expand_all_activate(self, menu_item): - """ - Expand all - """ - LOG.debug(self.on_expand_all_activate.__doc__.strip()) - self.view['discs'].expand_all() - - def on_collapse_all_activate(self, menu_item): - """ - Collapse all - """ - LOG.debug(self.on_collapse_all_activate.__doc__.strip()) - self.view['discs'].collapse_all() - - def on_update_activate(self, menu_item): - """ - Trigger update specified tree entry - """ - LOG.debug(self.on_update_activate.__doc__.strip()) - raise NotImplementedError - - def on_rename_activate(self, menu_item): - """ - Rename disk or directory - """ - LOG.debug(self.on_rename_activate.__doc__.strip()) - treeview = self.view['discs'] - selection = treeview.get_selection() - path = selection.get_selected_rows()[1][0] - treeview.set_cursor(path, treeview.get_column(0), start_editing=True) - - def on_delete_activate(self, menu_item): - """ - Delete disk or directory from catalog - """ - LOG.debug(self.on_delete_activate.__doc__.strip()) - raise NotImplementedError - - def on_statistics_activate(self, menu_item): - """ - Show statistics for selected item - """ - LOG.debug(self.on_statistics_activate.__doc__.strip()) - raise NotImplementedError - - # observable properties - def property_currentdir_value_change(self, model, old, new): - """ - Change of a current dir signalized by other controllers/models - """ - LOG.debug(self.property_currentdir_value_change.__doc__.strip()) - self._set_cursor_to_obj_position(new) - - # private methods - def _popup_menu(self, selection, event, button): - """ - Popup menu for discs treeview. Gather information from discs model, - and trigger menu popup. - """ - LOG.debug(self._popup_menu.__doc__.strip()) - if selection is None: - self.view.menu.set_menu_items_sensitivity(False) - else: - model, list_of_paths = selection.get_selected_rows() - - for path in list_of_paths: - self.view.menu.set_menu_items_sensitivity(True) - self.view.menu.set_update_sensitivity(model.get_value(\ - model.get_iter(path), 0).parent_id == 1) - - self.view.menu['discs_popup'].popup(None, None, None, - button, event.time) - - def _set_cursor_to_obj_position(self, obj): - """ - Set cursor/focus to specified object postion in Discs treeview. - """ - path = self.model.discs.find_path(obj) - self.view['discs'].expand_to_path(path) - self.view['discs'].set_cursor(path) diff --git a/pygtktalog/controllers/files.py b/pygtktalog/controllers/files.py deleted file mode 100644 index 6ed2c0a..0000000 --- a/pygtktalog/controllers/files.py +++ /dev/null @@ -1,282 +0,0 @@ -""" - Project: pyGTKtalog - Description: Controller for Files TreeView - Type: core - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2009-08-30 -""" -import gtk - -from gtkmvc import Controller - -from pygtktalog.pygtkutils import get_tv_item_under_cursor -from pygtktalog.logger import get_logger - -LOG = get_logger("files ctrl") - - -class FilesController(Controller): - """ - Controller for files TreeView list. - """ - - def __init__(self, model, view): - """ - FilesController initialization - """ - Controller.__init__(self, model, view) - self.DND_TARGETS = [('files_tags', 0, 69)] - self.files_model = self.model.files - - def register_view(self, view): - """ - Register view, and setup columns for files treeview - """ - view['files'].set_model(self.files_model.files) - - sigs = {"add_tag": ("activate", self.on_add_tag1_activate), - "delete_tag": ("activate", self.on_delete_tag_activate), - "add_thumb": ("activate", self.on_add_thumb1_activate), - "remove_thumb": ("activate", self.on_remove_thumb1_activate), - "add_image": ("activate", self.on_add_image1_activate), - "remove_image": ("activate", self.on_remove_image1_activate), - "edit": ("activate", self.on_edit2_activate), - "delete": ("activate", self.on_delete3_activate), - "rename": ("activate", self.on_rename2_activate)} - for signal in sigs: - view.menu[signal].connect(sigs[signal][0], sigs[signal][1]) - - view['files'].get_selection().set_mode(gtk.SELECTION_MULTIPLE) - - col = gtk.TreeViewColumn(_('Disc'), gtk.CellRendererText(), text=1) - col.set_sort_column_id(1) - col.set_resizable(True) - col.set_visible(False) - view['files'].append_column(col) - - col = gtk.TreeViewColumn(_('Filename')) - cellpb = gtk.CellRendererPixbuf() - cell = gtk.CellRendererText() - col.pack_start(cellpb, False) - col.pack_start(cell, True) - col.set_attributes(cellpb, stock_id=7) - col.set_attributes(cell, text=2) - col.set_sort_column_id(2) - col.set_resizable(True) - self.view['files'].append_column(col) - - col = gtk.TreeViewColumn(_('Path'), gtk.CellRendererText(), text=3) - col.set_sort_column_id(3) - col.set_resizable(True) - col.set_visible(False) - self.view['files'].append_column(col) - - col = gtk.TreeViewColumn(_('Size'), gtk.CellRendererText(), text=4) - col.set_sort_column_id(4) - col.set_resizable(True) - self.view['files'].append_column(col) - - col = gtk.TreeViewColumn(_('Date'), gtk.CellRendererText(), text=5) - col.set_sort_column_id(5) - col.set_resizable(True) - self.view['files'].append_column(col) - self.view['files'].set_search_column(2) - - # setup d'n'd support - self.view['files'].drag_source_set(gtk.gdk.BUTTON1_MASK, - self.DND_TARGETS, - gtk.gdk.ACTION_COPY) - - # signals - def on_files_drag_data_get(self, treeview, context, selection, - targetType, eventTime): - """responce to "data get" DnD signal""" - # get selection, and send it to the client - if targetType == self.DND_TARGETS[0][2]: - # get selection - treesrl = treeview.get_selection() - model, list_of_paths = treesrl.get_selected_rows() - ids = [] - for path in list_of_paths: - fid = model.get_value(model.get_iter(path), 0) - ids.append(fid) - string = str(tuple(ids)).replace(",)", ")") - selection.set(selection.target, 8, string) - - def on_files_button_press_event(self, treeview, event): - """ - Handle right click on files treeview - show popup menu. - """ - LOG.debug("Mouse button pressed") - pathinfo = treeview.get_path_at_pos(int(event.x), int(event.y)) - - if event.button == 3: # Right mouse button. Show context menu. - LOG.debug("It's a right button") - if pathinfo: - path = pathinfo[0] - - # Make sure, that there is selected row - sel = treeview.get_selection() - sel.unselect_all() - sel.select_path(path) - - self._popup_menu(sel, event, event.button) - else: - self._popup_menu(None, event, event.button) - return True - #try: - # selection = tree.get_selection() - # model, list_of_paths = selection.get_selected_rows() - #except TypeError: - # list_of_paths = [] - - #if len(list_of_paths) == 0: - # # try to select item under cursor - # try: - # path, column, x, y = tree.get_path_at_pos(int(event.x), - # int(event.y)) - # except TypeError: - # # failed, do not show any popup and return - # tree.get_selection().unselect_all() - # return False - # selection.select_path(path[0]) - - #if len(list_of_paths) > 1: - # self.view['add_image'].set_sensitive(False) - # self.view['rename'].set_sensitive(False) - # self.view['edit'].set_sensitive(False) - #else: - # self.view['add_image'].set_sensitive(True) - # self.view['rename'].set_sensitive(True) - # self.view['edit'].set_sensitive(True) - #self.__popup_menu(event, 'files_popup') - #return True - LOG.debug("It's other button") - - def on_files_cursor_changed(self, treeview): - """Show details of selected file/directory""" - file_id = get_tv_item_under_cursor(treeview) - LOG.debug("found item: %s" % file_id) - return - - def on_files_key_release_event(self, treeview, event): - """do something with pressed keys""" - if gtk.gdk.keyval_name(event.keyval) == 'Menu': - try: - selection = treeview.get_selection() - model, list_of_paths = selection.get_selected_rows() - if not list_of_paths: - return - except TypeError: - return - self._popup_menu(selection, event, 0) - - if gtk.gdk.keyval_name(event.keyval) == 'BackSpace': - row, gtk_column = self.view['files'].get_cursor() - if row and gtk_column: - fileob = self.files_model.get_value(row) - if fileob.parent.parent.id != 1: - #self.files_model.refresh(fileob.parent.parent) - # TODO: synchronize with disks - self.model.discs.currentdir = fileob.parent.parent - - self.view['files'].grab_focus() - - - def on_files_row_activated(self, files_obj, row, column): - """ - On directory doubleclick in files listview dive into desired branch. - """ - - fileob = self.files_model.get_value(row=row) - if not fileob.children: - # ONLY directories. files are omitted. - return - - self.files_model.refresh(fileob) - self.model.discs.currentdir = fileob - self.view['files'].grab_focus() - - # TODO: synchronize with disks - return - - def on_add_tag1_activate(self, menu_item): - """ - TODO - """ - LOG.debug(self.on_add_tag1_activate.__doc__.strip()) - raise NotImplementedError - - def on_delete_tag_activate(self, menuitem): - """ - TODO - """ - LOG.debug(self.on_delete_tag_activate.__doc__.strip()) - raise NotImplementedError - - def on_add_thumb1_activate(self, menuitem): - """ - TODO - """ - LOG.debug(self.on_add_thumb1_activate.__doc__.strip()) - raise NotImplementedError - - def on_remove_thumb1_activate(self, menuitem): - """ - TODO - """ - LOG.debug(self.on_remove_thumb1_activate.__doc__.strip()) - raise NotImplementedError - - def on_add_image1_activate(self, menuitem): - """ - TODO - """ - LOG.debug(self.on_add_image1_activate.__doc__.strip()) - raise NotImplementedError - - def on_remove_image1_activate(self, menuitem): - """ - TODO - """ - LOG.debug(self.on_remove_image1_activate.__doc__.strip()) - raise NotImplementedError - - def on_edit2_activate(self, menuitem): - """ - TODO - """ - LOG.debug(self.on_edit2_activate.__doc__.strip()) - raise NotImplementedError - - def on_delete3_activate(self, menuitem): - """ - TODO - """ - LOG.debug(self.on_delete3_activate.__doc__.strip()) - raise NotImplementedError - - def on_rename2_activate(self, menuitem): - """ - TODO - """ - LOG.debug(self.on_rename2_activate.__doc__.strip()) - raise NotImplementedError - - # private methods - def _popup_menu(self, selection, event, button): - """ - Popup menu for files treeview. Gather information from discs model, - and trigger menu popup. - """ - LOG.debug(self._popup_menu.__doc__.strip()) - if selection is None: - self.view.menu.set_menu_items_sensitivity(False) - else: - model, list_of_paths = selection.get_selected_rows() - - for path in list_of_paths: - self.view.menu.set_menu_items_sensitivity(True) - - self.view.menu['files_popup'].popup(None, None, None, - button, event.time) diff --git a/pygtktalog/controllers/main.py b/pygtktalog/controllers/main.py deleted file mode 100644 index f8c1a5e..0000000 --- a/pygtktalog/controllers/main.py +++ /dev/null @@ -1,201 +0,0 @@ -""" - Project: pyGTKtalog - Description: Controller for main window - Type: core - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2009-05-02 -""" -import gtk - -from gtkmvc import Controller - -from pygtktalog.controllers.discs import DiscsController -from pygtktalog.controllers.files import FilesController -#from pygtktalog.controllers.details import DetailsController -#from pygtktalog.controllers.tags import TagcloudController -#from pygtktalog.dialogs import yesno, okcancel, info, warn, error -from pygtktalog.dialogs import open_catalog, save_catalog, error, yesno, about -from pygtktalog.logger import get_logger - -LOG = get_logger("main controller") - -class MainController(Controller): - """ - Controller for main application window - """ - TITLE = "pyGTKtalog" - UNTITLED = _("untitled") - - def __init__(self, model, view): - """ - Initialize MainController, add controllers for trees and details. - """ - LOG.debug(self.__init__.__doc__.strip()) - Controller.__init__(self, model, view) - - # add controllers for files/tags/details components - self.discs = DiscsController(model, view.discs) - self.files = FilesController(model, view.files) - #self.details = DetailsController(model, view.details) - #self.tags = TagcloudController(model, view.tags) - - - def register_view(self, view): - """ - Registration view for MainController class - """ - LOG.debug(self.register_view.__doc__.strip()) - LOG.debug("replace hardcoded defaults with configured!") - view['main'].set_default_size(800, 600) - view['hpaned1'].set_position(200) - if not self.model.tmp_filename: - view.set_widgets_app_sensitivity(False) - view['main'].show() - - # status bar - LOG.debug("register statusbar") - self.context_id = self.view['mainStatus'].get_context_id('status') - self.statusbar_id = \ - self.view['mainStatus'].push(self.context_id, - self.model.status_bar_message) - - - # signals - def on_main_destroy_event(self, widget, event): - """ - Window destroyed. Cleanup before quit. - """ - LOG.debug(self.on_main_destroy_event.__doc__.strip()) - self.on_quit_activate(widget) - return True - - def on_quit_activate(self, widget): - """ - Quit and save window parameters to config file - """ - LOG.debug(self.on_quit_activate.__doc__.strip()) - #if yesno(_("Do you really want to quit?"), - # _("Current database is not saved, any changes will be " - # "lost."), _("Quit application") + " - pyGTKtalog", 0): - self.model.cleanup() - LOG.debug("quit application") - gtk.main_quit() - return False - - def on_new_activate(self, widget): - """ - Create new catalog file - """ - LOG.debug(self.on_new_activate.__doc__.strip()) - self.model.new() - self._set_title() - self.view.set_widgets_app_sensitivity(True) - - def on_open_activate(self, widget): - """ - Open catalog file - """ - LOG.debug(self.on_open_activate.__doc__.strip()) - - if self.model.db_unsaved and 'confirm' in self.model.config and \ - self.model.config['confirm']: - if not yesno(_("Current database is not saved"), - _("Do you really want to open new file and abandon " - "current one?"), - _("Unsaved data")): - LOG.debug("Cancel opening catalog file - unsaved data remain") - return - - initial_path = None - #if self.model.config.recent and self.model.config.recent[0]: - # initial_path = os.path.dirname(self.model.config.recent[0]) - - #if not path: - path = open_catalog(path=initial_path) - if not path: - return - - # cleanup files and details - try: - self.model.files_list.clear() - except: - pass - #self.__hide_details() - #self.view['tag_path_box'].hide() - #buf = self.view['tag_cloud_textview'].get_buffer() - #buf.set_text('') - #self.view['tag_cloud_textview'].set_buffer(buf) - - if not self.model.open(path): - error(_("Cannot open file."), - _("File %s cannot be open") % path, - _("Error opening file")) - else: - #self.__generate_recent_menu() - #self.__activate_ui(path) - #self.__tag_cloud() - self.view.set_widgets_app_sensitivity() - self._set_title() - - return - - def on_about1_activate(self, widget): - """Show about dialog""" - about() - - def on_save_activate(self, widget): - """ - Save current catalog - """ - LOG.debug(self.on_save_activate.__doc__.strip()) - if not self.model.cat_fname: - self.on_save_as_activate(widget) - else: - self.model.save() - self._set_title() - - def on_save_as_activate(self, widget): - """ - Save current catalog under differnet file - """ - LOG.debug(self.on_save_as_activate.__doc__.strip()) - initial_path = None - #if self.model.config.recent[0]: - # initial_path = os.path.dirname(self.model.config.recent[0]) - - path = save_catalog(path=initial_path) - if path: - ret, err = self.model.save(path) - if ret: - #self.model.config.add_recent(path) - self._set_title() - pass - else: - error(_("Cannot write file %s.") % path, - "%s" % err, - _("Error writing file")) - - - # helpers - def _set_title(self): - """ - Get title of the main window, to reflect state of the catalog file - Returns: - String with apropriate title for main form - """ - LOG.debug("change the title") - - if not self.model.tmp_filename: - LOG.debug("application has been initialized, title should" - " be empty") - fname = "" - - elif not self.model.cat_fname: - fname = self.UNTITLED - else: - fname = self.model.cat_fname - - modified = self.model.db_unsaved and "*" or "" - - self.view['main'].set_title("%s%s - %s" % (fname, - modified, self.TITLE)) diff --git a/pygtktalog/controllers/tags.py b/pygtktalog/controllers/tags.py deleted file mode 100644 index 20dfb7d..0000000 --- a/pygtktalog/controllers/tags.py +++ /dev/null @@ -1,23 +0,0 @@ -""" - Project: pyGTKtalog - Description: Controller for Tagcloud TextView - Type: core - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2009-08-30 -""" -from gtkmvc import Controller - - -class TagcloudController(Controller): - """ - Controller for Tagcloud TextView - """ - - #def __init__(self, model, view): - # """Initialize main controller""" - # Controller.__init__(self, model, view) - # return - - def register_view(self, view): - """Default view registration stuff""" - pass diff --git a/pygtktalog/models/__init__.py b/pygtktalog/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pygtktalog/models/details.py b/pygtktalog/models/details.py deleted file mode 100644 index 268b5cc..0000000 --- a/pygtktalog/models/details.py +++ /dev/null @@ -1,31 +0,0 @@ -""" - Project: pyGTKtalog - Description: Model(s) for details part of the application - Type: core - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2010-11-09 -""" -import gtk -import gobject - -from gtkmvc import Model - - -class DetailsModel(Model): - """ - Main model for application. - It is responsible for communicate with database objects and I/O - operations. - """ - - exif = gtk.ListStore(gobject.TYPE_PYOBJECT, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_UINT64, - gobject.TYPE_STRING, - gobject.TYPE_INT, - str) - - __observables__ = ['exif'] - diff --git a/pygtktalog/models/discs.py b/pygtktalog/models/discs.py deleted file mode 100644 index 93db34a..0000000 --- a/pygtktalog/models/discs.py +++ /dev/null @@ -1,107 +0,0 @@ -""" - Project: pyGTKtalog - Description: Model for discs representation - Type: core - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2009-05-02 -""" -import gtk -import gobject - -from gtkmvc import Model - -from pygtktalog.dbobjects import File -from pygtktalog.dbcommon import Session -from pygtktalog.logger import get_logger - -LOG = get_logger("discs model") - - -class DiscsModel(Model): - """ - Model for discs representation. - """ - - currentdir = None - - __observables__ = ("currentdir",) - - def __init__(self): - """ - Initialization. Make some nice defaults. - """ - Model.__init__(self) - self.discs = gtk.TreeStore(gobject.TYPE_PYOBJECT, - gobject.TYPE_STRING, - str) - self.files_model = None - - def clear(self): - """ - Make TreeStore empty - """ - self.discs.clear() - - def refresh(self, session=Session()): - """ - Read objects from database, fill TreeStore model with discs - information - Arguments: - @session current sqlalchemy.orm.session.Session object - """ - LOG.debug("session obj: %s" % str(session)) - dirs = session.query(File).filter(File.type == 1) - dirs = dirs.order_by(File.filename).all() - - def get_children(parent_id=1, iterator=None): - """ - Get all children of the selected parent. - Arguments: - @parent_id - integer with id of the parent (from db) - @iterator - gtk.TreeIter, which points to a path inside model - """ - for fileob in dirs: - if fileob.parent_id == parent_id: - myiter = self.discs.insert_before(iterator, None) - self.discs.set_value(myiter, 0, fileob) - self.discs.set_value(myiter, 1, fileob.filename) - if iterator is None: - self.discs.set_value(myiter, 2, gtk.STOCK_CDROM) - else: - self.discs.set_value(myiter, 2, gtk.STOCK_DIRECTORY) - get_children(fileob.id, myiter) - return - get_children() - return True - - def find_path(self, obj): - """ - Return path of specified File object (which should be the first one) - """ - path = None - gtkiter = self.discs.get_iter_first() - - def get_children(iterator): - """ - Iterate through entire TreeModel, and return path for specified in - outter scope File object - """ - if self.discs.get_value(iterator, 0) == obj: - return self.discs.get_path(iterator) - - if self.discs.iter_has_child(iterator): - path = get_children(self.discs.iter_children(iterator)) - if path: - return path - - iterator = self.discs.iter_next(iterator) - if iterator is None: - return None - - return get_children(iterator) - - path = get_children(gtkiter) - LOG.debug("found path for object '%s': %s" % (str(obj), str(path))) - return path - - diff --git a/pygtktalog/models/files.py b/pygtktalog/models/files.py deleted file mode 100644 index d88069d..0000000 --- a/pygtktalog/models/files.py +++ /dev/null @@ -1,79 +0,0 @@ -""" - Project: pyGTKtalog - Description: Model for files representation - Type: core - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2010-11-12 -""" -import gtk -import gobject - -from gtkmvc import Model - -from pygtktalog.dbcommon import Session -from pygtktalog.logger import get_logger - -LOG = get_logger("files model") - - -class FilesModel(Model): - """ - Model for files representation - """ - - def __init__(self): - """ - Initialization. Make some nice defaults. - """ - Model.__init__(self) - self.files = gtk.ListStore(gobject.TYPE_PYOBJECT, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_UINT64, - gobject.TYPE_STRING, - gobject.TYPE_INT, - str) - self.discs_model = None - - def clear(self): - """ - Cleanup ListStore model - """ - self.files.clear() - - def refresh(self, fileob): - """ - Update files ListStore - Arguments: - fileob - File object - """ - LOG.info("found %d files for File object: %s" % (len(fileob.children), - str(fileob))) - self.files.clear() - - for child in fileob.children: - myiter = self.files.insert_before(None, None) - self.files.set_value(myiter, 0, child) - self.files.set_value(myiter, 1, child.parent_id \ - if child.parent_id != 1 else None) - self.files.set_value(myiter, 2, child.filename) - self.files.set_value(myiter, 3, child.filepath) - self.files.set_value(myiter, 4, child.size) - self.files.set_value(myiter, 5, child.date) - self.files.set_value(myiter, 6, 1) - self.files.set_value(myiter, 7, gtk.STOCK_DIRECTORY \ - if child.type == 1 else gtk.STOCK_FILE) - - def get_value(self, row=None, fiter=None, column=0): - """ - TODO: - """ - if row: - fiter = self.files.get_iter(row) - if not fiter: - LOG.error("ERROR: there is no way to determine gtk_iter object!" - " Please specify valid row or gtk_iter!") - return None - - return self.files.get_value(fiter, column) diff --git a/pygtktalog/models/main.py b/pygtktalog/models/main.py deleted file mode 100644 index e84f98e..0000000 --- a/pygtktalog/models/main.py +++ /dev/null @@ -1,289 +0,0 @@ -""" - Project: pyGTKtalog - Description: Model for main application - Type: core - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2009-05-02 -""" -import os -import bz2 -from string import printable -from tempfile import mkstemp - -import gtk -import gobject -from gtkmvc import ModelMT -from sqlalchemy import create_engine - -from pygtktalog.dbobjects import File, Exif, Group, Gthumb -from pygtktalog.dbobjects import Image, Tag, Thumbnail -from pygtktalog.dbcommon import connect, Meta, Session -from pygtktalog.logger import get_logger -from pygtktalog.models.details import DetailsModel -from pygtktalog.models.discs import DiscsModel -from pygtktalog.models.files import FilesModel - -LOG = get_logger("main model") - - -class MainModel(ModelMT): - """ - Main model for application. - It is responsible for communicate with database objects and I/O - operations. - """ - status_bar_message = _("Idle") - current_disc = None - - __observables__ = ("status_bar_message", "current_disc") - - def __init__(self, filename=None): - """ - Initialization. Make some nice defaults. - Arguments: - filename - String that indicates optionally compressed with bzip2 - file containing sqlite3 database. - """ - ModelMT.__init__(self) - # Opened/saved database location in filesystem. Could be compressed. - self.cat_fname = filename - # Temporary (usually in /tmp) working database. - self.tmp_filename = None - self.config = {} - # SQLAlchemy session object for internal use - self._session = None - # Flag indicates, that db was compressed - # TODO: make it depend on configuration - self.compressed = False - - self.db_unsaved = None - - self.discs = DiscsModel() - self.files = FilesModel() - - if self.cat_fname: - self.open(self.cat_fname) - - def open(self, filename): - """ - Open catalog file and read db - Arguments: - @filename - see MainModel __init__ docstring. - Returns: Bool - true for success, false otherwise. - """ - LOG.debug("filename: '%s'", filename) - self.unsaved_project = False - if not os.path.exists(filename): - LOG.warn("db file '%s' doesn't exist.", filename) - return False - - if not os.path.isfile(filename): - LOG.warn("db file '%s' is not a regular file.", filename) - return False - - self.cat_fname = filename - - if self._open_or_decompress(): - return self.discs.refresh(self._session) - else: - return False - - def save(self, filename=None): - """ - Save tared directory at given catalog fielname - Arguments: - @filename - see MainModel __init__ docstring. - Returns: tuple: - Bool - true for success, false otherwise. - String or None - error message - """ - - if not filename and not self.cat_fname: - LOG.debug("no filename detected!") - return False, None - - if filename: - if not '.sqlite' in filename: - filename += '.sqlite' - else: - filename = filename[:filename.rindex('.sqlite')] + '.sqlite' - - if 'compress' in self.config and self.config['compress']: - filename += '.bz2' - - self.cat_fname = filename - val, err = self._compress_and_save() - if not val: - self.cat_fname = None - return val, err - - def new(self): - """ - Create new catalog - """ - self.cleanup() - self._create_temp_db_file() - self._create_schema() - self.discs.clear() - self.files.clear() - self.db_unsaved = False - - def cleanup(self): - """ - Remove temporary directory tree from filesystem - """ - - if self._session: - self._session.close() - self._session = None - - if self.tmp_filename is None: - return - - try: - os.unlink(self.tmp_filename) - except OSError: - LOG.error("temporary db file doesn't exists!") - except TypeError: - # TODO: file does not exist - create? print error message? - LOG.error("temporary db file doesn't exists!") - else: - LOG.debug("file %s succesfully deleted", self.tmp_filename) - - def _examine_file(self, filename): - """ - Try to recognize file. - Arguments: - @filename - String with full path to file to examine - Returns: 'sql', 'bz2' or None for file sqlite, compressed with bzip2 - or other respectively - """ - try: - head_of_file = open(filename).read(15) - LOG.debug("head of file: %s", filter(lambda x: x in printable, - head_of_file)) - except IOError: - LOG.exception("Error opening file '%s'!", filename) - self.cleanup() - self.cat_fname = None - self.tmp_filename = None - return None - - if head_of_file == "SQLite format 3": - LOG.debug("File format: uncompressed sqlite") - return 'sql' - elif head_of_file[0:10] == "BZh91AY&SY": - LOG.debug("File format: bzip2") - return 'bz2' - else: - return None - - def _open_or_decompress(self): - """ - Try to open file, which user thinks is our catalog file. - Returns: Bool - true for success, false otherwise - """ - filename = os.path.abspath(self.cat_fname) - LOG.info("catalog file: %s", filename) - - if self._session: - self.cleanup() - - self._create_temp_db_file() - LOG.debug("tmp database file: %s", str(self.tmp_filename)) - - examine = self._examine_file(filename) - if examine == "sql": - db_tmp = open(self.tmp_filename, "wb") - db_tmp.write(open(filename).read()) - db_tmp.close() - elif examine == "bz2": - open_file = bz2.BZ2File(filename) - try: - db_tmp = open(self.tmp_filename, "w") - db_tmp.write(open_file.read()) - db_tmp.close() - open_file.close() - except IOError: - self.cleanup() - self.cat_fname = None - self.internal_dirname = None - LOG.exception("File is probably not a bz2!") - return False - if self._examine_file(self.tmp_filename) is not 'sql': - LOG.error("Error opening file '%s' - not a catalog file!", - self.tmp_filename) - self.cleanup() - self.cat_fname = None - self.tmp_filename = None - return False - - else: - LOG.error("Error opening file '%s' - not a catalog file!", - self.tmp_filename) - self.cleanup() - self.cat_fname = None - self.internal_dirname = None - return False - - connect(os.path.abspath(self.tmp_filename)) - self._session = Session() - LOG.debug("session obj: %s" % str(self._session)) - return True - - def _create_temp_db_file(self): - """ - Create new DB file, populate schema. - """ - fd, self.tmp_filename = mkstemp() - LOG.debug("new db filename: %s" % self.tmp_filename) - # close file descriptor, otherwise it can be source of app crash! - # http://www.logilab.org/blogentry/17873 - os.close(fd) - - def _create_schema(self): - """ - """ - self._session = Session() - LOG.debug("session obj: %s" % str(self._session)) - - connect(os.path.abspath(self.tmp_filename)) - - root = File() - root.id = 1 - root.filename = 'root' - root.size = 0 - root.source = 0 - root.type = 0 - root.parent_id = 1 - - self._session.add(root) - self._session.commit() - - def _compress_and_save(self): - """ - Create (and optionaly compress) tar archive from working directory and - write it to specified file. - """ - - # flush all changes - self._session.commit() - - try: - if 'compress' in self.config and self.config['compress']: - output_file = bz2.BZ2File(self.cat_fname, "w") - else: - output_file = open(self.cat_fname, "w") - LOG.debug("save (and optionally compress) successed") - - except IOError, (errno, strerror): - LOG.error("error saving or compressing file", errno, strerror) - return False, strerror - - dbpath = open(self.tmp_filename) - output_file.write(dbpath.read()) - dbpath.close() - output_file.close() - - self.db_unsaved = False - return True, None diff --git a/pygtktalog/views/__init__.py b/pygtktalog/views/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pygtktalog/views/glade/details.glade b/pygtktalog/views/glade/details.glade deleted file mode 100644 index 012db42..0000000 --- a/pygtktalog/views/glade/details.glade +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - GTK_WRAP_WORD - 2 - 2 - False - - - - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - 3 - gtk-missing-image - 6 - - - - - False - False - 1 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - File info - - - tab - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Double click to open image - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Images - - - tab - 1 - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - - - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - EXIF - - - tab - 2 - False - - - - - - diff --git a/pygtktalog/views/glade/discs.glade b/pygtktalog/views/glade/discs.glade deleted file mode 100644 index 20ec0ac..0000000 --- a/pygtktalog/views/glade/discs.glade +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - True - True - False - True - - - - - - - - - - - True - Expand all nodes - _Expand all - True - - - - - - True - Collapse all nodes - _Collapse all - True - - - - - - True - - - - - True - _Update - True - - - - - - True - _Rename - True - - - - - - True - _Delete - True - - - - - - True - _Statistics - True - - - - - diff --git a/pygtktalog/views/glade/files.glade b/pygtktalog/views/glade/files.glade deleted file mode 100644 index e0e0d89..0000000 --- a/pygtktalog/views/glade/files.glade +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - True - True - True - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Add tag - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Remo_ve tag - True - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Add _Thumbnail - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Re_move Thumbnail - True - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - Add images to file. If file have no thumbnail, -thumbnail from first image will be generated. - Add _Images - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Rem_ove All Images - True - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Edit - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Delete - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Rename - True - - - - - diff --git a/pygtktalog/views/glade/main.glade b/pygtktalog/views/glade/main.glade deleted file mode 100644 index ee224bb..0000000 --- a/pygtktalog/views/glade/main.glade +++ /dev/null @@ -1,729 +0,0 @@ - - - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - pyGTKtalog - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical - - - True - - - True - _File - True - - - - - gtk-new - True - True - True - - - - - - gtk-open - True - True - True - - - - - - gtk-save - True - True - True - - - - - - gtk-save-as - True - True - True - - - - - - True - - - - - True - Import - True - - - - - - True - Export - True - - - - - - True - - - - - True - Recent files - True - - - - - - True - - - - - gtk-quit - True - True - True - - - - - - - - - - True - _Edit - True - - - - - - gtk-delete - True - True - True - - - - - - - True - - - - - gtk-find - True - True - True - - - - - - - True - - - - - gtk-preferences - True - True - True - - - - - - - - - - True - _Catalog - True - - - - - True - Add _CD/DVD - True - - - - - - - True - Add _Directory - True - - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Deletes all images from files in current colection - Delete all images - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Delete all thumbnals - True - - - - - - True - Save all images... - True - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Catalog _statistics - True - - - - - - True - - - - - gtk-cancel - True - True - True - - - - - - - - - - True - _View - True - - - - - True - Toolbar - True - - - - - - True - Status bar - True - - - - - - - - - - True - _Help - True - - - - - gtk-about - True - True - True - - - - - - - - - - False - False - 0 - - - - - True - both - - - True - Create new catalog - gtk-new - - - - False - True - - - - - True - Open catalog file - gtk-open - - - - False - True - - - - - True - Save catalog - gtk-save - - - - False - True - - - - - True - - - True - - - - - False - - - - - True - Add CD/DVD to catalog - Add CD - True - gtk-cdrom - - - - False - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Add Dir - gtk-directory - - - - False - True - - - - - True - Find file - gtk-find - - - - False - True - - - - - True - - - True - - - - - False - - - - - True - False - Cancel - True - gtk-cancel - - - - False - True - - - - - True - Quit pyGTKtalog - gtk-quit - - - - False - True - - - - - False - False - 1 - - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - 2 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - etched-in - False - gtk-clear - True - True - - - 0 - - - - - gtk-clear - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - - False - 1 - - - - - False - 2 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - automatic - automatic - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Discs - - - False - tab - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - automatic - automatic - - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Tags - - - 1 - False - tab - - - - - False - True - - - - - True - True - vertical - - - True - True - automatic - automatic - in - - - - - - False - True - - - - - - - - True - False - - - - - 3 - - - - - True - - - False - 1 - 4 - - - - - True - - - True - False - - - 0 - - - - - True - 0.10000000149 - - - False - False - 1 - - - - - False - 5 - - - - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - pyGTKtalog - Image - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-missing-image - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Add images - True - - - - - - True - - - - - True - _Delete images - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Set as _thumbnail - True - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Save images to... - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Remove Thumbnail - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Delete tag - True - - - - - diff --git a/pygtktalog/views/glade/tagcloud.glade b/pygtktalog/views/glade/tagcloud.glade deleted file mode 100644 index 985a68f..0000000 --- a/pygtktalog/views/glade/tagcloud.glade +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - word - False - - - - - - - - - - diff --git a/pygtktalog/views/glade/test.glade b/pygtktalog/views/glade/test.glade deleted file mode 100644 index 3c80715..0000000 --- a/pygtktalog/views/glade/test.glade +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - - True - GTK_ORIENTATION_VERTICAL - GTK_ORIENTATION_VERTICAL - - - True - - - True - _File - True - - - True - - - True - gtk-new - True - True - - - - - True - gtk-open - True - True - - - - - True - gtk-save - True - True - - - - - True - gtk-save-as - True - True - - - - - True - - - - - True - gtk-quit - True - True - - - - - - - - - True - _Edit - True - - - True - - - True - gtk-cut - True - True - - - - - True - gtk-copy - True - True - - - - - True - gtk-paste - True - True - - - - - True - gtk-delete - True - True - - - - - - - - - True - _View - True - - - - - True - _Help - True - - - True - - - True - gtk-about - True - True - - - - - - - - - False - - - - - True - True - - - True - GTK_RESIZE_QUEUE - - - True - True - - - - - False - True - - - - - True - True - - - True - True - - - - - 1 - - - - - True - - - True - 2 - False - - - - - True - - - False - 1 - - - - - False - 2 - - - - - - diff --git a/pygtktalog/views/main.py b/pygtktalog/views/main.py deleted file mode 100644 index 2e2db36..0000000 --- a/pygtktalog/views/main.py +++ /dev/null @@ -1,189 +0,0 @@ -""" - Project: pyGTKtalog - Description: View for main window - Type: core - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2009-05-02 -""" -import os.path - -from gtkmvc import View - - -def get_glade(glade_filename): - """ - Return full path to specified glade file - """ - return os.path.join(os.path.dirname(__file__), "glade", glade_filename) - - -class MainView(View): - """ - Create window from glade file. Note, that glade file is placed in view - module under glade subdirectory. - """ - glade = get_glade("main.glade") - top = "main" - - def __init__(self, top="main"): - """ - Initialize view - """ - View.__init__(self) - self.app_sensitive = None - self['tag_path_box'].hide() - - self.discs = DiscsView() - #self['scrolledwindow_discs'].add_with_viewport(\ - # self.discs.get_top_widget()) - self['scrolledwindow_discs'].add(self.discs.get_top_widget()) - - self.files = FilesView() - self['scrolledwindow_files'].add_with_viewport(\ - self.files.get_top_widget()) - - self.details = DetailsView() - self['vpaned1'].add2(self.details.get_top_widget()) - - def set_widgets_scan_sensitivity(self, sensitive=True): - """ - Activate/deactivate selected widgets while scanning is active - """ - pass - - def set_widgets_app_sensitivity(self, sensitive=True): - """ - Enable/disable widgets for empty application. Usefull for first run - of an application (without any db filename as an argument). - """ - if self.app_sensitive is sensitive: - return - - for widget in ['scrolledwindow_discs', 'scrolledwindow_files', - 'tb_save', 'tb_addcd', 'tb_adddir', 'tb_find', - 'edit1', 'catalog1', 'save1', 'save_as1', 'import', - 'export']: - self[widget].set_sensitive(sensitive) - - # widgets from subclasses - self.details['notebook_details'].set_sensitive(sensitive) - - -class DiscsView(View): - """ - Separate Discs TreeView subview. - """ - glade = get_glade("discs.glade") - top = 'discs' - - def __init__(self): - """ - Initialize view - """ - View.__init__(self) - self.menu = DiscsPopupView() - - -class DiscsPopupView(View): - """ - Separate Discs PopUp subview. - """ - glade = get_glade("discs.glade") - top = 'discs_popup' - - def __init__(self): - """ - Initialize view - """ - View.__init__(self) - - def set_update_sensitivity(self, state): - """ - Set sensitivity for 'update' popup menu item - Arguments: - @state - Bool, if True update menu item will be sensitive, - otherwise not - """ - self['update'].set_sensitive(state) - - def set_menu_items_sensitivity(self, state): - """ - Set sensitivity for couple of popup menu items, which should be - disabled if user right-clicks on no item in treeview. - Arguments: - @state - Bool, if True update menu item will be sensitive, - otherwise not - """ - for item in ['update', 'rename', 'delete', 'statistics']: - self[item].set_sensitive(state) - - -class FilesView(View): - """ - Separate subview of Files TreeView as a table. - """ - glade = get_glade("files.glade") - top = 'files' - - def __init__(self): - """ - Initialize view - """ - View.__init__(self) - self.menu = FilesPopupView() - - -class FilesPopupView(View): - """ - Separate Files PopUp subview. - """ - glade = get_glade("files.glade") - top = 'files_popup' - - def __init__(self): - """ - Initialize view - """ - View.__init__(self) - - def set_menu_items_sensitivity(self, state): - """ - Set sensitivity for couple of popup menu items, which should be - disabled if user right-clicks on no item in treeview. - Arguments: - @state - Bool, if True update menu item will be sensitive, - otherwise not - """ - for item in ["add_tag", "delete_tag", "add_thumb", "remove_thumb", - "add_image", "remove_image", "edit", "delete", "rename"]: - self[item].set_sensitive(state) - - -class TagcloudView(View): - """ - Textview subview with clickable tags. - """ - glade = get_glade("tagcloud.glade") - top = 'tag_cloud_textview' - - def __init__(self): - """ - Initialize view - """ - View.__init__(self) - - -class DetailsView(View): - """ - Notebook subview containing tabs with details and possibly Exif, images - assocated with object and alternatively thumbnail. - """ - glade = get_glade("details.glade") - top = 'notebook_details' - - def __init__(self): - """ - Initialize view - """ - View.__init__(self) - diff --git a/pygtktalog/views/main_menu.py b/pygtktalog/views/main_menu.py deleted file mode 100644 index d22b647..0000000 --- a/pygtktalog/views/main_menu.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Project: pyGTKtalog -Description: Menu for the main window -Type: interface -Author: Roman 'gryf' Dobosz, gryf73@gmail.com -Created: 2010-03-14 21:31:57 -""" - -import gtk - -from gtkmvc import View - - -class MainMenu(View): - def __init__(self): - View.__init__(self) - self['mainMenu'] = gtk.MenuBar() - - self['file_menu'] = gtk.MenuItem(_("_File")) - - accel_group = gtk.AccelGroup() - menu_items = (("/_File", None, None, 0, ""), - ("/File/_New", "N", None, 0, None), - ("/File/_Open", "O", None, 0, None), - ("/File/_Save", "S", None, 0, None), - ("/File/Save _As", None, None, 0, None), - ("/File/sep1", None, None, 0, ""), - ("/File/Quit", "Q", gtk.main_quit, 0, None), - ("/_Options", None, None, 0, ""), - ("/Options/Test", None, None, 0, None), - ("/_Help", None, None, 0, ""), - ("/_Help/About", None, None, 0, None),) - item_factory = gtk.ItemFactory(gtk.MenuBar, "
", accel_group) - item_factory.create_items(menu_items) - - diff --git a/pygtktalog/views/main_toolbar.py b/pygtktalog/views/main_toolbar.py deleted file mode 100644 index 882b4ab..0000000 --- a/pygtktalog/views/main_toolbar.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -Project: pyGTKtalog -Description: Toolbar for the main window -Type: interface -Author: Roman 'gryf' Dobosz, gryf73@gmail.com -Created: 2010-04-20 18:47:49 -""" - -import gtk - -from gtkmvc import View - - -class ToolBar(View): - def __init__(self): - View.__init__(self) - self['maintoolbar'] = gtk.Toolbar() - diff --git a/resources/glade/config.glade b/resources/glade/config.glade deleted file mode 100644 index 481bbb6..0000000 --- a/resources/glade/config.glade +++ /dev/null @@ -1,803 +0,0 @@ - - - - - - 550 - 400 - Preferences - pyGTKtalog - True - GDK_WINDOW_TYPE_HINT_NORMAL - - - True - - - 168 - True - True - 140 - - - True - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - - - True - True - False - True - - - - - - False - True - - - - - True - - - True - 3 - 3 - True - - - False - False - - - - - True - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - - - True - - - 1 - - - True - 5 - 0 - - - True - True - 5 - 12 - - - True - 5 - 2 - 3 - 3 - 3 - - - True - 0 - Mount point: - mnt_entry - - - - - - - - - 100 - True - True - - - 1 - 2 - - - - - - True - True - Browse... - True - 0 - - - - - 2 - 3 - - - - - - - 100 - True - True - - - 1 - 2 - 1 - 2 - - - - - - True - True - Browse... - True - 0 - - - - - 2 - 3 - 1 - 2 - - - - - - - True - 0 - Eject program: - ejt_entry - - - 1 - 2 - - - - - - - - - - - True - <b>CD/DVD drive options</b> - True - - - label_item - - - - - - - - - 1 - - - True - 5 - 0 - - - True - True - 5 - 12 - - - True - - - True - True - Save main window size - True - 0 - True - - - False - False - - - - - True - True - Save paned window sizes - True - 0 - True - - - False - False - 1 - - - - - True - True - Eject CD/DVD after scan - True - 0 - True - - - False - False - 2 - - - - - True - Compress collection - True - 0 - True - - - False - False - 3 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Use external image viewer - 0 - True - - - - 4 - - - - - True - 5 - 1 - 3 - 3 - 3 - - - True - False - 0 - Image viewer: - mnt_entry - - - - - - - - - 100 - True - False - True - - - 1 - 2 - - - - - - True - False - True - Browse... - True - 0 - - - - 2 - 3 - - - - - - - 5 - - - - - - - - - True - <b>General options</b> - True - - - label_item - - - - - - - True - 5 - 0 - - - True - True - 5 - 12 - - - True - - - True - True - Export to XLS - True - 0 - True - - - False - False - - - - - - - - - True - <b>Misc</b> - True - - - label_item - - - - - 1 - - - - - True - 5 - 0 - - - True - True - 5 - 12 - - - True - - - True - True - Confirm quit if there are unsaved data - True - 0 - True - - - False - False - - - - - True - True - Confirm "new" if there are unsaved data - True - 0 - True - - - False - False - 1 - - - - - True - True - Warn about mount/umount errors - True - 0 - True - - - False - False - 2 - - - - - True - True - Warn on delete - True - 0 - True - - - False - False - 3 - - - - - - - - - True - <b>Confirmations</b> - True - - - label_item - - - - - 2 - - - - - 1 - - - - - 1 - - - True - 5 - 0 - - - True - True - 5 - 12 - - - True - - - True - True - Create thumbnails for images - True - 0 - True - - - False - False - - - - - True - True - Scan EXIF data - True - 0 - True - - - False - False - 1 - - - - - True - True - Include gThumb image description - True - 0 - True - - - False - False - 2 - - - - - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Retrive extra information - 0 - True - - - - label_item - - - - - - - 2 - - - - - 1 - - - True - 5 - 0 - - - True - True - 5 - 12 - - - True - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - 2 - 3 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Extension: - GTK_JUSTIFY_RIGHT - ext_entry - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Command: - - - 1 - 2 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 1 - 2 - 1 - 2 - - - - - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - GTK_BUTTONBOX_END - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Add/Change - 0 - - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Delete - 0 - - - - 1 - - - - - False - 1 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - - - - 2 - - - - - - - - - True - <b>Files extensions</b> - True - - - label_item - - - - - - - 3 - - - - - - - - - 1 - - - - - True - True - - - - - 2 - - - - - True - GTK_BUTTONBOX_END - - - True - True - True - gtk-cancel - True - -6 - - - - - - True - True - True - gtk-save - True - -5 - - - - 1 - - - - - False - GTK_PACK_END - - - - - - diff --git a/resources/glade/dialogs.glade b/resources/glade/dialogs.glade deleted file mode 100644 index bd7a587..0000000 --- a/resources/glade/dialogs.glade +++ /dev/null @@ -1,838 +0,0 @@ - - - - - - True - Disk label - pyGTKtalog - GDK_WINDOW_TYPE_HINT_DIALOG - - - True - - - True - - - True - 3 - Disk label - - - False - False - - - - - True - True - True - True - - - - 1 - - - - - 2 - - - - - True - GTK_BUTTONBOX_END - - - True - True - True - gtk-cancel - True - -6 - - - - - True - True - True - True - True - gtk-ok - True - -5 - - - 1 - - - - - False - GTK_PACK_END - - - - - - - True - dialog1 - GDK_WINDOW_TYPE_HINT_DIALOG - - - True - - - True - 3 - - - True - 3 - 0 - - - True - 3 - 12 - - - True - 2 - 3 - 3 - 3 - - - - - - True - True - Browse... - True - 0 - - - - 2 - 3 - 1 - 2 - GTK_FILL - - - - - - True - 0 - Select directory: - - - 1 - 2 - GTK_FILL - - - - - - True - 0 - Disk Label: - - - GTK_FILL - - - - - - True - True - - - 1 - 2 - 1 - 2 - - - - - - True - True - New - - - 1 - 2 - - - - - - - - - - True - <b>Select directory and enter label</b> - True - - - label_item - - - - - - - 2 - - - - - True - GTK_BUTTONBOX_END - - - True - True - True - gtk-cancel - True - -6 - - - - - True - True - True - gtk-ok - True - -5 - - - 1 - - - - - False - GTK_PACK_END - - - - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - pyGTKtalog - stats - GTK_WIN_POS_CENTER_ON_PARENT - GDK_WINDOW_TYPE_HINT_DIALOG - False - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 4 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Total size: - GTK_JUSTIFY_RIGHT - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Number of files: - GTK_JUSTIFY_RIGHT - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Number of directories: - GTK_JUSTIFY_RIGHT - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Number of discs: - GTK_JUSTIFY_RIGHT - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - GTK_SHADOW_ETCHED_IN - - - 1 - 2 - 3 - 4 - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - GTK_SHADOW_ETCHED_IN - - - 1 - 2 - 2 - 3 - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - GTK_SHADOW_ETCHED_IN - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - GTK_SHADOW_ETCHED_IN - - - 1 - 2 - GTK_FILL - - - - - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_END - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-close - True - 0 - - - - - False - GTK_PACK_END - - - - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - pyGTKtalog - rename - GTK_WIN_POS_CENTER_ON_PARENT - GDK_WINDOW_TYPE_HINT_DIALOG - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - - - True - - - True - 3 - Rename - - - False - False - - - - - True - True - True - True - - - 1 - - - - - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_END - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-cancel - True - -6 - - - - - True - True - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-ok - True - -5 - - - 1 - - - - - False - GTK_PACK_END - - - - - - - 500 - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - GTK_WIN_POS_CENTER_ON_PARENT - GDK_WINDOW_TYPE_HINT_DIALOG - False - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - 3 - 12 - 3 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Filename</b> - True - - - label_item - - - - - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - 3 - 12 - 3 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Description</b> - True - - - label_item - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - 3 - 12 - 3 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Note</b> - True - - - label_item - - - - - 2 - - - - - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_END - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-cancel - True - -6 - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-save - True - -5 - - - 1 - - - - - False - GTK_PACK_END - - - - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - GTK_WIN_POS_CENTER_ON_PARENT - GDK_WINDOW_TYPE_HINT_DIALOG - False - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Fill field below with comma separated keywords you will desired to tag selected files. - True - - - False - 3 - - - - - True - True - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - False - 1 - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_END - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-cancel - True - -6 - - - - - True - True - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-ok - True - -5 - - - 1 - - - - - False - GTK_PACK_END - - - - - - - 600 - 400 - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - pyGTKtalog - remove tags - True - GTK_WIN_POS_CENTER_ON_PARENT - GDK_WINDOW_TYPE_HINT_DIALOG - False - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - True - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_END - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-cancel - True - -6 - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-remove - True - -5 - - - 1 - - - - - False - GTK_PACK_END - - - - - - diff --git a/resources/glade/search.glade b/resources/glade/search.glade deleted file mode 100644 index 9ffbbed..0000000 --- a/resources/glade/search.glade +++ /dev/null @@ -1,276 +0,0 @@ - - - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Search for: - - - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-find - True - 1 - - - - False - - - - - False - 2 - - - - - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - 3 - 12 - 3 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - - - 600 - 300 - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - True - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - False - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Search results</b> - True - - - label_item - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - GTK_BUTTONBOX_END - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-close - True - -6 - - - - - - False - 2 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Add tag - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Remo_ve tag - True - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Add _Thumbnail - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Re_move Thumbnail - True - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Add images to file. If file have no thumbnail, -thumbnail from first image will be generated. - Add _Images - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Rem_ove All Images - True - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Edit - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Delete - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Rename - True - - - - - diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index 5e9f6f7..0000000 --- a/src/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - - diff --git a/src/ctrls/__init__.py b/src/ctrls/__init__.py deleted file mode 100644 index 5e9f6f7..0000000 --- a/src/ctrls/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - - diff --git a/src/ctrls/c_config.py b/src/ctrls/c_config.py deleted file mode 100644 index b6efd4b..0000000 --- a/src/ctrls/c_config.py +++ /dev/null @@ -1,282 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- -from gtkmvc import Controller -import views.v_dialogs as Dialogs - -import gtk - -class ConfigController(Controller): - category_dict = { - 'Disk options':'disk_group', - 'General':'general_group', - 'Scan options':'scan_group', - 'Files extensions':'ft_group', - } - category_order = ['General', 'Disk options', 'Scan options', - 'Files extensions',] - - def __init__(self, model): - Controller.__init__(self, model) - return - - def register_view(self, view): - Controller.register_view(self, view) - - # get data from Config object and put it into view - self.view['mnt_entry'].set_text(self.model.confd['cd']) - self.view['ejt_entry'].set_text(self.model.confd['ejectapp']) - self.view['ch_win'].set_active(self.model.confd['savewin']) - self.view['ch_pan'].set_active(self.model.confd['savepan']) - self.view['ch_eject'].set_active(self.model.confd['eject']) - self.view['ch_xls'].set_active(self.model.confd['exportxls']) - self.view['ch_quit'].set_active(self.model.confd['confirmquit']) - self.view['ch_wrnmount'].set_active(self.model.confd['mntwarn']) - self.view['ch_wrndel'].set_active(self.model.confd['delwarn']) - self.view['ch_warnnew'].set_active(self.model.confd['confirmabandon']) - self.view['ch_thumb'].set_active(self.model.confd['thumbs']) - self.view['ch_exif'].set_active(self.model.confd['exif']) - self.view['ch_gthumb'].set_active(self.model.confd['gthumb']) - self.view['ch_compress'].set_active(self.model.confd['compress']) - self.view['ch_retrive'].set_active(self.model.confd['retrive']) - self.view['ch_imageviewer'].set_active(self.model.confd['imgview']) - self.view['entry_imv'].set_text(self.model.confd['imgprog']) - - self.__toggle_scan_group() - - # initialize tree view - self.__setup_category_tree() - - # initialize models for files extensions - vi = self.view['extension_tree'] - vi.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - #self.view['extension_tree'].set_model(self.model.ext_tree) - self.__setup_extension_tree() - - self.view['config'].show() - return - - ################# - # connect signals - def on_extension_tree_cursor_changed(self, tree): - model = tree.get_model() - selected = model.get_value(model.get_iter(tree.get_cursor()[0]), 0) - ext = self.model.confd['extensions'] - self.view['ext_entry'].set_text(selected) - self.view['com_entry'].set_text(ext[selected]) - - def on_category_tree_cursor_changed(self, tree): - """change view to selected row corresponding to group of properties""" - model = tree.get_model() - selected = model.get_value(model.get_iter(tree.get_cursor()[0]), 0) - iterator = tree.get_model().get_iter_first(); - while iterator != None: - if model.get_value(iterator, 0) == selected: - self.view[self.category_dict[model.get_value(iterator, - 0)]].show() - self.view['desc'].set_markup("%s" % selected) - else: - self.view[self.category_dict[model.get_value(iterator, - 0)]].hide() - iterator = tree.get_model().iter_next(iterator); - return - - def on_cancelbutton_clicked(self, button): - self.view['config'].destroy() - return - - def on_okbutton_clicked(self, button): - # get data from view and put it into Config object - self.model.confd['cd'] = self.view['mnt_entry'].get_text() - self.model.confd['ejectapp'] = self.view['ejt_entry'].get_text() - self.model.confd['savewin'] = self.view['ch_win'].get_active() - self.model.confd['savepan'] = self.view['ch_pan'].get_active() - self.model.confd['eject'] = self.view['ch_eject'].get_active() - self.model.confd['exportxls'] = self.view['ch_xls'].get_active() - self.model.confd['confirmquit'] = self.view['ch_quit'].get_active() - self.model.confd['mntwarn'] = self.view['ch_wrnmount'].get_active() - self.model.confd['delwarn'] = self.view['ch_wrndel'].get_active() - v = self.view['ch_warnnew'] - self.model.confd['confirmabandon'] = v.get_active() - self.model.confd['thumbs'] = self.view['ch_thumb'].get_active() - self.model.confd['exif'] = self.view['ch_exif'].get_active() - self.model.confd['gthumb'] = self.view['ch_gthumb'].get_active() - self.model.confd['compress'] = self.view['ch_compress'].get_active() - self.model.confd['retrive'] = self.view['ch_retrive'].get_active() - self.model.confd['imgview'] = self.view['ch_imageviewer'].get_active() - self.model.confd['imgprog'] = self.view['entry_imv'].get_text() - self.model.save() - self.view['config'].destroy() - - def on_button_ejt_clicked(self, button): - fn = self.__show_filechooser("Choose eject program") - self.view['ejt_entryentry_imv'].set_text(fn) - - def on_button_mnt_clicked(self, button): - fn = self.__show_filechooser("Choose mount point") - self.view['mnt_entry'].set_text(fn) - - def on_ch_retrive_toggled(self, widget): - self.__toggle_scan_group() - - def on_ch_imageviewer_toggled(self, checkbox): - state = self.view['ch_imageviewer'].get_active() - for i in ['label_imv', 'entry_imv', 'button_imv']: - self.view[i].set_sensitive(state) - - def on_button_imv_clicked(self, widget): - fn = self.__show_filechooser("Choose image viewer") - self.view['entry_imv'].set_text(fn) - - def on_ext_add_clicked(self, widget): - ext = self.view['ext_entry'].get_text().lower() - com = self.view['com_entry'].get_text() - if len(ext) == 0 and len(com) == 0: - Dialogs.Err("Config - pyGTKtalog", "Error", - "Extension and command required") - return - - if len(com) == 0: - Dialogs.Err("Config - pyGTKtalog", "Error", "Command is empty") - return - - if len(ext) == 0: - Dialogs.Err("Config - pyGTKtalog", "Error", "Extension is empty") - return - - if ext in self.model.confd['extensions'].keys(): - obj = Dialogs.Qst('Alter extension', - 'Alter extension?', - 'Extension "%s" will be altered.' % ext) - if not obj.run(): - return - self.model.confd['extensions'][ext] = com - - self.__setup_extension_tree() - return - - def on_ext_del_clicked(self, widget): - v = self.view['extension_tree'] - model, selection = v.get_selection().get_selected_rows() - if len(selection) == 0: - Dialogs.Err("Config - pyGTKtalog", "Error", "No item selected") - return - elif len(selection) == 1: - sufix = '' - else: - sufix = "s" - - if self.model.confd['delwarn']: - obj = Dialogs.Qst('Delete extension%s' % sufix, - 'Delete extension%s?' % sufix, - 'Object%s will be permanently removed.' % sufix) - if not obj.run(): - return - - for i in selection: - m = self.model.confd['extensions'] - m.pop(model.get_value(model.get_iter(i), 0)) - - self.__setup_extension_tree() - return - - ############################ - # private controller methods - def __setup_extension_tree(self): - self.model.refresh_ext() - - self.view['extension_tree'].set_model(self.model.ext_tree) - - for i in self.view['extension_tree'].get_columns(): - self.view['extension_tree'].remove_column(i) - cell = gtk.CellRendererText() - column = gtk.TreeViewColumn("Extension", cell, text=0) - column.set_resizable(True) - self.view['extension_tree'].append_column(column) - - column = gtk.TreeViewColumn("Command", cell, text=1) - column.set_resizable(True) - self.view['extension_tree'].append_column(column) - - def __toggle_scan_group(self): - for i in ('ch_thumb','ch_exif','ch_gthumb'): - self.view[i].set_sensitive(self.view['ch_retrive'].get_active()) - return - - def __setup_category_tree(self): - category_tree = self.view['category_tree'] - category_tree.set_model(self.model.category_tree) - - self.model.category_tree.clear() - for i in self.category_order: - myiter = self.model.category_tree.insert_before(None,None) - self.model.category_tree.set_value(myiter,0,i) - - cell = gtk.CellRendererText() - column = gtk.TreeViewColumn("Name",cell,text=0) - column.set_resizable(True) - category_tree.append_column(column) - - def __show_filechooser(self, title): - """dialog for choose eject""" - fn = None - dialog = gtk.FileChooserDialog( - title=title, - action=gtk.FILE_CHOOSER_ACTION_OPEN, - buttons=(gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, - gtk.RESPONSE_OK)) - - dialog.set_default_response(gtk.RESPONSE_OK) - - response = dialog.run() - if response == gtk.RESPONSE_OK: - if __debug__: - print "c_config.py: __show_filechooser()", - print dialog.get_filename() - fn = dialog.get_filename() - dialog.destroy() - return fn - - def __show_dirchooser(self): - """dialog for point the mountpoint""" - dialog = gtk.FileChooserDialog( - title="Choose mount point", - action=gtk.FILE_CHOOSER_ACTION_OPEN, - buttons=(gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, - gtk.RESPONSE_OK)) - - dialog.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - dialog.set_filename(self.view['mnt_entry'].get_text()) - dialog.set_default_response(gtk.RESPONSE_OK) - - response = dialog.run() - if response == gtk.RESPONSE_OK: - self.view['mnt_entry'].set_text(dialog.get_filename()) - dialog.destroy() - - pass # end of class - diff --git a/src/ctrls/c_main.py b/src/ctrls/c_main.py deleted file mode 100644 index 0e99b9d..0000000 --- a/src/ctrls/c_main.py +++ /dev/null @@ -1,1696 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - -LICENCE = open('LICENCE').read() - -import os.path -from os import popen -import gtk -import pango - -from gtkmvc import Controller - -from lib import device_helper -from lib.globs import APPL_VERSION -from c_config import ConfigController -from views.v_config import ConfigView -from c_search import SearchController -from views.v_search import SearchView -import views.v_dialogs as Dialogs -from views.v_image import ImageView - - - -class MainController(Controller): - """Controller for main application window""" - scan_cd = False - widgets_all = ('tag_path_box', 'hpaned1', - 'file1', 'edit1', 'view1', 'help1', - 'add_cd', 'add_directory1', 'del_all_images', - 'del_all_thumb', 'stat1', - 'tb_new','tb_open', 'tb_save', 'tb_addcd', 'tb_adddir', - 'tb_find', 'tb_quit') - widgets_cancel = ('cancel','cancel1') - - def __init__(self, model): - """Initialize controller""" - self.DND_TARGETS = [('files_tags', 0, 69)] - Controller.__init__(self, model) - self.hovering = False - self.first = True - return - - def register_view(self, view): - Controller.register_view(self, view) - - # Make not active "Cancel" button and menu_item - for widget in self.widgets_cancel: - self.view[widget].set_sensitive(False) - - # hide "debug" button, if production - # (i.e. python OT running with -OO option) - if __debug__: - self.view['debugbtn'].show() - else: - self.view['debugbtn'].hide() - - # load configuration/defaults and set it to properties - bo = self.model.config.confd['showtoolbar'] - self.view['toolbar1'].set_active(bo) - if bo: - self.view['maintoolbar'].show() - else: - self.view['maintoolbar'].hide() - statusbar_state = self.model.config.confd['showstatusbar'] - self.view['status_bar1'].set_active(statusbar_state) - if self.model.config.confd['showstatusbar']: - self.view['statusprogress'].show() - else: - self.view['statusprogress'].hide() - self.view['hpaned1'].set_position(self.model.config.confd['h']) - self.view['vpaned1'].set_position(self.model.config.confd['v']) - self.view['main'].resize(self.model.config.confd['wx'], - self.model.config.confd['wy']) - - # initialize statusbar - context = self.view['mainStatus'].get_context_id('detailed res') - self.context_id = context - self.statusbar_id = self.view['mainStatus'].push(self.context_id, - "Idle") - - # make tag_cloud_textview recive dnd signals - self.view['tag_cloud_textview'].drag_dest_set(gtk.DEST_DEFAULT_ALL, - self.DND_TARGETS, - gtk.gdk.ACTION_COPY) - - # initialize treeviews - self.__setup_disc_treeview() - self.__setup_files_treeview() - self.__setup_exif_treeview() - - # in case passing catalog filename in command line, unlock gui - if self.model.filename: - self.__activate_ui(self.model.filename) - - # generate recent menu - self.__generate_recent_menu() - - self.view['tag_cloud_textview'].connect("populate-popup", - self.on_tag_cloud_textview_popup) - # in case model has opened file, register tags - if self.model.db_tmp_path: - self.__tag_cloud() - else: - self.model.new() - #self.__activate_ui() - - # Show main window - self.view['main'].show(); - self.view['main'].drag_dest_set(0, [], 0) - return - - ######################################################################### - # Connect signals from GUI, like menu objects, toolbar buttons and so on. - def on_tag_cloud_textview_drag_drop(self, wid, context, x, y, time): - context.finish(True, False, time) - return True - - def on_tag_cloud_textview_drag_motion(self, filestv, context, x, y, time): - context.drag_status(gtk.gdk.ACTION_COPY, time) - iter = filestv.get_iter_at_location(x, y) - buff = filestv.get_buffer() - tag_table = buff.get_tag_table() - - # clear weight of tags - def foreach_tag(texttag, user_data): - """set every text tag's weight to normal""" - texttag.set_property("underline", pango.UNDERLINE_NONE) - tag_table.foreach(foreach_tag, None) - - try: - tag = iter.get_tags()[0] - tag.set_property("underline", pango.UNDERLINE_LOW) - except: - pass - return True - - def on_files_drag_data_get(self, treeview, context, selection, - targetType, eventTime): - """responce to "data get" DnD signal""" - # get selection, and send it to the client - if targetType == self.DND_TARGETS[0][2]: - # get selection - treesrl = treeview.get_selection() - model, list_of_paths = treesrl.get_selected_rows() - ids = [] - for path in list_of_paths: - id = model.get_value(model.get_iter(path), 0) - ids.append(id) - string = str(tuple(ids)).replace(",)", ")") - selection.set(selection.target, 8, string) - - def on_tag_cloud_textview_popup(self, textview, menu): - menu = None - return True - - def on_tag_cloud_textview_event_after(self, textview, event): - """check, if and what tag user clicked. generate apropriate output - in files treview""" - if event.type != gtk.gdk.BUTTON_RELEASE: - return False - if event.button != 1: - return False - - buff = textview.get_buffer() - try: - (start, end) = buff.get_selection_bounds() - except ValueError: - pass - else: - if start.get_offset() != end.get_offset(): - return False - - # get the iter at the mouse position - (x, y) = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, - int(event.x), int(event.y)) - iterator = textview.get_iter_at_location(x, y) - - tags = iterator.get_tags() - - if len(tags) == 1: - tag = tags[0] - self.model.add_tag_to_path(tag.get_property('name')) - self.view['tag_path_box'].show() - - # fill the path of tag - self.view['tag_path'].set_text('') - temp = self.model.selected_tags.values() - self.model.refresh_discs_tree() - #self.on_discs_cursor_changed(textview) - - temp.sort() - for tag1 in temp: - txt = self.view['tag_path'].get_text() - if txt == '': - self.view['tag_path'].set_text(tag1) - else: - self.view['tag_path'].set_text(txt + ", " +tag1) - self.__tag_cloud() - self.model.get_root_entries() - self.__set_files_hiden_columns_visible(True) - self.view['files'].set_model(self.model.files_list) - self.__hide_details() - - def on_tag_cloud_textview_drag_data_received(self, widget, context, x, y, - selection, targetType, time): - """recive data from source TreeView""" - if targetType == self.DND_TARGETS[0][2]: - iter = widget.get_iter_at_location(x, y) - ids = selection.data.rstrip(")").lstrip("(").split(",") - try: - tag = iter.get_tags()[0] - for id in ids: - it = int(tag.get_property('name')) - tn = self.model.get_tag_by_id(it) - self.model.add_tags(int(id.strip()), tn) - except: - if selection.data: - tags = Dialogs.TagsDialog().run() - if not tags: - return - for id in ids: - self.model.add_tags(int(id.strip()), tags) - - self.__tag_cloud() - - def on_edit2_activate(self, menu_item): - try: - selection = self.view['files'].get_selection() - model, list_of_paths = selection.get_selected_rows() - id = model.get_value(model.get_iter(list_of_paths[0]), 0) - except TypeError: - if __debug__: - print "c_main.py: on_edit2_activate(): 0", - print "selected rows" - return - - val = self.model.get_file_info(id) - ret = Dialogs.EditDialog(val).run() - if ret: - self.model.rename(id, ret['filename']) - self.model.update_desc_and_note(id, - ret['description'], ret['note']) - self.__get_item_info(id) - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - - def on_add_thumb1_activate(self, menu_item): - image, only_thumbs = Dialogs.LoadImageFile().run() - if not image: - return - selection = self.view['files'].get_selection() - model, list_of_paths = selection.get_selected_rows() - for path in list_of_paths: - id = model.get_value(model.get_iter(path), 0) - self.model.add_thumbnail(image, id) - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - self.__get_item_info(id) - return - - def on_remove_thumb1_activate(self, menu_item): - if self.model.config.confd['delwarn']: - obj = Dialogs.Qst(_("Delete thumbnails"), _("Delete thumbnails?"), - _("Thumbnails for selected items will be " - "permanently removed from catalog.")) - if not obj.run(): - return - try: - selection = self.view['files'].get_selection() - model, list_of_paths = selection.get_selected_rows() - for path in list_of_paths: - id = model.get_value(model.get_iter(path), 0) - self.model.del_thumbnail(id) - except: - if __debug__: - print "c_main.py: on_remove_thumb1_activate(): error on", - print "getting selected items or removing thumbnails" - return - - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - self.__get_item_info(id) - return - - def on_remove_image1_activate(self, menu_item): - if self.model.config.confd['delwarn']: - obj = Dialogs.Qst(_("Delete images"), _("Delete all images?"), - _("All images for selected items will be " - "permanently removed from catalog.")) - if not obj.run(): - return - try: - selection = self.view['files'].get_selection() - model, list_of_paths = selection.get_selected_rows() - for path in list_of_paths: - id = model.get_value(model.get_iter(path), 0) - self.model.del_images(id) - except: - if __debug__: - print "c_main.py: on_remove_thumb1_activate(): error on", - print "getting selected items or removing thumbnails" - return - - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - self.__get_item_info(id) - return - - def on_images_item_activated(self, iconview, path): - model = iconview.get_model() - iter = model.get_iter(path) - id = model.get_value(iter, 0) - img = self.model.get_image_path(id) - if img: - if self.model.config.confd['imgview'] and \ - len(self.model.config.confd['imgprog'])>0: - popen("%s %s" % (self.model.config.confd['imgprog'], img)) - else: - ImageView(img) - else: - Dialogs.Inf(_("Image view"), _("No Image"), - _("Image file does not exist.")) - - def on_rename1_activate(self, widget): - model, iter = self.view['discs'].get_selection().get_selected() - name = model.get_value(iter, 1) - id = model.get_value(iter, 0) - new_name = Dialogs.InputNewName(name).run() - - if __debug__: - print "c_main.py: on_rename1_activate(): label:", new_name - - if new_name and new_name != name: - self.model.rename(id, new_name) - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - return True - - def on_rename2_activate(self, widget): - try: - selection = self.view['files'].get_selection() - model, list_of_paths = selection.get_selected_rows() - except TypeError: - return - - if len(list_of_paths) != 1: - return - - fid = model.get_value(model.get_iter(list_of_paths[0]), 0) - name = model.get_value(model.get_iter(list_of_paths[0]),1) - - new_name = Dialogs.InputNewName(name).run() - if __debug__: - print "c_main.py: on_rename1_activate(): label:", new_name - - if new_name and new_name != name: - self.model.rename(fid, new_name) - self.__get_item_info(fid) - self.__set_title(filepath=self.model.filename, modified=True) - - #try: - # path, column = self.view['discs'].get_cursor() - # iter = model.get_iter(path) - # self.model.get_root_entries(model.get_value(iter, 0)) - #except TypeError: - # self.model.get_root_entries(1) - # return - - return - - def on_tag_cloud_textview_motion_notify_event(self, widget): - if __debug__: - print "c_main.py: on_tag_cloud_textview_motion_notify_event():" - w = self.view['tag_cloud_textview'].get_window(gtk.TEXT_WINDOW_TEXT) - if w: - w.set_cursor(None) - - def on_clear_clicked(self, w): - self.view['tag_path_box'].hide() - self.model.selected_tags = [] - self.model.refresh_discs_tree() - self.__set_files_hiden_columns_visible(False) - - # cleanup files and detiles - try: - self.model.files_list.clear() - except: - pass - self.__hide_details() - self.on_discs_cursor_changed(w) - self.__tag_cloud() - - def on_tag_cloud_textview_drag_leave(self, textview, dragcontext, time): - """clean up tags properties""" - buff = textview.get_buffer() - tag_table = buff.get_tag_table() - - # clear weight of tags - def foreach_tag(texttag, user_data): - """set every text tag's weight to normal""" - texttag.set_property("underline", pango.UNDERLINE_NONE) - tag_table.foreach(foreach_tag, None) - - # NOTE: text view "links" functions - def on_tag_cloud_textview_visibility_notify_event(self, textview, event): - (wx, wy, mod) = textview.window.get_pointer() - (bx, by) = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, - wx, wy) - self.check_hovering(bx, by) - return False - - def check_hovering(self, x, y): - """Check if the mouse is above a tagged link and if yes show - a hand cursor""" - _hovering = False - textview = self.view['tag_cloud_textview'] - # get the iter at the mouse position - iter = textview.get_iter_at_location(x, y) - - # set _hovering if the iter has the tag "url" - tags = iter.get_tags() - for tag in tags: - _hovering = True - break - - # change the global hovering state - if _hovering != self.hovering or self.first == True: - self.first = False - self.hovering = _hovering - # Set the appropriate cursur icon - if self.hovering: - textview.get_window(gtk.TEXT_WINDOW_TEXT).\ - set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) - else: - textview.get_window(gtk.TEXT_WINDOW_TEXT).\ - set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) - - def on_tag_cloud_click(self, tag, textview, event, b_iter, data): - """react on click on connected tag items""" - tag_cloud = self.view['tag_cloud_textview'] - if event.type == gtk.gdk.BUTTON_RELEASE: - self.model.add_tag_to_path(tag.get_property('name')) - self.view['tag_path_box'].show() - - # fill the path of tag - self.view['tag_path'].set_text('') - temp = self.model.selected_tags.values() - self.model.refresh_discs_tree() - #self.on_discs_cursor_changed(textview) - - temp.sort() - for tag1 in temp: - txt = self.view['tag_path'].get_text() - if txt == '': - self.view['tag_path'].set_text(tag1) - else: - self.view['tag_path'].set_text(txt + ", " +tag1) - self.__tag_cloud() - - def on_main_destroy_event(self, window, event): - """NOTE: quit / close window signal""" - self.on_quit_activate(window) - return True - - def on_quit_activate(self, widget): - """Quit and save window parameters to config file""" - # check if any unsaved project is on go. - if self.model.unsaved_project and \ - self.model.config.confd['confirmquit']: - if not Dialogs.Qst(_("Quit application") + " - pyGTKtalog", - _("Do you really want to quit?"), - _("Current database is not saved, any changes " - "will be lost.")).run(): - return - self.__store_settings() - self.model.cleanup() - gtk.main_quit() - return False - - def on_new_activate(self, widget): - """Create new database file""" - if self.model.unsaved_project: - if not Dialogs.Qst(_("Unsaved data") + " - pyGTKtalog", - _("Do you want to abandon changes?"), - _("Current database is not saved, any changes " - "will be lost.")).run(): - return - self.model.new() - - # cleanup files and details - try: - self.model.files_list.clear() - except: - pass - self.__hide_details() - self.view['tag_path_box'].hide() - self.__activate_ui() - self.__tag_cloud() - - def on_add_cd_activate(self, widget, label=None, current_id=None): - """Add directory structure from cd/dvd disc""" - mount, msg = device_helper.volmount(self.model.config.confd['cd']) - if mount: - guessed_label = device_helper.volname(self.model.config.confd['cd']) - if not label: - label = Dialogs.InputDiskLabel(guessed_label).run() - if label: - self.scan_cd = True - for widget in self.widgets_all: - self.view[widget].set_sensitive(False) - self.model.source = self.model.CD - self.model.scan(self.model.config.confd['cd'], label, - current_id) - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - else: - device_helper.volumount(self.model.config.confd['cd']) - return True - else: - Dialogs.Wrn(_("Error mounting device") + " - pyGTKtalog", - _("Cannot mount device pointed to %s") % - self.model.config.confd['cd'], - _("Last mount message:\n%s") % msg) - return False - - def on_add_directory_activate(self, widget, path=None, label=None, - current_id=None): - """Show dialog for choose drectory to add from filesystem.""" - if not label or not path: - res = Dialogs.PointDirectoryToAdd().run() - if res != (None, None): - path = res[1] - label = res[0] - else: - return False - - self.scan_cd = False - self.model.source = self.model.DR - self.model.scan(path, label, current_id) - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - return - - # NOTE: about - def on_about1_activate(self, widget): - """Show about dialog""" - Dialogs.Abt("pyGTKtalog", "%d.%d.%d" % APPL_VERSION, "About", - ["Roman 'gryf' Dobosz"], LICENCE) - return - - def on_preferences_activate(self, widget): - c = ConfigController(self.model.config) - v = ConfigView(c) - return - - def on_status_bar1_activate(self, widget): - """Toggle visibility of statusbat and progress bar.""" - activity = self.view['status_bar1'].get_active() - self.model.config.confd['showstatusbar'] = activity - if self.view['status_bar1'].get_active(): - self.view['statusprogress'].show() - else: - self.view['statusprogress'].hide() - - def on_toolbar1_activate(self, widget): - """Toggle visibility of toolbar bar.""" - activity = self.view['toolbar1'].get_active() - self.model.config.confd['showtoolbar'] = activity - if self.view['toolbar1'].get_active(): - self.view['maintoolbar'].show() - else: - self.view['maintoolbar'].hide() - - def on_save_activate(self, widget): - """Save catalog to file""" - # FIXME: Traceback when recent is null - if self.model.filename: - self.model.save() - self.__set_title(filepath=self.model.filename) - else: - self.on_save_as_activate(widget) - - def on_save_as_activate(self, widget): - """Save database to file under different filename.""" - initial_path = None - if self.model.config.recent[0]: - initial_path = os.path.dirname(self.model.config.recent[0]) - - path = Dialogs.ChooseDBFilename(initial_path).run() - if path: - ret, err = self.model.save(path) - if ret: - self.model.config.add_recent(path) - self.__set_title(filepath=path) - else: - Dialogs.Err(_("Error writing file") + " - pyGTKtalog", - _("Cannot write file %s.") % path, "%s" % err) - - def on_stat1_activate(self, menu_item, selected_id=None): - data = self.model.get_stats(selected_id) - label = Dialogs.StatsDialog(data).run() - - def on_statistics1_activate(self, menu_item): - model = self.view['discs'].get_model() - try: - path, column = self.view['discs'].get_cursor() - selected_iter = self.model.discs_tree.get_iter(path) - except: - return - - selected_id = self.model.discs_tree.get_value(selected_iter, 0) - self.on_stat1_activate(menu_item, selected_id) - - def on_open_activate(self, widget, path=None): - """Open catalog file""" - confirm = self.model.config.confd['confirmabandon'] - if self.model.unsaved_project and confirm: - obj = Dialogs.Qst(_("Unsaved data") + " - pyGTKtalog", - _("There is not saved database"), - _("Pressing Ok will abandon catalog.")) - if not obj.run(): - return - - initial_path = None - if self.model.config.recent and self.model.config.recent[0]: - initial_path = os.path.dirname(self.model.config.recent[0]) - - if not path: - path = Dialogs.LoadDBFile(initial_path).run() - - # cleanup files and details - try: - self.model.files_list.clear() - except: - pass - self.__hide_details() - self.view['tag_path_box'].hide() - buf = self.view['tag_cloud_textview'].get_buffer() - buf.set_text('') - self.view['tag_cloud_textview'].set_buffer(buf) - - if path: - if not self.model.open(path): - Dialogs.Err(_("Error opening file") + " - pyGTKtalog", - _("Cannot open file %s.") % path) - else: - self.__generate_recent_menu() - self.__activate_ui(path) - self.__tag_cloud() - return - - def on_discs_cursor_changed(self, widget): - """Show files on right treeview, after clicking the left disc - treeview.""" - model = self.view['discs'].get_model() - path, column = self.view['discs'].get_cursor() - if path: - iter = self.model.discs_tree.get_iter(path) - id = self.model.discs_tree.get_value(iter, 0) - self.__set_files_hiden_columns_visible(False) - self.model.get_root_entries(id) - self.__get_item_info(id) - - return - - def on_discs_row_activated(self, treeview, path, treecolumn): - """If possible, expand or collapse branch of discs tree""" - if treeview.row_expanded(path): - treeview.collapse_row(path) - else: - treeview.expand_row(path, False) - return - - def on_discs_key_release_event(self, treeview, event): - if gtk.gdk.keyval_name(event.keyval) == 'Menu': - ids = self.__get_tv_selection_ids(treeview) - menu_items = ['update1','rename1','delete2', 'statistics1'] - for menu_item in menu_items: - self.view[menu_item].set_sensitive(not not ids) - self.__popup_menu(event, 'discs_popup') - return True - return False - - def on_images_key_release_event(self, iconview, event): - if gtk.gdk.keyval_name(event.keyval) == 'Menu': - self.__popup_menu(event, 'img_popup') - return True - return False - - def on_images_button_press_event(self, iconview, event): - #try: - # path_and_cell = iconview.get_item_at_pos(int(event.x), - # int(event.y)) - #except TypeError: - # return False - - if event.button == 3: # Right mouse button. Show context menu. - #try: - # iconview.select_path(path_and_cell[0]) - #except TypeError: - # return False - - self.__popup_menu(event, 'img_popup') - return True - return False - - def on_img_delete_activate(self, menu_item): - """delete selected images (with thumbnails)""" - list_of_paths = self.view['images'].get_selected_items() - if not list_of_paths: - Dialogs.Inf(_("Delete images"), _("No images selected"), - _("You have to select at least one image to delete.")) - return - - if self.model.config.confd['delwarn']: - obj = Dialogs.Qst(_("Delete images"), _("Delete selected images?"), - _("Selected images will be permanently removed" - " from catalog.")) - if not obj.run(): - return - - model = self.view['images'].get_model() - ids = [] - for path in list_of_paths: - iterator = model.get_iter(path) - ids.append(model.get_value(iterator, 0)) - - for fid in ids: - self.model.delete_image(fid) - - # refresh files tree - try: - path, column = self.view['files'].get_cursor() - model = self.view['files'].get_model() - iterator = model.get_iter(path) - fid = model.get_value(iterator, 0) - self.__get_item_info(fid) - except: - pass - - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - return - - def on_img_save_activate(self, menu_item): - """export images (not thumbnails) into desired direcotry""" - dialog = Dialogs.SelectDirectory(_("Choose directory to save images")) - filepath = dialog.run() - - if not filepath: - return - - list_of_paths = self.view['images'].get_selected_items() - model = self.view['images'].get_model() - - count = 0 - - if len(list_of_paths) == 0: - # no picture was selected. default to save all of them - for image in model: - if self.model.save_image(image[0], filepath): - count += 1 - else: - # some pictures was selected, save them - for path in list_of_paths: - icon_iter = model.get_iter(path) - img_id = model.get_value(icon_iter, 0) - if self.model.save_image(img_id, filepath): - count += 1 - - if count > 0: - Dialogs.Inf(_("Save images"), - _("%d images was succsefully saved.") % count, - _("Images are placed in directory:\n%s.") % filepath) - else: - description = _("Images probably don't have real images - only" - " thumbnails.") - Dialogs.Inf(_("Save images"), _("No images was saved."), - description) - return - - def on_img_thumbset_activate(self, menu_item): - """set selected image as thumbnail""" - list_of_paths = self.view['images'].get_selected_items() - - if not list_of_paths: - Dialogs.Inf(_("Set thumbnail"), _("No image selected"), - _("You have to select one image to set as thumbnail.")) - return - if len(list_of_paths) >1: - Dialogs.Inf(_("Set thumbnail"), _("To many images selected"), - _("You have to select one image to set as thumbnail.")) - return - - model = self.view['images'].get_model() - iter = model.get_iter(list_of_paths[0]) - id = model.get_value(iter, 0) - self.model.set_image_as_thumbnail(id) - - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - return - - def on_img_add_activate(self, menu_item): - self.on_add_image1_activate(menu_item) - - def on_thumb_box_button_press_event(self, widget, event): - if event.button == 3: - self.__popup_menu(event, 'th_popup') - - def on_discs_button_press_event(self, treeview, event): - try: - path, column, x, y = treeview.get_path_at_pos(int(event.x), - int(event.y)) - except TypeError: - treeview.get_selection().unselect_all() - return False - - if event.button == 3: - """Right mouse button. Show context menu.""" - try: - selection = treeview.get_selection() - model, list_of_paths = selection.get_selected_rows() - except TypeError: - list_of_paths = [] - - if path not in list_of_paths: - treeview.get_selection().unselect_all() - treeview.get_selection().select_path(path) - # setup menu - ids = self.__get_tv_selection_ids(treeview) - menu_items = ['update1','rename1','delete2', 'statistics1'] - for menu_item in menu_items: - self.view[menu_item].set_sensitive(not not ids) - - # checkout, if we dealing with disc or directory - # if ancestor is 'root', then activate "update" menu item - treeiter = self.model.discs_tree.get_iter(path) - ancestor = self.model.discs_tree.get_value(treeiter, 3) == 1 - self.view['update1'].set_sensitive(ancestor) - - self.__popup_menu(event) - - def on_export_activate(self, menu_item): - """export db file and coressponding images to tar.bz2 archive""" - dialog = Dialogs.ChooseFilename(None, _("Choose export file")) - filepath = dialog.run() - - if not filepath: - return - - # TODO: dokończyć ten shit - - def on_collapse_all1_activate(self, menu_item): - self.view['discs'].collapse_all() - return - - def on_files_button_press_event(self, tree, event): - if event.button == 3: # Right mouse button. Show context menu. - try: - selection = tree.get_selection() - model, list_of_paths = selection.get_selected_rows() - except TypeError: - list_of_paths = [] - - if len(list_of_paths) == 0: - # try to select item under cursor - try: - path, column, x, y = tree.get_path_at_pos(int(event.x), - int(event.y)) - except TypeError: - # failed, do not show any popup and return - tree.get_selection().unselect_all() - return False - selection.select_path(path[0]) - - if len(list_of_paths) > 1: - self.view['add_image1'].set_sensitive(False) - self.view['rename2'].set_sensitive(False) - self.view['edit2'].set_sensitive(False) - else: - self.view['add_image1'].set_sensitive(True) - self.view['rename2'].set_sensitive(True) - self.view['edit2'].set_sensitive(True) - self.__popup_menu(event, 'files_popup') - return True - - def on_files_cursor_changed(self, treeview): - """Show details of selected file/directory""" - file_id = self.__get_tv_id_under_cursor(treeview) - self.__get_item_info(file_id) - return - - def on_files_key_release_event(self, treeview, event): - """do something with pressed keys""" - if gtk.gdk.keyval_name(event.keyval) == 'Menu': - try: - selection = treeview.get_selection() - model, list_of_paths = selection.get_selected_rows() - if not list_of_paths: - return - except TypeError: - return - self.__popup_menu(event, 'files_popup') - - if gtk.gdk.keyval_name(event.keyval) == 'BackSpace': - d_path, d_column = self.view['discs'].get_cursor() - if d_path and d_column: - # easy way - model = self.view['discs'].get_model() - child_iter = model.get_iter(d_path) - parent_iter = model.iter_parent(child_iter) - if parent_iter: - self.view['discs'].set_cursor(model.get_path(parent_iter)) - else: - # hard way - f_model = treeview.get_model() - first_iter = f_model.get_iter_first() - first_child_value = f_model.get_value(first_iter, 0) - # get two steps up - val = self.model.get_parent_id(first_child_value) - parent_value = self.model.get_parent_id(val) - iter = self.model.discs_tree.get_iter_first() - while iter: - current_value = self.model.discs_tree.get_value(iter, - 0) - if current_value == parent_value: - path = self.model.discs_tree.get_path(iter) - self.view['discs'].set_cursor(path) - iter = None - else: - iter = self.model.discs_tree.iter_next(iter) - #if gtk.gdk.keyval_name(event.keyval) == 'Delete': - # for file_id in self.__get_tv_selection_ids(treeview): - # self.main.delete(file_id) - - ids = self.__get_tv_selection_ids(self.view['files']) - - def on_files_row_activated(self, files_obj, row, column): - """On directory doubleclick in files listview dive into desired - branch.""" - f_iter = self.model.files_list.get_iter(row) - current_id = self.model.files_list.get_value(f_iter, 0) - - if self.model.files_list.get_value(f_iter, 6) == 1: - # ONLY directories. files are omitted. - self.__set_files_hiden_columns_visible(False) - self.model.get_root_entries(current_id) - - d_path, d_column = self.view['discs'].get_cursor() - if d_path: - if not self.view['discs'].row_expanded(d_path): - self.view['discs'].expand_row(d_path, False) - - discs_model = self.model.discs_tree - iterator = discs_model.get_iter(d_path) - new_iter = discs_model.iter_children(iterator) - if new_iter: - while new_iter: - current_value = discs_model.get_value(new_iter, 0) - if current_value == current_id: - path = discs_model.get_path(new_iter) - self.view['discs'].set_cursor(path) - new_iter = discs_model.iter_next(new_iter) - return - - def on_cancel_clicked(self, widget): - """When scanning thread is runing and user push the cancel button, - models abort attribute trigger cancelation for scan operation""" - self.model.abort = True - return - - def on_find_activate(self, widget): - """search button/menu activated. Show search window""" - if not self.model.search_created: - c = SearchController(self.model) - v = SearchView(c) - return - - # NOTE: recent signal - def recent_item_response(self, path): - self.on_open_activate(self, path) - return - - # NOTE: add tags / images - def on_delete_tag2_activate(self, menu_item): - pass - - def on_delete_tag_activate(self, menu_item): - ids = self.__get_tv_selection_ids(self.view['files']) - if not ids: - Dialogs.Inf(_("Remove tags"), _("No files selected"), - _("You have to select some files first.")) - return - - tags = self.model.get_tags_by_file_id(ids) - if tags: - d = Dialogs.TagsRemoveDialog(tags) - retcode, retval = d.run() - if retcode=="ok" and not retval: - Dialogs.Inf(_("Remove tags"), _("No tags selected"), - _("You have to select any tag to remove from" - " files.")) - return - elif retcode == "ok" and retval: - self.model.delete_tags(ids, retval) - self.model.get_root_entries() - self.view['files'].set_model(self.model.files_list) - self.__tag_cloud() - return - - def on_add_tag1_activate(self, menu_item): - tags = Dialogs.TagsDialog().run() - if not tags: - return - ids = self.__get_tv_selection_ids(self.view['files']) - for id in ids: - self.model.add_tags(id, tags) - - self.__tag_cloud() - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - self.__get_item_info(id) - return - - def on_add_image1_activate(self, menu_item): - dialog = Dialogs.LoadImageFile(True) - msg = _("Don't copy images. Generate only thumbnails.") - toggle = gtk.CheckButton(msg) - toggle.show() - dialog.dialog.set_extra_widget(toggle) - - images, only_thumbs = dialog.run() - if not images: - return - - for image in images: - try: - selection = self.view['files'].get_selection() - model, list_of_paths = selection.get_selected_rows() - id = model.get_value(model.get_iter(list_of_paths[0]), 0) - except: - try: - path, column = self.view['files'].get_cursor() - model = self.view['files'].get_model() - iter = model.get_iter(path) - id = model.get_value(iter, 0) - except: - return - self.model.add_image(image, id, only_thumbs) - - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - - self.__get_item_info(id) - return - - def on_update1_activate(self, menu_item): - """Update disc under cursor position""" - path, column = self.view['discs'].get_cursor() - model = self.view['discs'].get_model() - - # determine origin label and filepath - filepath, label = self.model.get_label_and_filepath(path) - - fid = model.get_value(model.get_iter(path), 0) - - if self.model.get_source(path) == self.model.CD: - self.on_add_cd_activate(menu_item, label, fid) - elif self.model.get_source(path) == self.model.DR: - self.on_add_directory_activate(menu_item, filepath, label, fid) - - return - - def on_delete1_activate(self, menu_item): - """Main menu delete dispatcher""" - if self.view['files'].is_focus(): - self.on_delete3_activate(menu_item) - if self.view['discs'].is_focus(): - self.on_delete2_activate(menu_item) - if self.view['images'].is_focus(): - self.on_img_delete_activate(menu_item) - - def on_delete2_activate(self, menu_item): - try: - selection = self.view['discs'].get_selection() - model, selected_iter = selection.get_selected() - except: - return - - if not selected_iter: - Dialogs.Inf(_("Delete disc"), _("No disc selected"), - _("You have to select disc first before you " - "can delete it")) - return - - if self.model.config.confd['delwarn']: - name = model.get_value(selected_iter, 1) - obj = Dialogs.Qst(_("Delete %s") % name, _("Delete %s?") % name, - _("Object will be permanently removed.")) - if not obj.run(): - return - - # remove from model - path = model.get_path(selected_iter) - fid = self.model.discs_tree.get_value(selected_iter, 0) - model.remove(selected_iter) - selection.select_path(path) - - if not selection.path_is_selected(path): - row = path[0]-1 - if row >= 0: - selection.select_path((row,)) - path = (row, ) - - # delete from db - self.model.delete(fid) - - # refresh files treeview - try: - id = model.get_value(model.get_iter(path), 0) - except: - id = model.get_value(model.get_iter_first(), 0) - self.model.get_root_entries(id) - - # refresh file info view - self.__get_item_info(id) - - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - return - - def on_delete3_activate(self, menu_item): - """delete files selected on files treeview""" - dmodel = self.model.discs_tree - try: - selection = self.view['files'].get_selection() - model, list_of_paths = selection.get_selected_rows() - except TypeError: - return - - if not list_of_paths: - Dialogs.Inf(_("Delete files"), _("No files selected"), - _("You have to select at least one file to delete.")) - return - - if self.model.config.confd['delwarn']: - obj = Dialogs.Qst(_("Delete files"), _("Delete files?"), - _("Selected files and directories will be " - "permanently\n removed from catalog.")) - if not obj.run(): - return - - def foreach_disctree(zmodel, zpath, ziter, d): - if d[0] == zmodel.get_value(ziter, 0): - d[1].append(zpath) - return False - - ids = [] - for p in list_of_paths: - val = model.get_value(model.get_iter(p), 0) - ids.append(val) - - for fid in ids: - # delete from db - self.model.delete(fid) - - try: - # try to select something - selection = self.view['discs'].get_selection() - model, list_of_paths = selection.get_selected_rows() - if not list_of_paths: - list_of_paths = [1] - fiter = model.get_iter(list_of_paths[0]) - self.model.get_root_entries(model.get_value(fiter, 0)) - except TypeError: - return - - buf = gtk.TextBuffer() - self.view['description'].set_buffer(buf) - self.view['thumb_box'].hide() - self.view['exifinfo'].hide() - self.view['img_container'].hide() - - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - return - - def on_th_delete_activate(self, menu_item): - if self.model.config.confd['delwarn']: - obj = Dialogs.Qst(_("Delete thumbnail"), _("Delete thumbnail?"), - _("Current thumbnail will be permanently removed" - " from catalog.")) - if not obj.run(): - return - path, column = self.view['files'].get_cursor() - model = self.view['files'].get_model() - iter = model.get_iter(path) - id = model.get_value(iter, 0) - if id: - self.model.del_thumbnail(id) - self.__get_item_info(id) - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - return - - def on_del_all_images_activate(self, menu_item): - if self.model.config.confd['delwarn']: - title = 'Delete images' - question = 'Delete all images?' - dsc = "All images and it's thumbnails will be permanently removed" - dsc += " from catalog." - obj = Dialogs.Qst(title, question, dsc) - if not obj.run(): - return - - self.model.delete_all_images() - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - - try: - path, column = self.view['files'].get_cursor() - model = self.view['files'].get_model() - fiter = model.get_iter(path) - fid = model.get_value(fiter, 0) - if fid: - self.__get_item_info(fid) - except: - pass - return - - def on_del_all_thumb_activate(self, menu_item): - if self.model.config.confd['delwarn']: - title = 'Delete images' - question = 'Delete all images?' - dsc = "All images without thumbnails will be permanently removed" - dsc += " from catalog." - obj = Dialogs.Qst(title, question, dsc) - if not obj.run(): - return - - self.model.del_all_thumbnail() - self.model.unsaved_project = True - self.__set_title(filepath=self.model.filename, modified=True) - - try: - path, column = self.view['files'].get_cursor() - model = self.view['files'].get_model() - fiter = model.get_iter(path) - fid = model.get_value(fiter, 0) - if fid: - self.__get_item_info(fid) - except: - pass - return - - def on_edit1_activate(self, menu_item): - """Make sufficient menu items sensitive in right cases""" - # TODO: consolidate popup-menus with edit menu - if self.view['tag_cloud_textview'].is_focus(): - self.view['delete1'].set_sensitive(False) - else: - self.view['delete1'].set_sensitive(True) - - def on_debugbtn_clicked(self, widget): - """Debug. To remove in stable version, including button in GUI""" - if __debug__: - print "\nc_main.py: on_debugbtn_clicked()" - print "------" - print "unsaved_project = %s" % self.model.unsaved_project - print "filename = %s" % self.model.filename - print "internal_filename = %s" % self.model.internal_dirname - print "db_connection = %s" % self.model.db_connection - print "abort = %s" % self.model.abort - print "self.model.config.recent = %s" % self.model.config.recent - print "source: %s" % self.model.source - print "files have focus", self.view['files'].is_focus() - print "discs have focus", self.view['discs'].is_focus() - print "images have focus", self.view['images'].is_focus() - c = self.view['files'].get_column(0) - c.set_visible(not c.get_visible()) - c = self.view['files'].get_column(2) - c.set_visible(not c.get_visible()) - - ##################### - # observed properetis - def property_point_value_change(self, model, old, new): - """File was activated in search window through the observable - property - select it on the discs and files treeview, and get - file description""" - - if new: - discs_tree = self.view['discs'] - discs_model = discs_tree.get_model() - parent_id = self.model.get_parent_id(new) - - def foreach_disctree(model, path, iterator, data): - """find path in model to desired value""" - if model.get_value(iterator, 0) == data: - discs_tree.expand_to_path(path) - discs_tree.set_cursor(path) - return False - - discs_model.foreach(foreach_disctree, parent_id) - - files_list = self.view['files'] - files_model = files_list.get_model() - - def foreach_fileslist(model, path, iterator, data): - """find path in model to desired value""" - if model.get_value(iterator, 0) == data: - files_list.set_cursor(path) - self.__get_item_info(data) - return False - - files_model.foreach(foreach_fileslist, new) - return - - def property_statusmsg_value_change(self, model, old, new): - if self.statusbar_id: - self.view['mainStatus'].remove(self.context_id, self.statusbar_id) - self.statusbar_id = self.view['mainStatus'].push(self.context_id, - "%s" % new) - return - - def property_busy_value_change(self, model, old, new): - if new != old: - for w in self.widgets_all: - self.view[w].set_sensitive(not new) - for widget in self.widgets_cancel: - self.view[widget].set_sensitive(new) - if not new and self.scan_cd: - self.scan_cd = False - # umount/eject cd - ejectapp = self.model.config.confd['ejectapp'] - if self.model.config.confd['eject'] and ejectapp: - msg = device_helper.eject_cd(ejectapp, - self.model.config.confd['cd']) - if msg != 'ok': - title = _("Error ejecting device") - Dialogs.Wrn(title + " - pyGTKtalog", - _("Cannot eject device pointed to %s") % - self.model.config.confd['cd'], - _("Last eject message:\n%s") % msg) - else: - msg = device_helper.volumount(self.model.config.confd['cd']) - if msg != 'ok': - title = _("Error unmounting device") - Dialogs.Wrn(title + " - pyGTKtalog", - _("Cannot unmount device pointed to %s") % - self.model.config.confd['cd'], - _("Last umount message:\n%s") % msg) - return - - def property_progress_value_change(self, model, old, new): - self.view['progressbar1'].set_fraction(new) - return - - ######################### - # private class functions - def __set_files_hiden_columns_visible(self, boolean): - """switch visibility of default hidden columns in files treeview""" - self.view['files'].get_column(0).set_visible(boolean) - self.view['files'].get_column(2).set_visible(boolean) - - def __get_tv_selection_ids(self, treeview): - """get selection from treeview and return coresponding ids' from - connected model or None""" - ids = [] - try: - selection = treeview.get_selection() - model, list_of_paths = selection.get_selected_rows() - for path in list_of_paths: - ids.append(model.get_value(model.get_iter(path), 0)) - return ids - except: - # DEBUG: treeview have no selection or smth is broken - if __debug__: - print "c_main.py: __get_tv_selection_ids(): error on", - print "getting selected items" - return - return None - - def __get_tv_id_under_cursor(self, treeview): - """get id of item form tree view under cursor""" - path, column = treeview.get_cursor() - if path and column: - model = treeview.get_model() - tm_iter = model.get_iter(path) - item_id = model.get_value(tm_iter, 0) - return item_id - return None - - def __setup_disc_treeview(self): - """Setup TreeView discs widget as tree.""" - self.view['discs'].set_model(self.model.discs_tree) - - c = gtk.TreeViewColumn('Filename') - - # one row contains image and text - cellpb = gtk.CellRendererPixbuf() - cell = gtk.CellRendererText() - c.pack_start(cellpb, False) - c.pack_start(cell, True) - c.set_attributes(cellpb, stock_id=2) - c.set_attributes(cell, text=1) - - self.view['discs'].append_column(c) - - # registration of treeview signals: - - return - - def __setup_iconview(self): - """Setup IconView images widget.""" - self.view['images'].set_model(self.model.images_store) - self.view['images'].set_pixbuf_column(1) - self.view['images'].set_selection_mode(gtk.SELECTION_MULTIPLE) - return - - def __setup_files_treeview(self): - """Setup TreeView files widget, as columned list.""" - v = self.view['files'] - v.set_model(self.model.files_list) - - v.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - - c = gtk.TreeViewColumn('Disc', gtk.CellRendererText(), text=1) - c.set_sort_column_id(1) - c.set_resizable(True) - c.set_visible(False) - self.view['files'].append_column(c) - - c = gtk.TreeViewColumn('Filename') - cellpb = gtk.CellRendererPixbuf() - cell = gtk.CellRendererText() - c.pack_start(cellpb, False) - c.pack_start(cell, True) - c.set_attributes(cellpb, stock_id=7) - c.set_attributes(cell, text=2) - c.set_sort_column_id(2) - c.set_resizable(True) - self.view['files'].append_column(c) - - c = gtk.TreeViewColumn('Path', gtk.CellRendererText(), text=3) - c.set_sort_column_id(3) - c.set_resizable(True) - c.set_visible(False) - self.view['files'].append_column(c) - - c = gtk.TreeViewColumn('Size', gtk.CellRendererText(), text=4) - c.set_sort_column_id(4) - c.set_resizable(True) - self.view['files'].append_column(c) - - c = gtk.TreeViewColumn('Date', gtk.CellRendererText(), text=5) - c.set_sort_column_id(5) - c.set_resizable(True) - self.view['files'].append_column(c) - self.view['files'].set_search_column(2) - - #v.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, - # self.DND_TARGETS, - # gtk.gdk.ACTION_DEFAULT) - v.drag_source_set(gtk.gdk.BUTTON1_MASK, self.DND_TARGETS, - gtk.gdk.ACTION_COPY) - return - - def __setup_exif_treeview(self): - self.view['exif_tree'].set_model(self.model.exif_list) - - c = gtk.TreeViewColumn('EXIF key', gtk.CellRendererText(), text=0) - c.set_sort_column_id(0) - c.set_resizable(True) - self.view['exif_tree'].append_column(c) - - c = gtk.TreeViewColumn('EXIF value', gtk.CellRendererText(), text=1) - c.set_sort_column_id(1) - c.set_resizable(True) - self.view['exif_tree'].append_column(c) - return - - def __activate_ui(self, name=None): - """Make UI active, and set title""" - self.model.unsaved_project = False - self.__set_title(filepath=name) - return - - def __set_title(self, filepath=None, modified=False): - """Set main window title""" - if modified: - mod = " *" - else: - mod = "" - - if filepath: - self.view['main'].set_title("%s - pyGTKtalog%s" % - (os.path.basename(filepath), mod)) - else: - self.view['main'].set_title("untitled - pyGTKtalog%s" % mod) - return - - def __store_settings(self): - """Store window size and pane position in config file (using config - object from model)""" - if self.model.config.confd['savewin']: - self.model.config.confd['wx'], self.model.config.confd['wy'] = \ - self.view['main'].get_size() - if self.model.config.confd['savepan']: - self.model.config.confd['h'] = self.view['hpaned1'].get_position() - self.model.config.confd['v'] = self.view['vpaned1'].get_position() - self.model.config.save() - return - - def __popup_menu(self, event, menu='discs_popup'): - """Popoup desired menu""" - self.view[menu].popup(None, None, None, 0, 0) - #self.view[menu].popup(None, None, None, event.button, - # event.time) - self.view[menu].show_all() - return - - def __generate_recent_menu(self): - self.recent_menu = gtk.Menu() - for i in self.model.config.recent: - name = os.path.basename(i) - item = gtk.MenuItem("%s" % name.replace('_', '__')) - item.connect_object("activate", self.recent_item_response, i) - self.recent_menu.append(item) - item.show() - self.view['recent_files1'].set_submenu(self.recent_menu) - return - - def __get_item_info(self, file_id): - """Get item under cusor, fetch information from model and depending on - what kind of information file has display it""" - buf = gtk.TextBuffer() - if not file_id: - self.__hide_details() - return - #self.view['description'].show() - set = self.model.get_file_info(file_id) - - tag = buf.create_tag() - tag.set_property('weight', pango.WEIGHT_BOLD) - - if __debug__ and 'fileinfo' in set: - buf.insert_with_tags(buf.get_end_iter(), "ID: ", tag) - buf.insert(buf.get_end_iter(), str(set['fileinfo']['id']) + "\n") - buf.insert_with_tags(buf.get_end_iter(), "Type: ", tag) - buf.insert(buf.get_end_iter(), str(set['fileinfo']['type']) + "\n") - - if set['fileinfo']['disc']: - buf.insert_with_tags(buf.get_end_iter(), "Disc: ", tag) - buf.insert(buf.get_end_iter(), set['fileinfo']['disc'] + "\n") - if set['fileinfo']['disc'] and set['fileinfo']['type'] == 1: - buf.insert_with_tags(buf.get_end_iter(), "Directory: ", tag) - elif not set['fileinfo']['disc'] and set['fileinfo']['type'] == 1: - buf.insert_with_tags(buf.get_end_iter(), "Disc: ", tag) - else: - buf.insert_with_tags(buf.get_end_iter(), "Filename: ", tag) - buf.insert(buf.get_end_iter(), set['filename'] + "\n") - buf.insert_with_tags(buf.get_end_iter(), "Date: ", tag) - buf.insert(buf.get_end_iter(), str(set['fileinfo']['date']) + "\n") - buf.insert_with_tags(buf.get_end_iter(), "Size: ", tag) - buf.insert(buf.get_end_iter(), str(set['fileinfo']['size']) + "\n") - - if 'gthumb' in set: - tag = buf.create_tag() - tag.set_property('weight', pango.WEIGHT_BOLD) - buf.insert_with_tags(buf.get_end_iter(), "\ngThumb comment:\n", tag) - if set['gthumb']['note']: - buf.insert(buf.get_end_iter(), set['gthumb']['note'] + "\n") - if set['gthumb']['place']: - buf.insert(buf.get_end_iter(), set['gthumb']['place'] + "\n") - if set['gthumb']['date']: - buf.insert(buf.get_end_iter(), set['gthumb']['date'] + "\n") - - if 'description' in set: - tag = buf.create_tag() - tag.set_property('weight', pango.WEIGHT_BOLD) - buf.insert_with_tags(buf.get_end_iter(), "\nDetails:\n", tag) - buf.insert(buf.get_end_iter(), set['description'] + "\n") - - if 'note' in set: - tag = buf.create_tag() - tag.set_property('weight', pango.WEIGHT_BOLD) - buf.insert_with_tags(buf.get_end_iter(), "\nNote:\n", tag) - buf.insert(buf.get_end_iter(), set['note'] + "\n") - - tags = self.model.get_file_tags(file_id) - if tags: - tag = buf.create_tag() - tag.set_property('weight', pango.WEIGHT_BOLD) - buf.insert_with_tags(buf.get_end_iter(), "\nFile tags:\n", tag) - tags = tags.values() - tags.sort() - first = True - for tag in tags: - if first: - first = False - buf.insert(buf.get_end_iter(), tag) - else: - buf.insert(buf.get_end_iter(), ", " + tag) - - self.view['description'].set_buffer(buf) - - if 'images' in set: - self.__setup_iconview() - self.view['img_container'].show() - else: - self.view['img_container'].hide() - - if 'exif' in set: - self.view['exif_tree'].set_model(self.model.exif_list) - self.view['exifinfo'].show() - else: - self.view['exifinfo'].hide() - - if 'thumbnail' in set: - self.view['thumb'].set_from_file(set['thumbnail']) - self.view['thumb_box'].show() - else: - self.view['thumb_box'].hide() - return - - def __tag_cloud(self): - """generate tag cloud""" - tag_cloud = self.view['tag_cloud_textview'] - self.model.get_tags() - - def insert_blank(buff, b_iter): - if b_iter.is_end() and b_iter.is_start(): - return b_iter - else: - buff.insert(b_iter, " ") - b_iter = buff.get_end_iter() - return b_iter - - buff = tag_cloud.get_buffer() - - # NOTE: remove old tags - def foreach_rem(texttag, data): - """remove old tags""" - tag_table.remove(texttag) - - tag_table = buff.get_tag_table() - while tag_table.get_size() > 0: - tag_table.foreach(foreach_rem, None) - - buff.set_text('') - - if len(self.model.tag_cloud) > 0: - for cloud in self.model.tag_cloud: - buff_iter = insert_blank(buff, buff.get_end_iter()) - try: - tag = buff.create_tag(str(cloud['id'])) - tag.set_property('size-points', cloud['size']) - #tag.connect('event', self.on_tag_cloud_click, tag) - tag_repr = cloud['name'] + "(%d)" % cloud['count'] - buff.insert_with_tags(buff_iter, tag_repr, tag) - except: - if __debug__: - print "c_main.py: __tag_cloud: error on tag:", cloud - - def __hide_details(self): - """hide details and "reset" tabs visibility""" - buf = self.view['description'].get_buffer() - buf.set_text('') - self.view['img_container'].hide() - self.view['exifinfo'].hide() - self.view['thumb_box'].hide() - self.view['description'].set_buffer(buf) - - pass # end of class diff --git a/src/ctrls/c_search.py b/src/ctrls/c_search.py deleted file mode 100644 index 98c3f1c..0000000 --- a/src/ctrls/c_search.py +++ /dev/null @@ -1,379 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - -import gtk - -from gtkmvc import Controller -import views.v_dialogs as Dialogs - -class SearchController(Controller): - """Controller for main application window""" - - def __init__(self, model): - """Initialize controller""" - Controller.__init__(self, model) - self.search_string = "" - return - - def register_view(self, view): - Controller.register_view(self, view) - - # Setup TreeView result widget, as columned list - v = self.view['result'] - v.set_model(self.model.search_list) - - v.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - - c = gtk.TreeViewColumn('Disc', gtk.CellRendererText(), text=1) - c.set_sort_column_id(1) - c.set_resizable(True) - v.append_column(c) - - c = gtk.TreeViewColumn('Filename') - cellpb = gtk.CellRendererPixbuf() - cell = gtk.CellRendererText() - c.pack_start(cellpb, False) - c.pack_start(cell, True) - c.set_attributes(cellpb, stock_id=7) - c.set_attributes(cell, text=2) - c.set_sort_column_id(2) - c.set_resizable(True) - v.append_column(c) - - c = gtk.TreeViewColumn('Path', gtk.CellRendererText(), text=3) - c.set_sort_column_id(3) - c.set_resizable(True) - v.append_column(c) - - c = gtk.TreeViewColumn('Size', gtk.CellRendererText(), text=4) - c.set_sort_column_id(4) - c.set_resizable(True) - v.append_column(c) - - c = gtk.TreeViewColumn('Date', gtk.CellRendererText(), text=5) - c.set_sort_column_id(5) - c.set_resizable(True) - v.append_column(c) - v.set_search_column(2) - - # combobox - self.view['comboboxentry'].set_model(self.model.search_history) - self.view['comboboxentry'].set_text_column(0) - # statusbar - self.context_id = self.view['statusbar'].get_context_id('search') - self.view['statusbar'].pop(self.context_id) - self.view['search_window'].show() - self.model.search_created = True; - return - - ######################################################################### - # Connect signals from GUI, like menu objects, toolbar buttons and so on. - def on_search_window_delete_event(self, window, event): - """if window was closed, reset attributes""" - self.model.point = None - self.model.search_created = False; - return False - - def on_close_clicked(self, button): - """close search window""" - self.model.point = None - self.model.search_created = False; - self.view['search_window'].destroy() - - def on_search_activate(self, entry): - """find button or enter pressed on entry search. Do the search""" - search_txt = self.view['search_entry'].get_text() - self.search_string = search_txt - found = self.model.search(search_txt) - self.model.add_search_history(search_txt) - self.__set_status_bar(found) - - def on_result_row_activated(self, treeview, path, treecolumn): - """result treeview row activated, change models 'point' observable - variable to id of elected item. rest is all in main controler hands.""" - model = treeview.get_model() - s_iter = model.get_iter(path) - self.model.point = model.get_value(s_iter, 0) - - def on_result_button_release_event(self, tree, event): - if event.button == 3: # Right mouse button. Show context menu. - try: - selection = tree.get_selection() - model, list_of_paths = selection.get_selected_rows() - except TypeError: - list_of_paths = [] - - if len(list_of_paths) == 0: - # try to select item under cursor - try: - path, column, x, y = tree.get_path_at_pos(int(event.x), - int(event.y)) - except TypeError: - # failed, do not show any popup and return - tree.get_selection().unselect_all() - return False - selection.select_path(path[0]) - - if len(list_of_paths) > 1: - self.view['add_image'].set_sensitive(False) - self.view['rename'].set_sensitive(False) - self.view['edit'].set_sensitive(False) - else: - self.view['add_image'].set_sensitive(True) - self.view['rename'].set_sensitive(True) - self.view['edit'].set_sensitive(True) - self.view['files_popup'].popup(None, None, None, 0, 0) - self.view['files_popup'].show_all() - return True - - def on_add_tag_activate(self, menu_item): - """Add tags to selected files""" - tags = Dialogs.TagsDialog().run() - if not tags: - return - ids = self.__get_tv_selection_ids(self.view['result']) - for item_id in ids: - self.model.add_tags(item_id, tags) - - self.model.unsaved_project = True - return - - def on_delete_tag_activate(self, menu_item): - ids = self.__get_tv_selection_ids(self.view['result']) - if not ids: - Dialogs.Inf("Remove tags", "No files selected", - "You have to select some files first.") - return - - tags = self.model.get_tags_by_file_id(ids) - if tags: - d = Dialogs.TagsRemoveDialog(tags) - retcode, retval = d.run() - if retcode=="ok" and not retval: - Dialogs.Inf("Remove tags", "No tags selected", - "You have to select any tag to remove from files.") - return - elif retcode == "ok" and retval: - self.model.delete_tags(ids, retval) - found = self.model.search(self.search_string) - self.__set_status_bar(found) - - def on_add_thumb_activate(self, menu_item): - image, only_thumbs = Dialogs.LoadImageFile().run() - if not image: - return - ids = self.__get_tv_selection_ids(self.view['result']) - for item_id in ids: - self.model.add_thumbnail(image, item_id) - - self.model.unsaved_project = True - return - - def on_remove_thumb_activate(self, menu_item): - if self.model.config.confd['delwarn']: - title = 'Delete thumbnails' - question = 'Delete thumbnails?' - description = "Thumbnails for selected items will be permanently" - description += " removed from catalog." - obj = Dialogs.Qst(title, question, description) - if not obj.run(): - return - try: - ids = self.__get_tv_selection_ids(self.view['result']) - for item_id in ids: - self.model.del_thumbnail(item_id) - except: - if __debug__: - print "c_search.py: on_remove_thumb_activate(): error on", - print "getting selected items or removing thumbnails" - return - - self.model.unsaved_project = True - return - - def on_add_image_activate(self, menu_item): - dialog = Dialogs.LoadImageFile(True) - msg = "Don't copy images. Generate only thumbnails." - toggle = gtk.CheckButton(msg) - toggle.show() - dialog.dialog.set_extra_widget(toggle) - - images, only_thumbs = dialog.run() - if not images: - return - - for image in images: - try: - selection = self.view['result'].get_selection() - model, list_of_paths = selection.get_selected_rows() - fid = model.get_value(model.get_iter(list_of_paths[0]), 0) - except: - try: - path, column = self.view['result'].get_cursor() - model = self.view['result'].get_model() - fiter = model.get_iter(path) - fid = model.get_value(fiter, 0) - except: - return - self.model.add_image(image, fid, only_thumbs) - - self.model.unsaved_project = True - return - - def on_remove_image_activate(self, menu_item): - if self.model.config.confd['delwarn']: - title = 'Delete images' - question = 'Delete all images?' - description = 'All images for selected items will be permanently' - description += ' removed from catalog.' - obj = Dialogs.Qst(title, question, description) - if not obj.run(): - return - try: - ids = self.__get_tv_selection_ids(self.view['result']) - for item_id in ids: - self.model.del_images(item_id) - except: - if __debug__: - print "c_search.py: on_remove_thumb_activate(): error on", - print "getting selected items or removing thumbnails" - return - - self.model.unsaved_project = True - return - - def on_edit_activate(self, menu_item): - try: - selection = self.view['result'].get_selection() - model, list_of_paths = selection.get_selected_rows() - fid = model.get_value(model.get_iter(list_of_paths[0]), 0) - except TypeError: - if __debug__: - print "c_main.py: on_edit2_activate(): 0", - print "zaznaczonych wierszy" - return - - val = self.model.get_file_info(fid) - ret = Dialogs.EditDialog(val).run() - if ret: - self.model.rename(fid, ret['filename']) - self.model.update_desc_and_note(fid, - ret['description'], ret['note']) - self.model.unsaved_project = True - - def on_delete_activate(self, menu_item): - dmodel = self.model.discs_tree - try: - selection = self.view['result'].get_selection() - model, list_of_paths = selection.get_selected_rows() - except TypeError: - return - - if not list_of_paths: - Dialogs.Inf("Delete files", "No files selected", - "You have to select at least one file to delete.") - return - - if self.model.config.confd['delwarn']: - description = "Selected files and directories will be " - description += "permanently\n removed from catalog." - obj = Dialogs.Qst("Delete files", "Delete files?", description) - if not obj.run(): - return - - def foreach_searchtree(zmodel, zpath, ziter, d): - if d[0] == zmodel.get_value(ziter, 0): - d[1].append(zpath) - return False - - ids = [] - for p in list_of_paths: - ids.append(model.get_value(model.get_iter(p), 0)) - - for fid in ids: - # delete from db - self.model.delete(fid) - - self.model.unsaved_project = True - found = self.model.search(self.search_string) - self.__set_status_bar(found) - return - - def on_rename_activate(self, menu_item): - try: - selection = self.view['result'].get_selection() - model, list_of_paths = selection.get_selected_rows() - fid = model.get_value(model.get_iter(list_of_paths[0]), 0) - except TypeError: - if __debug__: - print "c_main.py: on_edit2_activate(): 0", - print "zaznaczonych wierszy" - return - - fid = model.get_value(model.get_iter(list_of_paths[0]), 0) - name = model.get_value(model.get_iter(list_of_paths[0]),2) - new_name = Dialogs.InputNewName(name).run() - - if __debug__: - print "c_search.py: on_rename_activate(): label:", new_name - - if new_name and new_name != name: - self.model.rename(fid, new_name) - self.model.unsaved_project = True - return - - ##################### - # observed properetis - - ######################### - # private class functions - def __set_status_bar(self, found): - """sets number of founded items in statusbar""" - if found == 0: - msg = "No files found." - elif found == 1: - msg = "Found 1 file." - else: - msg = "Found %d files." % found - self.view['statusbar'].push(self.context_id, "%s" % msg) - - def __get_tv_selection_ids(self, treeview): - """get selection from treeview and return coresponding ids' from - connected model or None""" - ids = [] - try: - selection = treeview.get_selection() - model, list_of_paths = selection.get_selected_rows() - for path in list_of_paths: - ids.append(model.get_value(model.get_iter(path), 0)) - return ids - except: - # DEBUG: treeview have no selection or smth is broken - if __debug__: - print "c_search.py: __get_tv_selection_ids(): error on", - print "getting selected items" - return - return None - - pass # end of class diff --git a/src/gtkmvc/__init__.py b/src/gtkmvc/__init__.py deleted file mode 100644 index 6dfbcbd..0000000 --- a/src/gtkmvc/__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -# Author: Roberto Cavada -# -# Copyright (c) 2005 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - - -__all__ = ["model", "view", "controller", "observable", "observer", "support"] - -__version = (1,2,2) - -from model import Model, TreeStoreModel, ListStoreModel, TextBufferModel -from model_mt import ModelMT -from controller import Controller -from view import View -from observer import Observer -import observable -import adapters - -def get_version(): return __version - -def require(ver): - if isinstance(ver, str): ver = ver.split(".") - ver = tuple(map(int, ver)) - - if get_version() < ver: - raise AssertionError("gtkmvc required version '%s', found '%s'"\ - % (ver, get_version())) - pass - return - - diff --git a/src/gtkmvc/adapters/__init__.py b/src/gtkmvc/adapters/__init__.py deleted file mode 100644 index b843b72..0000000 --- a/src/gtkmvc/adapters/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# Author: Roberto Cavada -# -# Copyright (c) 2007 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - -from gtkmvc.adapters.basic import Adapter, UserClassAdapter, RoUserClassAdapter -from gtkmvc.adapters.containers import StaticContainerAdapter diff --git a/src/gtkmvc/adapters/basic.py b/src/gtkmvc/adapters/basic.py deleted file mode 100644 index 9d0664c..0000000 --- a/src/gtkmvc/adapters/basic.py +++ /dev/null @@ -1,428 +0,0 @@ -# Author: Roberto Cavada -# -# Copyright (c) 2007 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - - -import types -import gtk -import time - -from gtkmvc.adapters.default import * -from gtkmvc.observer import Observer - - -# ---------------------------------------------------------------------- -class Adapter (Observer): - - def __init__(self, model, prop_name, - prop_read=None, prop_write=None, - value_error=None): - """ - Creates a new adapter that handles setting of value of a - model single model property when a corresponding widgets set - is changed and viceversa when the property is also - observable. - - This class handles only assignments to properties. For other - kinds of setting (e.g. user-defined classes used as - observable properties, containers, etc.) use other types of - Adapters derived from this class. - - prop_name is the model's property name (as a string). It is - possible to use a dotted notation to identify a property - contained into a hierarchy of models. For example 'a.b.c' - identifies property 'c' into model 'b' inside model 'a', - where model 'a' is an attribute of given top level model. - Last name must be an observable or non-observable attribute, - and previous names (if specified) must all refer to - instances of class Model. First name from the left must be - the name of a model instance inside the given model. - - prop_{write,read} are two optional functions that apply - custom modifications to the value of the property before - setting and reading it. Both take a value and must return a - transformed value whose type must be compatible with the - type of the property. - - value_error can be a function (or a method) to be called - when a ValueError exception occurs while trying to set a - wrong value for the property inside the model. The function - will receive: the adapter, the property name and the value - coming from the widget that offended the model. - """ - - # registration is delayed, as we need to create possible - # listener before: - Observer.__init__(self) - - self._prop_name = prop_name - self._prop_read = prop_read - self._prop_write = prop_write - self._value_error = value_error - self._wid = None - self._wid_info = {} - - # this flag is set when self is changing the property or the - # widget, in order to avoid infinite looping. - self._itsme = False - - self._connect_model(model) - return - - def connect_widget(self, wid, - getter=None, setter=None, - signal=None, arg=None, update=True): - - """ - Called when the widget is instantiated, and the adapter is - ready to connect the widget and the property inside the - observed model. arg is the (optional) argument that will be - passed when connecting the signal. - - getter and setter are the (optional) methods used - for reading and writing the widget's value. When not - specified, default getter and setter will be guessed by - looking at the widget type the adapter will be connected - with. Guessing is carried out by querying information - specified into module 'adapters.default'. - - Finally, if update is false, the widget will not be updated - """ - - if self._wid_info.has_key(wid): - raise ValueError("Widget " + str(wid) + " was already connected") - - wid_type = None - - if None in (getter, setter, signal): - w = search_adapter_info(wid) - if getter is None: getter = w[GETTER] - if setter is None: - setter = w[SETTER] - wid_type = w[WIDTYPE] - pass - - if signal is None: signal = w[SIGNAL] - pass - - # saves information about the widget - self._wid_info[wid] = (getter, setter, wid_type) - - # connects the widget - if signal: - if arg: wid.connect(signal, self._on_wid_changed, arg) - else: wid.connect(signal, self._on_wid_changed) - pass - - self._wid = wid - - # updates the widget: - if update: self.update_widget() - return - - def update_model(self): - """Forces the property to be updated from the value hold by - the widget. This method should be called directly by the - user in very unusual conditions.""" - self._write_property(self._read_widget()) - return - - def update_widget(self): - """Forces the widget to be updated from the property - value. This method should be called directly by the user - when the property is not observable, or in very unusual - conditions.""" - self._write_widget(self._read_property()) - return - - - # ---------------------------------------------------------------------- - # Private methods - # ---------------------------------------------------------------------- - def _connect_model(self, model): - """ - Used internally to connect the property into the model, and - register self as a value observer for that property""" - - parts = self._prop_name.split(".") - if len(parts) > 1: - # identifies the model - models = parts[:-1] - for name in models: - model = getattr(model, name) - if not isinstance(model, gtkmvc.Model): - raise TypeError("Attribute '" + name + - "' was expected to be a Model, but found: " + - str(model)) - pass - prop = parts[-1] - else: prop = parts[0] - - # prop is inside model? - if not hasattr(model, prop): - raise ValueError("Attribute '" + prop + - "' not found in model " + str(model)) - - # is it observable? - if model.has_property(prop): - # we need to create an observing method before registering - self._add_method(self._get_observer_src(prop)) - pass - - self._prop = getattr(model, prop) - self._prop_name = prop - - # registration of model: - self.register_model(model) - return - - - def _get_observer_src(self, prop_name): - """This is the code for an value change observer""" - return """def property_%s_value_change(self, model, old, new): - if self._itsme or old == new: return - self._on_prop_changed()""" % prop_name - - - def _add_method(self, src): - """Private service to add a new method to the instance, - given method code""" - - from gtkmvc.support.utils import get_function_from_source - import new - - func = get_function_from_source(src) - meth = new.instancemethod(func, self, self.__class__) - setattr(self, func.__name__, meth) - return - - def _get_property(self): - """Private method that returns the value currently stored - into the property""" - return getattr(self.get_model(), self._prop_name) - #return self._prop # bug fix reported by A. Dentella - - def _set_property(self, val): - """Private method that sets the value currently of the property.""" - return setattr(self.get_model(), self._prop_name, val) - - def _read_property(self, *args): - """Returns the (possibly transformed) value that is stored - into the property""" - if self._prop_read: return self._prop_read(self._get_property(*args)) - return self._get_property(*args) - - def _write_property(self, val, *args): - """Sets the value of property. Given val is transformed - accodingly to prop_write function when specified at - construction-time. A try to cast the value to the property - type is given.""" - - # 'finally' would be better here, but not supported in 2.4 :( - try: - totype = type(self._get_property(*args)) - val_prop = self._cast_value(val, totype) - if self._prop_write: val_prop = self._prop_write(val_prop) - - self._itsme = True - self._set_property(val_prop, *args) - - except ValueError: - self._itsme = False - if self._value_error: self._value_error(self, self._prop_name, val) - else: raise - pass - - except: self._itsme = False; raise - - self._itsme = False - return - - def _read_widget(self): - """Returns the value currently stored into the widget, after - transforming it accordingly to possibly specified function.""" - getter = self._wid_info[self._wid][0] - return getter(self._wid) - - def _write_widget(self, val): - """Writes value into the widget. If specified, user setter - is invoked.""" - self._itsme = True - try: - setter = self._wid_info[self._wid][1] - wtype = self._wid_info[self._wid][2] - if wtype is not None: setter(self._wid, self._cast_value(val, wtype)) - else: setter(self._wid, val) - finally: - self._itsme = False - pass - - return - - def _cast_value(self, val, totype): - """Casts given val to given totype. Raises TypeError if not able to cast.""" - t = type(val) - if issubclass(t, totype): return val - if issubclass(totype, types.StringType): return str(val) - if issubclass(t, types.StringType): - if issubclass(totype, types.IntType): - if val: return int(float(val)) - return 0 - if issubclass(totype, types.FloatType): - if val: return float(val) - return 0.0 - pass - - raise TypeError("Not able to cast " + str(t) + " to " + str(totype)) - - - # ---------------------------------------------------------------------- - # Callbacks and observation - # ---------------------------------------------------------------------- - - def _on_wid_changed(self, wid): - """Called when the widget is changed""" - if self._itsme: return - self.update_model() - return - - def _on_prop_changed(self): - """Called by the observation code, when the value in the - observed property is changed""" - if not self._itsme: self.update_widget() - return - - pass # end of class Adapter - - - -#---------------------------------------------------------------------- -class UserClassAdapter (Adapter): - """ - This class handles the communication between a widget and a - class instance (possibly observable) that is a property inside - the model. The value to be shown is taken and stored by using a - getter and a setter. getter and setter can be: names of user - class methods, bound or unbound methods of the user class, or a - function that will receive the user class instance and possible - arguments whose number depends on whether it is a getter or a - setter.""" - - def __init__(self, model, prop_name, - getter, setter, - prop_read=None, prop_write=None, - value_error=None): - - Adapter.__init__(self, model, prop_name, - prop_read, prop_write, value_error) - - self._getter = self._resolve_to_func(getter) - self._setter = self._resolve_to_func(setter) - return - - # ---------------------------------------------------------------------- - # Private methods - # ---------------------------------------------------------------------- - - def _resolve_to_func(self, what): - """This method resolves whatever is passed: a string, a - bound or unbound method, a function, to make it a - function. This makes internal handling of setter and getter - uniform and easier.""" - if isinstance(what, types.StringType): - what = getattr(Adapter._get_property(self), what) - pass - - # makes it an unbounded function if needed - if type(what) == types.MethodType: what = what.im_func - - if not type(what) == types.FunctionType: raise TypeError("Expected a method name, a method or a function") - return what - - - def _get_observer_src(self, prop_name): - """This is the code for a method after_change observer""" - return """def property_%s_after_change(self, model, \ - instance, meth_name, res, args, kwargs): - if self._itsme: return - self._on_prop_changed(instance, meth_name, res, args, kwargs)""" % prop_name - - - def _on_prop_changed(self, instance, meth_name, res, args, kwargs): - """Called by the observation code, when a modifying method - is called""" - Adapter._on_prop_changed(self) - return - - def _get_property(self, *args): - """Private method that returns the value currently stored - into the property""" - val = self._getter(Adapter._get_property(self), *args) - if self._prop_read: return self._prop_read(val, *args) - return val - - def _set_property(self, val, *args): - """Private method that sets the value currently of the property""" - if self._prop_write: val = self._prop_write(val) - return self._setter(Adapter._get_property(self), val, *args) - - pass # end of class UserClassAdapter -# ---------------------------------------------------------------------- - - - -#---------------------------------------------------------------------- -class RoUserClassAdapter (UserClassAdapter): - """ - This class is for Read-Only user classes. RO classes are those - whose setting methods do not change the instance, but return a - new instance that has been changed accordingly to the setters - semantics. An example is python datetime class, whose replace - method does not change the instance it is invoked on, but - returns a new datetime instance. - - This class is likely to be used very rarely. - """ - - def __init__(self, model, prop_name, - getter, setter, - prop_read=None, prop_write=None, - value_error=None): - - UserClassAdapter.__init__(self, model, prop_name, - getter, setter, - prop_read, prop_write, value_error) - - return - - # ---------------------------------------------------------------------- - # Private methods - # ---------------------------------------------------------------------- - - def _set_property(self, val, *args): - """Private method that sets the value currently of the property""" - val = UserClassAdapter._set_property(self, val, *args) - if val: Adapter._set_property(self, val, *args) - return val - - pass # end of class RoUserClassAdapter -# ---------------------------------------------------------------------- diff --git a/src/gtkmvc/adapters/containers.py b/src/gtkmvc/adapters/containers.py deleted file mode 100644 index b2d6341..0000000 --- a/src/gtkmvc/adapters/containers.py +++ /dev/null @@ -1,217 +0,0 @@ -# Author: Roberto Cavada -# -# Copyright (c) 2007 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - - -import types -import gtk - -from gtkmvc.adapters.basic import UserClassAdapter, Adapter - -from gtkmvc.adapters.default import * -from gtkmvc.observer import Observer -from gtkmvc.support.wrappers import ObsMapWrapper - -# ---------------------------------------------------------------------- -class StaticContainerAdapter (UserClassAdapter): - """ - This class can be used to bound a set of widgets to a property - that is a container, like a tuple, a list or a map, or in - general a class that implements __getitem__ and __setitem__ - methods. - - From the other hand, the set of widgets can be a list provided - by the user, or a container widget like a Box, a notebook, etc. - Widgets will be linked by their position when the property is - list-like, or by their name when the property is map-like. - - This class supports only properties that are static containers, - i.e. those containers that do not change their length - dynamically. If the container grows up in length, no change will - occur in the view-side. - """ - def __init__(self, model, prop_name, - prop_read=None, prop_write=None, value_error=None): - - UserClassAdapter.__init__(self, model, prop_name, - lambda c,i: c.__getitem__(i), - lambda c,v,i: c.__setitem__(i,v), - prop_read, prop_write, - value_error) - - prop = Adapter._get_property(self) - #prop = self._get_property() # bug fix reported by A. Dentella - if not (hasattr(prop, "__getitem__") and - hasattr(prop, "__setitem__")): - raise TypeError("Property " + self._prop_name + - " is not a valid container") - - - self._prop_is_map = isinstance(prop, types.DictType) or \ - isinstance(prop, ObsMapWrapper) - # contained widgets - self._idx2wid = {} - self._wid2idx = {} - - self._widgets = None - return - - - def connect_widget(self, wid, getters=None, setters=None, - signals=None, arg=None): - """ - Called when the widget is instantiated, and the adapter is - ready to connect the widgets inside it (if a container) or - each widget if wid is a list of widgets. getters and setters - can be None, a function or a list or a map of - functions. signals can be None, a signal name, or a list or - a map of signal names. When maps are used, keys can be - widgets or widget names. The length of the possible lists or - maps must be lesser or equal to the number of widgets that - will be connected. - """ - - if isinstance(wid, gtk.Container): self._widgets = wid.get_children() - elif isinstance(wid, types.ListType) or isinstance(wid, types.TupleType): self._widgets = wid - else: raise TypeError("widget must be either a gtk.Container or a list or tuple") - - # prepares the mappings: - for idx, w in enumerate(self._widgets): - if self._prop_is_map: idx=w.get_name() - self._idx2wid[idx] = w - self._wid2idx[w] = idx - pass - - # prepares the lists for signals - getters = self.__handle_par("getters", getters) - setters = self.__handle_par("setters", setters) - signals = self.__handle_par("signals", signals) - - for wi,ge,se,si in zip(self._widgets, getters, setters, signals): - if type(ge) == types.MethodType: ge = ge.im_func - if type(se) == types.MethodType: se = se.im_func - UserClassAdapter.connect_widget(self, wi, ge, se, si, arg, False) - pass - - self.update_widget() - self._wid = wid - return - - - def update_model(self, idx=None): - """Updates the value of property at given index. If idx is - None, all controlled indices will be updated. This method - should be called directly by the user in very unusual - conditions.""" - if idx is None: - for w in self._widgets: - idx = self._get_idx_from_widget(w) - self._write_property(self._read_widget(idx), idx) - pass - pass - else: self._write_property(self._read_widget(idx), idx) - return - - def update_widget(self, idx=None): - """Forces the widget at given index to be updated from the - property value. If index is not given, all controlled - widgets will be updated. This method should be called - directly by the user when the property is not observable, or - in very unusual conditions.""" - if idx is None: - for w in self._widgets: - idx = self._get_idx_from_widget(w) - self._write_widget(self._read_property(idx), idx) - pass - else: self._write_widget(self._read_property(idx), idx) - return - - # ---------------------------------------------------------------------- - # Private methods - # ---------------------------------------------------------------------- - - def _get_idx_from_widget(self, wid): - """Given a widget, returns the corresponding index for the - model. Returned value can be either an integer or a string""" - return self._wid2idx[wid] - - def _get_widget_from_idx(self, idx): - """Given an index, returns the corresponding widget for the view. - Given index can be either an integer or a string""" - return self._idx2wid[idx] - - - def _read_widget(self, idx): - sav = self._wid - self._wid = self._get_widget_from_idx(idx) - val = UserClassAdapter._read_widget(self) - self._wid = sav - return val - - def _write_widget(self, val, idx): - sav = self._wid - self._wid = self._get_widget_from_idx(idx) - UserClassAdapter._write_widget(self, val) - self._wid = sav - return - - # This is a private service to factorize code of connect_widget - def __handle_par(self, name, par): - if par is None or type(par) in (types.FunctionType, - types.MethodType, types.StringType): - par = [par] * len(self._widgets) - pass - - elif isinstance(par, types.DictType): - val = [] - for w in self._widgets: - if par.has_key(w): val.append(par[w]) - elif par.has_key(w.get_name()): val.append(par[w.get_name()]) - else: val.append(None) - pass - par = val - pass - - elif isinstance(par, types.ListType) or isinstance(par, types.TupleType): - par = list(par) - par.extend([None]*(len(self._widgets)-len(par))) - pass - - else: raise TypeError("Parameter %s has an invalid type (should be None, a sequence or a string)" % name) - - return par - - - # Callbacks: - def _on_wid_changed(self, wid): - """Called when the widget is changed""" - if self._itsme: return - self.update_model(self._get_idx_from_widget(wid)) - return - - def _on_prop_changed(self, instance, meth_name, res, args, kwargs): - """Called by the observation code, we are interested in - __setitem__""" - if not self._itsme and meth_name == "__setitem__": self.update_widget(args[0]) - return - - pass # end of class StaticContainerAdapter diff --git a/src/gtkmvc/adapters/default.py b/src/gtkmvc/adapters/default.py deleted file mode 100644 index 43058f4..0000000 --- a/src/gtkmvc/adapters/default.py +++ /dev/null @@ -1,55 +0,0 @@ -__all__ = ("search_adapter_info", - "SIGNAL", "GETTER", "SETTER", "WIDTYPE") - -import types -import gtk - -# ---------------------------------------------------------------------- -# This list defines a default behavior for widgets. -# If no particular behaviour has been specified, adapters will -# use information contained into this list to create themself. -# This list is ordered: the earlier a widget occurs, the better it -# will be matched by the search function. -# ---------------------------------------------------------------------- -__def_adapter = [ # class, default signal, getter, setter, value type - (gtk.Entry, "changed", gtk.Entry.get_text, gtk.Entry.set_text, types.StringType), - (gtk.Label, None, gtk.Label.get_text, gtk.Label.set_text, types.StringType), - (gtk.Arrow, None, lambda a: a.get_property("arrow-type"), - lambda a,v: a.set(v,a.get_property("shadow-type")), gtk.ArrowType), - (gtk.ToggleButton, "toggled", gtk.ToggleButton.get_active, gtk.ToggleButton.set_active, types.BooleanType), - (gtk.CheckMenuItem, "toggled", gtk.CheckMenuItem.get_active, gtk.CheckMenuItem.set_active, types.BooleanType), - (gtk.Expander, "activate", lambda w: not w.get_expanded(), gtk.Expander.set_expanded, types.BooleanType), - (gtk.ColorButton, "color-set", gtk.ColorButton.get_color, gtk.ColorButton.set_color, gtk.gdk.Color), - (gtk.ColorSelection, "color-changed", gtk.ColorSelection.get_current_color, gtk.ColorSelection.set_current_color, gtk.gdk.Color), - ] - -if gtk.pygtk_version >= (2,10,0): - # conditionally added support - __def_adapter.append( - (gtk.LinkButton, "clicked", gtk.LinkButton.get_uri, gtk.LinkButton.set_uri, types.StringType)) - pass - - -# constants to access values: -SIGNAL =1 -GETTER =2 -SETTER =3 -WIDTYPE =4 -# ---------------------------------------------------------------------- - - -# To optimize the search -__memoize__ = {} -def search_adapter_info(wid): - """Given a widget returns the default tuple found in __def_adapter""" - t = type(wid) - if __memoize__.has_key(t): return __memoize__[t] - - for w in __def_adapter: - if isinstance(wid, w[0]): - __memoize__[t] = w - return w - pass - - raise TypeError("Adapter type " + str(t) + " not found among supported adapters") - diff --git a/src/gtkmvc/controller.py b/src/gtkmvc/controller.py deleted file mode 100644 index 3c75b7d..0000000 --- a/src/gtkmvc/controller.py +++ /dev/null @@ -1,218 +0,0 @@ -# Author: Roberto Cavada -# -# Copyright (c) 2005 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - - -from gtkmvc.observer import Observer -import types - - -class Controller (Observer): - """ - We put all of our gtk signal handlers into a class. This lets - us bind all of them at once, because their names are in the - class dict. This class automatically register its instances as - observers into the corresponding model. Also, when a view is - created, the view calls method register_view, which can be - oveloaded in order to connect signals and perform other specific - operation. A controller possibly handles and contains also a set - of adapters that makes easier to connect widgets and observable - properties into the model. - - parameter spurious controls the way spurious value change - notifications are handled. If True, assignments to observable - properties that do not actually change the value are - notified anyway.""" - - def __init__(self, model, spurious=False): - Observer.__init__(self, model, spurious) - - self.view = None - self.__adapters = [] - return - - def register_view(self, view): - """ - This method is called by the framework when registering a - view. Derived classes can exploit this call to connect - signals manually, create and connect treeview, textview, - etc. - """ - assert(self.view is None) - self.view = view - self.register_adapters() - return - - def register_adapters(self): - """ - This method is called by register_view during view - registration process, when it is time to possibly create all - adapters. model and view can safely be taken from self.model - and self.view. Derived classes can specilize this method. In - this implementation the method does nothing. - """ - assert(self.model is not None) - assert(self.view is not None) - return - - def adapt(self, *args): - """ - Adapts a set of (observable) properties and a set of - widgets, by connecting them. - - This method can be used to simplify the creation of one or - more adapters, by letting the framework do most of the work - needed to connect ('adapt') properties from one hand, and - widgets on the other. - - This method is provided in three flavours: - - 1. An instance of an Adapter class can be created by the - caller and passed as a unique argument. The adapter must - be already fully configured. - - 2. The name of a model's property is passed as a unique - argument. A correponding widget name is searched and if - found, an adapter is created. Name matching is performed - by searching into view's widget names for words that end - with the given property name. Matching is case - insensitive and words can be separated by spaces, - underscore (_) and CapitalizedWords. For example property - 'prop' will match widget 'cb_prop'. If no matches or - multiple matches are found, a ValueError will be raised. - The way names are matched can be customized by deriving - method match_prop_name. - - 3. Two strings can be passed, respectively containing the - name of an observable property in the model, and the name - of a widget in the view. - - Flavour 1 allows for a full control, but flavour 2 and 3 - make easier to create adpaters with basic (default) - behaviour. - - This method can be called into the method register_adapters - which derived classes can specialise. - """ - - # checks arguments - n = len(args) - if n not in (1,2): raise TypeError("adapt() takes 1 or 2 arguments (%d given)" % n) - - if n == 1: #one argument - from gtkmvc.adapters.basic import Adapter - - if isinstance(args[0], Adapter): adapters = (args[0],) - - elif isinstance(args[0], types.StringType): - prop_name = args[0] - names = [] - for k in self.view: - if self.__match_prop_name(prop_name, k): names.append(k) - pass - if len(names) != 1: - raise ValueError("%d widget candidates match property '%s': %s" % \ - (len(names), prop_name, names)) - wid_name = names[0] - adapters = self.__create_adapters__(prop_name, wid_name) - pass - else: raise TypeError("Argument of adapt() must be an Adapter or a string") - - else: # two arguments - if not (isinstance(args[0], types.StringType) and - isinstance(args[1], types.StringType)): - raise TypeError("Arguments of adapt() must be two strings") - - # retrieves both property and widget, and creates an adapter - prop_name, wid_name = args - adapters = self.__create_adapters__(prop_name, wid_name) - pass - - for ad in adapters: self.__adapters.append(ad) - return - - - def __match_prop_name(self, prop_name, wid_name): - """ - Used internally when searching for a suitable widget. To customize - its behaviour, re-implement this method into derived classes - """ - return wid_name.lower().endswith(prop_name.lower()) - - - def __create_adapters__(self, prop_name, wid_name): - """ - Private service that looks at property and widgets types, - and possibly creates one or more (best) fitting adapters - that are returned as a list. - """ - from gtkmvc.adapters.basic import Adapter, RoUserClassAdapter - from gtkmvc.adapters.containers import StaticContainerAdapter - import gtk - - res = [] - - wid = self.view[wid_name] - if wid is None: raise ValueError("Widget '%s' not found" % wid_name) - - # Decides the type of adapters to be created. - if isinstance(wid, gtk.Calendar): - # calendar creates three adapter for year, month and day - ad = RoUserClassAdapter(self.model, prop_name, - lambda d: d.year, lambda d,y: d.replace(year=y)) - ad.connect_widget(wid, lambda c: c.get_date()[0], - lambda c,y: c.select_month(c.get_date()[1], y), - "day-selected") - res.append(ad) # year - - ad = RoUserClassAdapter(self.model, prop_name, - lambda d: d.month, lambda d,m: d.replace(month=m)) - ad.connect_widget(wid, lambda c: c.get_date()[1]+1, - lambda c,m: c.select_month(m-1, c.get_date()[0]), - "day-selected") - res.append(ad) # month - - ad = RoUserClassAdapter(self.model, prop_name, - lambda d: d.day, lambda d,v: d.replace(day=v)) - ad.connect_widget(wid, lambda c: c.get_date()[2], - lambda c,d: c.select_day(d), - "day-selected") - res.append(ad) # day - return res - - - try: # tries with StaticContainerAdapter - ad = StaticContainerAdapter(self.model, prop_name) - ad.connect_widget(wid) - res.append(ad) - - except TypeError: - # falls back to a simple adapter - ad = Adapter(self.model, prop_name) - ad.connect_widget(wid) - res.append(ad) - pass - - return res - - - pass # end of class Controller diff --git a/src/gtkmvc/model.py b/src/gtkmvc/model.py deleted file mode 100644 index 085723a..0000000 --- a/src/gtkmvc/model.py +++ /dev/null @@ -1,336 +0,0 @@ -# Author: Roberto Cavada -# -# Copyright (c) 2005 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free -# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - -import support.metaclasses -from support.wrappers import ObsWrapperBase -from observable import Signal - - -class Model (object): - """ - This class is the application model base class. It handles a set - of observable properties which you are interested in showing by - one ore more view - via one or more observers of course. The - mechanism is the following: - - 1. You are interested in showing a set of model property, that - you can declare in the __properties__ member map. - - 2. You define one or more observers that observe one or more - properties you registered. When someone changes a property - value the model notifies the changing to each observer. - - The property-observer[s] association is given by the implicit - rule in observers method names: if you want the model notified - the changing event of the value of the property 'p' you have to - define the method called 'property_p_value_change' in each - listening observer class. - - Notice that tipically 'controllers' implement the observer - pattern. The notification method gets the emitting model, the - old value for the property and the new one. Properties - functionalities are automatically provided by the - ObservablePropertyMeta meta-class.""" - - __metaclass__ = support.metaclasses.ObservablePropertyMeta - __properties__ = {} # override this - - def __init__(self): - object.__init__(self) - self.__observers = [] - # keys are properties names, values are methods inside the observer: - self.__value_notifications = {} - self.__instance_notif_before = {} - self.__instance_notif_after = {} - self.__signal_notif = {} - - for key in (self.__properties__.keys() + self.__derived_properties__.keys()): - self.register_property(key) - pass - - return - - def register_property(self, name): - """Registers an existing property to be monitored, and sets - up notifiers for notifications""" - if not self.__value_notifications.has_key(name): - self.__value_notifications[name] = [] - pass - - # registers observable wrappers - prop = getattr(self, "_prop_%s" % name) - - if isinstance(prop, ObsWrapperBase): - prop.__set_model__(self, name) - - if isinstance(prop, Signal): - if not self.__signal_notif.has_key(name): - self.__signal_notif[name] = [] - pass - pass - else: - if not self.__instance_notif_before.has_key(name): - self.__instance_notif_before[name] = [] - pass - if not self.__instance_notif_after.has_key(name): - self.__instance_notif_after[name] = [] - pass - pass - pass - - return - - - def has_property(self, name): - """Returns true if given property name refers an observable - property inside self or inside derived classes""" - return self.__properties__.has_key(name) or \ - self.__derived_properties__.has_key(name) - - - def register_observer(self, observer): - if observer in self.__observers: return # not already registered - - self.__observers.append(observer) - for key in (self.__properties__.keys() + self.__derived_properties__.keys()): - self.__add_observer_notification(observer, key) - pass - - return - - - def unregister_observer(self, observer): - if observer not in self.__observers: return - - for key in (self.__properties__.keys() + self.__derived_properties__.keys()): - self.__remove_observer_notification(observer, key) - pass - - self.__observers.remove(observer) - return - - - def _reset_property_notification(self, prop_name): - """Called when it has be done an assignment that changes the - type of a property or the instance of the property has been - changed to a different instance. In this case it must be - unregistered and registered again""" - - self.register_property(prop_name) - - for observer in self.__observers: - self.__remove_observer_notification(observer, prop_name) - self.__add_observer_notification(observer, prop_name) - pass - return - - - def __add_observer_notification(self, observer, prop_name): - """Searches in the observer for any possible listener, and - stores the notification methods to be called later""" - - method_name = "property_%s_value_change" % prop_name - if hasattr(observer, method_name): - method = getattr(observer, method_name) - if method not in self.__value_notifications[prop_name]: - list.append(self.__value_notifications[prop_name], method) - pass - pass - - # is it a signal? - orig_prop = getattr(self, "_prop_%s" % prop_name) - if isinstance(orig_prop, Signal): - method_name = "property_%s_signal_emit" % prop_name - if hasattr(observer, method_name): - method = getattr(observer, method_name) - if method not in self.__signal_notif[prop_name]: - list.append(self.__signal_notif[prop_name], method) - pass - pass - pass - - # is it an instance change notification type? - elif isinstance(orig_prop, ObsWrapperBase): - method_name = "property_%s_before_change" % prop_name - if hasattr(observer, method_name): - method = getattr(observer, method_name) - if method not in self.__instance_notif_before[prop_name]: - list.append(self.__instance_notif_before[prop_name], method) - pass - pass - - method_name = "property_%s_after_change" % prop_name - if hasattr(observer, method_name): - method = getattr(observer, method_name) - if method not in self.__instance_notif_after[prop_name]: - list.append(self.__instance_notif_after[prop_name], method) - pass - pass - pass - - return - - - def __remove_observer_notification(self, observer, prop_name): - if self.__value_notifications.has_key(prop_name): - method_name = "property_%s_value_change" % prop_name - if hasattr(observer, method_name): - method = getattr(observer, method_name) - if method in self.__value_notifications[prop_name]: - self.__value_notifications[prop_name].remove(method) - pass - pass - pass - - - orig_prop = getattr(self, "_prop_%s" % prop_name) - # is it a signal? - if isinstance(orig_prop, Signal): - method_name = "property_%s_signal_emit" % prop_name - if hasattr(observer, method_name): - method = getattr(observer, method_name) - if method in self.__signal_notif[prop_name]: - self.__signal_notif[prop_name].remove(method) - pass - pass - pass - - # is it an instance change notification type? - elif isinstance(orig_prop, ObsWrapperBase): - if self.__instance_notif_before.has_key(prop_name): - method_name = "property_%s_before_change" % prop_name - if hasattr(observer, method_name): - method = getattr(observer, method_name) - if method in self.__instance_notif_before[prop_name]: - self.__instance_notif_before[prop_name].remove(method) - pass - pass - pass - - if self.__instance_notif_after.has_key(prop_name): - method_name = "property_%s_after_change" % prop_name - if hasattr(observer, method_name): - method = getattr(observer, method_name) - if method in self.__instance_notif_after[prop_name]: - self.__instance_notif_after[prop_name].remove(method) - pass - pass - pass - pass - - return - - - def __notify_observer__(self, observer, method, *args, **kwargs): - """This can be overridden by derived class in order to call - the method in a different manner (for example, in - multithreading, or a rpc, etc.) This implementation simply - calls the given method with the given arguments""" - return method(*args, **kwargs) - - - # ---------- Notifiers: - - def notify_property_value_change(self, prop_name, old, new): - assert(self.__value_notifications.has_key(prop_name)) - for method in self.__value_notifications[prop_name] : - obs = method.im_self - # notification occurs checking spuriousness of the observer - if old != new or obs.accepts_spurious_change(): - self.__notify_observer__(obs, method, - self, old, new) # notifies the change - pass - pass - return - - def notify_method_before_change(self, prop_name, instance, meth_name, - args, kwargs): - assert(self.__instance_notif_before.has_key(prop_name)) - for method in self.__instance_notif_before[prop_name] : - self.__notify_observer__(method.im_self, method, self, instance, - meth_name, args, kwargs) # notifies the change - pass - return - - def notify_method_after_change(self, prop_name, instance, meth_name, - res, args, kwargs): - assert(self.__instance_notif_after.has_key(prop_name)) - for method in self.__instance_notif_after[prop_name] : - self.__notify_observer__(method.im_self, method, self, instance, - meth_name, res, args, kwargs) # notifies the change - pass - return - - def notify_signal_emit(self, prop_name, args, kwargs): - assert(self.__signal_notif.has_key(prop_name)) - for method in self.__signal_notif[prop_name] : - self.__notify_observer__(method.im_self, method, self, - args, kwargs) # notifies the signal emit - pass - return - - - pass # end of class Model -# ---------------------------------------------------------------------- - - - -import gtk -# ---------------------------------------------------------------------- -class TreeStoreModel (Model, gtk.TreeStore): - """Use this class as base class for your model derived by - gtk.TreeStore""" - __metaclass__ = support.metaclasses.ObservablePropertyGObjectMeta - - def __init__(self, column_type, *args): - Model.__init__(self) - gtk.TreeStore.__init__(self, column_type, *args) - return - pass - - -# ---------------------------------------------------------------------- -class ListStoreModel (Model, gtk.ListStore): - """Use this class as base class for your model derived by - gtk.ListStore""" - __metaclass__ = support.metaclasses.ObservablePropertyGObjectMeta - - def __init__(self, column_type, *args): - Model.__init__(self) - gtk.ListStore.__init__(self, column_type, *args) - return - pass - - -# ---------------------------------------------------------------------- -class TextBufferModel (Model, gtk.TextBuffer): - """Use this class as base class for your model derived by - gtk.TextBuffer""" - __metaclass__ = support.metaclasses.ObservablePropertyGObjectMeta - - def __init__(self, table=None): - Model.__init__(self) - gtk.TextBuffer.__init__(self, table) - return - pass - diff --git a/src/gtkmvc/model_mt.py b/src/gtkmvc/model_mt.py deleted file mode 100644 index f4ff2b5..0000000 --- a/src/gtkmvc/model_mt.py +++ /dev/null @@ -1,124 +0,0 @@ -# Author: Roberto Cavada -# -# Copyright (c) 2006 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - - -from gtkmvc.model import Model -import support.metaclasses - -try: import threading as _threading -except ImportError: import dummy_threading as _threading - -import gobject -if hasattr(gobject, "threads_init"): gobject.threads_init() -else: import gtk; gtk.threads_init() - - -class ModelMT (Model): - """A base class for models whose observable properties can be - changed by threads different than gtk main thread. Notification is - performed by exploiting the gtk idle loop only if needed, - otherwise the standard notification system (direct method call) is - used. In this model, the observer is expected to run in the gtk - main loop thread.""" - - __metaclass__ = support.metaclasses.ObservablePropertyMetaMT - - def __init__(self): - Model.__init__(self) - self.__observer_threads = {} - self._prop_lock = _threading.Lock() - return - - def register_observer(self, observer): - Model.register_observer(self, observer) - self.__observer_threads[observer] = _threading.currentThread() - return - - def unregister_observer(self, observer): - Model.unregister_observer(self, observer) - del self.__observer_threads[observer] - return - - # ---------- Notifiers: - - def __notify_observer__(self, observer, method, *args, **kwargs): - """This makes a call either through the gtk.idle list or a - direct method call depending whether the caller's thread is - different from the observer's thread""" - - assert self.__observer_threads.has_key(observer) - if _threading.currentThread() == self.__observer_threads[observer]: - # standard call - return Model.__notify_observer__(self, observer, method, - *args, **kwargs) - - # multi-threading call - gobject.idle_add(self.__idle_callback, observer, method, args, kwargs) - return - - def __idle_callback(self, observer, method, args, kwargs): - method(*args, **kwargs) - return False - - - pass # end of class - - -import gtk -# ---------------------------------------------------------------------- -class TreeStoreModelMT (ModelMT, gtk.TreeStore): - """Use this class as base class for your model derived by - gtk.TreeStore""" - __metaclass__ = support.metaclasses.ObservablePropertyGObjectMetaMT - - def __init__(self, column_type, *args): - ModelMT.__init__(self) - gtk.TreeStore.__init__(self, column_type, *args) - return - pass - - -# ---------------------------------------------------------------------- -class ListStoreModelMT (ModelMT, gtk.ListStore): - """Use this class as base class for your model derived by - gtk.ListStore""" - __metaclass__ = support.metaclasses.ObservablePropertyGObjectMetaMT - - def __init__(self, column_type, *args): - ModelMT.__init__(self) - gtk.ListStore.__init__(self, column_type, *args) - return - pass - - -# ---------------------------------------------------------------------- -class TextBufferModelMT (ModelMT, gtk.TextBuffer): - """Use this class as base class for your model derived by - gtk.TextBuffer""" - __metaclass__ = support.metaclasses.ObservablePropertyGObjectMetaMT - - def __init__(self, table=None): - ModelMT.__init__(self) - gtk.TextBuffer.__init__(self, table) - return - pass diff --git a/src/gtkmvc/observable.py b/src/gtkmvc/observable.py deleted file mode 100644 index 0ec78fd..0000000 --- a/src/gtkmvc/observable.py +++ /dev/null @@ -1,69 +0,0 @@ -# ------------------------------------------------------------------------- -# Author: Roberto Cavada -# -# Copyright (C) 2006 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA.ridge, MA 02139, USA. -# -# For more information on pygtkmvc see -# or email to the author . -# ------------------------------------------------------------------------- - - -from support import decorators -from support.wrappers import ObsWrapperBase - -# ---------------------------------------------------------------------- -class Observable (ObsWrapperBase): - def __init__(self): - ObsWrapperBase.__init__(self) - return - pass # end of class - - -@decorators.good_decorator -def observed(func): - """Use this decorator to make your class methods observable. - - Your observer will receive at most two notifications: - - property__before_change - - property__after_change - - """ - - def wrapper(*args, **kwargs): - self = args[0] - assert(isinstance(self, Observable)) - - self._notify_method_before(self, func.__name__, args, kwargs) - res = func(*args, **kwargs) - self._notify_method_after(self, func.__name__, res, args, kwargs) - return res - return wrapper - - -# ---------------------------------------------------------------------- -class Signal (Observable): - """Base class for signals properties""" - def __init__(self): - Observable.__init__(self) - return - - def emit(self, *args, **kwargs): - return self.__get_model__().notify_signal_emit( - self.__get_prop_name__(), args, kwargs) - pass # end of class - diff --git a/src/gtkmvc/observer.py b/src/gtkmvc/observer.py deleted file mode 100644 index 52c3ee2..0000000 --- a/src/gtkmvc/observer.py +++ /dev/null @@ -1,91 +0,0 @@ -# ------------------------------------------------------------------------- -# Author: Roberto Cavada -# -# Copyright (C) 2006 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . -# ------------------------------------------------------------------------- - - -class Observer (object): - """Use this class as base class of all observers""" - - def __init__(self, model=None, spurious=False): - """ - When parameter spurious is set to False - (default value) the observer declares that it is not - interested in receiving value-change notifications when - property's value does not really change. This happens when a - property got assigned to a value that is the same it had - before being assigned. - - A notification was used to be sent to the observer even in - this particular condition, because spurious (non-changing) - assignments were used as signals when signals were not - supported by early version of the framework. The observer - was in charge of deciding what to do with spurious - assignments, by checking if the old and new values were - different at the beginning of the notification code. With - latest version providing new notification types like - signals, this requirement seems to be no longer needed, and - delivering a notification is no longer a sensible - behaviour. - - This is the reason for providing parameter - spurious that changes the previous behaviour - but keeps availability of a possible backward compatible - feature. - """ - - self.model = None - self.__accepts_spurious__ = spurious - self.register_model(model) - return - - def register_model(self, model): - self.unregister_model() - self.model = model - if self.model: self.model.register_observer(self) - return - - def accepts_spurious_change(self): - """ - Returns True if this observer is interested in receiving - spurious value changes. This is queried by the model when - notifying a value change.""" - return self.__accepts_spurious__ - - def unregister_model(self): - if self.model: - self.model.unregister_observer(self) - self.model = None - pass - return - - def __del__(self): - self.unregister_model() - return - - def get_model(self): return self.model - - pass # end of class - - - diff --git a/src/gtkmvc/support/__init__.py b/src/gtkmvc/support/__init__.py deleted file mode 100644 index a796a37..0000000 --- a/src/gtkmvc/support/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# Author: Roberto Cavada -# -# Copyright (c) 2005 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - - -__all__ = ["metaclass_base", "metaclasses", "wrappers", "decorators", - "factories"] diff --git a/src/gtkmvc/support/decorators.py b/src/gtkmvc/support/decorators.py deleted file mode 100644 index 3f9d32a..0000000 --- a/src/gtkmvc/support/decorators.py +++ /dev/null @@ -1,44 +0,0 @@ -# ------------------------------------------------------------------------- -# Author: Roberto Cavada -# -# Copyright (C) 2006 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . -# ------------------------------------------------------------------------- - - - -# This file contains decorators to be used (privately) by other parts -# of the framework - -def good_decorator(decorator): - """This decorator makes decorators behave well wrt to decorated - functions names, doc, etc.""" - def new_decorator(f): - g = decorator(f) - g.__name__ = f.__name__ - g.__doc__ = f.__doc__ - g.__dict__.update(f.__dict__) - return g - - new_decorator.__name__ = decorator.__name__ - new_decorator.__doc__ = decorator.__doc__ - new_decorator.__dict__.update(decorator.__dict__) - return new_decorator diff --git a/src/gtkmvc/support/exceptions.py b/src/gtkmvc/support/exceptions.py deleted file mode 100644 index 1ca0887..0000000 --- a/src/gtkmvc/support/exceptions.py +++ /dev/null @@ -1,24 +0,0 @@ -# Author: Roberto Cavada -# -# Copyright (c) 2005 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - - diff --git a/src/gtkmvc/support/factories.py b/src/gtkmvc/support/factories.py deleted file mode 100644 index 4b67d18..0000000 --- a/src/gtkmvc/support/factories.py +++ /dev/null @@ -1,86 +0,0 @@ -# ------------------------------------------------------------------------- -# Author: Roberto Cavada -# -# Copyright (C) 2008 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . -# ------------------------------------------------------------------------- - -import new -from gtkmvc import Model -from noconflict import get_noconflict_metaclass - -class ModelFactory (object): - """This factory constructs classes for models. Use it to build - the classes to derive your own models""" - - __memoized = {} - - @staticmethod - def __fix_bases(base_classes, have_mt): - """This function check whether base_classes contains a Model - instance. If not, choose the best fitting class for - model. Furthermore, it makes the list in a cannonical - ordering form in a way that ic can be used as memoization - key""" - fixed = list(base_classes) - contains_model = False - for b in fixed: - if isinstance(fixed, Model): contains_model = True; break - pass - - # adds a model when user is lazy - if not contains_model: - if have_mt: - from gtkmvc.model_mt import ModelMT - fixed.insert(0, ModelMT) - else: fixed.insert(0, Model) - pass - - class ModelFactoryWrap (object): - __metaclass__ = get_noconflict_metaclass(tuple(fixed), (), ()) - def __init__(self, *args, **kwargs): pass - pass - - fixed.append(ModelFactoryWrap) - fixed.sort() - return tuple(fixed) - - @staticmethod - def make(base_classes=(), have_mt=False): - """Use this static method to build a model class that - possibly derives from other classes. If have_mt is True, - then returned class will take into account multi-threading - issues when dealing with observable properties.""" - - good_bc = ModelFactory.__fix_bases(base_classes, have_mt) - print "Base classes are:", good_bc - key = "".join(map(str, good_bc)) - if ModelFactory.__memoized.has_key(key): - return ModelFactory.__memoized[key] - - cls = new.classobj('', good_bc, {'__module__': '__main__', '__doc__': None}) - ModelFactory.__memoized[key] = cls - return cls - - #__ - #make = staticmethod(make) - - pass # end of class diff --git a/src/gtkmvc/support/metaclass_base.py b/src/gtkmvc/support/metaclass_base.py deleted file mode 100644 index 70c5963..0000000 --- a/src/gtkmvc/support/metaclass_base.py +++ /dev/null @@ -1,240 +0,0 @@ -# Author: Roberto Cavada -# -# Copyright (c) 2005 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - - -import new -import re -import types - -import gtkmvc.support.wrappers as wrappers -from gtkmvc.support.utils import get_function_from_source - - -# ---------------------------------------------------------------------- - -VERBOSE_LEVEL = 5 - -class PropertyMeta (type): - """This is a meta-class that provides auto-property support. - The idea is to allow programmers to define some properties which - will be automatically connected to auto-generated code which handles - access to those properties. - How can you use this meta-class? - First, '__metaclass__ = PropertyMeta' must be class member of the class - you want to make the automatic properties handling. - Second, '__properties__' must be a map containing the properties names - as keys, values will be initial values for properties. - That's all: after the instantiation, your class will contain all properties - you named inside '__properties__'. Each of them will be also associated - to a couple of automatically-generated functions which get and set the - property value inside a generated member variable. - About names: suppose the property is called 'x'. The generated variable - (which keeps the real value of the property x) is called _prop_x. - The getter is called get_prop_x(self), and the setter is called - 'set_prop_x(self, value)'. - - Customization: - The base implementation of getter is to return the value stored in the - variable associated to the property. The setter simply sets its value. - Programmers can override basic behaviour for getters or setters simply by - defining their getters and setters (see at the names convention above). - The customized function can lie everywhere in the user classes hierarchy. - Every overridden function will not be generated by the metaclass. - - To supply your own methods is good for few methods, but can result in a - very unconfortable way for many methods. In this case you can extend - the meta-class, and override methods get_[gs]etter_source with your - implementation (this can be probably made better). - An example is provided in meta-class PropertyMetaVerbose below. - """ - - def __init__(cls, name, bases, dict): - """class constructor""" - properties = {} - type.__init__(cls, name, bases, dict) - - props = getattr(cls, '__properties__', {}) - setattr(cls, '__derived_properties__', {}) - der_props = getattr(cls, '__derived_properties__') - - # Calculates derived properties: - for base in bases: - maps = ( getattr(base, '__properties__', {}), - getattr(base, '__derived_properties__', {}) ) - for _map in maps: - for p in _map.keys(): - if not props.has_key(p) and not der_props.has_key(p): - der_props[p] = _map[p] - pass - pass - pass - pass - - # Generates code for all properties (but not for derived props): - props = getattr(cls, '__properties__', {}) - for prop in props.keys(): - type(cls).__create_prop_accessors__(cls, prop, props[prop]) - pass - - return - - - def __msg__(cls, msg, level): - """if level is less or equal to VERBOSE_LEVEL, ths message will - be printed""" - if level <= VERBOSE_LEVEL: print msg - return - - def __create_prop_accessors__(cls, prop_name, default_val): - """Private method that creates getter and setter, and the - corresponding property""" - getter_name = "get_prop_%s" % prop_name - setter_name = "set_prop_%s" % prop_name - - members_names = cls.__dict__.keys() - - # checks if accessors are already defined: - if getter_name not in members_names: - src = type(cls).get_getter_source(cls, getter_name, prop_name) - func = get_function_from_source(src) - setattr(cls, getter_name, func) - else: - cls.__msg__("Warning: Custom member '%s' overloads generated accessor of property '%s'" \ - % (getter_name, prop_name), 2) - pass - - if setter_name not in members_names: - src = type(cls).get_setter_source(cls, setter_name, prop_name) - func = get_function_from_source(src) - setattr(cls, setter_name, func) - else: - cls.__msg__("Warning: Custom member '%s' overloads generated accessor of property '%s'" \ - % (setter_name, prop_name), 2) - pass - - prop = property(getattr(cls, getter_name), getattr(cls, setter_name)) - - if prop_name in members_names: - cls.__msg__("Warning: automatic property builder overrids property %s in class %s" \ - % (prop_name, cls.__name__), 2) - pass - setattr(cls, prop_name, prop) - - varname = "_prop_%s" % prop_name - if not varname in members_names: cls.__create_property(varname, default_val) - else: cls.__msg__("Warning: automatic property builder found a possible clashing for variable %s inside class %s" \ - % (varname, cls.__name__), 2) - return - - def __create_property(cls, name, default_val): - setattr(cls, name, cls.create_value(name, default_val)) - return - - def check_value_change(cls, old, new): - """Checks whether the value of the property changed in type - or if the instance has been changed to a different instance. - If true, a call to model._reset_property_notification should - be called in order to re-register the new property instance - or type""" - return type(old) != type(new) or \ - isinstance(old, wrappers.ObsWrapperBase) and (old != new) - - def create_value(cls, prop_name, val, model=None): - """This is used to create a value to be assigned to a - property. Depending on the type of the value, different values - are created and returned. For example, for a list, a - ListWrapper is created to wrap it, and returned for the - assignment. model is different from model when the value is - changed (a model exists). Otherwise, during property creation - model is None""" - - if isinstance(val, tuple): - # this might be a class instance to be wrapped - if len(val) == 3 and \ - isinstance(val[1], val[0]) and \ - (isinstance(val[2], tuple) or isinstance(val[2], list)): - res = wrappers.ObsUserClassWrapper(val[1], val[2]) - if model: res.__set_model__(model, prop_name) - return res - pass - - elif isinstance(val, list): - res = wrappers.ObsListWrapper(val) - if model: res.__set_model__(model, prop_name) - return res - - elif isinstance(val, dict): - res = wrappers.ObsMapWrapper(val) - if model: res.__set_model__(model, prop_name) - return res - - return val - - - # ------------------------------------------------------------ - # Services - # ------------------------------------------------------------ - - # Override these: - def get_getter_source(cls, getter_name, prop_name): - """This must be overridden if you need a different implementation. - Simply the generated implementation returns the variable name - _prop_name""" - - return "def %s(self): return self._prop_%s" % (getter_name, prop_name) - - def get_setter_source(cls, setter_name, prop_name): - """This must be overridden if you need a different implementation. - Simply the generated implementation sets the variable _prop_name""" - return "def %s(self, val): self._prop_%s = val" \ - % (setter_name, prop_name) - - pass # end of class -# ---------------------------------------------------------------------- - - -# What follows underneath is a set of examples of usage - -## class PropertyMetaVerbose (PropertyMeta): -## """An example of customization""" -## def get_getter_source(cls, getter_name, prop_name): -## return "def %s(self): print 'Calling %s!'; return self._prop_%s" \ -## % (getter_name, getter_name, prop_name) - -## def get_setter_source(cls, setter_name, prop_name): -## return "def %s(self, val): print 'Calling %s!'; self._prop_%s = val;" \ -## % (setter_name, setter_name, prop_name) -## pass #end of class -# ---------------------------------------------------------------------- - -## class User: -## """An example of usage""" -## __metaclass__ = PropertyMetaVerbose -## __properties__ = {'x':10, 'y':20} - -## def __init__(self): -## print self.x # x is 10 -## self.x = self.y + 10 # x is now 30 -## return -## pass -# ---------------------------------------------------------------------- diff --git a/src/gtkmvc/support/metaclasses.py b/src/gtkmvc/support/metaclasses.py deleted file mode 100644 index 34cd3d2..0000000 --- a/src/gtkmvc/support/metaclasses.py +++ /dev/null @@ -1,82 +0,0 @@ -# Author: Roberto Cavada -# -# Copyright (c) 2005 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - -from metaclass_base import PropertyMeta -import types - - -class ObservablePropertyMeta (PropertyMeta): - """Classes instantiated by this meta-class must provide a method named - notify_property_change(self, prop_name, old, new)""" - def __init__(cls, name, bases, dict): - PropertyMeta.__init__(cls, name, bases, dict) - return - - def get_setter_source(cls, setter_name, prop_name): - return """def %(setter)s(self, val): - old = self._prop_%(prop)s - new = type(self).create_value('%(prop)s', val, self) - self._prop_%(prop)s = new - if type(self).check_value_change(old, new): self._reset_property_notification('%(prop)s') - self.notify_property_value_change('%(prop)s', old, val) - return -""" % {'setter':setter_name, 'prop':prop_name} - - pass #end of class - - -class ObservablePropertyMetaMT (ObservablePropertyMeta): - """This class provides multithreading support for accesing - properties, through a locking mechanism. It is assumed a lock is - owned by the class that uses it. A Lock object called _prop_lock - is assumed to be a member of the using class. see for example class - ModelMT""" - def __init__(cls, name, bases, dict): - ObservablePropertyMeta.__init__(cls, name, bases, dict) - return - - def get_setter_source(cls, setter_name, prop_name): - return """def %(setter)s(self, val): - old = self._prop_%(prop)s - new = type(self).create_value('%(prop)s', val, self) - self._prop_lock.acquire() - self._prop_%(prop)s = new - self._prop_lock.release() - if type(self).check_value_change(old, new): self._reset_property_notification('%(prop)s') - self.notify_property_value_change('%(prop)s', old, val) - return -""" % {'setter':setter_name, 'prop':prop_name} - - pass #end of class - - -try: - from gobject import GObjectMeta - class ObservablePropertyGObjectMeta (ObservablePropertyMeta, GObjectMeta): pass - class ObservablePropertyGObjectMetaMT (ObservablePropertyMetaMT, GObjectMeta): pass -except: - class ObservablePropertyGObjectMeta (ObservablePropertyMeta): pass - class ObservablePropertyGObjectMetaMT (ObservablePropertyMetaMT): pass - pass - - diff --git a/src/gtkmvc/support/noconflict.py b/src/gtkmvc/support/noconflict.py deleted file mode 100644 index 3976bcd..0000000 --- a/src/gtkmvc/support/noconflict.py +++ /dev/null @@ -1,65 +0,0 @@ -# Author: Michele Simionato -# Copyright (C) 2004 by Michele Simionato -# License: Python License (version not specified) -# Last Updated: 2nd of March 2007, 10:23 GMT -# -# Any serious user of metaclasses has been bitten at least once by -# the infamous metaclass/metatype conflict. This script contains a -# general recipe to solve the problem, as well as some theory and -# some examples. - - -import inspect, types, __builtin__ - -############## preliminary: two utility functions ##################### - -def skip_redundant(iterable, skipset=None): - "Redundant items are repeated items or items in the original skipset." - if skipset is None: skipset = set() - for item in iterable: - if item not in skipset: - skipset.add(item) - yield item - - -def remove_redundant(metaclasses): - skipset = set([types.ClassType]) - for meta in metaclasses: # determines the metaclasses to be skipped - skipset.update(inspect.getmro(meta)[1:]) - return tuple(skip_redundant(metaclasses, skipset)) - -################################################################## -## now the core of the module: two mutually recursive functions ## -################################################################## - -memoized_metaclasses_map = {} - -def get_noconflict_metaclass(bases, left_metas, right_metas): - """Not intended to be used outside of this module, unless you know - what you are doing.""" - # make tuple of needed metaclasses in specified priority order - metas = left_metas + tuple(map(type, bases)) + right_metas - needed_metas = remove_redundant(metas) - - # return existing confict-solving meta, if any - if needed_metas in memoized_metaclasses_map: - return memoized_metaclasses_map[needed_metas] - # nope: compute, memoize and return needed conflict-solving meta - elif not needed_metas: # wee, a trivial case, happy us - meta = type - elif len(needed_metas) == 1: # another trivial case - meta = needed_metas[0] - # check for recursion, can happen i.e. for Zope ExtensionClasses - elif needed_metas == bases: - raise TypeError("Incompatible root metatypes", needed_metas) - else: # gotta work ... - metaname = '_' + ''.join([m.__name__ for m in needed_metas]) - meta = classmaker()(metaname, needed_metas, {}) - memoized_metaclasses_map[needed_metas] = meta - return meta - -def classmaker(left_metas=(), right_metas=()): - def make_class(name, bases, adict): - metaclass = get_noconflict_metaclass(bases, left_metas, right_metas) - return metaclass(name, bases, adict) - return make_class diff --git a/src/gtkmvc/support/test.py b/src/gtkmvc/support/test.py deleted file mode 100644 index f0139ba..0000000 --- a/src/gtkmvc/support/test.py +++ /dev/null @@ -1,4 +0,0 @@ -class A (object): - a = 10 - pass - diff --git a/src/gtkmvc/support/utils.py b/src/gtkmvc/support/utils.py deleted file mode 100644 index 02725f2..0000000 --- a/src/gtkmvc/support/utils.py +++ /dev/null @@ -1,39 +0,0 @@ -# Author: Roberto Cavada -# -# Copyright (c) 2007 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - - - -def get_function_from_source(source): - """Given source code of a function, a function object is - returned""" - - import re - m = re.compile("def\s+(\w+)\s*\(.*\):").match(source) - if m is None: raise ValueError("Given source is not a valid function:\n"+ - source) - name = m.group(1) - - exec source - code = eval("%s.func_code" % name) - import new - return new.function(code, globals(), name) diff --git a/src/gtkmvc/support/wrappers.py b/src/gtkmvc/support/wrappers.py deleted file mode 100644 index c50a79b..0000000 --- a/src/gtkmvc/support/wrappers.py +++ /dev/null @@ -1,162 +0,0 @@ -# ------------------------------------------------------------------------- -# Author: Roberto Cavada -# -# Copyright (C) 2006 by Roberto Cavada -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . -# ------------------------------------------------------------------------- - - -import new - - -# ---------------------------------------------------------------------- -class ObsWrapperBase (object): - """ - This class is a base class wrapper for user-defined classes and - containers like lists and maps. - """ - - def __init__(self): - self.__prop_name = None - self.__gtkmvc_model = None - return - - def __set_model__(self, model, prop_name): - self.__prop_name = prop_name - self.__gtkmvc_model = model - return - - def __get_prop_name__(self): return self.__prop_name - def __get_model__(self): return self.__gtkmvc_model - - def _notify_method_before(self, instance, name, args, kwargs): - self.__get_model__().notify_method_before_change(self.__prop_name, - instance, - name, args, kwargs) - return - - def _notify_method_after(self, instance, name, res_val, args, kwargs): - self.__get_model__().notify_method_after_change(self.__prop_name, - instance, - name, res_val, args, kwargs) - return - - pass - - -# ---------------------------------------------------------------------- -class ObsWrapper (ObsWrapperBase): - """ - Base class for wrappers, like user-classes and sequences. - """ - - - def __init__(self, obj, method_names): - ObsWrapperBase.__init__(self) - - self._obj = obj - self.__doc__ = obj.__doc__ - - for name in method_names: - if hasattr(self._obj, name): - src = self.__get_wrapper_code(name) - exec src - - code = eval("%s.func_code" % name) - func = new.function(code, globals()) - meth = new.instancemethod(func, self, type(self).__name__) - setattr(self, name, meth) - pass - pass - - return - - def __get_wrapper_code(self, name): - return """def %(name)s(self, *args, **kwargs): - self._notify_method_before(self._obj, "%(name)s", args, kwargs) - res = self._obj.%(name)s(*args, **kwargs) - self._notify_method_after(self._obj, "%(name)s", res, args, kwargs) - return res""" % {'name' : name} - - # For all fall backs - def __getattr__(self, name): return getattr(self._obj, name) - def __repr__(self): return self._obj.__repr__() - def __str__(self): return self._obj.__str__() - - pass #end of class - - -# ---------------------------------------------------------------------- -class ObsSeqWrapper (ObsWrapper): - def __init__(self, obj, method_names): - ObsWrapper.__init__(self, obj, method_names) - return - - def __setitem__(self, key, val): - - self._notify_method_before(self._obj, "__setitem__", (key,val), {}) - res = self._obj.__setitem__(key, val) - self._notify_method_after(self._obj, "__setitem__", res, (key,val), {}) - return res - - def __delitem__(self, key): - self._notify_method_before(self._obj, "__delitem__", (key,), {}) - res = self._obj.__delitem__(key) - self._notify_method_after(self._obj, "__delitem__", res, (key,), {}) - return res - - - def __getitem__(self, key): - return self._obj.__getitem__(key) - - pass #end of class - - -# ---------------------------------------------------------------------- -class ObsMapWrapper (ObsSeqWrapper): - def __init__(self, m): - methods = ("clear", "pop", "popitem", "update", - "setdefault") - ObsSeqWrapper.__init__(self, m, methods) - return - pass #end of class - - -# ---------------------------------------------------------------------- -class ObsListWrapper (ObsSeqWrapper): - def __init__(self, l): - methods = ("append", "extend", "insert", - "pop", "remove", "reverse", "sort") - ObsSeqWrapper.__init__(self, l, methods) - return - pass #end of class - - - -# ---------------------------------------------------------------------- -class ObsUserClassWrapper (ObsWrapper): - def __init__(self, user_class_instance, obs_method_names): - ObsWrapper.__init__(self, user_class_instance, obs_method_names) - return - pass #end of class - - - diff --git a/src/gtkmvc/view.py b/src/gtkmvc/view.py deleted file mode 100644 index 8f485dc..0000000 --- a/src/gtkmvc/view.py +++ /dev/null @@ -1,215 +0,0 @@ -# Author: Roberto Cavada -# Modified by: Guillaume Libersat -# -# Copyright (c) 2005 by Roberto Cavada -# Copyright (c) 2007 by Guillaume Libersat -# -# pygtkmvc is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# pygtkmvc is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110, USA. -# -# For more information on pygtkmvc see -# or email to the author Roberto Cavada . -# Please report bugs to . - - -import gtk.glade -from controller import Controller -import types - -class View (object): - - def __init__(self, controller, glade_filename=None, - glade_top_widget_name=None, parent_view=None, register=True): - """If register is False you *must* call 'controller.register_view(self)' - from the derived class constructor (i.e. registration is delayed) - If filename is not given (or None) all following parameters must be - not given (or None). In that case widgets must be connected manually. - glade_top_widget_name can be either a string name or list of names.""" - self.manualWidgets = {} - self.autoWidgets = None - - self.xmlWidgets = [] - - # Sets a callback for custom widgets - gtk.glade.set_custom_handler(self._custom_widget_create) - - if (( type(glade_top_widget_name) == types.StringType) - or (glade_top_widget_name is None) ): - wids = (glade_top_widget_name,) - else: wids = glade_top_widget_name # Already a list or tuple - - # retrieves XML objects from glade - if (glade_filename is not None): - for i in range(0,len(wids)): - self.xmlWidgets.append(gtk.glade.XML(glade_filename, wids[i])) - pass - pass - - # top widget list or singleton: - if (glade_top_widget_name is not None): - if len(wids) > 1: - self.m_topWidget = [] - for i in range(0, len(wids)): - self.m_topWidget.append(self[wids[i]]) - pass - else: self.m_topWidget = self[wids[0]] - else: self.m_topWidget = None - - if (glade_filename is not None): self.__autoconnect_signals(controller) - if (register): controller.register_view(self) - if (not parent_view is None): self.set_parent_view(parent_view) - return - - # Gives us the ability to do: view['widget_name'].action() - # Returns None if no widget name has been found. - def __getitem__(self, key): - wid = None - - if self.autoWidgets: - if self.autoWidgets.has_key(key): wid = self.autoWidgets[key] - pass - else: - for xml in self.xmlWidgets: - wid = xml.get_widget(key) - if wid is not None: break - pass - pass - - if wid is None: - # try with manually-added widgets: - if self.manualWidgets.has_key(key): - wid = self.manualWidgets[key] - pass - pass - return wid - - # You can also add a single widget: - def __setitem__(self, key, wid): - self.manualWidgets[key] = wid - if (self.m_topWidget is None): self.m_topWidget = wid - return - - def show(self): - ret = True - top = self.get_top_widget() - if type(top) in (types.ListType, types.TupleType): - for t in top: - if t is not None: ret = ret and t.show() - pass - elif (top is not None): ret = top.show_all() - else: ret = False - return ret - - - def hide(self): - top = self.get_top_widget() - if type(top) in (types.ListType, types.TupleType): - for t in top: - if t is not None: t.hide_all() - pass - elif top is not None: top.hide_all() - return - - # Returns the top-level widget, or a list of top widgets - def get_top_widget(self): - return self.m_topWidget - - - # Set parent view: - def set_parent_view(self, parent_view): - top = self.get_top_widget() - if type(top) in (types.ListType, types.TupleType): - for t in top: - if t is not None: - t.set_transient_for(parent_view.get_top_widget()) - pass - pass - elif (top is not None): - top.set_transient_for(parent_view.get_top_widget()) - pass - - return - - # Set the transient for the view: - def set_transient(self, transient_view): - top = self.get_top_widget() - if type(top) in (types.ListType, types.TupleType): - for t in top: - if t is not None: - transient_view.get_top_widget().set_transient_for(t) - pass - pass - elif (top is not None): - transient_view.get_top_widget().set_transient_for(top) - pass - return - - # Finds the right callback for custom widget creation and calls it - # Returns None if an undefined or invalid handler is found - def _custom_widget_create(self, glade, function_name, widget_name, - str1, str2, int1, int2): - # This code was kindly provided by Allan Douglas - if function_name is not None: - handler = getattr(self, function_name, None) - if handler is not None: return handler(str1, str2, int1, int2) - pass - return None - - # implements the iteration protocol - def __iter__(self): - # pre-calculates the auto widgets if needed: - if self.autoWidgets is None: - self.autoWidgets = {} - - for xml in self.xmlWidgets: - for wid in xml.get_widget_prefix(""): - wname = gtk.glade.get_widget_name(wid) - assert not self.autoWidgets.has_key(wname) - self.autoWidgets[wname] = wid - pass - pass - pass - - self.__idx = 0 - self.__max1 = len(self.autoWidgets) - self.__max2 = self.__max1 + len(self.manualWidgets) - return self - - # implements the iteration protocol - def next(self): - if self.__idx >= self.__max2: raise StopIteration() - if self.__idx >= self.__max1: m = self.manualWidgets - else: m = self.autoWidgets - self.__idx += 1 - return m.keys()[self.__idx-1] - - - # performs Controller's signals auto-connection: - def __autoconnect_signals(self, controller): - dic = {} - for name in dir(controller): - method = getattr(controller, name) - if (not callable(method)): continue - assert(not dic.has_key(name)) # not already connected! - dic[name] = method - pass - - for xml in self.xmlWidgets: xml.signal_autoconnect(dic) - return - - - - pass # end of class View diff --git a/src/lib/__init__.py b/src/lib/__init__.py deleted file mode 100644 index 5e9f6f7..0000000 --- a/src/lib/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - - diff --git a/src/lib/device_helper.py b/src/lib/device_helper.py deleted file mode 100644 index 1435996..0000000 --- a/src/lib/device_helper.py +++ /dev/null @@ -1,86 +0,0 @@ -""" - Project: pyGTKtalog - Description: Simple functions for device management. - Type: lib - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2008-12-15 -""" -import os -import locale -import gettext - -from src.lib.globs import APPL_SHORT_NAME - -locale.setlocale(locale.LC_ALL, '') -gettext.install(APPL_SHORT_NAME, 'locale', unicode=True) - -def volname(mntp): - """read volume name from cd/dvd""" - dev = mountpoint_to_dev(mntp) - label = None - if dev != None: - try: - disk = open(dev, "rb") - disk.seek(32808) - label = disk.read(32).strip() - disk.close() - except IOError: - return None - return label - -def volmount(mntp): - """ - Mount device. - @param mountpoint - @returns tuple with bool status of mount, and string with error message - """ - _in, _out, _err = os.popen3("mount %s" % mntp) - inf = _err.readlines() - if len(inf) > 0: - return False, inf[0].strip() - else: - return True, '' - -def volumount(mntp): - """mount device, return 'ok' or error message""" - _in, _out, _err = os.popen3("umount %s" % mntp) - inf = _err.readlines() - if len(inf) > 0: - return inf[0].strip() - return 'ok' - -def check_mount(dev): - """Refresh the entries from fstab or mount.""" - mounts = os.popen('mount') - for line in mounts.readlines(): - parts = line.split() - device = parts - if device[0] == dev: - return True - return False - -def mountpoint_to_dev(mntp): - """guess device name by mountpoint from fstab""" - fstab = open("/etc/fstab") - device = None - for line in fstab.readlines(): - output = line.split() - # lengtht of single valid fstab line is at least 5 - if len(output) > 5 and output[1] == mntp and output[0][0] != '#': - device = output[0] - - fstab.close() - return device - -def eject_cd(eject_app, cdrom): - """mount device, return 'ok' or error message""" - if len(eject_app) > 0: - _in, _out, _err = os.popen3("%s %s" % (eject_app, cdrom)) - inf = _err.readlines() - - if len(inf) > 0 and inf[0].strip() != '': - return inf[0].strip() - - return 'ok' - return _("Eject program not specified") - diff --git a/src/lib/globs.py b/src/lib/globs.py deleted file mode 100644 index 89e0120..0000000 --- a/src/lib/globs.py +++ /dev/null @@ -1,41 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - -import os.path -import sys - -if sys.argv[0]: - top_dir = os.path.dirname(os.path.abspath(sys.argv[0])) -else: - top_dir = "." - -# ---------------------------------------------------------------------- -TOPDIR = top_dir -RESOURCES_DIR = os.path.join(TOPDIR, "resources") -GLADE_DIR = os.path.join(RESOURCES_DIR, "glade") -STYLES_DIR = os.path.join(RESOURCES_DIR, "styles") -APPL_SHORT_NAME = "pygtktalog" -APPL_VERSION = (1, 0, 2) -# ---------------------------------------------------------------------- - diff --git a/src/lib/gthumb.py b/src/lib/gthumb.py deleted file mode 100644 index 38c5f5e..0000000 --- a/src/lib/gthumb.py +++ /dev/null @@ -1,82 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- -from xml.dom import minidom -import gzip -import os -from datetime import date - -class GthumbCommentParser(object): - """Read and return comments created eith gThumb program""" - def __init__(self, image_path, image_filename): - self.path = image_path - self.filename = image_filename - - def parse(self): - """Return dictionary with apropriate fields, or None if no comment - available""" - try: - gzf = gzip.open(os.path.join(self.path, '.comments', - self.filename + '.xml')) - except: - return None - - try: - xml = gzf.read() - gzf.close() - except: - gzf.close() - return None - - if not xml: - return None - - retval = {} - doc = minidom.parseString(xml) - - try: - retval['note'] = doc.getElementsByTagName('Note').item(0) - retval['note'] = retval['note'].childNodes.item(0).data - except: retval['note'] = None - - try: - retval['place'] = doc.getElementsByTagName('Place').item(0) - retval['place'] = retval['place'].childNodes.item(0).data - except: retval['place'] = None - - try: - d = doc.getElementsByTagName('Time').item(0).childNodes - d = d.item(0).data - if int(d) > 0: retval['date'] = date.fromtimestamp(int(d)) - else: retval['date'] = None - except: retval['date'] = None - - try: - retval['keywords'] = doc.getElementsByTagName('Keywords').item(0) - retval['keywords'] = retval['keywords'].childNodes.item(0) - retval['keywords'] = retval['keywords'].data.split(',') - except: pass - - if len(retval) > 0: return retval - else: return None - diff --git a/src/lib/img.py b/src/lib/img.py deleted file mode 100644 index 1fa4a75..0000000 --- a/src/lib/img.py +++ /dev/null @@ -1,78 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - -from shutil import copy -from os import path -from hashlib import sha512 - -from PIL import Image - - -class Img(object): - - def __init__(self, filename, base=''): - self.root = 'images' - self.x = 160 - self.y = 160 - self.filename = filename - self.base = base - f = open(filename, "r") - self.sha512 = sha512(f.read()).hexdigest() - f.close() - - def save(self): - """Save image and asociated thumbnail into specific directory structure - returns filename for image""" - - image_filename = path.join(self.base, self.sha512) - thumbnail = path.join(self.base, self.sha512 + "_t") - - # check wheter image already exists - if path.exists(image_filename) and path.exists(thumbnail): - if __debug__: - print "image", self.filename, "with hash", - print self.sha512, "already exist" - return self.sha512 - - if not path.exists(thumbnail): - im = self.__scale_image() - im.save(thumbnail, "JPEG") - - # copy image - if not path.exists(image_filename): - copy(self.filename, image_filename) - - return self.sha512 - - # private class functions - def __scale_image(self): - """create thumbnail. returns image object or None""" - try: - im = Image.open(self.filename).convert('RGB') - except: - return None - x, y = im.size - if x > self.x or y > self.y: - im.thumbnail((self.x, self.y), Image.ANTIALIAS) - return im diff --git a/src/lib/no_thumb.py b/src/lib/no_thumb.py deleted file mode 100644 index 28765c7..0000000 --- a/src/lib/no_thumb.py +++ /dev/null @@ -1,2397 +0,0 @@ -no_thumb = "" +\ - "GdkP" +\ - "\0\1\0\30" +\ - "\1\1\0\2" +\ - "\0\0\2\0" +\ - "\0\0\0\200" +\ - "\0\0\0\200" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\\TR\21\\TR\21\\TR\21\\TR\21\\TR\21\\TR\21\\TR\21\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\\" +\ - "TR\"\\TR\"\\TR\"\\TR\"\\TR\"ZRK\21ZRK\21ZRK\21\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\\TR\21\\TR\21\\TR\21\\TR\21\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0B;9\21ZRK\21ZRK\21ZRK\21ZRK\21ZRK\21ZRK\21ZRK\21" +\ - "ZRK\21B;9\21B;9\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0ZRK\21ZRK\21ZRK\21ZRK\21\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\\TR\21\\T" +\ - "R\"\\TR\"\\TR\"\\TR\"!\37\34w!\37\34w!\37\34wJB:\377B;9\377931\37793" +\ - "1\3772/*\377!\37\34\335!\37\34\335!\37\34\335\31\31\31\273\31\31\31\273" +\ - "\20\20\20\273\20\20\20\273\20\20\20\273\20\20\20\273\31\26\22\273\31" +\ - "\31\31\273\31\31\31\273\31\31\31\273\31\31\31\231!\37\34w\\TR\"\\TR\"" +\ - "ZRK\21ZRK\21ZRK\21\\TR\"\\TR\"\\TR\"\\TR\"\\TR\21\\TR\21\\TR\21\\TR\21" +\ - "\\TR\21\\TR\21\\TR\21\\TR\21\\TR\21\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\\TR\21\\TR\21\\TR\21" +\ - "\\TR\21\\TR\21ZRK\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0B;9\21" +\ - "\20\20\20f\31\31\31\231!\37\34w!\37\34w\31\31\31f\31\31\31f\20\20\20" +\ - "3\20\20\20""3\20\20\20""3\20\20\20""3\20\20\20""3\20\20\20""3\20\20\20" +\ - "3\20\20\20""3\20\20\20""3\20\20\20""3!\37\34w\31\31\31\273\31\31\31\273" +\ - "!\37\34\335!\37\34\335!\37\34\335!\37\34\335!\37\34\335!\37\34\335!\37" +\ - "\34\335!\37\34\335!\37\34\335!\37\34\335!\37\34\335!\37\34\335!\37\34" +\ - "\3352/*\3771*'\377)\"\40\377!\37\34\377\31\31\31\377\31\31\31\377\31" +\ - "\31\31\377\31\26\22\377\31\26\22\377\20\20\20\377\20\20\20\377\20\20" +\ - "\20\377\27\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377\27\21\20" +\ - "\377\27\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377" +\ - "\27\21\20\377\27\21\20\377\27\21\20\377\20\20\20\377\20\20\20\377\31" +\ - "\26\22\356\31\31\31\335\31\31\31\231!\37\34w\20\20\20U\20\20\20""3\20" +\ - "\20\20""3B;9\21B;9\21B;9\21B;9\21B;9\21B;9\21B;9\21B;9\21B;9\21B;9\21" +\ - "B;9\21B;9\21B;9\21B;9\21B;9\21B;9\21B;9\21B;9\21B;9\21\31\31\31f!\37" +\ - "\34w\31\31\31f!\37\34w\31\31\31\210\31\31\31\252\31\31\31\273\31\31\31" +\ - "\273\31\31\31\273\31\31\31\231!\37\34w\20\20\20""3\20\20\20""3\20\20" +\ - "\20""3\10\10\10\"\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0B;9\21\20\20\20\252\31\26" +\ - "\22\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22" +\ - "\377\31\26\22\356\27\21\20\356\27\21\20\356\27\21\20\356\20\20\20\356" +\ - "\20\20\20\356\20\20\20\356\20\20\20\356\20\20\20\377\20\20\20\377\20" +\ - "\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\27\21\20\377\31\26" +\ - "\22\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20" +\ - "\377\20\20\20\377\27\21\20\377\20\20\20\377\20\20\20\377\27\21\20\377" +\ - "\31\26\22\377\27\21\20\377\31\26\22\377\27\21\20\377\27\21\20\377\27" +\ - "\21\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20" +\ - "\20\377\20\20\20\377\27\21\20\377\20\20\20\377\20\20\20\377\27\21\20" +\ - "\377\27\21\20\377\31\26\22\377\31\26\22\377\31\26\22\377\31\31\31\377" +\ - "\31\31\31\377\31\31\31\377\31\26\22\377\31\26\22\377\31\26\22\377\20" +\ - "\20\20\377\27\21\20\377\31\26\22\377\31\26\22\377\31\26\22\377\20\20" +\ - "\20\356\20\20\20\335\20\20\20\314\20\20\20\273\20\20\20\252\20\20\20" +\ - "\231\20\20\20\210\20\20\20\210\27\21\20w\31\26\22w\31\26\22w\31\26\22" +\ - "f\27\21\20w\27\21\20w\31\26\22w\31\26\22w\31\31\31\210\20\20\20\210\20" +\ - "\20\20\231\20\20\20\252\20\20\20\273\20\20\20\314\27\21\20\335\27\21" +\ - "\20\356\27\21\20\377\27\21\20\377\31\26\22\377\31\26\22\377\31\26\22" +\ - "\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22\356" +\ - "\31\26\22\356\27\21\20\335\20\20\20""3\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\10\10\10\"B;9\21\20\20\20\252" +\ - "!\31\30\377!\31\30\377!\31\30\377!\31\30\377\31\26\22\377\27\21\20\377" +\ - "\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22\377\31" +\ - "\26\22\377\31\26\22\377!\31\30\377\27\21\20\377\20\20\20\377\20\20\20" +\ - "\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\27\21\20\377" +\ - "\27\21\20\377\27\21\20\377\27\21\20\377\20\20\20\377\27\21\20\377\27" +\ - "\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377\31\26" +\ - "\22\377\31\26\22\377\31\26\22\377\31\26\22\377!\31\30\377!\31\30\377" +\ - "!\37\34\377)\"\40\3772*\"\3772*\"\377;1)\377;1)\377;1)\377;1)\377;1)" +\ - "\377;1)\377;1)\377;1)\377B81\377B81\377B81\377;1)\3771*'\377)\"\40\377" +\ - "!\37\34\377\31\31\31\377\31\26\22\377\27\21\20\377\27\21\20\377\27\21" +\ - "\20\377\27\21\20\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22" +\ - "\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22\377\27\21\20\377" +\ - "\27\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377\27" +\ - "\21\20\377\27\21\20\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26" +\ - "\22\377\31\26\22\377\31\26\22\377\31\26\22\377\31\31\31\377!\31\30\377" +\ - "!\31\30\377!\31\30\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22" +\ - "\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22\377" +\ - "\27\21\20\231\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\10\10\10\"B;9\21\20\20\20\252!\31\30\377!\31\30\377" +\ - "!\31\30\3772*\"\377K<1\377PD2\377[J;\377[J;\377kSD\377kSD\377kSD\377" +\ - "dRB\377cMA\377[J;\377PD2\377K<1\377K=8\377PD2\377SA8\377PD2\377SA8\377" +\ - "SA8\377SA8\377PD2\377K<1\377C5)\377C5)\377;1)\377;1)\377B81\377B81\377" +\ - "B81\377B81\377K<1\377SA8\377cMA\377kSD\377lSI\377sZK\377scQ\377{cR\377" +\ - "{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377" +\ - "{cR\377{cR\377{cR\377sZK\377sZK\377lSI\377dRB\377[J;\377K<1\3772*\"\377" +\ - "!\37\34\377!\31\30\377\31\26\22\377\31\26\22\377\27\21\20\377\31\26\22" +\ - "\377\31\26\22\377!\31\30\377!\31\30\377!\31\30\377!\31\30\377!\31\30" +\ - "\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22\377" +\ - "\31\26\22\377\31\26\22\377!\31\30\377\31\26\22\377\31\26\22\377\31\26" +\ - "\22\377\31\26\22\377\31\26\22\377!\31\30\377!\31\30\377!\31\30\377!\31" +\ - "\30\377\31\26\22\377\31\26\22\377\31\26\22\377!\31\30\377!\31\30\377" +\ - "!\31\30\377!\31\30\377!\37\34\377!\31\30\377\20\20\20\314\10\10\10\"" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\10\10\10\"" +\ - "\20\20\20""3\20\20\20\252\31\26\22\377!\31\30\377C5)\377\224{U\377\352" +\ - "\301\212\377\352\301\212\377\324\255y\377\303\235l\377\274\225c\377\255" +\ - "\214`\377\246\203Y\377\245|Z\377\233~Z\377\233~Z\377\224rZ\377\224rZ" +\ - "\377\224rZ\377\211wZ\377\211wZ\377\204j[\377\203jR\377\203jR\377\203" +\ - "jR\377\203jR\377\203jR\377|kP\377\203jR\377\203jR\377\203jR\377\203j" +\ - "R\377{cR\377\203jR\377\214qR\377\203jR\377{cR\377{cR\377{cR\377{cR\377" +\ - "{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377" +\ - "{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377\203d" +\ - "R\377\203jR\377\203dR\377{cJ\377kSD\377cMA\377[J;\377SA8\377B81\377K" +\ - "<1\377SA8\377SA8\377[J;\377[J;\377[J;\377[J;\377SA8\377SA8\377PD2\377" +\ - "K<1\377B81\377;1)\377;1)\377;1)\377;1)\3771*'\3771*'\3772/*\377;1)\377" +\ - ";1)\377C5)\377C5)\377C5)\377K<1\377PD2\377dL;\377dL;\377k[A\377k[A\377" +\ - "PD2\377!\37\34\377!\31\30\377\27\21\20\335\10\10\10\"\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\0\0\0\21\5\5\4\"\10\10\10\"\31\26\22\252\31" +\ - "\26\22\377\27\21\20\377ZJC\377\246\204b\377\352\301\212\377\352\301\212" +\ - "\377\352\301\212\377\324\255y\377\307\242r\377\274\225c\377\274\225c" +\ - "\377\263\214c\377\255\214`\377\255\214`\377\255\204a\377\246\204b\377" +\ - "\246\204b\377\235|c\377\235|c\377\233~Z\377\225vb\377\224rZ\377\224r" +\ - "Z\377\224rZ\377\214qR\377\214qR\377\214qR\377\214qR\377\203jR\377\214" +\ - "qR\377\203jR\377\214qR\377\235zR\377\235zR\377\224{U\377\214qR\377\203" +\ - "jR\377\214kQ\377\214kQ\377\214qR\377\214kQ\377\214kQ\377\214kQ\377\203" +\ - "jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203" +\ - "jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\214" +\ - "kQ\377\214kQ\377\214qR\377\224sQ\377\224sQ\377\214qR\377\214qR\377\214" +\ - "kQ\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203" +\ - "jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203" +\ - "jR\377\203dR\377{cR\377{cR\377\203jR\377\214kQ\377\214qR\377\224{U\377" +\ - "\235zR\377\233~Z\377\246\203Y\377\246\203Y\377\255\204a\377\255\204a" +\ - "\377\263\214c\377\276\231l\377\314\245r\377\267\224i\377K<1\377\27\21" +\ - "\20\377\31\26\22\377\27\21\20\335\14\13\12""3\0\0\0\21\0\0\0\21\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\0\0\0\21\5\5\4\"\10\10\10\"\31\26\22\252\31\26\22\377\20\20\20" +\ - "\377931\377JCA\377kZJ\377\276\231l\377\352\301\212\377\352\301\212\377" +\ - "\343\255\215\377\307\242r\377\276\231l\377\267\224i\377\267\224i\377" +\ - "\267\224i\377\263\214c\377\263\214c\377\263\214c\377\263\214c\377\255" +\ - "\214`\377\255\204a\377\255\204a\377\255\204a\377\255\204a\377\246\204" +\ - "b\377\246\204b\377\246\204b\377\246\204b\377\246\204b\377\235|c\377\246" +\ - "\204b\377\246\204b\377\246\204b\377\255\214`\377\255\214`\377\255\214" +\ - "`\377\255\214`\377\255\214`\377\246\204b\377\246|c\377\245|Z\377\245" +\ - "|Z\377\245|Z\377\233~Z\377\233~Z\377\224{U\377\224rZ\377\224sQ\377\224" +\ - "sQ\377\224sQ\377\214qR\377\214kQ\377\214kQ\377\214qR\377\214qR\377\224" +\ - "sQ\377\224sQ\377\224sQ\377\224sQ\377\224sQ\377\235zR\377\235zR\377\246" +\ - "\203Y\377\246\203Y\377\235zR\377\235zR\377\235zR\377\232rR\377\224sQ" +\ - "\377\224sQ\377\224sQ\377\214qR\377\214qR\377\214qR\377\214qR\377\214" +\ - "qR\377\214qR\377\224sQ\377\224{U\377\235zR\377\235zR\377\233~Z\377\246" +\ - "\203Y\377\246\203Y\377\257\204\\\377\257\204\\\377\257\204\\\377\255" +\ - "\214`\377\257\204\\\377\255\214`\377\255\214`\377\263\214c\377\276\231" +\ - "l\377\314\245r\377\310\244m\377\211wZ\377YSC\377!\37\34\377\20\20\20" +\ - "\377\31\26\22\377\20\20\20\314\14\13\12""3\5\5\4\"\0\0\0\21\0\0\0\21" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\21\5\5\4\"\10\10\10\"\27\21\20\231\31\26\22\377\20\20\20\3771*'" +\ - "\377B;9\377931\377JCA\377tcY\377\267\224i\377\352\301\212\377\352\301" +\ - "\212\377\352\301\212\377\324\255y\377\314\245r\377\304\235s\377\276\231" +\ - "l\377\276\231l\377\303\235l\377\276\231l\377\267\224i\377\263\214c\377" +\ - "\267\224i\377\267\224i\377\267\224i\377\267\224i\377\267\224i\377\267" +\ - "\224i\377\267\224i\377\267\224i\377\267\224i\377\267\224i\377\274\225" +\ - "c\377\276\231l\377\276\231l\377\303\235l\377\303\235l\377\303\235l\377" +\ - "\310\244m\377\303\235l\377\267\224i\377\267\224i\377\267\224i\377\263" +\ - "\214c\377\255\204a\377\246\204b\377\246\204b\377\246|c\377\246|c\377" +\ - "\246|c\377\245|Z\377\255\214`\377\263\214c\377\255\214`\377\263\214c" +\ - "\377\246\204b\377\255\204a\377\255\204a\377\246\204b\377\245|Z\377\233" +\ - "~Z\377\233~Z\377\257\204\\\377\263\214c\377\263\214c\377\263\214c\377" +\ - "\255\214`\377\255\214`\377\255\214`\377\255\214`\377\246\203Y\377\246" +\ - "\203Y\377\246\204b\377\255\214`\377\255\214`\377\255\214`\377\255\214" +\ - "`\377\246\204b\377\255\214`\377\255\214`\377\255\214`\377\263\214c\377" +\ - "\263\214c\377\263\214c\377\255\214`\377\255\214`\377\263\214c\377\263" +\ - "\214c\377\255\214`\377\255\214`\377\263\214c\377\267\224i\377\310\244" +\ - "m\377\310\244m\377\246\204b\377kZJ\377JCA\377JCA\377!\31\30\377\20\20" +\ - "\20\377\31\26\22\377\20\20\20\314\14\13\12D\10\10\10\"\0\0\0\21\0\0\0" +\ - "\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\21\5\5\4\"\10\10\10\"\27\21\20\231\31\26\22\377\27\21\20\3771*'" +\ - "\377B;9\377B;9\377JB:\377LHA\377ZRK\377\203jR\377\243\224j\377\252\226" +\ - "l\377\324\255y\377\352\301\212\377\324\255y\377\324\255y\377\324\255" +\ - "y\377\324\255y\377\324\255y\377\314\245r\377\304\235s\377\304\235s\377" +\ - "\304\235s\377\303\235l\377\307\242r\377\307\242r\377\307\242r\377\314" +\ - "\245r\377\314\245r\377\314\245r\377\314\245r\377\314\245r\377\314\245" +\ - "r\377\310\244m\377\310\244m\377\310\244m\377\310\244m\377\310\244m\377" +\ - "\267\224i\377\263\214c\377\267\224i\377\303\235l\377\307\242r\377\304" +\ - "\235s\377\304\235s\377\307\242r\377\307\242r\377\304\235s\377\276\231" +\ - "l\377\303\235l\377\307\242r\377\314\245r\377\314\245r\377\314\245r\377" +\ - "\314\245r\377\314\245r\377\314\245r\377\314\245r\377\314\245r\377\307" +\ - "\242r\377\303\235l\377\314\245r\377\314\245r\377\310\244m\377\314\245" +\ - "r\377\314\245r\377\314\245r\377\307\242r\377\307\242r\377\303\235l\377" +\ - "\276\231l\377\276\231l\377\276\231l\377\303\235l\377\303\235l\377\303" +\ - "\235l\377\303\235l\377\303\235l\377\307\242r\377\307\242r\377\307\242" +\ - "r\377\310\244m\377\310\244m\377\303\235l\377\274\225c\377\274\225c\377" +\ - "\267\224i\377\267\224i\377\263\214c\377\255\214`\377\255\214`\377\211" +\ - "wZ\377dYI\377QDA\377JCA\377RJB\377B81\377\27\21\20\377\27\21\20\377\31" +\ - "\26\22\377\14\13\12\273\10\10\10D\5\5\4\"\0\0\0\"\0\0\0\21\0\0\0\21\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0\0\0\"\10\10" +\ - "\10\"\20\20\20\210\31\26\22\377\27\21\20\3771*'\377B;9\377B;9\377B;9" +\ - "\377RJB\377LHA\377JCA\377QDA\377cMA\377rcI\377\211wZ\377\211wZ\377\235" +\ - "\214d\377\252\226l\377\304\235s\377\324\255y\377\314\245r\377\303\235" +\ - "l\377\307\242r\377\307\242r\377\303\235l\377\267\224i\377\267\224i\377" +\ - "\267\224i\377\246\204b\377\233~Z\377\224{U\377\204rQ\377{cJ\377kZJ\377" +\ - "kZJ\377dYI\377dYI\377dRB\377YSC\377ZJC\377ZJC\377[J;\377dRB\377kZJ\377" +\ - "{cJ\377{cR\377\203jR\377\235|c\377\255\204a\377\224{U\377\246\203Y\377" +\ - "\263\214c\377\246\204b\377\246\204b\377\246\204b\377\246\204b\377\246" +\ - "\204b\377\246\204b\377\233~Z\377\233~Z\377\214qR\377|kP\377|kP\377|k" +\ - "P\377\214qR\377\233~Z\377\255\214`\377\255\214`\377\255\214`\377\255" +\ - "\214`\377\267\224i\377\276\231l\377\276\231l\377\267\224i\377\267\224" +\ - "i\377\267\224i\377\255\214`\377\246\204b\377\246\204b\377\246\204b\377" +\ - "\235\214d\377\233~Z\377\224{U\377\204rQ\377\224{U\377\224{U\377\233~" +\ - "Z\377\214zR\377{cJ\377dRB\377YSC\377YLH\377JCA\377JCA\377QDA\377QDA\377" +\ - "RJB\3771*'\377\20\20\20\377\31\26\22\377\31\26\22\377\14\13\12\252\5" +\ - "\5\4D\5\5\4""3\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\0\0\0\21\0\0\0\21\0\0\0\"\10\10\10\"\20\20\20w\31\26" +\ - "\22\377\20\20\20\377)'#\377B;9\377B;9\377B;9\377LHA\377SKI\377JCA\377" +\ - "BA;\377JB:\377JB:\377JB:\377K=8\377B81\377RJB\377YSC\377dYI\377dYI\377" +\ - "PI9\377scQ\377scQ\377dRB\377ZJC\377QDA\377PI9\377JB:\377B81\377BA;\377" +\ - "B;9\377JB:\377B;9\377B81\377B;9\377B;9\377B;9\377B;9\377BA;\377K=8\377" +\ - "B;9\377:72\377B;9\377B;9\377B;9\377931\377B;9\377JB:\377B;9\377931\377" +\ - "K=8\377JB:\377B;9\377B;9\377JB:\377JB:\377BA;\3771*'\3772/*\377B;9\377" +\ - "B;9\377:72\377:72\377B;9\377BA;\377B;9\377B;9\377BA;\377JB:\377JB:\377" +\ - "LHA\377RJB\377LHA\377JB:\377JB:\377JB:\377JB:\377JB:\377JB:\377JB:\377" +\ - "B;9\377B;9\377B;9\377:72\377B;9\377B;9\377B;9\377B;9\377BA;\377JCA\377" +\ - "JCA\377LHA\377QDA\377QDA\377QDA\377QDA\377!\37\34\377\20\20\20\377\31" +\ - "\26\22\377\27\21\20\377\10\10\10\231\10\10\10D\5\5\4""3\0\0\0\"\0\0\0" +\ - "\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\21\0\0\0\"\5\5\4\"\20\20\20f\31\26\22\377\27\21\20\377!\37\34\377" +\ - "B;9\377B;9\377B;9\377LHA\377ZRK\377RJB\377JCA\377BA;\377B;9\377B;9\377" +\ - "B;9\377931\377:72\377B;9\377:72\377B;9\377931\377931\377B;9\377:72\377" +\ - "B;9\377B;9\377B;9\377B;9\377B;9\377B81\377JB:\377JB:\377JB:\377B;9\377" +\ - ":72\377B;9\377B81\377B;9\377JB:\377BA;\377JB:\377JB:\377B;9\377JB:\377" +\ - "JCA\377JB:\377B81\377B;9\377JB:\377B81\3772/*\377B;9\377JB:\377B;9\377" +\ - "B;9\377JB:\377JB:\377931\377)'#\377B81\377JB:\377JB:\377B81\377B;9\377" +\ - "JB:\377JB:\377B;9\377B;9\377JB:\377JB:\377B;9\377BA;\377JB:\377JB:\377" +\ - "B81\377B;9\377JB:\377JB:\377JB:\377JB:\377JB:\377B;9\377JB:\377JB:\377" +\ - "B;9\377B;9\377JB:\377JB:\377JCA\377JCA\377SKI\377SKI\377QDA\377QDA\377" +\ - "QDA\377JB:\377!\31\30\377\20\20\20\377\31\26\22\377\20\20\20\356\5\5" +\ - "\4w\5\5\4D\0\0\0\"\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\5\5\4\"\27\21\20" +\ - "f\31\26\22\377\31\26\22\377\31\31\31\377:72\377B;9\377B;9\377JCA\377" +\ - "ZRK\377YLH\377RJB\377JB:\377:72\377B81\377JB:\377B81\377931\377B;9\377" +\ - "B;9\377B81\377B;9\377:72\377B;9\377JB:\377JB:\377JB:\377BA;\377B;9\377" +\ - "JB:\377B;9\377B;9\377JB:\377JB:\377JCA\377JB:\377B;9\377B;9\377B;9\377" +\ - "JCA\377JCA\377JB:\377RJB\377LHA\377BA;\377LHA\377RJB\377LHA\377B;9\377" +\ - "JB:\377RJB\377BA;\377:72\377LHA\377LHA\377JB:\377JB:\377LHA\377LHA\377" +\ - "931\377)'#\377B;9\377LHA\377JB:\377B;9\377B;9\377JB:\377JB:\377B;9\377" +\ - "JB:\377JCA\377JB:\377BA;\377JB:\377LHA\377JB:\377B;9\377JB:\377LHA\377" +\ - "JB:\377JB:\377JCA\377JB:\377JB:\377LHA\377JB:\377JB:\377LHA\377JCA\377" +\ - "BA;\377SKI\377ZRK\377RJB\377QDA\377QDA\377QDA\377JB:\377\31\31\31\377" +\ - "\20\20\20\377\31\26\22\377\20\20\20\335\5\5\4w\5\5\4D\0\0\0\"\0\0\0\"" +\ - "\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\0\0\0\21\0\0\0\"\0\0\0\"\20\20\20U\31\26\22\377\31\26\22\377" +\ - "\31\26\22\377:72\377B;9\377B;9\377JB:\377YLH\377YLH\377ZRK\377:72\377" +\ - "!\37\34\377)\"\40\377B81\377JB:\377B;9\377B81\377JB:\377JCA\377BA;\377" +\ - "JB:\377B;9\377JCA\377LHA\377JB:\377JB:\377BA;\377B;9\377JB:\377B;9\377" +\ - "B;9\377JB:\377B;9\377JB:\377B81\377B81\377B;9\377JB:\377QDA\377RJB\377" +\ - "JB:\377ZJC\377ZJC\377QDA\377ZJC\377YLH\377ZJC\377JB:\377JCA\377RJB\377" +\ - "JB:\377B;9\377RJB\377RJB\377JB:\377JB:\377LHA\377LHA\377931\3771*'\377" +\ - "BA;\377LHA\377JCA\377BA;\377JB:\377QDA\377JCA\377JB:\377JCA\377LHA\377" +\ - "JCA\377JB:\377JCA\377LHA\377JCA\377B;9\377JCA\377JCA\377JCA\377JCA\377" +\ - "JCA\377BA;\377RJB\377RJB\377LHA\377BA;\377BA;\377SKI\377ZRK\377YLH\377" +\ - "RJB\377QDA\377QDA\377QDA\377JB:\377\31\31\31\377\20\20\20\377\27\21\20" +\ - "\377\20\20\20\314\5\5\4f\5\5\4D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\21\0\0\0\"\20\20\20U\31\26\22\356\31\26\22\377\27\21\20\377931\377" +\ - "BA;\377B;9\377JB:\377SKI\377YLH\377ZRK\377B;9\377\31\31\31\377\27\21" +\ - "\20\377\31\26\22\377)\"\40\3771*'\377931\377931\377BA;\377JCA\377B;9" +\ - "\377:72\377931\377931\377931\377931\377931\3772/*\3771*'\3771*'\3772" +\ - "*\"\3772*\"\3772*\"\3771*'\377)'#\3771*'\377931\377JB:\377JB:\377RJB" +\ - "\377LHA\377JB:\377RJB\377RJB\377JB:\377RJB\377ZJC\377RJB\377QDA\377Q" +\ - "DA\377RJB\377JB:\377BA;\377RJB\377SKI\377LHA\377LHA\377RJB\377LHA\377" +\ - ":72\377931\377JB:\377RJB\377QDA\377JB:\377JB:\377JB:\377JB:\377JCA\377" +\ - "JCA\377JCA\377JB:\377JB:\377JCA\377JCA\377B;9\377B;9\377JCA\377JCA\377" +\ - "JCA\377BA;\377B;9\377B;9\377JCA\377JCA\377931\377B;9\377ZRK\377YLH\377" +\ - "YLH\377RJB\377QDA\377QDA\377QDA\377JB:\377\31\31\31\377\20\20\20\377" +\ - "\31\26\22\377\14\13\12\273\5\5\4f\5\5\4D\0\0\0\"\0\0\0\21\0\0\0\21\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0" +\ - "\0\0\21\0\0\0\21\0\0\0\"\14\13\12D\31\31\31\335\31\26\22\377\20\20\20" +\ - "\3771*'\377BA;\377B;9\377BA;\377SKI\377YLH\377ZRK\377B;9\377)\"\40\377" +\ - ")\"\40\377!\31\30\377\31\26\22\377\31\26\22\377\27\21\20\377\31\26\22" +\ - "\377\31\26\22\377\31\31\31\377\31\26\22\377\31\26\22\377!\31\30\377!" +\ - "\31\30\377!\37\34\377)\"\40\3772*\"\3772*\"\377C5)\377SA8\377SA8\377" +\ - "[J;\377[J;\377ZJC\377RJB\377RJB\377RJB\377QDA\377RJB\377QDA\377QDA\377" +\ - "LHA\377LHA\377LHA\377JB:\377BA;\377BA;\377BA;\377BA;\377B;9\377BA;\377" +\ - "K=8\377B81\377B;9\377B;9\377B;9\377B;9\377B;9\377B;9\377K=8\377B81\377" +\ - "B81\377B81\377B81\377:72\377B;9\377B;9\377BA;\377JB:\377JCA\377BA;\377" +\ - "BA;\377BA;\377JCA\377JCA\377QDA\377QDA\377ZJC\377ZRK\377YLH\377YLH\377" +\ - "RJB\377JCA\377B;9\377B;9\3772/*\377B;9\377ZRK\377YLH\377YLH\377RJB\377" +\ - "QDA\377QDA\377QDA\377JB:\377\31\31\31\377\27\21\20\377\31\26\22\377\14" +\ - "\13\12\252\10\10\10f\5\5\4""3\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\21\0\0\0\"\20\20\20""3\31\31\31\273\31\26\22\377\20\20\20\377)'" +\ - "#\377BA;\377B;9\377BA;\377RJB\377ZRK\377ZRK\377B;9\3771*'\377JB:\377" +\ - "B81\377B81\377B81\377931\3771*'\3771*'\3771*'\377;1)\377C5)\377K<1\377" +\ - "K<1\377SA8\377[J;\377dRB\377kSD\377sZK\377sZK\377sZK\377sZK\377sZK\377" +\ - "{cJ\377{cJ\377{cJ\377{cJ\377sZK\377rcI\377rcI\377sZK\377sZK\377sZK\377" +\ - "sZK\377kZJ\377kZJ\377dRB\377dRB\377ZJC\377QDA\377RJB\377[J;\377[J;\377" +\ - "QDA\377QDA\377QDA\377QDA\377ZJC\377ZJC\377ZJC\377cMA\377cMA\377cMA\377" +\ - "dRB\377cRK\377dYI\377kZJ\377kZJ\377dRB\377YLH\377cRK\377cRK\377cRK\377" +\ - "kZJ\377kZJ\377sZK\377sZK\377sZK\377sZK\377sZK\377sZK\377lZR\377c[T\377" +\ - "SKI\377B;9\3771*'\377B81\377cRK\377YLH\377SKI\377RJB\377QDA\377QDA\377" +\ - "QDA\377JB:\377\31\31\31\377\27\21\20\377\31\26\22\377\10\10\10\231\10" +\ - "\10\10f\5\5\4""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0" +\ - "\0\0\21\20\20\20""3\31\31\31\252\31\26\22\377\20\20\20\377!\37\34\377" +\ - "JB:\377JB:\377BA;\377RJB\377ZRK\377ZRK\377BA;\377)'#\377ZRK\377SKI\377" +\ - "SKI\377SKI\377cRK\377lSI\377kZJ\377tZR\377tZR\377tZR\377tZR\377tZR\377" +\ - "y[S\377tZR\377scQ\377scQ\377sZK\377sZK\377sZK\377y[S\377{cJ\377{cJ\377" +\ - "{cJ\377{cJ\377{cJ\377scQ\377scQ\377{cR\377scQ\377{cR\377{cJ\377scQ\377" +\ - "{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cJ\377{cJ\377{cJ\377{cJ\377" +\ - "{cJ\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377" +\ - "{cR\377{cR\377scQ\377scQ\377scQ\377scQ\377scQ\377sZK\377sZK\377sZK\377" +\ - "sZK\377sZK\377sZK\377sZK\377sZK\377sZK\377sZK\377sZK\377lZR\377\\TR\377" +\ - "LHA\3772/*\377B81\377cRK\377YLH\377SKI\377RJB\377QDA\377QDA\377QDA\377" +\ - "JB:\377!\31\30\377\20\20\20\377\31\26\22\377\10\10\10\210\10\10\10f\5" +\ - "\5\4""3\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0\0\0\21\20" +\ - "\20\20""3\31\31\31\210\31\26\22\377\27\21\20\377\31\31\31\377JB:\377" +\ - "JCA\377JB:\377RJB\377YLH\377ZRK\377B;9\377!\37\34\377YLH\377bTP\377b" +\ - "TP\377bTP\377lZR\377tZR\377tZR\377tZR\377tZR\377tZR\377sZK\377sZK\377" +\ - "sZK\377sZK\377sZK\377sZK\377sZK\377sZK\377{cJ\377{cJ\377{cJ\377{cJ\377" +\ - "{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377" +\ - "{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377" +\ - "{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377" +\ - "{cJ\377{cJ\377scQ\377scQ\377scQ\377sZK\377sZK\377sZK\377sZK\377sZK\377" +\ - "sZK\377sZK\377scQ\377{cJ\377scQ\377scQ\377sZK\377sZK\377sZK\377dYI\377" +\ - "ZRK\377:72\377K=8\377cRK\377YLH\377RJB\377RJB\377QDA\377QDA\377QDA\377" +\ - "JCA\377!\37\34\377\27\21\20\377\27\21\20\356\5\5\4w\10\10\10U\5\5\4""3" +\ - "\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\10" +\ - "\10\10\"!\37\34w\31\26\22\377\31\26\22\377\31\26\22\377JB:\377QDA\377" +\ - "JCA\377RJB\377YLH\377ZRK\377LHA\3771*'\377RJB\377bTP\377lZR\377tZR\377" +\ - "tZR\377tZR\377tZR\377tZR\377sZK\377sZK\377sZK\377sZK\377sZK\377sZK\377" +\ - "{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377" +\ - "{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377|iI\377" +\ - "|iI\377|iI\377|iI\377|iI\377|iI\377|iI\377|iI\377|iI\377|iI\377{cJ\377" +\ - "{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377" +\ - "{cJ\377{cJ\377{cJ\377scQ\377scQ\377scQ\377scQ\377sZK\377scQ\377{cJ\377" +\ - "{cJ\377{cJ\377{cJ\377{cJ\377scQ\377sZK\377rcI\377kZJ\377ZRK\377:72\377" +\ - "K=8\377cRK\377YLH\377RJB\377RJB\377QDA\377QDA\377QDA\377JCA\377)\"\40" +\ - "\377\31\26\22\377\20\20\20\335\5\5\4w\10\10\10U\0\0\0""3\0\0\0\21\0\0" +\ - "\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\10\10\10\"!\37\34" +\ - "w\31\31\31\377\31\26\22\377\27\21\20\377B;9\377RJB\377JCA\377LHA\377" +\ - "YLH\377YLH\377YLH\377B81\377JB:\377lZR\377tZR\377tZR\377tZR\377scQ\377" +\ - "scQ\377tZR\377sZK\377sZK\377sZK\377scQ\377{cJ\377{cJ\377{cJ\377{cJ\377" +\ - "{cJ\377{cJ\377{cJ\377{cJ\377\203dJ\377|kP\377\203dR\377\203dR\377{cJ" +\ - "\377{cJ\377{cJ\377{cJ\377{cJ\377|iI\377|iI\377|iI\377|iI\377|iI\377\203" +\ - "lL\377\203lL\377|iI\377\203lL\377\203lL\377\203lL\377\203lL\377\203l" +\ - "L\377\203lL\377\203lL\377\203lL\377|iI\377|iI\377|iI\377|iI\377{cJ\377" +\ - "{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377" +\ - "scQ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377rcI\377" +\ - "rcI\377rcI\377dYI\377:72\377K=8\377cRK\377YLH\377RJB\377RJB\377QDA\377" +\ - "QDA\377QDA\377JCA\377)'#\377\31\26\22\377\20\20\20\314\5\5\4f\10\10\10" +\ - "U\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\21\10\10\10\"!\37\34w!\37\34\377\31\26\22\377\20\20\20\377:72\377" +\ - "RJB\377LHA\377LHA\377YLH\377ZRK\377ZRK\377K=8\377K=8\377sZK\377tZR\377" +\ - "tZR\377scQ\377scQ\377scQ\377sZK\377scQ\377scQ\377{cJ\377{cJ\377{cJ\377" +\ - "{cJ\377{cJ\377|iI\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377|iI\377" +\ - "{cJ\377|iI\377\203lL\377\203lL\377\203lL\377\203lL\377\203lL\377\203" +\ - "lL\377\203lL\377\203lL\377\203lL\377\204rK\377\204rK\377\204rK\377\203" +\ - "lL\377\203lL\377\204rK\377\204rK\377\204rQ\377\204rQ\377\204rK\377|i" +\ - "I\377|kP\377\203lL\377|iI\377|iI\377\203lL\377|iI\377|iI\377{cJ\377{" +\ - "cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377rcI\377rcI\377rcI\377" +\ - "rcI\377rcI\377rcI\377{cJ\377rcI\377rcI\377rcI\377sZK\377rcI\377kZJ\377" +\ - ":72\377K=8\377cRK\377YLH\377RJB\377RJB\377QDA\377QDA\377QDA\377JCA\377" +\ - ")\"\40\377\31\26\22\377\20\20\20\314\5\5\4f\10\10\10U\0\0\0\"\0\0\0\21" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\10\10" +\ - "\10\"\31\31\31f!!!\377\27\21\20\377\20\20\20\377931\377SKI\377LHA\377" +\ - "LHA\377YLH\377ZRK\377cRK\377K=8\377;1)\377kZJ\377tZR\377scQ\377{cR\377" +\ - "{cR\377scQ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377{cJ\377" +\ - "\203lL\377\203jR\377\214qR\377\232rR\377\244uY\377\254|`\377\245|Z\377" +\ - "\224sQ\377\224sQ\377\203jR\377kZJ\377kZJ\377dYI\377[J;\377[J;\377[J;" +\ - "\377[J;\377PI9\377[J;\377YSC\377[J;\377[J;\377YSC\377[J;\377[J;\377P" +\ - "I9\377B81\377B;9\377JB:\377JCA\377ZRK\377l`Z\377k]X\377l`Z\377scQ\377" +\ - "kZJ\377cRK\377YSC\377cRK\377dYI\377lZR\377scQ\377scQ\377scQ\377{cR\377" +\ - "\204j[\377\224rZ\377\246|c\377\255\204a\377\307\212h\377\332\232s\377" +\ - "\255\204a\377k[A\377rcI\377rcI\377sZK\377rcI\377kZJ\377931\377K=8\377" +\ - "cRK\377YLH\377SKI\377RJB\377QDA\377QDA\377QDA\377JCA\377)\"\40\377\31" +\ - "\26\22\377\20\20\20\273\5\5\4f\5\5\4D\0\0\0\"\0\0\0\21\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\5\5\4\"\31\31\31" +\ - "f!\37\34\335\31\26\22\377\20\20\20\3771*'\377RJB\377LHA\377LHA\377YL" +\ - "H\377ZRK\377cRK\377B81\3772*\"\377sZK\377scQ\377{cR\377{cR\377{cJ\377" +\ - "rcI\377rcI\377{cJ\377\203jR\377\214kQ\377\214qR\377\224rZ\377\234s[\377" +\ - "\244uY\377\254|`\377\270\204d\377\307\212h\377\332\232s\377\333\242\200" +\ - "\377\333\242\200\377\307\212h\377\263}^\377\254|`\377y[S\377AAA\3773" +\ - "33\377931\377B81\377C5)\377B81\377JB:\377YLH\377lZR\377tcY\377zbZ\377" +\ - "scQ\377lZR\377YLH\377LHA\377LHA\377BA;\3772/*\377\40')\377\40')\377B" +\ - ";9\377c[T\377l`Z\377l`Z\377bTP\377RJB\377RJB\377SKI\377ZRK\377bTP\377" +\ - "c[T\377c[T\377lZR\377lZR\377tZR\377\215jZ\377\225vb\377\270\206i\377" +\ - "\322\222l\377\332\232s\377\332\232s\377\270\204d\377k[A\377rcI\377sZ" +\ - "K\377sZK\377{cJ\377sZK\377C5)\377SA8\377kSD\377dRB\377YLH\377RJB\377" +\ - "QDA\377QDA\377QDA\377JCA\377)'#\377\31\26\22\377\14\13\12\273\5\5\4f" +\ - "\5\5\4D\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\0\0\0\21\5\5\4\"\20\20\20U!\37\34\335\31\26\22\377\20\20\20" +\ - "\377)\"\40\377LHA\377RJB\377LHA\377SKI\377ZRK\377cRK\377K=8\3772*\"\377" +\ - "sZK\377{cJ\377{cR\377|kP\377rcI\377dRB\377{cJ\377\203jR\377\224rZ\377" +\ - "\244uY\377\246|c\377\254|`\377\254|`\377\254|`\377\263}^\377\270\204" +\ - "d\377\275\214j\377\322\222l\377\332\232s\377\263}^\377\214kQ\377\203" +\ - "dJ\377cMA\377:72\377931\377K<1\377[J;\377cMA\377sZK\377\205xd\377\240" +\ - "\206v\377\240\206v\377\255\211s\377\255\211s\377\255\211s\377\271\220" +\ - "w\377\255\211s\377\235|c\377\204j[\377{cR\377cRK\377PI9\377B81\3772/" +\ - "*\377!!!\377)))\377LHA\377l`Z\377c[T\377bTP\377ZRK\377SKI\377YLH\377" +\ - "ZRK\377bTP\377bTP\377bTP\377cRK\377lSI\377\203dR\377\225vb\377\246|c" +\ - "\377\270\206i\377\275\214j\377\322\222l\377\270\204d\377k[A\377rcI\377" +\ - "rcI\377rcI\377{cJ\377sZK\377C5)\377dL;\377kZJ\377dRB\377YLH\377RJB\377" +\ - "RJB\377QDA\377QDA\377QDA\377)'#\377\31\26\22\377\14\13\12\273\5\5\4f" +\ - "\5\5\4D\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\0\0\0\21\0\0\0\"\14\13\12D\31\31\31\273\31\31\31\377\27\21" +\ - "\20\377!\31\30\377LHA\377RJB\377LHA\377SKI\377ZRK\377cRK\377K=8\377)" +\ - "'#\377kZJ\377{cJ\377|kP\377|iI\377rcI\377dRB\377sZK\377{cR\377\203dR" +\ - "\377\215jZ\377\234s[\377\244uY\377\246|c\377\254|`\377\254|`\377\254" +\ - "|`\377\270\204d\377\254|`\377\214kQ\377cMA\377PI9\377B;9\377931\377B" +\ - "81\377SA8\377kSD\377{l[\377\215xc\377\224\204l\377\240\206v\377\255\211" +\ - "s\377\235|c\377\235|c\377\246|c\377\235|c\377\234s[\377\234s[\377\234" +\ - "s[\377\234s[\377\235|c\377\224rZ\377{cR\377YSC\377PI9\377K<1\3771*'\377" +\ - "2/*\377bTP\377l`Z\377zbZ\377\234s[\377y[S\377SKI\377SKI\377SKI\377YL" +\ - "H\377ZRK\377bTP\377lZR\377\204dY\377\234s[\377\235|c\377\246|c\377\270" +\ - "\206i\377\275\214j\377\263}^\377sbB\377{cJ\377{cJ\377{cJ\377{cJ\377s" +\ - "ZK\377;1)\377dL;\377sZK\377kZJ\377YLH\377RJB\377RJB\377QDA\377RJB\377" +\ - "QDA\377)\"\40\377\31\26\22\377\14\13\12\273\5\5\4f\10\10\10D\0\0\0\"" +\ - "\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0" +\ - "\0\0\21\0\0\0\"\20\20\20""3\31\31\31\252\31\31\31\377\27\21\20\377\31" +\ - "\31\31\377JCA\377RJB\377LHA\377SKI\377ZRK\377cRK\377SA8\377)\"\40\377" +\ - "dRB\377|kP\377|kP\377|iI\377{cJ\377k[A\377sZK\377{cR\377{cJ\377{cJ\377" +\ - "\203dR\377\214kQ\377\224rZ\377\234s[\377\246|c\377\224rZ\377\203dR\377" +\ - "dYI\377JB:\377B;9\377:72\3772/*\377B81\377[J;\377scQ\377\204j[\377\215" +\ - "xc\377\224rZ\377\224rZ\377\234s[\377\224rZ\377\225lP\377\225lP\377\225" +\ - "lP\377\214eJ\377\214eJ\377\214eJ\377\214eJ\377\214eJ\377\225lP\377\214" +\ - "eJ\377\214kQ\377\203dR\377kZJ\377[J;\377PD2\377;1)\377333\377\\TR\377" +\ - "c[T\377\204j[\377\204j[\377c[T\377\\TR\377bTP\377lZR\377k]X\377t^Y\377" +\ - "zbZ\377\204dY\377\222k[\377\234s[\377\235|c\377\246\204b\377\255\204" +\ - "a\377\257\204\\\377sbB\377{cJ\377{cJ\377{cJ\377{cJ\377sZK\377;1)\377" +\ - "dRB\377sZK\377kZJ\377YLH\377RJB\377RJB\377RJB\377RJB\377QDA\377)\"\40" +\ - "\377\31\26\22\377\14\13\12\273\5\5\4f\10\10\10D\0\0\0\"\0\0\0\21\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"" +\ - "\14\13\12""3\31\31\31\231!\37\34\377\27\21\20\377\31\26\22\377B;9\377" +\ - "SKI\377LHA\377SKI\377ZRK\377cRK\377QDA\377)\"\40\377[J;\377\203jR\377" +\ - "|iI\377|iI\377|iI\377k[A\377sZK\377\203dR\377sZK\377sZK\377kZJ\377lS" +\ - "I\377lSI\377sZK\377kZJ\377cRK\377SKI\377JCA\377B;9\377B;9\377333\377" +\ - "C5)\377ZRK\377tcY\377{l[\377\204j[\377\203jR\377\214kQ\377\214kQ\377" +\ - "\214eJ\377\214eJ\377\214eJ\377\214eJ\377\214eJ\377\203dJ\377\203dJ\377" +\ - "}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377\203dJ\377\203dJ\377sZK\377" +\ - "[J;\377K<1\377;1)\377B;9\377\\TR\377SKI\377l`Z\377l`Z\377c[T\377bTP\377" +\ - "k]X\377l`Z\377tcY\377zbZ\377zbZ\377\204dY\377\222k[\377\234s[\377\235" +\ - "|c\377\245|Z\377\245|Z\377{cJ\377{cJ\377{cJ\377{cJ\377\203dJ\377k[A\377" +\ - ";1)\377dRB\377sZK\377kZJ\377cRK\377RJB\377RJB\377RJB\377RJB\377QDA\377" +\ - ")'#\377\31\26\22\377\14\13\12\273\5\5\4f\10\10\10U\0\0\0\"\0\0\0\21\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"" +\ - "\10\10\10""3!\37\34w!!!\377\27\21\20\377\20\20\20\377:72\377SKI\377L" +\ - "HA\377RJB\377ZRK\377cRK\377QDA\377)'#\377[J;\377\203jR\377|kP\377\203" +\ - "lL\377|iI\377sbB\377k[A\377\222k[\377sZK\377cRK\377ZJC\377ZJC\377RJB" +\ - "\377JB:\377BA;\377LHA\377JCA\377BA;\377B;9\377:72\377931\377ZRK\377v" +\ - "je\377{l[\377\203dR\377{cJ\377}ZG\377\203dJ\377\203dJ\377\203dJ\377\203" +\ - "dJ\377\203dJ\377\203dJ\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377" +\ - "sUB\377}ZG\377sUB\377sUB\377}ZG\377sUB\377dRB\377SA8\377B81\377;1)\377" +\ - "ZJC\377ZRK\377SKI\377l`Z\377k]X\377bTP\377bTP\377l`Z\377t^Y\377t^Y\377" +\ - "zbZ\377\204dY\377\215jZ\377\222k[\377\224rZ\377\234s[\377\233~Z\377{" +\ - "cJ\377{cJ\377{cJ\377{cJ\377\203dJ\377sZK\377;1)\377dRB\377sZK\377kZJ" +\ - "\377dRB\377RJB\377RJB\377RJB\377ZJC\377RJB\377)'#\377\31\26\22\377\14" +\ - "\13\12\273\5\5\4f\10\10\10U\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\10\10\10""3!\37\34w" +\ - ")'#\377\27\21\20\377\20\20\20\3772/*\377SKI\377RJB\377RJB\377YLH\377" +\ - "cRK\377RJB\3772*\"\377PD2\377\203jR\377|kP\377\203lL\377\203lL\377|i" +\ - "I\377dRB\377\222k[\377\204dY\377sZK\377dRB\377RJB\377JB:\377B;9\377B" +\ - ";9\377B;9\377B;9\377:72\377333\377B81\377RJB\377tro\377vje\377{cR\377" +\ - "sZK\377sUB\377sUB\377|\\B\377|\\B\377|\\B\377}ZG\377}ZG\377\203dJ\377" +\ - "}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377sUB\377sUB\377" +\ - "sUB\377hM\77\377cMA\377SA8\377;1)\3772/*\377B81\377lZR\377ZRK\377c[T" +\ - "\377l`Z\377c[T\377\\TR\377bTP\377t^Y\377t^Y\377tcY\377zbZ\377\204dY\377" +\ - "\215jZ\377\215jZ\377\224rZ\377\224rZ\377{cJ\377{cJ\377{cJ\377{cJ\377" +\ - "\203dJ\377sZK\377;1)\377dRB\377sZK\377kZJ\377cRK\377RJB\377RJB\377ZJ" +\ - "C\377ZJC\377ZJC\377)'#\377\31\26\22\377\14\13\12\273\5\5\4f\10\10\10" +\ - "U\0\0\0""3\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\0\0\0\21\0\0\0\21\5\5\4""3\31\31\31f1*'\377\27\21\20\377\20\20" +\ - "\20\3771*'\377SKI\377RJB\377LHA\377YLH\377cRK\377ZJC\3772/*\377B81\377" +\ - "\203dR\377|kP\377\203lL\377\203lL\377|iI\377dRB\377\203dR\377\215jZ\377" +\ - "\204j[\377\204dY\377y[S\377ZJC\377B;9\377:72\377:72\377333\377)))\377" +\ - ")))\377B81\377fkm\377tro\377scQ\377kSD\377hM\77\377hM\77\377sUB\377s" +\ - "UB\377sUB\377|\\B\377|\\B\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377" +\ - "}ZG\377}ZG\377}ZG\377}ZG\377sUB\377sUB\377kSD\377hM\77\377hM\77\377c" +\ - "MA\3772/*\377)'#\377!!!\377:72\377zbZ\377kTO\377zbZ\377t^Y\377c[T\377" +\ - "bTP\377k]X\377t^Y\377t^Y\377tcY\377zbZ\377zbZ\377\204dY\377\204j[\377" +\ - "\215jZ\377sZK\377{cJ\377{cJ\377{cJ\377\203dJ\377sZK\377;1)\377dRB\377" +\ - "sZK\377kZJ\377cRK\377RJB\377RJB\377ZJC\377ZJC\377ZJC\3771*'\377\27\21" +\ - "\20\377\20\20\20\314\10\10\10w\10\10\10U\0\0\0""3\0\0\0\21\0\0\0\21\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\5\5\4""3\31" +\ - "\31\31f2/*\377\31\26\22\377\20\20\20\377)\"\40\377RJB\377RJB\377LHA\377" +\ - "YLH\377ZRK\377ZJC\377;1)\377;1)\377{cJ\377|iI\377\203lL\377\203lL\377" +\ - "|iI\377k[A\377\203dR\377\215jZ\377\204j[\377\215jZ\377\222k[\377\203" +\ - "jR\377YLH\377BA;\3778;:\3772/*\377\40')\377!!!\377PVV\377q~\203\377v" +\ - "je\377dRB\377dL;\377dL;\377hM\77\377hM\77\377hM\77\377sUB\377|\\B\377" +\ - "}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377" +\ - "sUB\377kSD\377hM\77\377hM\77\377hM\77\377hM\77\377sZK\377ZJC\377)'#\377" +\ - "!!!\377\33\40!\377cRK\377\222k[\377\215jZ\377\204j[\377tcY\377k]X\377" +\ - "k]X\377l`Z\377t^Y\377t^Y\377t^Y\377zbZ\377zbZ\377zbZ\377\204dY\377sZ" +\ - "K\377{cJ\377|iI\377|iI\377\203lL\377sZK\3772/*\377cMA\377kZJ\377kZJ\377" +\ - "cRK\377RJB\377RJB\377ZJC\377YLH\377YLH\3771*'\377\31\26\22\377\20\20" +\ - "\20\335\14\13\12\210\10\10\10f\0\0\0""3\0\0\0\21\0\0\0\21\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\5\5\4""3\20\20\20U2" +\ - "/*\377\31\26\22\377\20\20\20\377!\37\34\377JCA\377RJB\377LHA\377SKI\377" +\ - "ZRK\377ZJC\377;1)\377;1)\377sZK\377\203dJ\377\203lL\377\203lL\377\203" +\ - "lL\377sbB\377\203dR\377\204j[\377\204dY\377\204j[\377\215jZ\377\204j" +\ - "[\377tcY\377YLH\377:72\377\40')\377\33\40!\3778;:\377q~\203\377vje\377" +\ - "kSD\377[J;\377dL;\377dL;\377dL;\377dL;\377sUB\377sUB\377sUB\377}ZG\377" +\ - "}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377}ZG\377sUB\377kSD\377" +\ - "hM\77\377hM\77\377hM\77\377hM\77\377sUB\377\203dR\377\215jZ\377QDA\377" +\ - "\33\40!\377\33\40!\377:72\377\234s[\377\263}^\377\263}^\377\224rZ\377" +\ - "zbZ\377k]X\377k]X\377k]X\377l`Z\377t^Y\377t^Y\377t^Y\377t^Y\377zbZ\377" +\ - "sZK\377{cJ\377|iI\377|iI\377\203lL\377{cJ\3772/*\377cMA\377kZJ\377kZ" +\ - "J\377lSI\377RJB\377RJB\377ZJC\377YLH\377YLH\3772/*\377\31\26\22\377\20" +\ - "\20\20\335\20\20\20\210\14\13\12f\0\0\0""3\0\0\0\21\0\0\0\21\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\5\5\4\"\14\13\12" +\ - "D!\37\34\335\31\26\22\377\27\21\20\377\31\31\31\377BA;\377SKI\377LHA" +\ - "\377SKI\377ZRK\377YLH\377B81\377;1)\377k[A\377|iI\377\203lL\377\203l" +\ - "L\377\203lL\377|iI\377sZK\377tcY\377t^Y\377t^Y\377l`Z\377l`Z\377c[T\377" +\ - "JCA\377)))\377\33\40!\377)))\377fkm\377tro\377lZR\377[J;\377[J;\377[" +\ - "J;\377[J;\377[J;\377hM\77\377sUB\377sUB\377|\\B\377}ZG\377}ZG\377}ZG" +\ - "\377}ZG\377|\\B\377}ZG\377}ZG\377}ZG\377sUB\377kSD\377hM\77\377hM\77" +\ - "\377hM\77\377hM\77\377kSD\377\203dJ\377\254|`\377\234s[\377zbZ\37733" +\ - "3\377\33\40!\377)))\377{cR\377\244uY\377\263}^\377\270\204d\377\263}" +\ - "^\377\224rZ\377\204j[\377\204dY\377zbZ\377tcY\377t^Y\377zbZ\377zbZ\377" +\ - "t^Y\377sZK\377{cJ\377|iI\377|iI\377\203lL\377{cJ\3772/*\377ZJC\377kZ" +\ - "J\377kZJ\377lSI\377RJB\377RJB\377ZJC\377YLH\377YLH\3772/*\377\31\26\22" +\ - "\377\20\20\20\335\20\20\20\231\14\13\12f\0\0\0""3\0\0\0\21\0\0\0\21\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\5\5\4\"\14\13" +\ - "\12D\31\31\31\273\31\31\31\377\31\26\22\377\31\26\22\377B;9\377SKI\377" +\ - "LHA\377RJB\377ZRK\377YLH\377B81\3772*\"\377kSD\377|iI\377\203lL\377\204" +\ - "rK\377\203lL\377|iI\377kZJ\377k]X\377k]X\377k]X\377k]X\377l`Z\377ZRK" +\ - "\377333\377!!!\377\33\40!\377KKK\377q~\203\377vje\377dRB\377[J;\377[" +\ - "J;\377[J;\377[J;\377dL;\377hM\77\377hM\77\377sUB\377kSD\377sUB\377}Z" +\ - "G\377}ZG\377}ZG\377}ZG\377sUB\377sUB\377sUB\377sUB\377kSD\377hM\77\377" +\ - "hM\77\377hM\77\377kSD\377sUB\377\224rZ\377\333\242\200\377\304\235s\377" +\ - "\215jZ\377YLH\377!!!\377!!!\377ZJC\377\204j[\377\215jZ\377\244uY\377" +\ - "\263}^\377\270\204d\377\263}^\377\254|`\377\254|`\377\244uY\377\234s" +\ - "[\377\244uY\377\224rZ\377t^Y\377kZJ\377{cJ\377|iI\377|iI\377\203lL\377" +\ - "{cJ\377;1)\377ZJC\377kZJ\377kZJ\377lSI\377ZJC\377RJB\377ZJC\377YLH\377" +\ - "YLH\3772/*\377\27\21\20\377\27\21\20\356\20\20\20\231\14\13\12f\0\0\0" +\ - "3\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\21\0\0\0\"\10\10\10D\31\31\31\252\31\31\31\377\31\26\22\377\27\21" +\ - "\20\377:72\377SKI\377LHA\377RJB\377ZRK\377ZRK\377K=8\3772*\"\377dL;\377" +\ - "|iI\377\203lL\377\204rK\377\203lL\377|iI\377k[A\377ZRK\377ZRK\377bTP" +\ - "\377bTP\377JCA\377BA;\3772/*\377!!!\377)))\377fkm\377q~\203\377l`Z\377" +\ - "[J;\377[J;\377SA8\377SA8\377SA8\377[J;\377dL;\377}ZG\377\232rR\377\232" +\ - "rR\377\214eJ\377}ZG\377sUB\377sUB\377kSD\377sUB\377sUB\377kSD\377hM\77" +\ - "\377hM\77\377hM\77\377hM\77\377hM\77\377sUB\377sZK\377\234s[\377\315" +\ - "\231{\377\271\220w\377\215jZ\377t^Y\377931\377\33\40!\377B81\377{cR\377" +\ - "{cR\377{cR\377\214kQ\377\234s[\377\224rZ\377\224rZ\377\234s[\377\234" +\ - "s[\377\224rZ\377\215jZ\377zbZ\377t^Y\377kZJ\377{cJ\377|iI\377|iI\377" +\ - "\203lL\377{cJ\377K<1\377ZJC\377kZJ\377kZJ\377lSI\377YLH\377RJB\377ZJ" +\ - "C\377YLH\377YLH\377;1)\377\27\21\20\377\31\26\22\377\31\26\22\252\14" +\ - "\13\12f\0\0\0D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\0\0\0\21\0\0\0\"\10\10\10D!\37\34w!\37\34\377\27\21" +\ - "\20\377\27\21\20\3772/*\377RJB\377RJB\377RJB\377YLH\377ZRK\377JB:\377" +\ - ")'#\377[J;\377|iI\377\203lL\377\204rK\377\204rK\377\203lL\377k[A\377" +\ - "QDA\377BA;\377BA;\377B;9\377333\377333\377)))\377!!!\3778;:\377q~\203" +\ - "\377tro\377kZJ\377[J;\377[J;\377SA8\377K<1\377SA8\377cMA\377\215jZ\377" +\ - "\270\204d\377\322\222l\377\332\232s\377\332\232s\377\322\222l\377\245" +\ - "|Z\377}ZG\377hM\77\377dL;\377hM\77\377hM\77\377dL;\377hM\77\377hM\77" +\ - "\377hM\77\377sUB\377\203dJ\377\225lP\377\234s[\377\246|c\377\234s[\377" +\ - "\215jZ\377zbZ\377RJB\377\33\40!\377)))\377lZR\377y[S\377y[S\377tZR\377" +\ - "tZR\377{cR\377\203dR\377\204j[\377\204dY\377zbZ\377tcY\377t^Y\377k]X" +\ - "\377dYI\377{cJ\377\203lL\377|iI\377\203lL\377\203lL\377K<1\377ZJC\377" +\ - "kZJ\377kZJ\377lSI\377YLH\377RJB\377ZJC\377YLH\377YLH\377;1)\377\27\21" +\ - "\20\377\31\26\22\377\31\31\31\273\14\13\12w\0\0\0D\0\0\0\"\0\0\0\21\0" +\ - "\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\10" +\ - "\10\10""3!\37\34w!\37\34\377\27\21\20\377\31\26\22\377)'#\377RJB\377" +\ - "RJB\377RJB\377YLH\377ZRK\377LHA\377)'#\377[J;\377|iI\377\203lL\377\204" +\ - "rK\377\204rK\377\204rK\377sbB\377\203dR\377\215jZ\377\214kQ\377tZR\377" +\ - "RJB\377333\377)))\377!!!\377PVV\377q~\203\377vje\377dRB\377[J;\377[J" +\ - ";\377K=8\377K<1\377SA8\377\225vb\377\271\220w\377\275\214j\377\322\222" +\ - "l\377\322\222l\377\332\232s\377\332\232s\377\322\222l\377\270\204d\377" +\ - "\232rR\377}ZG\377hM\77\377dL;\377dL;\377hM\77\377kSD\377sUB\377\225l" +\ - "P\377\254|`\377\275\214j\377\255\204a\377\215jZ\377\215jZ\377\204dY\377" +\ - "t^Y\377lZR\377931\377\33\40!\377BA;\377tcY\377{cR\377\203jR\377\203d" +\ - "R\377lZR\377bTP\377kTO\377kTO\377lZR\377lZR\377c[T\377lZR\377cRK\377" +\ - "sbB\377\203lL\377|iI\377\203lL\377\203lL\377SA8\377ZJC\377kZJ\377kZJ" +\ - "\377lSI\377YLH\377RJB\377ZJC\377YLH\377YLH\377;1)\377\27\21\20\377\31" +\ - "\26\22\377!\37\34\335\20\20\20w\0\0\0D\0\0\0\"\0\0\0\21\0\0\0\21\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\5\5\4""3!\37" +\ - "\34w\31\31\31\356\31\26\22\377\31\26\22\377!!!\377LHA\377RJB\377RJB\377" +\ - "YLH\377ZRK\377YLH\3772*\"\377SA8\377\203lL\377\203lL\377\204rK\377\204" +\ - "rK\377\204rK\377|iI\377\224rZ\377\263}^\377\263}^\377zbZ\377LHA\3773" +\ - "33\377)'#\377)))\377fkm\377q~\203\377k]X\377cMA\377[J;\377SA8\377K=8" +\ - "\377K=8\377dRB\377\255\211s\377\265\246\205\377\271\220w\377\307\212" +\ - "h\377\322\222l\377\333\242\200\377\352\301\212\377\343\255\215\377\322" +\ - "\222l\377\307\212h\377\270\204d\377\234s[\377\203dR\377}ZG\377\203dJ" +\ - "\377\214kQ\377\245|Z\377\270\204d\377\275\214j\377\333\242\200\377\255" +\ - "\211s\377\204dY\377\204dY\377\204dY\377t^Y\377tcY\377YLH\377\33\40!\377" +\ - ":72\377{l[\377{cR\377\215jZ\377\244uY\377\234s[\377\203jR\377lZR\377" +\ - "ZRK\377ZRK\377cRK\377bTP\377c[T\377cRK\377sbB\377\203lL\377\203lL\377" +\ - "\203lL\377\203jR\377[J;\377ZJC\377kZJ\377kZJ\377kSD\377YLH\377RJB\377" +\ - "ZJC\377YLH\377YLH\377;1)\377\20\20\20\377\31\26\22\377!\37\34\335\20" +\ - "\20\20w\0\0\0D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\0\0\0\21\0\0\0\"\5\5\4""3\31\31\31f!\37\34\335\27\21" +\ - "\20\377\31\26\22\377!\37\34\377JB:\377RJB\377RJB\377YLH\377cRK\377ZR" +\ - "K\3771*'\377PD2\377\203lL\377\203lL\377\214qR\377\204rK\377\204rK\377" +\ - "|iI\377{cR\377{l[\377tcY\377bTP\377BA;\377333\377!!!\377333\377q~\203" +\ - "\377tro\377bTP\377[J;\377[J;\377JB:\377K=8\377K=8\377tZR\377\252\223" +\ - "\211\377\252\223\211\377\255\211s\377\270\204d\377\275\214j\377\322\222" +\ - "l\377\352\301\212\377\352\301\212\377\332\232s\377\307\212h\377\307\212" +\ - "h\377\270\204d\377\263}^\377\254|`\377\254|`\377\270\204d\377\270\206" +\ - "i\377\270\204d\377\275\214j\377\304\235s\377\235|c\377\204dY\377\204" +\ - "dY\377zbZ\377t^Y\377t^Y\377lZR\377)))\377)))\377lZR\377\204dY\377{cR" +\ - "\377\203dR\377\224rZ\377\245|Z\377\245|Z\377\203dR\377lZR\377dYI\377" +\ - "bTP\377kTO\377cRK\377sbB\377\203lL\377\203lL\377\203lL\377\204rQ\377" +\ - "[J;\377[J;\377kZJ\377kZJ\377lSI\377YLH\377RJB\377ZJC\377YLH\377ZRK\377" +\ - "931\377\20\20\20\377\31\26\22\377!\37\34\335\20\20\20w\0\0\0D\0\0\0\"" +\ - "\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0" +\ - "\21\0\0\0\21\5\5\4""3\31\31\31f!\37\34\335\27\21\20\377\31\26\22\377" +\ - "!\31\30\377BA;\377SKI\377RJB\377YLH\377cRK\377cRK\3772/*\377K<1\377|" +\ - "iI\377\203lL\377\214qR\377\204rK\377\204rK\377|iI\377scQ\377k]X\377b" +\ - "TP\377YLH\377BA;\377333\377!!!\3778;:\377q~\203\377tro\377dYI\377[J;" +\ - "\377QDA\377K=8\377JB:\377JB:\377\204j[\377\253\233\223\377\253\233\223" +\ - "\377\252\223\211\377\246|c\377\254|`\377\270\206i\377\322\222l\377\322" +\ - "\222l\377\307\212h\377\270\204d\377\270\204d\377\270\204d\377\263}^\377" +\ - "\263}^\377\254|`\377\254|`\377\254|`\377\244uY\377\246|c\377\246|c\377" +\ - "\204j[\377zbZ\377zbZ\377zbZ\377t^Y\377k]X\377k]X\377:72\377\40')\377" +\ - "JB:\377\215jZ\377\204dY\377{cR\377scQ\377{cR\377\203dR\377\203jR\377" +\ - "\204j[\377{cR\377t^Y\377tcY\377kZJ\377sbB\377\204rK\377\203lL\377\203" +\ - "lL\377\214qR\377dL;\377[J;\377kZJ\377kZJ\377lSI\377YLH\377RJB\377YLH" +\ - "\377ZRK\377cRK\377931\377\20\20\20\377\27\21\20\377!\37\34\335\20\20" +\ - "\20w\0\0\0D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\0\0\0\21\0\0\0\21\5\5\4""3\20\20\20U\31\31\31\273\31\26\22" +\ - "\377\31\26\22\377\31\31\31\377B;9\377YLH\377RJB\377YSC\377dYI\377dYI" +\ - "\377:72\377C5)\377{cJ\377\203lL\377\214qR\377\204rK\377\204rK\377\204" +\ - "rK\377kZJ\377ZRK\377ZRK\377JCA\377:72\377333\377!!!\377AAA\377q~\203" +\ - "\377l`Z\377cRK\377[J;\377JB:\377JB:\377K=8\377QDA\377\203tl\377\262\241" +\ - "\230\377\262\241\230\377\262\241\230\377\235|c\377\234s[\377\246|c\377" +\ - "\254|`\377\255\204a\377\254|`\377\246|c\377\234s[\377\244uY\377\244u" +\ - "Y\377\244uY\377\244uY\377\234s[\377\234s[\377\234s[\377\224rZ\377\215" +\ - "jZ\377zbZ\377zbZ\377zbZ\377t^Y\377t^Y\377k]X\377RJB\377)))\377)))\377" +\ - "K=8\377\234s[\377\244uY\377\214kQ\377tZR\377bTP\377bTP\377lZR\377lZR" +\ - "\377lZR\377t^Y\377tcY\377kZJ\377sbB\377\204rK\377\203lL\377\203lL\377" +\ - "\204rQ\377dRB\377SA8\377kZJ\377kZJ\377lSI\377YLH\377RJB\377ZRK\377ZR" +\ - "K\377cRK\377:72\377\20\20\20\377\31\26\22\377!\37\34\335\20\20\20w\0" +\ - "\0\0D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\0\0\0\21\0\0\0\21\5\5\4""3\14\13\12D\31\31\31\252\31\26\22\377\31" +\ - "\26\22\377\31\26\22\377B;9\377YLH\377RJB\377YSC\377dYI\377dYI\377B81" +\ - "\377;1)\377sbB\377\203lL\377\214qR\377\214qR\377\204rK\377\204rK\377" +\ - "kZJ\377ZRK\377RJB\377B;9\377:72\3778;:\377!!!\377333\377fkm\377c[T\377" +\ - "ZJC\377SA8\377K=8\377JB:\377JB:\377QDA\377\203tl\377\253\233\223\377" +\ - "\262\241\230\377\270\263\257\377\266\257\254\377\225vb\377\234s[\377" +\ - "\244uY\377\246|c\377\234s[\377\234s[\377\234s[\377\222k[\377\222k[\377" +\ - "\222k[\377\222k[\377\215jZ\377\215jZ\377\204dY\377\204dY\377zbZ\377z" +\ - "bZ\377t^Y\377t^Y\377k]X\377lZR\377JCA\3772/*\377)))\3772/*\377SA8\377" +\ - "\224rZ\377\244uY\377\254|`\377\244uY\377\203dR\377bTP\377\\TR\377\\T" +\ - "R\377bTP\377c[T\377k]X\377kZJ\377sbB\377\204rK\377\204rK\377\204rK\377" +\ - "\214qR\377k[A\377SA8\377kZJ\377kZJ\377lSI\377YLH\377YLH\377cRK\377ZR" +\ - "K\377cRK\377B81\377\20\20\20\377\31\26\22\377!\37\34\335\20\20\20w\0" +\ - "\0\0D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\0\0\0\21\0\0\0\21\0\0\0\"\14\13\12D\31\31\31\210\31\26\22\377\31\26" +\ - "\22\377\31\26\22\377B;9\377YLH\377RJB\377YSC\377dYI\377dYI\377JB:\377" +\ - "2*\"\377sbB\377\203lL\377\214zR\377\214zR\377\204rK\377\204rK\377{cJ" +\ - "\377{cR\377{cR\377JCA\377:72\377BA;\377!!!\377)))\377PVV\377LHA\377Q" +\ - "DA\377K=8\377K=8\377JB:\377JCA\377QDA\377vje\377\252\223\211\377\262" +\ - "\241\230\377\270\263\257\377\330\312\237\377\252\223\211\377\215jZ\377" +\ - "\224rZ\377\234s[\377\224rZ\377\222k[\377\215jZ\377\215jZ\377\204dY\377" +\ - "\204dY\377{cR\377\204dY\377lZR\377t^Y\377lZR\377lZR\377lZR\377kTO\377" +\ - "t^Y\377scQ\377cRK\377YLH\377cRK\377RJB\377333\377931\377y[S\377\222k" +\ - "[\377\234s[\377\244uY\377\263}^\377\244uY\377\214kQ\377{cR\377{cR\377" +\ - "{cR\377{cR\377sZK\377sbB\377\204rK\377\204rK\377\204rK\377\214zR\377" +\ - "k[A\377K=8\377kZJ\377kZJ\377kZJ\377YLH\377YLH\377cRK\377ZRK\377cRK\377" +\ - "B81\377\20\20\20\377\27\21\20\377!\37\34\335\20\20\20w\0\0\0D\0\0\0\"" +\ - "\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0" +\ - "\21\0\0\0\21\0\0\0\"\14\13\12D\31\31\31w\31\26\22\377\31\26\22\377\31" +\ - "\26\22\377K=8\377YLH\377SKI\377YSC\377dYI\377dYI\377RJB\3772*\"\377s" +\ - "bB\377\203lL\377\214zR\377\214zR\377\214zR\377\204rK\377\203lL\377\234" +\ - "s[\377\224rZ\377QDA\377:72\377:72\377!!!\377!!!\377\40')\377!\37\34\377" +\ - "333\377B;9\377B;9\377JB:\377JCA\377QDA\377t^Y\377\223\203|\377\243\210" +\ - "z\377\253\233\223\377\262\241\230\377\243\210z\377\222k[\377\215jZ\377" +\ - "\215jZ\377\215jZ\377\215jZ\377\204dY\377\204dY\377\204dY\377{cR\377t" +\ - "ZR\377tZR\377lZR\377t^Y\377lZR\377lZR\377ZRK\377lZR\377\204j[\377\215" +\ - "jZ\377\204j[\377\215jZ\377\204j[\377lZR\377JCA\377!!!\3772/*\377lSI\377" +\ - "y[S\377{cR\377\222k[\377\244uY\377\245|Z\377\244uY\377\244uY\377\234" +\ - "s[\377\222k[\377\203dR\377sbB\377\204rK\377\204rK\377\204rK\377\214z" +\ - "R\377rcI\377K=8\377kZJ\377kZJ\377kZJ\377YLH\377YLH\377cRK\377ZRK\377" +\ - "cRK\377B81\377\20\20\20\377\27\21\20\377!\37\34\335\20\20\20w\0\0\0D" +\ - "\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\0\0\0\21\0\0\0\21\0\0\0\"\10\10\10D\31\31\31w\31\26\22\356\31\26\22" +\ - "\377\27\21\20\377JB:\377ZRK\377SKI\377YSC\377dYI\377dYI\377RJB\3772*" +\ - "\"\377k[A\377\203lL\377\214zR\377\214zR\377\214zR\377\204rK\377\203l" +\ - "L\377\234s[\377zbZ\377B;9\3772/*\377\40')\377!!!\377!!!\377KKK\377\213" +\ - "\204\202\377KKK\377931\377JB:\377JCA\377JCA\377QDA\377cRK\377\222yk\377" +\ - "\240\206v\377\243\210z\377\225vb\377\234s[\377\234s[\377\246|c\377\234" +\ - "s[\377\224rZ\377\215jZ\377\204j[\377\204dY\377\204dY\377{cR\377y[S\377" +\ - "{cR\377\204dY\377{cR\377zbZ\377t^Y\377lZR\377\204j[\377\234s[\377\234" +\ - "s[\377\234s[\377\234s[\377\215jZ\377bTP\377BA;\377!!!\3772/*\377BA;\377" +\ - "ZJC\377cMA\377dYI\377scQ\377\204j[\377\224rZ\377\224rZ\377\215jZ\377" +\ - "\204j[\377{cR\377sbB\377\204rK\377\204rK\377\204rK\377\214zR\377rcI\377" +\ - "K<1\377kSD\377kZJ\377kZJ\377ZRK\377YLH\377cRK\377ZRK\377cRK\377B;9\377" +\ - "\20\20\20\377\27\21\20\377!\37\34\335\20\20\20w\0\0\0D\0\0\0\"\0\0\0" +\ - "\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\21\0\0\0\"\10\10\10D\31\31\31f\31\26\22\356\31\26\22\377\31\26\22" +\ - "\377JB:\377ZRK\377YLH\377YLH\377dYI\377dYI\377YSC\3772*\"\377dL;\377" +\ - "|iI\377\214zR\377\224{U\377\214zR\377\214zR\377{cJ\377zbZ\377bTP\377" +\ - "333\377!!!\377!!!\377\33\40!\377fkm\377\270\263\257\377\330\312\237\377" +\ - "\271\220w\377JB:\377JCA\377JCA\377JCA\377QDA\377lZR\377\243\210z\377" +\ - "\262\241\230\377\270\263\257\377\252\223\211\377\235|c\377\246|c\377" +\ - "\270\206i\377\275\214j\377\275\214j\377\270\204d\377\244uY\377\222k[" +\ - "\377\204dY\377{cR\377\246|c\377\235|c\377\204j[\377\203dR\377\204j[\377" +\ - "zbZ\377\224rZ\377\254|`\377\255\204a\377\254|`\377\254|`\377\225vb\377" +\ - "QDA\377!!!\377\33\40!\377\40')\377RJB\377JCA\377JCA\377RJB\377RJB\377" +\ - "RJB\377dRB\377kZJ\377{cR\377{cR\377{cR\377scQ\377k[A\377\204rQ\377\204" +\ - "rQ\377\204rQ\377\214zR\377|iI\377K=8\377dRB\377kZJ\377kZJ\377cRK\377" +\ - "YLH\377ZRK\377ZRK\377cRK\377B;9\377\27\21\20\377\27\21\20\377!\37\34" +\ - "\335\20\20\20w\0\0\0D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\10\10\10""3\31\31" +\ - "\31f\27\21\20\335\31\26\22\377\27\21\20\377JB:\377YLH\377YLH\377YSC\377" +\ - "dYI\377dYI\377cRK\3772/*\377[J;\377\203lL\377\224{U\377\224{U\377\214" +\ - "zR\377\214zR\377|iI\377bTP\377333\377!!!\377\40')\377)))\377)))\377l" +\ - "`Z\377JCA\377tcY\377\333\242\200\377\246|c\377RJB\377JCA\377JCA\377J" +\ - "CA\377k]X\377\223\203|\377\253\233\223\377\266\257\254\377\270\263\257" +\ - "\377\243\210z\377\215jZ\377\246|c\377\271\220w\377\315\231{\377\315\231" +\ - "{\377\307\212h\377\270\206i\377\246|c\377\204dY\377zbZ\377\255\211s\377" +\ - "\204j[\377\271\220w\377tZR\377{cR\377\270\206i\377\270\206i\377\270\206" +\ - "i\377\275\214j\377\255\211s\377B;9\377\31\31\31\377\33\40!\377\33\40" +\ - "!\377BA;\377l`Z\377bTP\377JCA\377JB:\377JB:\377JB:\377JB:\377JB:\377" +\ - "RJB\377cRK\377cRK\377kZJ\377k[A\377\204rQ\377\204rQ\377\204rQ\377\214" +\ - "zR\377\203lL\377K=8\377dRB\377kZJ\377kZJ\377cRK\377YLH\377ZRK\377ZRK" +\ - "\377cRK\377B81\377\20\20\20\377\27\21\20\377!\37\34\335\20\20\20w\0\0" +\ - "\0D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\0\0\0\21\0\0\0\"\10\10\10""3\31\31\31f\27\21\20\335" +\ - "\27\21\20\377\27\21\20\377JB:\377YLH\377YLH\377dYI\377dYI\377dYI\377" +\ - "dYI\377931\377PD2\377|iI\377\224{U\377\224{U\377\224{U\377\214zR\377" +\ - "k[A\377333\377)))\377)))\377)))\3772/*\377)))\377JCA\377333\377333\377" +\ - "k]X\377\315\231{\377\215jZ\377JCA\377JCA\377JCA\377k]X\377\223\203|\377" +\ - "\223\203|\377\223\203|\377\253\233\223\377\262\241\230\377\222yk\377" +\ - "\204j[\377\224rZ\377\246|c\377\315\231{\377\343\255\215\377\333\242\200" +\ - "\377\315\231{\377\275\214j\377SA8\377ZRK\377\222yk\377tcY\377B;9\377" +\ - "\204j[\377\333\242\200\377\315\231{\377\246|c\377\240\206v\377BA;\377" +\ - "\31\31\31\377!!!\377!!!\377\33\40!\377RJB\377c[T\377k]X\377bTP\377JB" +\ - ":\377B;9\377:72\3778;:\377B;9\377BA;\377ZJC\377lZR\377{cR\377sbB\377" +\ - "\204rQ\377\204rQ\377\204rQ\377\214qR\377\204rQ\377SA8\377cMA\377kZJ\377" +\ - "kZJ\377cRK\377YLH\377ZRK\377ZRK\377cRK\3772/*\377\20\20\20\377\27\21" +\ - "\20\377\31\31\31\356\20\20\20w\0\0\0D\0\0\0""3\0\0\0\21\0\0\0\21\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"" +\ - "\10\10\10""3\31\31\31f\27\21\20\335\27\21\20\377\27\21\20\377JB:\377" +\ - "ZRK\377YLH\377kZJ\377dYI\377dYI\377dYI\377JB:\377PI9\377|iI\377\224{" +\ - "U\377\225\204Z\377\224{U\377\224{U\377k[A\377B;9\377BA;\377333\3772/" +\ - "*\377B;9\377)))\377LHA\377LHA\377RJB\3778;:\377\203tl\377\270\206i\377" +\ - "YLH\377BA;\377JB:\377c[T\377\222yk\377}rm\377kZJ\377kTO\377ZRK\3778;" +\ - ":\377B;9\377ZJC\377y[S\377\204j[\377\255\211s\377\343\255\215\377\343" +\ - "\255\215\377\315\231{\377kZJ\377)))\377\222yk\377:72\377333\377k]X\377" +\ - "tcY\377tcY\3772/*\377\33\40!\377\33\40!\377\33\40!\3772/*\3772/*\377" +\ - ")'#\377JCA\377\\TR\377lZR\377t^Y\377y[S\377sZK\377cMA\377ZJC\377lSI\377" +\ - "\203dR\377\225lP\377\234s[\377\204j[\377k[A\377\204rQ\377\204rQ\377\204" +\ - "rQ\377\214qR\377\214qR\377RJB\377ZJC\377kZJ\377kZJ\377ZRK\377YLH\377" +\ - "ZRK\377ZRK\377cRK\3771*'\377\20\20\20\377\27\21\20\377)\"\40\377\27\21" +\ - "\20w\0\0\0D\0\0\0""3\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\10\10\10""3\31\26\22f\27\21" +\ - "\20\335\31\26\22\377\20\20\20\377JB:\377ZRK\377YLH\377kZJ\377dYI\377" +\ - "dYI\377dYI\377PI9\377PD2\377|iI\377\224{U\377\225\204Z\377\225\204Z\377" +\ - "\224{U\377rcI\377SKI\377:72\377333\377:72\377RJB\377)'#\377BA;\377c[" +\ - "T\377\222yk\377QDA\377KKK\377\223\203|\377{cR\377JCA\377JB:\377\\TR\377" +\ - "}rm\377tcY\377RJB\377)))\377\33\40!\377!!!\377\40')\377\40')\377)'#\377" +\ - "2/*\377JB:\377kZJ\377\204j[\377\203dR\377dRB\377)'#\377B;9\377\40')\377" +\ - "!!!\377B;9\377\40')\377\31\31\31\377\33\40!\377\33\40!\377\33\40!\377" +\ - ")))\377B;9\377)))\377)))\377RJB\377ZRK\377bTP\377tZR\377t^Y\377zbZ\377" +\ - "\204j[\377\224rZ\377\234s[\377\234s[\377\215jZ\377zbZ\377tcY\377kZJ\377" +\ - "\204rK\377\214zR\377\204rQ\377\214zR\377\214zR\377YSC\377[J;\377lSI\377" +\ - "lSI\377ZRK\377YLH\377ZRK\377ZRK\377ZRK\377)\"\40\377\20\20\20\377\27" +\ - "\21\20\377!!!\377\27\21\20w\0\0\0D\0\0\0""3\0\0\0\"\0\0\0\21\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\14" +\ - "\13\12""3\27\21\20f\27\21\20\335\31\26\22\377\20\20\20\377B;9\377ZRK" +\ - "\377YLH\377kZJ\377dYI\377dYI\377dYI\377RJB\377K<1\377|iI\377\224{U\377" +\ - "\225\204Z\377\225\204Z\377\224{U\377rcI\377BA;\377B;9\377B;9\377B;9\377" +\ - "RJB\3772/*\377)))\377\213\204\202\377\252\223\211\377QDA\377AAA\377\244" +\ - "\242\237\377{l[\377RJB\377BA;\377ZRK\377vje\377l`Z\377333\377)))\377" +\ - ")))\377!!!\377!!!\377!!!\377!!!\377!!!\377\33\40!\377!!!\377)))\377)" +\ - "'#\377\40')\377\40')\377!!!\377:72\377BA;\377\40')\377!!!\377!!!\377" +\ - "!!!\377!!!\377)'#\377)))\377333\377!!!\3772/*\377SKI\377YLH\377bTP\377" +\ - "c[T\377lZR\377tZR\377tZR\377t^Y\377zbZ\377zbZ\377zbZ\377zbZ\377zbZ\377" +\ - "kZJ\377\204rK\377\214zR\377\214zR\377\214zR\377\224{U\377kSD\377[J;\377" +\ - "lSI\377lSI\377ZRK\377YLH\377ZRK\377cRK\377YLH\377)\"\40\377\20\20\20" +\ - "\377\27\21\20\377\31\31\31\356\20\20\20w\0\0\0D\0\0\0""3\0\0\0\"\0\0" +\ - "\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0" +\ - "\21\0\0\0\"\14\13\12""3\20\20\20U\27\21\20\356\31\26\22\377\20\20\20" +\ - "\377B81\377ZRK\377YLH\377kZJ\377dYI\377dYI\377dYI\377ZJC\377C5)\377|" +\ - "iI\377\225\204Z\377\225\204Z\377\225\204Z\377\224{U\377rcI\377ZJC\377" +\ - "sZK\377tZR\377ZJC\377ZJC\377931\377\33\40!\377\213\204\202\377\255\211" +\ - "s\377YLH\377333\377fkm\377tcY\377YLH\377B;9\377ZRK\377vje\377c[T\377" +\ - "RJB\377ZRK\377BA;\377333\377)))\377)))\377\40')\377!!!\377!!!\377!!!" +\ - "\377!!!\377!!!\377!!!\377!!!\377QDA\377bTP\377SKI\3772/*\377)))\377!" +\ - "!!\377!!!\377)))\377!!!\377KKK\377\213\204\202\377!!!\377)))\377SKI\377" +\ - "YLH\377ZRK\377ZRK\377bTP\377c[T\377lZR\377t^Y\377zbZ\377zbZ\377zbZ\377" +\ - "zbZ\377zbZ\377rcI\377\204rK\377\214zR\377\214zR\377\214zR\377\224{U\377" +\ - "kZJ\377[J;\377lSI\377lSI\377YLH\377ZRK\377ZRK\377cRK\377YLH\377!\37\34" +\ - "\377\27\21\20\377\27\21\20\377!\37\34\335\20\20\20w\5\5\4U\0\0\0""3\0" +\ - "\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\0\0\0\21\0\0\0\"\20\20\20""3\20\20\20U\27\21\20\335\31\26\22\377\20" +\ - "\20\20\377B81\377ZRK\377YLH\377dYI\377dYI\377dYI\377dYI\377YSC\377C5" +\ - ")\377|iI\377\225\204Z\377\225\204Z\377\225\204Z\377\225\204Z\377\204" +\ - "rQ\377\204j[\377\204j[\377\204j[\377{cR\377cRK\377BA;\377\33\40!\377" +\ - "tro\377\255\211s\377lZR\377BA;\377333\377c[T\377JB:\377)'#\377\\TR\377" +\ - "l`Z\377l`Z\377tcY\377YLH\377:72\377B;9\377931\377B;9\377:72\377333\377" +\ - "\40')\377!!!\377!!!\377\40')\377!!!\377333\377cRK\377\224rZ\377bTP\377" +\ - "B;9\3778;:\377)))\377!!!\377KKK\377\40')\3778;:\377\\TR\3772*\"\377\31" +\ - "\31\31\377JCA\377YLH\377YLH\377YLH\377YLH\377ZRK\377bTP\377lZR\377t^" +\ - "Y\377zbZ\377zbZ\377zbZ\377zbZ\377rcI\377\204rK\377\214zR\377\214zR\377" +\ - "\214zR\377\224{U\377|kP\377[J;\377lSI\377lSI\377YLH\377ZRK\377cRK\377" +\ - "ZRK\377ZRK\377)\"\40\377\20\20\20\377\27\21\20\377\31\31\31\335\27\21" +\ - "\20w\5\5\4U\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\20\20\20""3\20\20\20U\20\20" +\ - "\20\335\31\26\22\377\20\20\20\377B81\377ZRK\377YLH\377dYI\377kZJ\377" +\ - "dYI\377dYI\377dRB\377C5)\377{cJ\377\225\204Z\377\225\204Z\377\225\204" +\ - "Z\377\225\204Z\377\204rQ\377\204j[\377\204j[\377\204j[\377zbZ\377lZR" +\ - "\377YLH\377)'#\377333\377}rm\377vje\377JCA\377333\377l`Z\377BA;\377\31" +\ - "\31\31\377SKI\377l`Z\377vje\377vje\377cRK\377)'#\377fkm\377\244\242\237" +\ - "\377fkm\377!!!\377)))\3778;:\377!!!\377)))\377)))\377)))\377YLH\377\203" +\ - "jR\377\275\214j\377\246|c\377cRK\3772/*\377JCA\377333\377333\3772/*\377" +\ - "cMA\377sZK\377)'#\377\31\26\22\377931\377SKI\377YLH\377YLH\377JCA\377" +\ - "SKI\377ZRK\377bTP\377lZR\377t^Y\377t^Y\377zbZ\377zbZ\377rcI\377\204r" +\ - "K\377\214zR\377\214zR\377\214zR\377\224{U\377\204rQ\377ZJC\377cRK\377" +\ - "lSI\377YLH\377ZRK\377bTP\377ZRK\377ZRK\377)\"\40\377\20\20\20\377\27" +\ - "\21\20\377\31\31\31\335\31\26\22w\5\5\4U\0\0\0""3\0\0\0\"\0\0\0\21\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"" +\ - "\20\20\20""3\20\20\20U\20\20\20\356\31\26\22\377\20\20\20\377931\377" +\ - "ZRK\377YLH\377dYI\377kZJ\377dYI\377dYI\377dYI\377C5)\377{cJ\377\225\204" +\ - "Z\377\225\204Z\377\225\204Z\377\225\204Z\377\204rQ\377\204j[\377\204" +\ - "j[\377\204dY\377{cR\377bTP\377\\TR\377B;9\377\33\40!\377)))\377ZRK\377" +\ - "2/*\377:72\377l`Z\377JCA\377\20\20\20\377JB:\377tcY\377}rm\377}rm\377" +\ - "{l[\377kZJ\377JCA\377\270\263\257\377fkm\377333\377)))\377LHA\377)))" +\ - "\377BA;\377JB:\377YLH\377y[S\377\224rZ\377\270\204d\377\255\204a\377" +\ - "{cR\3772/*\377:72\377YLH\377B;9\377QDA\377sZK\377SKI\377)'#\377\31\31" +\ - "\31\377931\377B;9\377SKI\377ZRK\377YLH\377QDA\377SKI\377YLH\377bTP\377" +\ - "lZR\377lZR\377tZR\377tcY\377rcI\377\204rK\377\224{U\377\224{U\377\214" +\ - "zR\377\224{U\377\214zR\377cMA\377dRB\377lSI\377YLH\377ZRK\377bTP\377" +\ - "ZRK\377ZRK\377)'#\377\27\21\20\377\27\21\20\377\20\20\20\314\31\31\31" +\ - "\210\5\5\4U\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\20\20\20""3\20\20\20U\20\20" +\ - "\20\356\31\26\22\377\20\20\20\377:72\377ZRK\377YLH\377cRK\377kZJ\377" +\ - "dYI\377dYI\377dYI\377C5)\377{cJ\377\225\204Z\377\225\204Z\377\225\204" +\ - "Z\377\225\204Z\377\204rQ\377zbZ\377zbZ\377zbZ\377lZR\377ZRK\377\\TR\377" +\ - "ZRK\377!!!\377\33\40!\377RJB\377K=8\377dRB\377c[T\377333\377\14\13\12" +\ - "\377)))\377l`Z\377}rm\377}rm\377tcY\377{cR\377\225vb\377kZJ\377333\377" +\ - ":72\377B;9\377)))\377B;9\377zbZ\377bTP\377LHA\377tZR\377\246|c\377\246" +\ - "|c\377\225vb\377tZR\377B;9\377\33\40!\377931\377cRK\377ZJC\377BA;\377" +\ - "RJB\3771*'\377\31\31\31\377BA;\377B;9\377BA;\377YLH\377ZRK\377YLH\377" +\ - "RJB\377YLH\377ZRK\377cRK\377bTP\377kTO\377lZR\377kZJ\377\203lL\377\225" +\ - "\204Z\377\224{U\377\224{U\377\214zR\377\224{U\377dRB\377dRB\377cRK\377" +\ - "YLH\377YLH\377bTP\377bTP\377bTP\377)'#\377\27\21\20\377\27\21\20\377" +\ - "\31\26\22\273\31\31\31\210\10\10\10U\0\0\0""3\0\0\0\"\0\0\0\21\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\20" +\ - "\20\20""3\20\20\20U\20\20\20\356\31\26\22\377\31\26\22\377B81\377ZRK" +\ - "\377YLH\377cRK\377kZJ\377kZJ\377kZJ\377dYI\377C5)\377{cJ\377\222\205" +\ - "c\377\225\204Z\377\235\214d\377\225\204Z\377\204rQ\377zbZ\377zbZ\377" +\ - "t^Y\377bTP\377bTP\377lZR\377YLH\377!!!\377\33\40!\3772/*\377c[T\377l" +\ - "ZR\377333\377)))\377\20\20\20\377:72\377vje\377vje\377zbZ\377zbZ\377" +\ - "YLH\377lZR\377\255\211s\377{l[\377B;9\377)'#\377B;9\377tcY\377LHA\377" +\ - "JCA\377ZRK\377\203dR\377\270\204d\377\254|`\377\214kQ\377[J;\377931\377" +\ - "tcY\377931\377B;9\377{cR\377\204j[\377\215jZ\3772*\"\377\31\31\31\377" +\ - "B;9\377RJB\377B;9\377JCA\377YLH\377YLH\377ZRK\377ZRK\377bTP\377bTP\377" +\ - "bTP\377bTP\377kTO\377lZR\377\204rK\377\225\204Z\377\225\204Z\377\224" +\ - "{U\377\224{U\377\224{U\377dYI\377dRB\377cRK\377YLH\377YLH\377bTP\377" +\ - "bTP\377bTP\3771*'\377\27\21\20\377\27\21\20\377\31\26\22\273\31\26\22" +\ - "w\5\5\4U\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\0\0\0\21\0\0\0\"\20\20\20""3\20\20\20U\20\20\20" +\ - "\356\31\26\22\377\31\26\22\377B81\377ZRK\377YLH\377cRK\377kZJ\377kZJ" +\ - "\377kZJ\377dYI\377C5)\377{cJ\377\222\205c\377\235\214d\377\235\214d\377" +\ - "\235\214d\377\204rQ\377lZR\377lZR\377lZR\377sZK\377tZR\377zbZ\377RJB" +\ - "\377\33\40!\377\33\40!\377\33\40!\377333\377333\377\31\31\31\377\20\20" +\ - "\20\377\20\20\20\377B;9\377}rm\377}rm\377{l[\377\215jZ\377\225vb\377" +\ - "{cR\377YLH\377ZJC\377:72\377ZJC\377\204j[\377JB:\377cRK\377\315\231{" +\ - "\377\225vb\377\203dR\377\246|c\377\270\206i\377\270\206i\377\215jZ\377" +\ - "RJB\377\204j[\377YLH\377)))\377ZRK\377lZR\377bTP\377\31\31\31\377\31" +\ - "\31\31\3772/*\377RJB\377LHA\377JCA\377JCA\377SKI\377YLH\377YLH\377bT" +\ - "P\377lZR\377t^Y\377lZR\377t^Y\377t^Y\377\204rQ\377\225\204Z\377\225\204" +\ - "Z\377\225\204Z\377\224{U\377\225\204Z\377kZJ\377cRK\377cRK\377YLH\377" +\ - "SKI\377bTP\377bTP\377cRK\3772/*\377\27\21\20\377\27\21\20\377\20\20\20" +\ - "\273\20\20\20w\10\10\10U\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\20\20\20""3\20" +\ - "\20\20U\27\21\20\356\31\26\22\377\31\26\22\377B81\377ZRK\377YLH\377Y" +\ - "LH\377kZJ\377kZJ\377kZJ\377kZJ\377C5)\377rcI\377\222\205c\377\222\205" +\ - "c\377\235\214d\377\235\214d\377|kP\377bTP\377cRK\377sZK\377{cR\377tZ" +\ - "R\377tZR\377ZRK\377!!!\377\33\40!\377\33\40!\377\33\40!\377\31\31\31" +\ - "\377\14\13\12\377\14\13\12\377\22\27\30\3772/*\377}rm\377\213\204\202" +\ - "\377\252\223\211\377\240\206v\377\271\220w\377\246|c\377\204j[\377kZ" +\ - "J\377\203dR\377\224rZ\377JB:\377333\377zbZ\377\235|c\377\215jZ\377zb" +\ - "Z\377\270\204d\377\332\232s\377\343\255\215\377\271\220w\377bTP\377Z" +\ - "RK\377JCA\377)))\377RJB\377bTP\377B;9\377\20\20\20\377)'#\377)))\377" +\ - "LHA\377LHA\377LHA\377JCA\377LHA\377RJB\377SKI\377YLH\377bTP\377k]X\377" +\ - "t^Y\377t^Y\377t^Y\377|kP\377\225\204Z\377\225\204Z\377\225\204Z\377\225" +\ - "\204Z\377\225\204Z\377rcI\377cRK\377cRK\377YLH\377SKI\377bTP\377bTP\377" +\ - "bTP\377;1)\377\27\21\20\377\27\21\20\377\20\20\20\252\20\20\20w\14\13" +\ - "\12U\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\0\0\0\21\0\0\0\"\20\20\20""3\20\20\20U\31\26\22\356" +\ - "\31\26\22\377\31\26\22\377B81\377ZRK\377YLH\377YLH\377kZJ\377kZJ\377" +\ - "kZJ\377kZJ\377;1)\377rcI\377\235\214d\377\235\214d\377\235\214d\377\235" +\ - "\214d\377rcI\377ZJC\377cRK\377{cR\377zbZ\377tZR\377bTP\377bTP\377333" +\ - "\377\33\40!\377\33\40!\377\33\40!\377\22\27\30\377\14\13\12\377\31\31" +\ - "\31\377\20\20\20\377!\37\34\377l`Z\377\213\204\202\377\244\242\237\377" +\ - "\266\257\254\377\265\246\205\377\255\211s\377\234s[\377\246|c\377\225" +\ - "vb\377JB:\377)))\3772/*\377ZRK\377c[T\377bTP\377lZR\377\204j[\377\315" +\ - "\231{\377\343\255\215\377\222yk\377YLH\377JCA\377333\377333\377JCA\377" +\ - "\\TR\377!!!\377\27\21\20\377:72\3772/*\377:72\377RJB\377LHA\377LHA\377" +\ - "LHA\377LHA\377LHA\377RJB\377SKI\377ZRK\377kTO\377kTO\377lZR\377|kP\377" +\ - "\225\204Z\377\235\214d\377\235\214d\377\225\204Z\377\225\204Z\377|kP" +\ - "\377cRK\377kZJ\377YLH\377SKI\377bTP\377bTP\377bTP\377931\377\27\21\20" +\ - "\377\27\21\20\377\20\20\20\252\20\20\20w\20\20\20U\0\0\0""3\0\0\0\"\0" +\ - "\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0" +\ - "\21\0\0\0\"\20\20\20""3\20\20\20U\31\26\22\356\31\26\22\377\31\31\31" +\ - "\377B81\377ZRK\377YLH\377YLH\377kZJ\377kZJ\377kZJ\377kZJ\377C5)\377r" +\ - "cI\377\235\214d\377\235\214d\377\235\214d\377\235\214d\377rcI\377RJB" +\ - "\377{cR\377{cR\377scQ\377lZR\377ZRK\377bTP\377JCA\377\33\40!\377\33\40" +\ - "!\377\33\40!\377\22\27\30\377\22\27\30\377!\37\34\377\20\20\20\377\22" +\ - "\27\30\377ZRK\377\203tl\377\213\204\202\377\266\257\254\377\203tl\377" +\ - "\234s[\377\275\214j\377\246|c\377QDA\377:72\377bTP\377BA;\377)))\377" +\ - "2/*\377)))\377B;9\377tcY\377t^Y\377tcY\377bTP\3772/*\377\40')\377BA;" +\ - "\377ZRK\377LHA\377JCA\377\22\27\30\377)\"\40\377B;9\377BA;\3772/*\377" +\ - "JCA\377RJB\377QDA\377LHA\377RJB\377LHA\377RJB\377SKI\377YLH\377ZRK\377" +\ - "cRK\377lZR\377{cR\377\225\204Z\377\235\214d\377\235\214d\377\225\204" +\ - "Z\377\225\204Z\377\204rQ\377cRK\377kZJ\377YLH\377SKI\377bTP\377bTP\377" +\ - "bTP\377B81\377\27\21\20\377\27\21\20\377\20\20\20\252\20\20\20w\20\20" +\ - "\20U\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\0\0\0\21\0\0\0\"\20\20\20""3\27\21\20f\31\26\22\356" +\ - "\31\26\22\377\31\26\22\377B;9\377ZRK\377ZRK\377YLH\377kZJ\377kZJ\377" +\ - "kZJ\377kZJ\377C5)\377rcI\377\235\214d\377\235\214d\377\235\214d\377\235" +\ - "\214d\377rcI\377kSD\377{cR\377lZR\377lZR\377bTP\377\\TR\377ZRK\377ZR" +\ - "K\377)))\377\31\31\31\377\33\40!\377\22\27\30\377\31\31\31\377\31\31" +\ - "\31\377\31\31\31\377\22\27\30\377:72\377l`Z\377}rm\377\203tl\377\203" +\ - "tl\377\333\242\200\377\333\242\200\377kSD\377\33\40!\377!!!\377JB:\377" +\ - "tcY\377ZRK\377B;9\3778;:\377)))\3778;:\377SKI\377BA;\3772/*\377JCA\377" +\ - "YLH\377ZJC\377RJB\377YLH\377)))\377\31\31\31\3772/*\377JCA\377JCA\377" +\ - "JCA\377:72\377SKI\377LHA\377LHA\377LHA\377SKI\377SKI\377bTP\377k]X\377" +\ - "k]X\377k]X\377k]X\377tcY\377\225\204Z\377\235\214d\377\235\214d\377\225" +\ - "\204Z\377\225\204Z\377\211wZ\377dYI\377kZJ\377YLH\377SKI\377cRK\377b" +\ - "TP\377bTP\377B81\377\27\21\20\377\27\21\20\377\20\20\20\252\14\13\12" +\ - "w\20\20\20U\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\14\13\12""3\31\26\22f\31\26" +\ - "\22\356\31\26\22\377\31\26\22\377B;9\377ZRK\377ZRK\377ZRK\377lSI\377" +\ - "sZK\377kZJ\377sZK\377C5)\377sbB\377\235\214d\377\235\214d\377\235\214" +\ - "d\377\235\214d\377rcI\377lSI\377t^Y\377bTP\377\\TR\377bTP\377bTP\377" +\ - "ZRK\377ZRK\377JCA\377!!!\377\31\31\31\377\20\20\20\377!\37\34\377\31" +\ - "\31\31\377!!!\377\31\31\31\377\31\31\31\377JCA\377l`Z\377}rm\377\203" +\ - "tl\377\215xc\377\204j[\377)))\377)))\377LHA\377333\3772/*\377cRK\377" +\ - "zbZ\377tZR\377ZRK\377BA;\377333\377333\377ZRK\377tZR\377JB:\377333\377" +\ - "LHA\377JCA\377\33\40!\377!\37\34\377B;9\377JCA\377RJB\377SKI\377B;9\377" +\ - "JCA\377SKI\377LHA\377LHA\377RJB\377ZRK\377bTP\377l`Z\377t^Y\377k]X\377" +\ - "k]X\377t^Y\377\225\204Z\377\235\214d\377\235\214d\377\235\214d\377\222" +\ - "\205c\377\214zR\377dYI\377lSI\377YLH\377SKI\377cRK\377bTP\377bTP\377" +\ - "B;9\377\27\21\20\377\27\21\20\377\20\20\20\252\14\13\12w\14\13\12U\0" +\ - "\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\0\0\0\21\0\0\0\"\5\5\4""3\31\26\22f\31\26\22\356\31\26\22" +\ - "\377\31\26\22\377B;9\377ZRK\377ZRK\377YLH\377lSI\377sZK\377kZJ\377sZ" +\ - "K\377C5)\377k[A\377\235\214d\377\235\214d\377\243\224j\377\235\214d\377" +\ - "rcI\377bTP\377bTP\377LHA\377SKI\377bTP\377ZRK\377ZRK\377YLH\377ZRK\377" +\ - "BA;\377\31\31\31\377\14\13\12\377\31\31\31\377\31\31\31\377)))\377)'" +\ - "#\377\27\21\20\377)))\377ZRK\377}rm\377vje\377lZR\377YLH\377)))\3772" +\ - "/*\377\223\203|\377\266\257\254\377\243\210z\377QDA\377LHA\377tcY\377" +\ - "\204j[\377{l[\377lZR\377tcY\377kZJ\377ZJC\377RJB\377333\377\\TR\3772" +\ - "/*\377\31\31\31\377!!!\377BA;\377JCA\377SKI\377ZRK\377YLH\377JCA\377" +\ - "LHA\377LHA\377LHA\377LHA\377SKI\377ZRK\377c[T\377k]X\377k]X\377k]X\377" +\ - "t^Y\377\224{U\377\243\224j\377\235\214d\377\235\214d\377\225\204Z\377" +\ - "\211wZ\377dYI\377dYI\377YLH\377SKI\377cRK\377bTP\377bTP\377B;9\377\27" +\ - "\21\20\377\27\21\20\377\20\20\20\273\14\13\12w\14\13\12U\0\0\0""3\0\0" +\ - "\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\0\0\0\21\0\0\0\"\5\5\4""3\31\26\22f\31\31\31\356\31\26\22\377\31\26" +\ - "\22\377B;9\377ZRK\377ZRK\377ZRK\377cRK\377sZK\377sZK\377sZK\377C5)\377" +\ - "k[A\377\235\214d\377\235\214d\377\243\224j\377\243\224j\377rcI\377ZR" +\ - "K\377LHA\377JCA\377ZRK\377\\TR\377bTP\377ZRK\377SKI\377SKI\377SKI\377" +\ - "931\377\14\13\12\377\31\31\31\377!!!\377)))\377)))\377!\37\34\377!\37" +\ - "\34\377B;9\377l`Z\377c[T\377bTP\377RJB\377YLH\377333\377JCA\377tro\377" +\ - "\266\257\254\377\330\312\237\377\215jZ\377931\3772/*\377B;9\377YLH\377" +\ - "JCA\377YLH\377\253\233\223\377B;9\377BA;\377LHA\377\33\40!\377\33\40" +\ - "!\377!!!\377B;9\377BA;\377RJB\377ZRK\377ZRK\377SKI\377JCA\377LHA\377" +\ - "LHA\377JCA\377LHA\377SKI\377ZRK\377bTP\377bTP\377k]X\377k]X\377\211w" +\ - "Z\377\243\224j\377\243\224j\377\235\214d\377\235\214d\377\224{U\377d" +\ - "YI\377dYI\377YLH\377SKI\377cRK\377bTP\377bTP\377B;9\377\27\21\20\377" +\ - "\27\21\20\377\20\20\20\273\14\13\12w\20\20\20U\0\0\0""3\0\0\0\"\0\0\0" +\ - "\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\"\5\5\4""3\31\31\31f\31\31\31\356\31\26\22\377\31\26\22\377B;9\377" +\ - "ZRK\377ZRK\377ZRK\377lSI\377sZK\377sZK\377sZK\377C5)\377k[A\377\234\213" +\ - "k\377\235\214d\377\243\224j\377\243\224j\377rcI\377YLH\377YLH\377ZRK" +\ - "\377bTP\377ZRK\377cRK\377ZRK\377YLH\377SKI\377RJB\377JCA\377\33\40!\377" +\ - "\22\27\30\377)))\377)))\377)))\3772/*\377\31\31\31\3771*'\377KKK\377" +\ - "\\TR\377\\TR\377SKI\377c[T\377bTP\377JB:\377\213\204\202\377\244\242" +\ - "\237\377\244\242\237\377\330\312\237\377\330\312\237\377\203tl\377B;" +\ - "9\3778;:\377c[T\377\270\263\257\377\223\203|\3772/*\377B;9\3772/*\377" +\ - "\33\40!\377\31\31\31\377!!!\377333\377B;9\377LHA\377YLH\377ZRK\377SK" +\ - "I\377B;9\377JCA\377JCA\377BA;\377BA;\377JCA\377SKI\377SKI\377SKI\377" +\ - "\\TR\377bTP\377\202uX\377\243\224j\377\243\224j\377\235\214d\377\235" +\ - "\214d\377\225\204Z\377dRB\377cRK\377ZRK\377SKI\377kZJ\377kTO\377bTP\377" +\ - "B;9\377\27\21\20\377\27\21\20\377\20\20\20\314\14\13\12w\20\20\20U\0" +\ - "\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\0\0\0\21\0\0\0\"\5\5\4""3!\37\34w\31\31\31\356\31\26\22\377" +\ - "\31\26\22\377K=8\377ZRK\377ZRK\377ZRK\377cRK\377sZK\377sZK\377sZK\377" +\ - "B81\377dRB\377\234\213k\377\243\224j\377\252\226l\377\243\224j\377|i" +\ - "I\377YLH\377bTP\377bTP\377YLH\377YLH\377ZRK\377ZRK\377YLH\377RJB\377" +\ - "LHA\377BA;\377\33\40!\377\31\31\31\377)))\377)))\3778;:\377333\377)'" +\ - "#\377\31\31\31\3771*'\377JCA\377\\TR\377bTP\377k]X\377l`Z\377bTP\377" +\ - "SKI\377\244\242\237\377\270\263\257\377\266\257\254\377\330\312\237\377" +\ - "\330\312\237\377\270\263\257\377\270\263\257\377\253\233\223\377\270" +\ - "\263\257\377\\TR\377:72\377)))\377\33\40!\377!!!\377\20\20\20\377!!!" +\ - "\377)))\3778;:\377JCA\377YLH\377YLH\377SKI\377JCA\3772/*\377)))\3772" +\ - "/*\377931\377931\377B;9\377BA;\377JCA\377SKI\377ZRK\377|kP\377\243\224" +\ - "j\377\243\224j\377\243\224j\377\235\214d\377\225\204Z\377cMA\377cRK\377" +\ - "cRK\377SKI\377kZJ\377kTO\377c[T\377K=8\377\27\21\20\377\27\21\20\377" +\ - "\20\20\20\314\14\13\12w\20\20\20U\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\5\5\4""3" +\ - "!\37\34w\31\31\31\356\31\26\22\377\31\26\22\377K=8\377ZRK\377ZRK\377" +\ - "YLH\377cRK\377sZK\377sZK\377kZJ\377C5)\377YSC\377\234\213k\377\243\224" +\ - "j\377\252\226l\377\243\224j\377|kP\377LHA\377JCA\377JCA\377RJB\377ZR" +\ - "K\377cRK\377ZRK\377YLH\377LHA\377LHA\377B;9\377\31\31\31\377\22\27\30" +\ - "\3772/*\377)))\377AAA\3772/*\377333\377!\37\34\377\31\26\22\377)'#\377" +\ - "BA;\377\\TR\377c[T\377c[T\377k]X\377lZR\377cRK\377\253\233\223\377\330" +\ - "\312\237\377\244\242\237\377\262\241\230\377\213\204\202\377\270\263" +\ - "\257\377\330\312\237\377\270\263\257\377BA;\377B;9\377\33\40!\377\22" +\ - "\27\30\377!\37\34\377\20\20\20\377)'#\377!\37\34\377333\377B;9\377SK" +\ - "I\377SKI\377SKI\377RJB\377)))\377!!!\377!!!\377)))\377)))\377)))\377" +\ - ":72\377RJB\377ZRK\377YLH\377meP\377\235\214d\377\243\224j\377\243\224" +\ - "j\377\235\214d\377\225\204Z\377cMA\377cRK\377cRK\377YLH\377kZJ\377lZ" +\ - "R\377c[T\377JB:\377\27\21\20\377\27\21\20\377\20\20\20\335\14\13\12w" +\ - "\20\20\20U\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\5\5\4""3!\37\34w\31\31\31\356" +\ - "\31\26\22\377\31\26\22\377K=8\377cRK\377ZRK\377YLH\377cRK\377sZK\377" +\ - "sZK\377kZJ\377;1)\377YSC\377\234\213k\377\243\224j\377\252\226l\377\243" +\ - "\224j\377|kP\377JB:\377BA;\377JCA\377ZRK\377ZRK\377YLH\377ZRK\377SKI" +\ - "\377JCA\377JCA\377B;9\377\22\27\30\377\31\31\31\377)))\3772/*\377333" +\ - "\377)))\377AAA\377)))\377\20\20\20\377\20\20\20\377)))\377JCA\377ZRK" +\ - "\377bTP\377ZRK\377tcY\377zbZ\377zbZ\377\262\241\230\377\270\263\257\377" +\ - "\330\312\237\377\262\241\230\377\330\312\237\377\262\241\230\377tcY\377" +\ - "\203dR\377:72\377\33\40!\377\31\31\31\377\20\20\20\377\20\20\20\3772" +\ - "/*\377\31\31\31\377)))\377:72\377SKI\377SKI\377SKI\377SKI\377LHA\377" +\ - "JCA\377JCA\377JCA\377BA;\377B;9\377JCA\377YLH\377ZRK\377ZRK\377g`K\377" +\ - "\222\205c\377\243\224j\377\243\224j\377\243\224j\377\225\204Z\377cRK" +\ - "\377dRB\377cRK\377YLH\377kZJ\377tZR\377bTP\377JB:\377\31\26\22\377\27" +\ - "\21\20\377\27\21\20\335\14\13\12\210\27\21\20f\5\5\4""3\0\0\0\"\0\0\0" +\ - "\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\"\5\5\4""3!\37\34w\31\26\22\356\31\26\22\377\31\26\22\377JB:\377" +\ - "cRK\377ZRK\377YLH\377cRK\377lSI\377sZK\377kZJ\377;1)\377[J;\377\235\214" +\ - "d\377\243\224j\377\252\226l\377\243\224j\377\204rQ\377BA;\377JCA\377" +\ - "ZRK\377bTP\377ZRK\377SKI\377SKI\377LHA\377JCA\377JCA\377B;9\377\22\27" +\ - "\30\377\31\31\31\377\31\31\31\377!!!\377333\3778;:\377KKK\3778;:\377" +\ - "\31\31\31\377\22\27\30\377\22\27\30\377)'#\377BA;\377SKI\377SKI\377S" +\ - "KI\377tcY\377\224rZ\377\204dY\377zbZ\377\222yk\377\225vb\377\204j[\377" +\ - "\245|Z\377\270\206i\377lSI\377\33\40!\377\20\20\20\377!\37\34\377\20" +\ - "\20\20\377\22\27\30\3772/*\377)))\377!\37\34\377333\377JCA\377SKI\377" +\ - "RJB\377LHA\377RJB\377SKI\377ZRK\377ZRK\377ZRK\377SKI\377RJB\377SKI\377" +\ - "ZRK\377ZRK\377g`K\377\225\204Z\377\243\224j\377\243\224j\377\243\224" +\ - "j\377\235\214d\377kZJ\377cMA\377cRK\377YLH\377cRK\377scQ\377bTP\377Q" +\ - "DA\377\31\26\22\377\27\21\20\377\27\21\20\356\14\13\12\210\27\21\20f" +\ - "\10\10\10""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\0\0\0\21\0\0\0\"\5\5\4""3!\37\34w\31\26\22\356\31\26" +\ - "\22\377\31\26\22\377JB:\377cRK\377ZRK\377YLH\377cRK\377sZK\377kZJ\377" +\ - "kZJ\377;1)\377PI9\377\234\213k\377\243\224j\377\252\226l\377\235\214" +\ - "d\377|kP\377LHA\377ZRK\377ZRK\377ZRK\377ZRK\377YLH\377JCA\377JCA\377" +\ - "JCA\377JCA\377B;9\377\33\40!\377\31\31\31\377\22\27\30\377!!!\377333" +\ - "\377333\377KKK\377PVV\377!!!\377\31\31\31\377\20\20\20\377\14\13\12\377" +\ - "!\37\34\377931\377JB:\377BA;\377JCA\377lZR\377\246|c\377\270\204d\377" +\ - "\263}^\377\275\214j\377\307\212h\377\275\214j\377sZK\377\33\40!\377!" +\ - "!!\377\22\27\30\377\20\20\20\377\20\20\20\377\22\27\30\377)))\377JCA" +\ - "\377\31\31\31\377)))\377BA;\377RJB\377LHA\377JCA\377JCA\377JCA\377RJ" +\ - "B\377SKI\377YLH\377YLH\377YLH\377ZRK\377ZRK\377ZRK\377g`K\377\225\204" +\ - "Z\377\243\224j\377\243\224j\377\243\224j\377\235\214d\377sZK\377dRB\377" +\ - "cRK\377ZRK\377cRK\377scQ\377bTP\377YLH\377!\31\30\377\27\21\20\377\27" +\ - "\21\20\377\14\13\12\231\27\21\20f\10\10\10""3\0\0\0\"\0\0\0\21\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\5\5" +\ - "\4""3!\37\34w\31\26\22\356\31\26\22\377\31\26\22\377JB:\377cRK\377ZR" +\ - "K\377YLH\377cRK\377sZK\377kZJ\377kZJ\377;1)\377PI9\377\234\213k\377\252" +\ - "\226l\377\252\226l\377\243\224j\377\204rQ\377YLH\377YLH\377YLH\377YL" +\ - "H\377ZRK\377SKI\377BA;\377JCA\377LHA\377JCA\377JCA\377JCA\377!!!\377" +\ - "\22\27\30\377\22\27\30\3772/*\377)))\377KKK\377PVV\377AAA\377!!!\377" +\ - "\22\27\30\377\31\31\31\377\31\31\31\377\31\26\22\3772/*\377B;9\377LH" +\ - "A\377B;9\377LHA\377zbZ\377\235|c\377\255\204a\377\224rZ\377JB:\377\33" +\ - "\40!\377\22\27\30\377\22\27\30\377!!!\377\14\13\12\377\20\20\20\377\31" +\ - "\31\31\377)))\377SKI\3772/*\377!!!\377:72\377RJB\377JCA\377JCA\377BA" +\ - ";\377B;9\377B;9\377JCA\377SKI\377SKI\377YLH\377ZRK\377ZRK\377YLH\377" +\ - "g`K\377\225\204Z\377\235\214d\377\243\224j\377\243\224j\377\235\214d" +\ - "\377scQ\377dRB\377cRK\377ZRK\377ZRK\377scQ\377bTP\377ZRK\377!\37\34\377" +\ - "\27\21\20\377\31\26\22\377\20\20\20\252\20\20\20f\10\10\10""3\0\0\0\"" +\ - "\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0" +\ - "\0\0\21\0\0\0\"\14\13\12""3!\37\34w\31\26\22\356\31\26\22\377\27\21\20" +\ - "\377JB:\377cRK\377ZRK\377YLH\377cRK\377sZK\377kZJ\377kZJ\377;1)\377P" +\ - "I9\377\252\226l\377\252\226l\377\252\226l\377\243\224j\377|kP\377YLH" +\ - "\377SKI\377SKI\377SKI\377SKI\377LHA\377BA;\377JCA\377JCA\377JCA\377J" +\ - "CA\377LHA\377:72\377\31\31\31\377\22\27\30\377)))\377)'#\3778;:\377J" +\ - "CA\377PVV\377333\377\31\31\31\377)'#\377!!!\377\14\13\12\377\20\20\20" +\ - "\377!\37\34\377:72\377BA;\377\40')\377)))\377)))\377\40')\377\40')\377" +\ - "\40')\377)'#\377!!!\377\20\20\20\377)))\377\20\20\20\377\20\20\20\377" +\ - "\31\31\31\377!!!\377RJB\377JCA\377!!!\377333\377JCA\377RJB\377JCA\377" +\ - "B;9\377B;9\377B;9\377B;9\377BA;\377LHA\377SKI\377YLH\377ZRK\377YLH\377" +\ - "dYI\377\211wZ\377\235\214d\377\252\226l\377\252\226l\377\235\214d\377" +\ - "|kP\377cMA\377cRK\377ZRK\377ZRK\377sZK\377bTP\377ZRK\377!\37\34\377\27" +\ - "\21\20\377\31\26\22\377\20\20\20\273\14\13\12f\5\5\4""3\0\0\0\"\0\0\0" +\ - "\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\"\14\13\12""3!\37\34w\31\26\22\356\27\21\20\377\31\26\22\377JB:" +\ - "\377cRK\377ZRK\377YLH\377kTO\377{cJ\377kZJ\377kZJ\3771*'\377PD2\377\252" +\ - "\226l\377\255\234t\377\252\225s\377\243\224j\377|kP\377SKI\377SKI\377" +\ - "SKI\377SKI\377RJB\377JCA\377JCA\377BA;\377BA;\377JCA\377JCA\377JCA\377" +\ - "JCA\377B;9\377:72\377\31\31\31\377\22\27\30\377333\377AAA\377KKK\377" +\ - "KKK\3772/*\377333\377)))\377!\37\34\377\33\40!\377\31\31\31\377\20\20" +\ - "\20\377\31\31\31\377!!!\377\33\40!\377\40')\377\31\31\31\377\33\40!\377" +\ - ")))\377!!!\377)))\377)'#\377)'#\377\22\27\30\377\31\31\31\377!!!\377" +\ - "\33\40!\377JCA\377SKI\377B;9\377)))\377JCA\377RJB\377RJB\377JCA\377B" +\ - ";9\377:72\377B;9\377B;9\377BA;\377JCA\377RJB\377YLH\377ZRK\377ZRK\377" +\ - "\211wZ\377\235\214d\377\252\226l\377\252\226l\377\243\224j\377\202uX" +\ - "\377cRK\377cRK\377ZRK\377YLH\377sZK\377kTO\377bTP\377)\"\40\377\27\21" +\ - "\20\377\31\26\22\377\27\21\20\335\10\10\10f\5\5\4D\0\0\0\"\0\0\0\21\0" +\ - "\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\10" +\ - "\10\10""3!\37\34w\31\26\22\356\31\26\22\377\31\26\22\377JB:\377cRK\377" +\ - "ZRK\377YLH\377cRK\377{cJ\377kZJ\377dYI\3771*'\377PD2\377\243\224j\377" +\ - "\255\234t\377\255\234t\377\243\224j\377|kP\377RJB\377SKI\377SKI\377R" +\ - "JB\377JCA\377BA;\377LHA\377JCA\377B;9\377JCA\377JCA\377JCA\377JCA\377" +\ - "LHA\377LHA\377!!!\377\20\20\20\377)))\377333\377JCA\377KKK\3778;:\377" +\ - "333\377AAA\377333\377)))\377!!!\377\33\40!\377\22\27\30\377!!!\377)'" +\ - "#\377!\37\34\377!!!\377!\37\34\377!!!\3772/*\377!!!\377\31\31\31\377" +\ - "!\37\34\377\31\31\31\377\22\27\30\377\33\40!\377!!!\377BA;\377RJB\377" +\ - "LHA\377:72\377B;9\377RJB\377LHA\377RJB\377LHA\377BA;\377:72\377B;9\377" +\ - "BA;\377JCA\377JCA\377LHA\377RJB\377RJB\377\202uX\377\235\214d\377\243" +\ - "\224j\377\252\226l\377\252\226l\377\211wZ\377cRK\377cRK\377cRK\377YL" +\ - "H\377sZK\377lZR\377bTP\3771*'\377\20\20\20\377\31\26\22\377\27\21\20" +\ - "\335\10\10\10f\5\5\4D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\0\0\0\21\0\0\0\"\5\5\4""3!\37\34w\31\26\22\356\31" +\ - "\26\22\377\31\26\22\377JB:\377cRK\377ZRK\377ZRK\377dYI\377{cJ\377kZJ" +\ - "\377dYI\3771*'\377PD2\377\243\224j\377\263\237{\377\263\237{\377\252" +\ - "\226l\377|kP\377RJB\377SKI\377SKI\377LHA\377JCA\377JCA\377LHA\377JCA" +\ - "\377B;9\377BA;\377JCA\377JCA\377JCA\377JCA\377JCA\377)))\377\22\27\30" +\ - "\377\31\31\31\377!!!\377333\377JCA\3778;:\377333\3778;:\377AAA\37733" +\ - "3\377)))\3772/*\377)))\377\31\31\31\377333\377!!!\377!\37\34\377)))\377" +\ - "2/*\377!!!\3772/*\377\20\20\20\377\22\27\30\377\22\27\30\377\22\27\30" +\ - "\377\33\40!\377)))\377JCA\377LHA\377LHA\377LHA\377BA;\377JCA\377LHA\377" +\ - "LHA\377RJB\377SKI\377JCA\377JCA\377JCA\377JCA\377JCA\377QDA\377QDA\377" +\ - "QDA\377scQ\377\235\214d\377\243\224j\377\252\226l\377\252\226l\377\222" +\ - "\205c\377cRK\377cRK\377cRK\377YLH\377sZK\377kTO\377kTO\377;1)\377\27" +\ - "\21\20\377\31\26\22\377\31\26\22\356\14\13\12w\5\5\4D\0\0\0""3\0\0\0" +\ - "\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\"\5\5\4""3!\37\34w\31\26\22\356\27\21\20\377\31\26\22\377QDA\377" +\ - "bTP\377ZRK\377ZRK\377kTO\377{cJ\377kZJ\377dYI\3771*'\377JB:\377\243\224" +\ - "j\377\266\245z\377\266\245z\377\252\226l\377|kP\377RJB\377RJB\377RJB" +\ - "\377LHA\377JCA\377JCA\377JCA\377JCA\377BA;\377B;9\377JCA\377JCA\377J" +\ - "CA\377JCA\377JCA\377333\377\33\40!\377\22\27\30\377\31\31\31\377\33\40" +\ - "!\377333\377AAA\3778;:\3778;:\377AAA\377333\3778;:\377333\377333\377" +\ - "!!!\377)))\377333\377!!!\377!!!\3772/*\377!!!\377!\37\34\377!!!\377\20" +\ - "\20\20\377\22\27\30\377\20\20\20\377)))\377BA;\377JCA\377LHA\377LHA\377" +\ - "JCA\377JCA\377JCA\377LHA\377RJB\377RJB\377SKI\377SKI\377YLH\377ZJC\377" +\ - "RJB\377ZJC\377ZJC\377ZJC\377YLH\377scQ\377\235\214d\377\243\224j\377" +\ - "\252\226l\377\252\226l\377\222\205c\377cRK\377cRK\377cRK\377ZRK\377s" +\ - "ZK\377lZR\377lZR\377B81\377\20\20\20\377\27\21\20\377\31\26\22\377\20" +\ - "\20\20\210\0\0\0D\5\5\4""3\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\0\0\0\21\0\0\0\"\0\0\0""3!\37\34w\31\31\31\377\27\21" +\ - "\20\377\31\26\22\377QDA\377bTP\377ZRK\377ZRK\377kZJ\377{cJ\377kZJ\377" +\ - "dYI\3771*'\377JB:\377\243\224j\377\266\245z\377\266\245z\377\252\225" +\ - "s\377\202uX\377RJB\377SKI\377RJB\377LHA\377LHA\377JCA\377JCA\377JCA\377" +\ - "BA;\377B;9\377JCA\377JCA\377BA;\377JCA\377JCA\377:72\377)))\377)))\377" +\ - "!!!\377\31\31\31\377)'#\377)))\3778;:\3778;:\377AAA\3778;:\3778;:\377" +\ - "8;:\377333\377)))\377333\377333\3772/*\377!!!\377\31\31\31\377\33\40" +\ - "!\377\31\31\31\377\31\31\31\377\31\31\31\377\33\40!\377\22\27\30\377" +\ - ":72\377RJB\377JCA\377JCA\377JCA\377JCA\377B;9\377B;9\377B;9\377QDA\377" +\ - "YLH\377cMA\377cRK\377cRK\377cRK\377dRB\377cRK\377cRK\377cRK\377cRK\377" +\ - "scQ\377\222\205c\377\243\224j\377\252\226l\377\252\226l\377\222\205c" +\ - "\377cRK\377YLH\377cRK\377ZRK\377sZK\377sZK\377lZR\377K=8\377\31\26\22" +\ - "\377\27\21\20\377\31\26\22\377\20\20\20\231\5\5\4U\5\5\4""3\0\0\0\21" +\ - "\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21" +\ - "\0\0\0""3\31\31\31\210\31\31\31\377\27\21\20\377\27\21\20\377QDA\377" +\ - "bTP\377bTP\377ZRK\377kZJ\377|iI\377kZJ\377cRK\3771*'\377JB:\377\234\213" +\ - "k\377\275\254|\377\266\245z\377\252\225s\377\202uX\377K=8\377JB:\377" +\ - "RJB\377LHA\377JCA\377JCA\377JCA\377JCA\377JCA\377JCA\377JCA\377BA;\377" +\ - "BA;\377JCA\377JCA\377B;9\377333\377:72\377\33\40!\377\31\31\31\377\33" +\ - "\40!\377\31\31\31\377!!!\3772/*\3778;:\377333\3772/*\377333\3778;:\377" +\ - "8;:\3772/*\3772/*\377!!!\377\22\27\30\377!!!\377\31\31\31\377\31\31\31" +\ - "\377\14\13\12\377\20\20\20\377\31\31\31\377)'#\377LHA\377LHA\377LHA\377" +\ - "LHA\377RJB\377RJB\377ZJC\377JB:\377931\377931\377SA8\377ZJC\377cMA\377" +\ - "cMA\377dRB\377cRK\377kTO\377sZK\377tZR\377y[S\377\204j[\377\222\205c" +\ - "\377\235\214d\377\252\226l\377\252\226l\377\235\214d\377dYI\377YLH\377" +\ - "cRK\377ZRK\377sZK\377sZK\377kTO\377cMA\377!\37\34\377\27\21\20\377\31" +\ - "\26\22\377\31\26\22\252\10\10\10U\0\0\0""3\0\0\0\21\0\0\0\21\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0\0\0\"\31\31\31" +\ - "\210\31\31\31\377\27\21\20\377\27\21\20\377QDA\377kTO\377cRK\377ZRK\377" +\ - "lZR\377\203dJ\377kZJ\377dYI\3771*'\377JB:\377\243\224j\377\275\254|\377" +\ - "\266\245z\377\255\234t\377\211wZ\3772/*\377)'#\377:72\377RJB\377YLH\377" +\ - "LHA\377JCA\377JCA\377JCA\377JCA\377B;9\377333\377BA;\377JCA\377BA;\377" +\ - "B;9\377333\377BA;\377!!!\377\31\31\31\377\22\27\30\377\22\27\30\377\31" +\ - "\31\31\377\33\40!\377333\377333\377)))\3772/*\377)))\377333\377!!!\377" +\ - "333\377\31\31\31\377!!!\377!!!\377!!!\377\22\27\30\377\20\20\20\377\20" +\ - "\20\20\377\22\27\30\377333\377SKI\377RJB\377RJB\377ZJC\377ZJC\377ZJC" +\ - "\377ZJC\377cMA\377QDA\377K=8\377K=8\377QDA\377ZJC\377cRK\377sZK\377t" +\ - "ZR\377{cR\377{cR\377\204dY\377\204dY\377\204j[\377\222\205c\377\235\214" +\ - "d\377\252\226l\377\252\226l\377\235\214d\377dYI\377YLH\377ZRK\377ZRK" +\ - "\377kTO\377sZK\377kTO\377kTO\377)\"\40\377\20\20\20\377\27\21\20\377" +\ - "\31\31\31\252\14\13\12f\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0\0\0\"\31\31\31\210!\37\34\377" +\ - "\27\21\20\377\27\21\20\377QDA\377kTO\377cRK\377cRK\377kZJ\377\203dJ\377" +\ - "dYI\377dYI\3772*\"\377[J;\377\243\224j\377\275\254|\377\266\245z\377" +\ - "\255\234t\377\211wZ\377JB:\377)\"\40\377\31\31\31\377)'#\377B81\377J" +\ - "B:\377RJB\377LHA\377JCA\377JCA\377:72\377333\377BA;\377JCA\377BA;\377" +\ - ":72\377333\377B;9\377B;9\377!!!\377\33\40!\377\22\27\30\377\22\27\30" +\ - "\377\22\27\30\377!!!\377)'#\377)'#\377)))\377)))\377)))\377)'#\377))" +\ - ")\377\31\31\31\377\31\31\31\377\31\31\31\377!\37\34\377\20\20\20\377" +\ - "\22\27\30\377\22\27\30\377\33\40!\377BA;\377YLH\377ZJC\377ZJC\377ZJC" +\ - "\377ZJC\377ZJC\377ZJC\377cMA\377cMA\377cMA\377cMA\377cRK\377lSI\377y" +\ - "[S\377{cR\377{cR\377\204dY\377\204dY\377\204j[\377\204dY\377\204j[\377" +\ - "\215xc\377\235\214d\377\252\226l\377\252\226l\377\243\224j\377kZJ\377" +\ - "YLH\377ZRK\377ZRK\377kTO\377tZR\377kTO\377lZR\377;1)\377\20\20\20\377" +\ - "\31\26\22\377\31\26\22\273\20\20\20f\0\0\0D\0\0\0\"\0\0\0\21\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0\0\0\"\20\20\20" +\ - "w\31\31\31\377\27\21\20\377\27\21\20\377JB:\377kTO\377cRK\377cRK\377" +\ - "sZK\377\203dJ\377dYI\377dYI\3771*'\377[J;\377\243\224j\377\275\254|\377" +\ - "\266\245z\377\255\234t\377\211wZ\377YSC\377cMA\3772/*\377\31\31\31\377" +\ - "\31\31\31\377!\37\34\3771*'\3772/*\3771*'\3772/*\3772/*\377333\377B;" +\ - "9\377JCA\377JCA\377:72\377333\377:72\377JCA\377)))\377)))\377\31\31\31" +\ - "\377)'#\377\33\40!\377\22\27\30\377\22\27\30\377!!!\377!!!\377)))\377" +\ - "!!!\377!\37\34\377!\37\34\377\22\27\30\377\31\31\31\377\20\20\20\377" +\ - "\31\31\31\377\22\27\30\377\22\27\30\377\22\27\30\377931\377cRK\377ZJ" +\ - "C\377ZJC\377ZJC\377cMA\377cMA\377ZJC\377cMA\377dRB\377cRK\377lSI\377" +\ - "kTO\377tZR\377{cR\377{cR\377zbZ\377zbZ\377zbZ\377zbZ\377\204dY\377\204" +\ - "j[\377zbZ\377\215xc\377\235\214d\377\252\226l\377\252\226l\377\252\226" +\ - "l\377lZR\377YLH\377ZRK\377ZRK\377kTO\377tZR\377kTO\377lZR\377K=8\377" +\ - "\20\20\20\377\31\26\22\377\20\20\20\314\20\20\20f\0\0\0D\0\0\0\"\0\0" +\ - "\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0" +\ - "\0\0\"\14\13\12w\31\31\31\377\31\26\22\377\20\20\20\377JB:\377kTO\377" +\ - "cRK\377cRK\377sZK\377\203dJ\377dYI\377dYI\3772*\"\377YSC\377\263\237" +\ - "{\377\275\254|\377\266\245z\377\255\234t\377\215xc\377PI9\377{cR\377" +\ - "sZK\377[J;\377;1)\377)\"\40\377\31\31\31\377\31\31\31\377\31\31\31\377" +\ - "\31\31\31\377\31\31\31\377\31\31\31\377\33\40!\377)))\377B;9\3778;:\377" +\ - "333\377:72\377JCA\377:72\377:72\377)))\377333\377)))\377\33\40!\377\22" +\ - "\27\30\377\22\27\30\377\22\27\30\377\31\31\31\377\31\31\31\377\31\31" +\ - "\31\377\22\27\30\377\20\20\20\377\22\27\30\377\31\26\22\377\22\27\30" +\ - "\377)))\377\33\40!\377\31\31\31\3772/*\377JB:\377QDA\377ZJC\377cRK\377" +\ - "cRK\377lSI\377lSI\377kTO\377tZR\377tZR\377y[S\377{cR\377\204dY\377\204" +\ - "dY\377{cR\377{cR\377zbZ\377zbZ\377zbZ\377\204dY\377\204j[\377zbZ\377" +\ - "\211wZ\377\235\214d\377\252\226l\377\252\225s\377\252\225s\377scQ\377" +\ - "YLH\377ZRK\377ZRK\377bTP\377y[S\377kTO\377lZR\377QDA\377\31\26\22\377" +\ - "\31\26\22\377\31\31\31\335\20\20\20w\0\0\0D\0\0\0\"\0\0\0\21\0\0\0\21" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0\0\0""3\10\10\10f\31" +\ - "\26\22\377\31\26\22\377\20\20\20\377JB:\377kTO\377cRK\377cRK\377sZK\377" +\ - "\214eJ\377dYI\377dYI\377)'#\377dYI\377\275\254|\377\275\254|\377\275" +\ - "\254|\377\255\234t\377\222\205c\377PI9\377{cR\377\203jR\377\203dR\377" +\ - "y[S\377dRB\377SA8\377K=8\377K<1\377B81\3771*'\377)\"\40\377\31\31\31" +\ - "\377\22\27\30\377!!!\377931\377:72\377B;9\377JCA\377JCA\377JCA\377BA" +\ - ";\377:72\377333\377333\377)'#\377\31\31\31\377\31\31\31\377\22\27\30" +\ - "\377\31\31\31\377\22\27\30\377\20\20\20\377\20\20\20\377\33\40!\377!" +\ - "!!\377\31\31\31\377!!!\377\33\40!\377!\37\34\377)'#\377B81\377ZJC\377" +\ - "cRK\377kTO\377kTO\377tZR\377tZR\377{cR\377{cR\377{cR\377{cR\377{cR\377" +\ - "\204dY\377\204dY\377{cR\377{cR\377{cR\377zbZ\377{cR\377zbZ\377\204dY" +\ - "\377zbZ\377\202uX\377\235\214d\377\252\225s\377\255\234t\377\252\225" +\ - "s\377meP\377ZRK\377ZRK\377ZRK\377bTP\377y[S\377kTO\377lZR\377ZJC\377" +\ - "\31\26\22\377\31\26\22\377\31\31\31\335\27\21\20w\0\0\0D\0\0\0\"\0\0" +\ - "\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0\0\0" +\ - "3\10\10\10f\27\21\20\356\31\26\22\377\20\20\20\377JB:\377bTP\377cRK\377" +\ - "cRK\377sZK\377\214kQ\377dYI\377ZRK\377)'#\377kZJ\377\275\254|\377\274" +\ - "\256\204\377\275\254|\377\255\234t\377\222\205c\377[J;\377{cR\377\203" +\ - "dR\377\203dR\377{cR\377{cR\377{cR\377sZK\377lSI\377cRK\377ZJC\377RJB" +\ - "\377K=8\377)'#\377\31\31\31\377!!!\377333\377B;9\377JCA\377JCA\377JC" +\ - "A\377JCA\377B;9\377931\377:72\377:72\377)))\377!!!\377!!!\377!!!\377" +\ - "!!!\377\33\40!\377\31\31\31\377)'#\377\33\40!\377\33\40!\377\31\31\31" +\ - "\377!!!\377\33\40!\377:72\377YLH\377lSI\377kTO\377kTO\377tZR\377y[S\377" +\ - "{cR\377{cR\377{cR\377{cR\377{cR\377\203dR\377\204dY\377\204dY\377zbZ" +\ - "\377y[S\377{cR\377{cR\377{cR\377zbZ\377zbZ\377tcY\377{l[\377\234\213" +\ - "k\377\252\225s\377\252\225s\377\255\234t\377meP\377YLH\377ZRK\377ZRK" +\ - "\377bTP\377y[S\377kTO\377kTO\377cMA\377!\31\30\377\27\21\20\377\31\31" +\ - "\31\356\31\31\31\210\0\0\0D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\0\0\0\21\0\0\0\21\0\0\0""3\10\10\10U\27\21\20\335\31\26\22" +\ - "\377\27\21\20\377JB:\377bTP\377cRK\377cRK\377y[S\377\214kQ\377cRK\377" +\ - "YLH\377)'#\377|kP\377\275\254|\377\275\254|\377\275\254|\377\255\234" +\ - "t\377\222\205c\377dRB\377tZR\377{cR\377{cR\377{cR\377{cR\377{cR\377{" +\ - "cR\377sZK\377lSI\377cMA\377ZJC\377ZJC\377JCA\377)'#\377\33\40!\377!!" +\ - "!\3772/*\377BA;\377JCA\377JCA\377JCA\377JCA\377B;9\377:72\377B;9\377" +\ - "B;9\377333\377333\377)))\377)'#\377)'#\377\31\31\31\377\31\31\31\377" +\ - "\31\31\31\377\31\31\31\377\33\40!\377)'#\3772/*\377ZJC\377cRK\377kTO" +\ - "\377kTO\377sZK\377tZR\377y[S\377{cR\377{cR\377{cR\377{cR\377{cR\377\204" +\ - "dY\377\204dY\377\204dY\377zbZ\377tZR\377tZR\377scQ\377scQ\377scQ\377" +\ - "tcY\377tcY\377tcY\377\222\205c\377\242\224s\377\252\225s\377\252\225" +\ - "s\377scQ\377SKI\377ZRK\377ZRK\377ZRK\377tZR\377sZK\377kTO\377cRK\377" +\ - "!\37\34\377\20\20\20\377\31\31\31\377\31\31\31\231\0\0\0D\0\0\0\"\0\0" +\ - "\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0\0\0" +\ - "3\10\10\10U\20\20\20\335\31\26\22\377\27\21\20\377B;9\377bTP\377cRK\377" +\ - "cRK\377{cJ\377\214eJ\377ZRK\377YLH\377)'#\377\202uX\377\274\256\204\377" +\ - "\274\256\204\377\275\254|\377\255\234t\377\222\205c\377dYI\377bTP\377" +\ - "kZJ\377tZR\377sZK\377sZK\377sZK\377tZR\377sZK\377lSI\377cRK\377ZJC\377" +\ - "RJB\377RJB\377:72\377)'#\377!!!\377)'#\377:72\377JCA\377JCA\377JCA\377" +\ - "JCA\377JCA\377BA;\377B;9\377B;9\377B;9\377B;9\377:72\377)'#\377\33\40" +\ - "!\377\31\31\31\377\31\31\31\377\31\31\31\377\33\40!\377\33\40!\377))" +\ - ")\377JB:\377ZRK\377cRK\377kTO\377kTO\377sZK\377tZR\377y[S\377{cR\377" +\ - "{cR\377{cR\377{cR\377zbZ\377zbZ\377zbZ\377{cR\377{cR\377scQ\377lZR\377" +\ - "tZR\377tZR\377lZR\377tcY\377tcY\377tcY\377\222\205c\377\242\224s\377" +\ - "\252\225s\377\252\225s\377{l[\377RJB\377YLH\377ZRK\377ZRK\377sZK\377" +\ - "tZR\377kTO\377cRK\377)\"\40\377\20\20\20\377\31\31\31\377\31\31\31\252" +\ - "\0\0\0D\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0" +\ - "\21\0\0\0\21\0\0\0""3\10\10\10U\20\20\20\314\31\26\22\377\31\26\22\377" +\ - "B81\377cRK\377cRK\377cRK\377}ZG\377\214eJ\377ZRK\377SKI\377)'#\377\202" +\ - "uX\377\304\265\212\377\274\256\204\377\266\245z\377\255\234t\377\222" +\ - "\205c\377kZJ\377bTP\377bTP\377bTP\377cRK\377cRK\377cRK\377cRK\377cRK" +\ - "\377YLH\377YLH\377YLH\377RJB\377RJB\377931\377)'#\377)))\377)))\3772" +\ - "/*\377:72\377BA;\377JCA\377JCA\377JCA\377JCA\377BA;\377B;9\377B;9\377" +\ - "B;9\377931\377!!!\377\31\31\31\377\31\31\31\377!!!\377!!!\377)))\377" +\ - ")))\377JB:\377YLH\377ZRK\377cRK\377cRK\377kTO\377kTO\377tZR\377y[S\377" +\ - "{cR\377{cR\377{cR\377{cR\377{cR\377zbZ\377zbZ\377{cR\377zbZ\377t^Y\377" +\ - "lZR\377lZR\377lZR\377cRK\377kTO\377lZR\377lZR\377\211wZ\377\243\224j" +\ - "\377\252\225s\377\252\225s\377\215xc\377RJB\377YLH\377ZRK\377ZRK\377" +\ - "sZK\377tZR\377kTO\377cRK\377)'#\377\20\20\20\377\31\31\31\377\31\31\31" +\ - "\273\0\0\0U\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0" +\ - "\0\0\21\0\0\0\21\0\0\0""3\10\10\10U\20\20\20\273\31\26\22\377\31\26\22" +\ - "\377:72\377cRK\377cRK\377cRK\377{cJ\377\214eJ\377ZRK\377SKI\377)\"\40" +\ - "\377{cR\377\304\265\212\377\274\256\204\377\274\256\204\377\266\245z" +\ - "\377\222\205c\377scQ\377t^Y\377lZR\377bTP\377bTP\377ZRK\377YLH\377ZR" +\ - "K\377YLH\377RJB\377RJB\377RJB\377SKI\377JCA\377333\377)'#\377)'#\377" +\ - ")))\377333\377931\377:72\377B;9\377BA;\377JCA\377JCA\377JCA\377JCA\377" +\ - "JCA\377B;9\377)))\377\33\40!\377\33\40!\377\33\40!\377:72\377931\377" +\ - ":72\377JB:\377QDA\377RJB\377YLH\377cRK\377cRK\377kTO\377kTO\377kTO\377" +\ - "kTO\377kZJ\377kZJ\377lZR\377lZR\377scQ\377{cR\377{cR\377scQ\377scQ\377" +\ - "meP\377g`K\377g`K\377g`K\377dYI\377dYI\377scQ\377{l[\377\215xc\377\234" +\ - "\213k\377\242\224s\377\252\225s\377\215xc\377RJB\377YLH\377ZRK\377ZR" +\ - "K\377sZK\377tZR\377kTO\377YLH\377)\"\40\377\20\20\20\377\31\26\22\377" +\ - "!\37\34\335\5\5\4f\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\0\0\0\21\0\0\0\"\0\0\0""3\10\10\10U\14\13\12\252\31\26\22\377\31\26" +\ - "\22\377:72\377cRK\377kTO\377cRK\377{cJ\377\214eJ\377ZRK\377SKI\377)\"" +\ - "\40\377scQ\377\304\265\212\377\304\265\212\377\274\256\204\377\253\236" +\ - "}\377\222\205c\377YSC\377K=8\377B81\377B81\377;1)\377;1)\377931\377B" +\ - "81\377B81\377B81\377B;9\377B;9\377BA;\377B81\377931\3772/*\377)))\377" +\ - "2/*\377333\377931\377:72\377B;9\377B;9\377B;9\377BA;\377JCA\377JCA\377" +\ - "B;9\377333\377!!!\377\31\31\31\377\31\31\31\3772/*\377JCA\377BA;\377" +\ - "BA;\377JCA\377BA;\377B;9\377B;9\377K=8\377JB:\377PI9\377RJB\377dYI\377" +\ - "meP\377{l[\377\202uX\377\205xd\377\215xc\377\215xc\377\215xc\377\205" +\ - "xd\377\205xd\377\202uX\377\211wZ\377\215xc\377\222\205c\377\222\205c" +\ - "\377\222\205c\377\234\213k\377\234\213k\377\242\224s\377\252\225s\377" +\ - "\255\234t\377\252\225s\377\252\225s\377\211wZ\377JB:\377YLH\377ZRK\377" +\ - "ZRK\377sZK\377y[S\377kTO\377YLH\377!\37\34\377\27\21\20\377\31\26\22" +\ - "\377\31\31\31\335\5\5\4f\0\0\0""3\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\0\0\0\21\0\0\0\"\0\0\0""3\10\10\10U\14\13\12\231\31\26\22\377\31\26" +\ - "\22\377931\377ZRK\377kTO\377kTO\377{cJ\377\203jR\377ZRK\377RJB\377)'" +\ - "#\377scQ\377\304\265\212\377\304\265\212\377\275\261\213\377\253\236" +\ - "}\377\234\213k\377g`K\377K<1\377PI9\377YSC\377YSC\377YSC\377YSC\377Y" +\ - "SC\377YSC\377dYI\377g`K\377meP\377meP\377meP\377meP\377meP\377meP\377" +\ - "g`K\377dYI\377YSC\377YSC\377YSC\377YSC\377YSC\377YSC\377YSC\377YSC\377" +\ - "RJB\377JB:\377BA;\377BA;\377JB:\377YSC\377ZRK\377ZRK\377ZRK\377YSC\377" +\ - "YSC\377YSC\377dYI\377meP\377{l[\377\205xd\377\224\204l\377\234\213k\377" +\ - "\242\224s\377\242\224s\377\242\224s\377\242\224s\377\242\224s\377\242" +\ - "\224s\377\234\213k\377\234\213k\377\234\213k\377\234\213k\377\243\224" +\ - "j\377\242\224s\377\252\225s\377\252\225s\377\252\225s\377\255\234t\377" +\ - "\255\234t\377\263\237{\377\263\237{\377\255\234t\377\252\225s\377\252" +\ - "\225s\377\224\204l\377LHA\377SKI\377ZRK\377ZRK\377sZK\377\203dR\377k" +\ - "TO\377ZJC\377!\37\34\377\27\21\20\377\31\26\22\377\31\31\31\335\10\10" +\ - "\10w\0\0\0D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"" +\ - "\0\0\0""3\10\10\10U\14\13\12\210\31\26\22\377\31\26\22\377931\377ZRK" +\ - "\377kTO\377kTO\377sZK\377\203jR\377ZRK\377RJB\3771*'\377\204j[\377\304" +\ - "\265\212\377\310\267\222\377\304\265\212\377\274\256\204\377\265\246" +\ - "\205\377\253\236}\377\255\234t\377\253\236}\377\255\234t\377\255\234" +\ - "t\377\263\237{\377\253\236}\377\255\234t\377\253\236}\377\253\236}\377" +\ - "\256\242\201\377\256\242\201\377\256\242\201\377\265\246\205\377\256" +\ - "\242\201\377\253\236}\377\245\227z\377\242\224s\377\242\224s\377\234" +\ - "\213k\377\234\213k\377\224\204l\377\222\205c\377\205xd\377\205xd\377" +\ - "\205xd\377\205xd\377\205xd\377\205xd\377\205xd\377\205xd\377\222\205" +\ - "c\377\224\204l\377\234\213k\377\242\224s\377\245\227z\377\253\236}\377" +\ - "\256\242\201\377\265\246\205\377\274\256\204\377\274\256\204\377\265" +\ - "\246\205\377\265\246\205\377\265\246\205\377\265\246\205\377\265\246" +\ - "\205\377\274\256\204\377\265\246\205\377\265\246\205\377\265\246\205" +\ - "\377\265\246\205\377\266\245z\377\263\237{\377\263\237{\377\255\234t" +\ - "\377\255\234t\377\255\234t\377\255\234t\377\255\234t\377\255\234t\377" +\ - "\255\234t\377\255\234t\377\263\237{\377\263\237{\377\255\234t\377\252" +\ - "\225s\377\252\225s\377\255\211s\377cRK\377SKI\377ZRK\377ZRK\377kZJ\377" +\ - "\214kQ\377tZR\377ZJC\377!\31\30\377\27\21\20\377\31\26\22\377\31\31\31" +\ - "\335\14\13\12w\0\0\0U\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0" +\ - "\21\0\0\0\"\0\0\0""3\10\10\10U\10\10\10\210\31\26\22\377\31\26\22\377" +\ - "2/*\377ZRK\377kTO\377kTO\377sZK\377\214kQ\377ZRK\377RJB\3771*'\377\204" +\ - "j[\377\304\265\212\377\315\277\225\377\304\265\212\377\304\265\212\377" +\ - "\304\265\212\377\274\256\204\377\275\261\213\377\274\256\204\377\274" +\ - "\256\204\377\274\256\204\377\274\256\204\377\274\256\204\377\304\265" +\ - "\212\377\304\265\212\377\304\265\212\377\304\265\212\377\304\265\212" +\ - "\377\304\265\212\377\304\265\212\377\304\265\212\377\304\265\212\377" +\ - "\304\265\212\377\275\261\213\377\275\261\213\377\275\261\213\377\275" +\ - "\261\213\377\275\261\213\377\275\261\213\377\275\261\213\377\274\256" +\ - "\204\377\265\246\205\377\265\246\205\377\256\242\201\377\256\242\201" +\ - "\377\253\236}\377\253\236}\377\256\242\201\377\265\246\205\377\274\256" +\ - "\204\377\275\261\213\377\275\261\213\377\275\261\213\377\275\261\213" +\ - "\377\275\261\213\377\275\261\213\377\304\265\212\377\310\267\222\377" +\ - "\310\267\222\377\310\267\222\377\310\267\222\377\310\267\222\377\304" +\ - "\265\212\377\304\265\212\377\304\265\212\377\274\256\204\377\274\256" +\ - "\204\377\265\246\205\377\266\245z\377\263\237{\377\263\237{\377\255\234" +\ - "t\377\255\234t\377\255\234t\377\255\234t\377\255\234t\377\255\234t\377" +\ - "\255\234t\377\263\237{\377\263\237{\377\263\237{\377\252\225s\377\263" +\ - "\237{\377\263\237{\377ZRK\377SKI\377ZRK\377ZRK\377kTO\377\225lP\377{" +\ - "cR\377ZJC\377!\31\30\377\27\21\20\377\31\26\22\377\31\26\22\356\14\13" +\ - "\12w\0\0\0f\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"" +\ - "\0\0\0""3\10\10\10U\10\10\10w\27\21\20\356\20\20\20\377)'#\377ZRK\377" +\ - "kTO\377kTO\377sZK\377\224rZ\377bTP\377RJB\3772/*\377\204j[\377\304\265" +\ - "\212\377\315\277\225\377\310\267\222\377\304\265\212\377\304\265\212" +\ - "\377\304\265\212\377\304\265\212\377\304\265\212\377\304\265\212\377" +\ - "\274\256\204\377\274\256\204\377\304\265\212\377\304\265\212\377\304" +\ - "\265\212\377\304\265\212\377\304\265\212\377\310\267\222\377\310\267" +\ - "\222\377\310\267\222\377\310\267\222\377\310\267\222\377\310\267\222" +\ - "\377\310\267\222\377\310\267\222\377\310\267\222\377\315\277\225\377" +\ - "\315\277\225\377\315\277\225\377\315\277\225\377\315\277\225\377\315" +\ - "\277\225\377\315\277\225\377\315\277\225\377\315\277\225\377\310\267" +\ - "\222\377\310\267\222\377\310\267\222\377\310\267\222\377\310\267\222" +\ - "\377\310\267\222\377\310\267\222\377\310\267\222\377\310\267\222\377" +\ - "\310\267\222\377\315\277\225\377\315\277\225\377\315\277\225\377\315" +\ - "\277\225\377\315\277\225\377\315\277\225\377\310\267\222\377\310\267" +\ - "\222\377\304\265\212\377\304\265\212\377\304\265\212\377\275\261\213" +\ - "\377\274\256\204\377\274\256\204\377\275\254|\377\266\245z\377\255\234" +\ - "t\377\255\234t\377\242\224s\377\234\213k\377\224\204l\377\224\204l\377" +\ - "\224\204l\377\224\204l\377\222yk\377\222yk\377\222yk\377\224\204l\377" +\ - "\222yk\377ZRK\377SKI\377ZRK\377ZRK\377bTP\377\214kQ\377\204dY\377cMA" +\ - "\377!\37\34\377\31\26\22\377\31\26\22\377\31\26\22\356\20\20\20\210\0" +\ - "\0\0f\0\0\0D\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\0\0\0""3" +\ - "\10\10\10U\10\10\10w\27\21\20\356\27\21\20\377)'#\377YLH\377kTO\377k" +\ - "TO\377sZK\377\224{U\377bTP\377RJB\3771*'\377\204j[\377\304\265\212\377" +\ - "\315\277\225\377\310\267\222\377\315\277\225\377\315\277\225\377\315" +\ - "\277\225\377\315\277\225\377\315\277\225\377\315\277\225\377\310\267" +\ - "\222\377\304\265\212\377\310\267\222\377\310\267\222\377\315\277\225" +\ - "\377\315\277\225\377\315\277\225\377\315\277\225\377\315\277\225\377" +\ - "\315\277\225\377\324\306\232\377\324\306\232\377\324\306\232\377\324" +\ - "\306\232\377\315\277\225\377\315\277\225\377\315\277\225\377\315\277" +\ - "\225\377\324\306\232\377\324\306\232\377\324\306\232\377\324\306\232" +\ - "\377\324\306\232\377\330\312\237\377\330\312\237\377\330\312\237\377" +\ - "\330\312\237\377\330\312\237\377\330\312\237\377\330\312\237\377\330" +\ - "\312\237\377\330\312\237\377\330\312\237\377\324\306\232\377\324\306" +\ - "\232\377\324\306\232\377\315\277\225\377\315\277\225\377\315\277\225" +\ - "\377\315\277\225\377\315\277\225\377\310\267\222\377\304\265\212\377" +\ - "\265\246\205\377\265\246\205\377\253\236}\377\234\213k\377\224\204l\377" +\ - "\224\204l\377\205xd\377\202uX\377{l[\377tcY\377c[T\377c[T\377\\TR\377" +\ - "bTP\377bTP\377dYI\377kZJ\377kZJ\377lZR\377scQ\377tcY\377tcY\377kZJ\377" +\ - "ZRK\377\\TR\377bTP\377\214kQ\377\215jZ\377cRK\377!\37\34\377\31\26\22" +\ - "\377\31\26\22\377\31\26\22\356\20\20\20\210\5\5\4f\0\0\0D\0\0\0\"\0\0" +\ - "\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\0\0\0\21\0\0\0\"\0\0\0\"\5\5\4U\10\10\10f\20\20\20\335" +\ - "\27\21\20\377)'#\377YLH\377kTO\377kTO\377sZK\377\233~Z\377c[T\377SKI" +\ - "\3771*'\377\202uX\377\310\267\222\377\310\267\222\377\304\265\212\377" +\ - "\310\267\222\377\315\277\225\377\315\277\225\377\315\277\225\377\315" +\ - "\277\225\377\315\277\225\377\315\277\225\377\310\267\222\377\304\265" +\ - "\212\377\274\256\204\377\304\265\212\377\304\265\212\377\304\265\212" +\ - "\377\304\265\212\377\304\265\212\377\310\267\222\377\304\265\212\377" +\ - "\275\261\213\377\304\265\212\377\310\267\222\377\315\277\225\377\310" +\ - "\267\222\377\304\265\212\377\275\261\213\377\265\246\205\377\256\242" +\ - "\201\377\253\236}\377\245\227z\377\245\227z\377\253\236}\377\245\227" +\ - "z\377\245\227z\377\245\227z\377\245\227z\377\242\224s\377\240\206v\377" +\ - "\224\204l\377\224\204l\377\205xd\377\205xd\377\205xd\377{l[\377{l[\377" +\ - "{l[\377{l[\377{l[\377\204j[\377{l[\377tcY\377{l[\377tcY\377meP\377lZ" +\ - "R\377bTP\377bTP\377dYI\377kZJ\377dYI\377kZJ\377kZJ\377lZR\377scQ\377" +\ - "scQ\377scQ\377{cR\377{cR\377\203jR\377\203jR\377\203jR\377\203jR\377" +\ - "\203jR\377\203jR\377kZJ\377\\TR\377bTP\377\203dR\377\222k[\377kTO\377" +\ - "1*'\377\27\21\20\377\27\21\20\377\31\26\22\377\20\20\20\231\5\5\4w\0" +\ - "\0\0U\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\0\0\0\"\5\5\4U\10\10" +\ - "\10f\20\20\20\335\27\21\20\377)\"\40\377ZJC\377kTO\377kTO\377lSI\377" +\ - "\233~Z\377lZR\377YLH\377:72\377ZJC\377\222yk\377\224\204l\377\224\204" +\ - "l\377\224\204l\377\222yk\377\215xc\377\215xc\377\215xc\377\215xc\377" +\ - "\211wZ\377{l[\377scQ\377scQ\377scQ\377{l[\377\202uX\377\202uX\377\202" +\ - "uX\377\205xd\377{l[\377scQ\377tcY\377{l[\377\205xd\377\205xd\377\211" +\ - "wZ\377\211wZ\377\204j[\377\203jR\377{cR\377scQ\377tcY\377tcY\377tcY\377" +\ - "tcY\377tcY\377tcY\377tcY\377scQ\377scQ\377scQ\377scQ\377{cR\377{cR\377" +\ - "{cR\377{l[\377{l[\377|kP\377|kP\377|kP\377|kP\377|kP\377|kP\377|kP\377" +\ - "|kP\377|kP\377|kP\377|kP\377|kP\377{cR\377{cR\377{cR\377\203dR\377\203" +\ - "dR\377\203dR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR\377{cR" +\ - "\377y[S\377{cR\377\203jR\377lZR\377bTP\377\203dR\377\234s[\377y[S\377" +\ - "B81\377\27\21\20\377\31\26\22\377\31\26\22\377\20\20\20\252\10\10\10" +\ - "w\0\0\0U\0\0\0""3\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\0\0\0\"\5\5\4U\10" +\ - "\10\10f\20\20\20\314\31\26\22\377)\"\40\377ZJC\377kTO\377kTO\377kTO\377" +\ - "\245|Z\377{cR\377SKI\377RJB\377SA8\377kZJ\377kZJ\377lZR\377kZJ\377kZ" +\ - "J\377kZJ\377kZJ\377kZJ\377kZJ\377lSI\377lSI\377sZK\377sZK\377sZK\377" +\ - "scQ\377scQ\377scQ\377scQ\377scQ\377{cR\377\204j[\377\211wZ\377\215xc" +\ - "\377\215xc\377\225vb\377\215xc\377\215xc\377\215xc\377\211wZ\377\211" +\ - "wZ\377\202uX\377\202uX\377\202uX\377\202uX\377\202uX\377\202uX\377\202" +\ - "uX\377\202uX\377\202uX\377\202uX\377\204j[\377\204j[\377\204j[\377\204" +\ - "j[\377\204j[\377\204j[\377\203jR\377\203jR\377\203jR\377\203jR\377\203" +\ - "jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203" +\ - "dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377{c" +\ - "R\377y[S\377y[S\377y[S\377y[S\377y[S\377y[S\377\203dR\377\203dR\377{" +\ - "cR\377tZR\377tZR\377lZR\377bTP\377\203dR\377\257\204\\\377\204dY\377" +\ - "SA8\377\27\21\20\377\27\21\20\377\31\26\22\377\20\20\20\273\10\10\10" +\ - "w\0\0\0f\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\0\0\0\"\5\5\4D\10" +\ - "\10\10f\20\20\20\314\31\26\22\377)\"\40\377QDA\377kTO\377kTO\377cRK\377" +\ - "\246\204b\377\211wZ\377YLH\377kTO\377scQ\377scQ\377scQ\377{cR\377{cR" +\ - "\377{cR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377" +\ - "\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377" +\ - "\203jR\377\203jR\377\204j[\377\204j[\377\215jZ\377\215jZ\377\215jZ\377" +\ - "\215jZ\377\215jZ\377\215jZ\377\215jZ\377\215jZ\377\215jZ\377\204j[\377" +\ - "\204j[\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377" +\ - "\203jR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377" +\ - "\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377" +\ - "\203dR\377\203dR\377\203dR\377\203dR\377{cR\377{cR\377\203dR\377\203" +\ - "dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203" +\ - "dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203" +\ - "dR\377{cR\377tZR\377kTO\377{cR\377\257\204\\\377\215jZ\377ZJC\377\31" +\ - "\26\22\377\31\26\22\377\31\26\22\377\27\21\20\335\14\13\12w\0\0\0f\0" +\ - "\0\0D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\0\0\0\21\0\0\0\"\0\0\0\"\10\10\10D\10\10\10f\20\20\20" +\ - "\314\31\26\22\377!\37\34\377QDA\377kTO\377kTO\377kTO\377\246\204b\377" +\ - "\233~Z\377cRK\377sZK\377tZR\377sZK\377scQ\377{cR\377{cR\377{cR\377\203" +\ - "dR\377\203dR\377\203dR\377\214eJ\377\214eJ\377\214eJ\377\214eJ\377\214" +\ - "eJ\377\203jR\377\203jR\377\203dR\377\203dR\377\203dR\377\203dR\377\203" +\ - "dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203" +\ - "dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203" +\ - "dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203" +\ - "dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203" +\ - "dR\377\203dR\377\214kQ\377\214kQ\377\214kQ\377\214kQ\377\214kQ\377\214" +\ - "kQ\377\214kQ\377\203dR\377\203dR\377\203dR\377\214kQ\377\214kQ\377\214" +\ - "kQ\377\214kQ\377\214kQ\377\214kQ\377\214kQ\377\214kQ\377\214kQ\377\214" +\ - "kQ\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203" +\ - "dR\377\203dR\377sZK\377\203dR\377\263\214c\377\215jZ\377cMA\377!\31\30" +\ - "\377\31\26\22\377\31\26\22\377\31\26\22\356\14\13\12\210\5\5\4f\0\0\0" +\ - "D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\0\0\0\21\0\0\0\"\0\0\0\"\10\10\10D\14\13\12f\20\20\20" +\ - "\273\31\26\22\377!\37\34\377SA8\377kTO\377kTO\377kTO\377\245|Z\377\235" +\ - "|c\377sZK\377\203lL\377y[S\377tZR\377{cR\377{cR\377\203dR\377\203dR\377" +\ - "\203dR\377\214kQ\377\214kQ\377\225lP\377\225lP\377\225lP\377\225lP\377" +\ - "\214kQ\377\214kQ\377\214kQ\377\214kQ\377\214kQ\377\214kQ\377\214kQ\377" +\ - "\214kQ\377\214kQ\377\214kQ\377\214kQ\377\203jR\377\203jR\377\203jR\377" +\ - "\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377" +\ - "\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203jR\377\203dR\377" +\ - "\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377\203dR\377" +\ - "\203dR\377\214eJ\377\214kQ\377\224rZ\377\224rZ\377\234s[\377\234s[\377" +\ - "\234s[\377\234s[\377\225lP\377\214kQ\377\214kQ\377\214kQ\377\225lP\377" +\ - "\224sQ\377\225lP\377\225lP\377\225lP\377\225lP\377\225lP\377\232rR\377" +\ - "\232rR\377\232rR\377\232rR\377\232rR\377\232rR\377\232rR\377\224rZ\377" +\ - "\234s[\377\245|Z\377\245|Z\377\233~Z\377\246\204b\377\203dR\377kTO\377" +\ - "1*'\377\27\21\20\377\31\26\22\377\31\26\22\356\14\13\12\210\5\5\4w\0" +\ - "\0\0D\0\0\0""3\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\0\0\0\21\0\0\0\"\0\0\0\"\10\10\10D\14\13\12f\14" +\ - "\13\12\273\31\26\22\377!\31\30\377SA8\377kTO\377kTO\377kTO\377\233~Z" +\ - "\377\255\204a\377\224sQ\377\203jR\377y[S\377{cR\377\203dR\377\203dR\377" +\ - "\203dR\377\214kQ\377\214kQ\377\225lP\377\232rR\377\244uY\377\245|Z\377" +\ - "\245|Z\377\245|Z\377\245|Z\377\244uY\377\244uY\377\244uY\377\244uY\377" +\ - "\232rR\377\232rR\377\225lP\377\225lP\377\225lP\377\214kQ\377\214kQ\377" +\ - "\214kQ\377\214kQ\377\214kQ\377\214kQ\377\214kQ\377\214kQ\377\214kQ\377" +\ - "\214kQ\377\214kQ\377\214kQ\377\214kQ\377\203jR\377\214kQ\377\214kQ\377" +\ - "\214kQ\377\225lP\377\222k[\377\224rZ\377\234s[\377\234s[\377\234s[\377" +\ - "\244uY\377\246|c\377\246|c\377\246|c\377\246|c\377\246|c\377\246|c\377" +\ - "\246|c\377\246|c\377\246|c\377\245|Z\377\245|Z\377\233~Z\377\232rR\377" +\ - "\246\203Y\377\245|Z\377\225lP\377\214eJ\377\214eJ\377\203dJ\377\203d" +\ - "J\377sUB\377sUB\377sZK\377sZK\377kSD\377kSD\377lSI\377sZK\377sZK\377" +\ - "sZK\377sZK\377{cJ\377{cJ\377kZJ\377lSI\377tZR\377K<1\377\27\21\20\377" +\ - "\31\26\22\377!\31\30\377\20\20\20\231\5\5\4w\0\0\0U\0\0\0""3\0\0\0\"" +\ - "\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0" +\ - "\0\0\21\0\0\0\"\0\0\0\"\10\10\10D\14\13\12f\14\13\12\273\31\26\22\377" +\ - "!\31\30\377K=8\377kTO\377kTO\377kTO\377\233~Z\377\263\214c\377\245|Z" +\ - "\377\203jR\377\214kQ\377\225lP\377\225lP\377\225lP\377\225lP\377\232" +\ - "rR\377\232rR\377\244uY\377\245|Z\377\246\203Y\377\257\204\\\377\257\204" +\ - "\\\377\255\204a\377\254|`\377\246\203Y\377\257\204\\\377\245|Z\377\245" +\ - "|Z\377\245|Z\377\244uY\377\244uY\377\245|Z\377\245|Z\377\245|Z\377\233" +\ - "~Z\377\235zR\377\235zR\377\235zR\377\224sQ\377\224sQ\377\224sQ\377\224" +\ - "sQ\377\224sQ\377\214qR\377\214kQ\377\214kQ\377\203dR\377\214kQ\377\225" +\ - "lP\377\232rR\377\245|Z\377\254|`\377\255\204a\377\263\214c\377\263\214" +\ - "c\377\270\206i\377\275\214j\377\276\231l\377\276\231l\377\276\231l\377" +\ - "\276\231l\377\275\214j\377\270\206i\377\255\204a\377\255\204a\377\255" +\ - "\204a\377\255\204a\377\246\204b\377\245|Z\377\203jR\377{cJ\377kSD\377" +\ - "SA8\377B81\377JB:\377B;9\377BA;\377:72\3772/*\377B;9\377JB:\377B;9\377" +\ - "B81\377JB:\377QDA\377B;9\377B;9\377JB:\377JB:\377QDA\377ZJC\377lSI\377" +\ - "sZK\377SA8\377\31\26\22\377\31\26\22\377!\31\30\377\20\20\20\273\5\5" +\ - "\4w\0\0\0f\0\0\0""3\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\0\0\0\21\0\0\0\"\0\0\0\"\5\5\4D\14\13\12f\14" +\ - "\13\12\252\31\26\22\377!\31\30\377K=8\377tZR\377kTO\377kTO\377\214qR" +\ - "\377\263\214c\377\246\203Y\377\235zR\377\235zR\377\235zR\377\235zR\377" +\ - "\245|Z\377\246\203Y\377\257\204\\\377\263\214c\377\257\204\\\377\263" +\ - "\214c\377\274\225c\377\274\225c\377\274\225c\377\274\225c\377\274\225" +\ - "c\377\274\225c\377\274\225c\377\274\225c\377\263\214c\377\263\214c\377" +\ - "\255\204a\377\257\204\\\377\246\203Y\377\246\203Y\377\246\203Y\377\245" +\ - "|Z\377\245|Z\377\235zR\377\232rR\377\235zR\377\235zR\377\233~Z\377\224" +\ - "sQ\377\232rR\377\224sQ\377\214kQ\377\203dJ\377}ZG\377\214eJ\377\225l" +\ - "P\377\225lP\377\232rR\377\232rR\377\234s[\377\224rZ\377\224rZ\377\224" +\ - "rZ\377\222k[\377\214kQ\377\214qR\377\214qR\377\203jR\377\203jR\377{c" +\ - "R\377{cR\377sZK\377kSD\377lSI\377cRK\377ZJC\377QDA\377K=8\377K=8\377" +\ - "JB:\377B;9\377B;9\377JCA\377JCA\377JCA\377B81\377931\377JB:\377QDA\377" +\ - "JCA\377B;9\377JB:\377QDA\377K=8\377B;9\377JB:\377QDA\377RJB\377cRK\377" +\ - "kTO\377SA8\377\31\26\22\377\31\26\22\377\31\26\22\377\27\21\20\335\5" +\ - "\5\4\210\5\5\4f\0\0\0D\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0\0\0\"\5\5\4D\14\13\12f\14" +\ - "\13\12\252\31\26\22\377\31\26\22\377K=8\377tZR\377tZR\377kTO\377{cR\377" +\ - "\255\204a\377\255\214`\377\274\225c\377\274\225c\377\267\224i\377\276" +\ - "\231l\377\263\214c\377\246\203Y\377\246\204b\377\233~Z\377\224{U\377" +\ - "\224sQ\377\224{U\377\224sQ\377\224sQ\377\214qR\377\204rQ\377rcI\377m" +\ - "eP\377kZJ\377dYI\377dRB\377kZJ\377cRK\377QDA\377QDA\377ZJC\377QDA\377" +\ - "RJB\377ZJC\377QDA\377SA8\377ZJC\377ZJC\377QDA\377RJB\377YSC\377ZJC\377" +\ - "[J;\377JB:\377QDA\377RJB\377JB:\377B;9\377JB:\377JCA\377JB:\377B;9\377" +\ - "B;9\377BA;\377B;9\377B;9\377BA;\377JCA\377JCA\377JCA\377JCA\377JCA\377" +\ - "BA;\377B;9\377JCA\377JCA\377LHA\377JCA\377BA;\377JB:\377LHA\377BA;\377" +\ - "BA;\377LHA\377QDA\377JCA\377B;9\377931\377B;9\377LHA\377JB:\377B;9\377" +\ - "JB:\377QDA\377JCA\377K=8\377B;9\377B;9\377QDA\377QDA\3772*\"\377\31\26" +\ - "\22\377\31\26\22\377\31\26\22\377\27\21\20\356\5\5\4\210\5\5\4w\0\0\0" +\ - "D\0\0\0""3\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\0\0\0\21\0\0\0\21\0\0\0\"\0\0\0D\14\13\12f\14\13\12\231\31\26\22\377" +\ - "\27\21\20\377K<1\377tZR\377tZR\377kTO\377QDA\377ZJC\377dRB\377sZK\377" +\ - "{cR\377sZK\377{cJ\377lSI\377JB:\377K=8\377JCA\377JCA\377JCA\377QDA\377" +\ - "JCA\377BA;\377JCA\377JCA\377BA;\377BA;\377LHA\377LHA\377BA;\377JCA\377" +\ - "RJB\377LHA\377BA;\377LHA\377LHA\377JCA\377LHA\377LHA\377BA;\377B;9\377" +\ - "JCA\377JCA\377B;9\377BA;\377JCA\377JB:\377JB:\377:72\377B;9\377JB:\377" +\ - ":72\377:72\377JCA\377LHA\377JB:\377B;9\377JB:\377LHA\377JCA\377JCA\377" +\ - "RJB\377RJB\377LHA\377JCA\377LHA\377RJB\377JCA\377BA;\377JCA\377RJB\377" +\ - "RJB\377QDA\377JB:\377JCA\377LHA\377B;9\377B;9\377JCA\377JCA\377QDA\377" +\ - "JB:\377:72\377BA;\377RJB\377JCA\377931\377931\3772/*\3771*'\377!\31\30" +\ - "\377\31\31\31\377)'#\3772/*\377;1)\377!\37\34\377\31\26\22\377\31\26" +\ - "\22\377\31\26\22\377\10\10\10\252\5\5\4w\0\0\0U\0\0\0""3\0\0\0\21\0\0" +\ - "\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0" +\ - "\0\0\"\0\0\0D\14\13\12U\14\13\12\231\31\26\22\377\27\21\20\377K=8\377" +\ - "tZR\377kTO\377QDA\3771*'\377931\377B;9\377:72\377BA;\377BA;\377B;9\377" +\ - "BA;\377JCA\377BA;\377JCA\377RJB\377RJB\377LHA\377RJB\377LHA\377JB:\377" +\ - "LHA\377RJB\377LHA\377JB:\377LHA\377RJB\377JB:\377JCA\377RJB\377LHA\377" +\ - "BA;\377LHA\377RJB\377JCA\377LHA\377RJB\377JB:\377BA;\377LHA\377JCA\377" +\ - "B;9\377JB:\377LHA\377JCA\377JB:\377:72\377JB:\377JCA\377B;9\377B;9\377" +\ - "LHA\377QDA\377LHA\377JB:\377JB:\377QDA\377BA;\377B;9\377BA;\377BA;\377" +\ - ":72\377B;9\377JB:\377JCA\377BA;\377:72\377B;9\377B;9\377BA;\377BA;\377" +\ - "B;9\377B;9\377B;9\377:72\377:72\377B;9\377BA;\377B;9\377931\377)'#\377" +\ - ")'#\377!\37\34\377\31\26\22\377\27\21\20\377\31\26\22\377\31\26\22\377" +\ - "\31\26\22\377\31\26\22\377\31\26\22\377\27\21\20\377\31\31\31\377!\37" +\ - "\34\377\31\26\22\377\31\26\22\377!\31\30\377\20\20\20\314\0\0\0w\5\5" +\ - "\4U\0\0\0""3\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\0\0\0\21\0\0\0\21\0\0\0\21\5\5\4D\14\13\12f\14\13\12\231\31\26\22" +\ - "\377\27\21\20\377K=8\377y[S\377ZJC\377B81\377B;9\3772/*\377B;9\377JC" +\ - "A\377BA;\377LHA\377RJB\377B;9\377BA;\377JB:\377B;9\377B;9\377LHA\377" +\ - "LHA\377LHA\377LHA\377RJB\377JB:\377LHA\377RJB\377LHA\377JB:\377LHA\377" +\ - "RJB\377JCA\377JB:\377RJB\377LHA\377BA;\377LHA\377RJB\377JCA\377LHA\377" +\ - "SKI\377LHA\377JB:\377SKI\377RJB\377B;9\377B;9\377B;9\377B81\377931\377" +\ - "1*'\377931\3772/*\377)'#\377)'#\3771*'\3771*'\3771*'\377)\"\40\377)\"" +\ - "\40\377!\37\34\377\31\31\31\377\31\26\22\377\31\26\22\377\27\21\20\377" +\ - "\31\26\22\377\31\31\31\377\31\31\31\377\31\31\31\377\31\26\22\377\31" +\ - "\26\22\377\31\26\22\377\31\31\31\377\31\31\31\377\31\31\31\377\31\31" +\ - "\31\377\31\31\31\377!\37\34\377!\31\30\377!\31\30\377!\31\30\377\31\26" +\ - "\22\377\27\21\20\377\27\21\20\377\20\20\20\377\20\20\20\377\27\21\20" +\ - "\377\27\21\20\377\27\21\20\377\27\21\20\377\31\26\22\377\31\26\22\377" +\ - "\31\26\22\377\31\26\22\377\31\26\22\377\27\21\20\377\27\21\20\377\31" +\ - "\26\22\377\31\26\22\377\27\21\20\356\0\0\0\210\5\5\4f\0\0\0""3\0\0\0" +\ - "\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0""3\14\13\12f\10\10\10\210\31\26\22\377\31\26\22\377S" +\ - "A8\377kTO\377K=8\377B81\377JB:\377B;9\377931\377B;9\377JCA\377B;9\377" +\ - "LHA\377RJB\377B;9\377JB:\377LHA\377BA;\377B;9\377BA;\377BA;\377BA;\377" +\ - "JB:\377LHA\377JB:\377JCA\377SKI\377SKI\377JCA\377LHA\377SKI\377LHA\377" +\ - "JB:\377RJB\377LHA\377BA;\377RJB\377SKI\377JCA\377LHA\377RJB\377BA;\377" +\ - "931\3772/*\377)\"\40\377\31\31\31\377\31\26\22\377\27\21\20\377\20\20" +\ - "\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20" +\ - "\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377" +\ - "\20\20\20\377\20\20\20\377\20\20\20\377\27\21\20\377\20\20\20\377\20" +\ - "\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20" +\ - "\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\27\21\20" +\ - "\377\27\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377" +\ - "\27\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377\27" +\ - "\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377\27\21\20\377\27\21" +\ - "\20\356\20\20\20\356\20\20\20\356\20\20\20\356\20\20\20\356\20\20\20" +\ - "\356\31\26\22\377\31\26\22\377\31\26\22\377\14\13\12\231\5\5\4f\0\0\0" +\ - "D\0\0\0\"\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\0\0\0\21\0\0\0\21\0\0\0""3\14\13\12U\10\10\10w\27\21\20\356\31" +\ - "\26\22\377931\377B81\377B81\377:72\3772/*\377931\377931\377)'#\3772/" +\ - "*\3771*'\377)\"\40\377)'#\377)))\377)\"\40\377)'#\3772/*\377)'#\377)" +\ - "\"\40\377)\"\40\377!\37\34\377!\37\34\377)\"\40\377)))\3771*'\377931" +\ - "\377B;9\377JB:\377BA;\377LHA\377SKI\377RJB\377LHA\377RJB\377JB:\377B" +\ - ";9\377:72\3772/*\377)\"\40\377!\37\34\377\31\31\31\377\27\21\20\377\20" +\ - "\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\27\21" +\ - "\20\377\27\21\20\377\20\20\20\377\27\21\20\377\27\21\20\377\27\21\20" +\ - "\377\27\21\20\377\20\20\20\377\20\20\20\377\27\21\20\377\27\21\20\377" +\ - "\27\21\20\377\27\21\20\377\27\21\20\377\31\26\22\377\31\26\22\377\31" +\ - "\31\31\377\31\31\31\377\31\31\31\377\31\26\22\377\31\26\22\377\31\26" +\ - "\22\377\31\26\22\377\31\26\22\377\31\26\22\377\31\26\22\377\27\21\20" +\ - "\377\27\21\20\377\27\21\20\377\27\21\20\377\20\20\20\377\20\20\20\356" +\ - "\20\20\20\356\27\21\20\377\27\21\20\377\27\21\20\356\20\20\20\356\27" +\ - "\21\20\356\20\20\20\356\20\20\20\335\20\20\20\314\20\20\20\314\20\20" +\ - "\20\273\14\13\12\273\10\10\10\273\5\5\4\252\10\10\10\252\5\5\4\231\5" +\ - "\5\4\231\5\5\4\252\14\13\12\252\14\13\12\210\0\0\0w\0\0\0D\0\0\0\"\0" +\ - "\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0" +\ - "\0\0\21\0\0\0""3\20\20\20U\14\13\12w\31\26\22\356\31\26\22\377\31\31" +\ - "\31\377!\31\30\377!\37\34\377!\31\30\377\31\31\31\377\31\31\31\377\31" +\ - "\31\31\377\31\26\22\377\31\31\31\377\31\26\22\377\31\26\22\377\27\21" +\ - "\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20" +\ - "\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377" +\ - "\20\20\20\377\27\21\20\377\27\21\20\377\27\21\20\377\31\26\22\377!\37" +\ - "\34\377)\"\40\377)'#\3771*'\3771*'\377)'#\377)\"\40\377\31\31\31\377" +\ - "\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\27\21\20\377\27" +\ - "\21\20\377\27\21\20\377\31\26\22\377\31\26\22\377\27\21\20\377\27\21" +\ - "\20\377\27\21\20\377\27\21\20\377\20\20\20\356\20\20\20\356\20\20\20" +\ - "\335\20\20\20\314\10\10\10\273\10\10\10\273\10\10\10\273\10\10\10\273" +\ - "\10\10\10\273\10\10\10\273\20\20\20\314\20\20\20\314\20\20\20\314\20" +\ - "\20\20\314\31\31\31\273\31\31\31\273\31\31\31\273\31\26\22\273\20\20" +\ - "\20\273\20\20\20\273\14\13\12\273\20\20\20\273\20\20\20\273\20\20\20" +\ - "\273\14\13\12\273\14\13\12\273\14\13\12\273\10\10\10\273\10\10\10\273" +\ - "\10\10\10\273\10\10\10\273\10\10\10\273\14\13\12\273\14\13\12\273\14" +\ - "\13\12\273\14\13\12\273\14\13\12\273\10\10\10\273\5\5\4\252\5\5\4\252" +\ - "\5\5\4\252\5\5\4\252\0\0\0\231\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210" +\ - "\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0f\0\0\0D\0\0\0\"\0\0\0\21\0\0\0\21" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\14\13" +\ - "\12D\20\20\20U\14\13\12f\20\20\20\356\27\21\20\377\20\20\20\377\20\20" +\ - "\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\31\26\22" +\ - "\377\31\26\22\377\31\26\22\377\31\31\31\377\31\31\31\377\31\26\22\377" +\ - "\31\26\22\377\27\21\20\377\31\26\22\377\27\21\20\377\31\26\22\377\27" +\ - "\21\20\377\27\21\20\377\31\26\22\377\27\21\20\377\31\26\22\377\27\21" +\ - "\20\377\27\21\20\377\27\21\20\377\20\20\20\377\20\20\20\377\20\20\20" +\ - "\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20\20\20\377" +\ - "\20\20\20\377\27\21\20\377\27\21\20\377\31\26\22\377\31\26\22\377\27" +\ - "\21\20\377\27\21\20\377\20\20\20\356\20\20\20\356\20\20\20\335\20\20" +\ - "\20\335\20\20\20\314\10\10\10\273\10\10\10\273\10\10\10\273\10\10\10" +\ - "\273\5\5\4\252\10\10\10\252\10\10\10\252\5\5\4\252\5\5\4\252\5\5\4\252" +\ - "\5\5\4\252\5\5\4\252\5\5\4\252\5\5\4\231\5\5\4\231\5\5\4\210\5\5\4\210" +\ - "\5\5\4\210\5\5\4\210\5\5\4\210\5\5\4\210\5\5\4\210\5\5\4\210\0\0\0\231" +\ - "\0\0\0\231\0\0\0\231\0\0\0\231\0\0\0\231\0\0\0\231\0\0\0\231\0\0\0\231" +\ - "\0\0\0\231\0\0\0\231\0\0\0\231\0\0\0\231\0\0\0\231\0\0\0\231\0\0\0\231" +\ - "\0\0\0\231\0\0\0\231\0\0\0\231\0\0\0\231\0\0\0\231\0\0\0\231\0\0\0\231" +\ - "\0\0\0\231\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0w\0" +\ - "\0\0f\0\0\0U\0\0\0U\0\0\0""3\0\0\0\21\0\0\0\21\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\0\0\0\21\5\5\4\"\20\20\20""3\20\20\20U\14\13\12" +\ - "f!\37\34\335!\37\34\377!\37\34\377\31\26\22\356\31\26\22\356\31\31\31" +\ - "\356\31\26\22\356\31\31\31\356\31\31\31\377\31\31\31\377\31\31\31\377" +\ - "\31\26\22\377\31\26\22\377\27\21\20\356\20\20\20\356\20\20\20\356\20" +\ - "\20\20\356\27\21\20\356\27\21\20\356\31\26\22\356\31\26\22\356\31\26" +\ - "\22\356\31\26\22\356\31\31\31\377\31\26\22\377\27\21\20\377\31\26\22" +\ - "\377\31\26\22\377\27\21\20\377\27\21\20\377\20\20\20\377\27\21\20\377" +\ - "\20\20\20\377\27\21\20\377\20\20\20\377\20\20\20\377\20\20\20\377\20" +\ - "\20\20\356\20\20\20\356\20\20\20\335\20\20\20\273\20\20\20\252\14\13" +\ - "\12\252\14\13\12\252\10\10\10\231\5\5\4\231\10\10\10\231\10\10\10\231" +\ - "\10\10\10\231\10\10\10\231\5\5\4\231\5\5\4\231\5\5\4\231\5\5\4\231\0" +\ - "\0\0\231\0\0\0\231\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210" +\ - "\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210" +\ - "\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210" +\ - "\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210" +\ - "\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0\0w\0\0\0w\0\0\0w\0\0\0w" +\ - "\0\0\0w\0\0\0w\0\0\0f\0\0\0f\0\0\0f\0\0\0f\0\0\0f\0\0\0f\0\0\0U\0\0\0" +\ - "U\0\0\0U\0\0\0D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0" +\ - "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\0\0\0\21\0\0\0\21\10\10\10""3\5\5\4D\5\5\4U\31\31\31" +\ - "\210\31\31\31\231\31\31\31\231\31\26\22\231\31\31\31\252\31\31\31\252" +\ - "\31\26\22\252\31\26\22\273\20\20\20\273\20\20\20\273\14\13\12\252\14" +\ - "\13\12\252\14\13\12\252\20\20\20\252\31\26\22\252\31\26\22\252\31\26" +\ - "\22\231\31\31\31\231\31\31\31\231\31\26\22\231\31\26\22\231\27\21\20" +\ - "\231\27\21\20\231\31\26\22\231\31\26\22\231\27\21\20\231\20\20\20\252" +\ - "\20\20\20\252\14\13\12\252\10\10\10\252\14\13\12\252\10\10\10\252\10" +\ - "\10\10\231\10\10\10\231\10\10\10\252\10\10\10\252\10\10\10\231\10\10" +\ - "\10\210\10\10\10w\10\10\10w\10\10\10w\10\10\10\210\10\10\10\210\5\5\4" +\ - "\210\5\5\4\210\5\5\4\210\5\5\4\210\0\0\0\210\0\0\0\210\0\0\0\210\0\0" +\ - "\0\210\0\0\0w\0\0\0w\0\0\0w\0\0\0f\0\0\0f\0\0\0f\0\0\0f\0\0\0f\0\0\0" +\ - "f\0\0\0f\0\0\0f\0\0\0f\0\0\0f\0\0\0w\0\0\0w\0\0\0w\0\0\0w\0\0\0f\0\0" +\ - "\0f\0\0\0f\0\0\0f\0\0\0f\0\0\0f\0\0\0f\0\0\0f\0\0\0f\0\0\0f\0\0\0f\0" +\ - "\0\0f\0\0\0U\0\0\0U\0\0\0U\0\0\0U\0\0\0U\0\0\0U\0\0\0U\0\0\0U\0\0\0U" +\ - "\0\0\0U\0\0\0U\0\0\0U\0\0\0U\0\0\0U\0\0\0U\0\0\0U\0\0\0U\0\0\0U\0\0\0" +\ - "U\0\0\0D\0\0\0D\0\0\0D\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\0\0\0\21\0\0\0\21\5\5\4\"\0\0\0D\0\0\0U\10\10\10" +\ - "f\14\13\12w\14\13\12w\14\13\12w\14\13\12\210\14\13\12\210\14\13\12\210" +\ - "\14\13\12\210\14\13\12\210\14\13\12w\20\20\20w\20\20\20w\20\20\20w\20" +\ - "\20\20w\27\21\20w\31\26\22w\31\26\22w\31\31\31w\31\26\22w\31\26\22w\31" +\ - "\26\22w\31\26\22w\31\26\22w\31\31\31w\31\31\31w\31\26\22f\20\20\20f\14" +\ - "\13\12f\10\10\10f\10\10\10f\10\10\10f\5\5\4U\5\5\4U\5\5\4U\5\5\4U\10" +\ - "\10\10U\10\10\10U\5\5\4U\5\5\4U\5\5\4U\0\0\0U\0\0\0D\0\0\0D\0\0\0D\0" +\ - "\0\0D\0\0\0U\0\0\0U\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D" +\ - "\0\0\0D\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3" +\ - "\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0" +\ - "D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0" +\ - "\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0" +\ - "\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0""3\0\0\0""3\0\0" +\ - "\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0\"\0\0\0\21\0\0\0\21\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\5\5\4\"\5\5\4""3\0\0" +\ - "\0U\5\5\4U\5\5\4U\0\0\0U\0\0\0U\5\5\4U\5\5\4U\5\5\4U\5\5\4U\5\5\4U\5" +\ - "\5\4U\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0D\0\0\0U\0\0\0U" +\ - "\0\0\0U\0\0\0U\0\0\0U\0\0\0U\5\5\4U\5\5\4U\5\5\4U\0\0\0U\0\0\0D\0\0\0" +\ - "D\0\0\0D\0\0\0D\0\0\0D\5\5\4D\5\5\4D\5\5\4D\5\5\4D\0\0\0D\0\0\0""3\0" +\ - "\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3" +\ - "\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0" +\ - "\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0" +\ - "\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0" +\ - "\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0""3\0" +\ - "\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3" +\ - "\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0" +\ - "3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0\"\0\0\0\"\0\0\0" +\ - "\"\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377" +\ - "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0" +\ - "\21\0\0\0\21\0\0\0\21\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0""3\0\0\0""3\0\0\0" +\ - "3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0" +\ - "\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3" +\ - "\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0" +\ - "3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0\0""3\0\0" +\ - "\0""3\0\0\0""3\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0" +\ - "\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0" +\ - "\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0" +\ - "\"\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0" +\ - "\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"" +\ - "\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0" +\ - "\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"" +\ - "\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\21\0\0" +\ - "\0\21\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0" +\ - "\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"" +\ - "\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0" +\ - "\0\"\0\0\0\"\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0" +\ - "\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0" +\ - "\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0" +\ - "\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0" +\ - "\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0" +\ - "\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0" +\ - "\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\21\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\"\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0" +\ - "\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0" +\ - "\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0" +\ - "\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0" +\ - "\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0" +\ - "\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0" +\ - "\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0" +\ - "\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0" +\ - "\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0" +\ - "\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0" +\ - "\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377" +\ - "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0" +\ - "\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21" +\ - "\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0" +\ - "\21\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377" +\ - "\0\377\377\377\0" diff --git a/src/lib/parse_exif.py b/src/lib/parse_exif.py deleted file mode 100644 index a97574f..0000000 --- a/src/lib/parse_exif.py +++ /dev/null @@ -1,117 +0,0 @@ -import re -import EXIF -class ParseExif(object): - - def __init__(self, exif_dict=None, exif_file=None): - self.camera = None - self.date = None - self.aperture = None - self.exposure_program = None - self.exposure_bias = None - self.iso = None - self.focal_length = None - self.subject_distance = None - self.metering_mode = None - self.flash = None - self.light_source = None - self.resolution = None - self.orientation = None - self.exif_dict = exif_dict - if not self.exif_dict: - try: - f = open(exif_file, 'rb') - e = EXIF.process_file(f) - if len(e.keys()) >0: - self.exif_dict = e - f.close() - except: - pass - - def parse(self): - try: - self.camera = "%s" % self.exif_dict['Image Make'] - self.camera = self.camera.strip() - except: pass - try: - model = "%s" % self.exif_dict['Image Model'] - self.camera += ", " + model.strip() - except: pass - - try: - self.date = "%s" % self.exif_dict['EXIF DateTimeOriginal'] - p = re.compile('[\d,:]+') - if not p.match(self.date): - self.date = None - except: pass - - try: - self.aperture = "%s" % self.exif_dict['EXIF FNumber'] - if len(self.aperture.split("/")) == 2: - self.aperture += '.' - self.aperture = "%.1f" % eval(self.aperture) - self.aperture = "f/%.1f" % float(self.aperture) - self.aperture = self.aperture.replace('.',',') - except: pass - - try: self.exposure_program = "%s" % \ - self.exif_dict['EXIF ExposureProgram'] - except: pass - - try: - self.exposure_bias = "%s" % \ - self.exif_dict['EXIF ExposureBiasValue'] - if len(self.exposure_bias.split("/")) == 2: - self.exposure_bias += '.' - self.exposure_bias = "%.1f" % eval(self.exposure_bias) - self.exposure_bias = "%.1f" % float(self.exposure_bias) - self.exposure_bias = self.exposure_bias.replace('.',',') - except: pass - - try: self.iso = "%s" % self.exif_dict['EXIF ISOSpeedRatings'] - except: pass - - try: - self.focal_length = "%s" % self.exif_dict['EXIF FocalLength'] - if len(self.focal_length.split("/")) == 2: - self.focal_length += '.' - self.focal_length = "%.2f" % eval(self.focal_length) - self.focal_length = "%.2f mm" % float(self.focal_length) - self.focal_length = self.focal_length.replace('.',',') - except: pass - - try: - self.subject_distance = "%s" % \ - self.exif_dict['EXIF SubjectDistance'] - if len(self.subject_distance.split("/")) == 2: - self.subject_distance += '.' - self.subject_distance = "%.3f" % eval(self.subject_distance) - self.subject_distance = "%.3f m" % float(self.subject_distance) - self.subject_distance = self.subject_distance.replace('.',',') - except: pass - - try: self.metering_mode = "%s" % self.exif_dict['EXIF MeteringMode'] - except: pass - - try: self.flash = "%s" % self.exif_dict['EXIF Flash'] - except: pass - - try: self.light_source = "%s" % self.exif_dict['EXIF LightSource'] - except: pass - - try: self.resolution = "%s" % self.exif_dict['Image XResolution'] - except: pass - try: self.resolution = self.resolution + " x %s" % \ - self.exif_dict['Image YResolution'] - except: pass - try: self.resolution = self.resolution + " (%s)" % \ - self.exif_dict['Image ResolutionUnit'] - except: pass - - try: self.orientation = "%s" % self.exif_dict['Image Orientation'] - except: pass - - return (self.camera, self.date, self.aperture, self.exposure_program, - self.exposure_bias, self.iso, self.focal_length, - self.subject_distance, self.metering_mode, self.flash, - self.light_source, self.resolution, self.orientation) - diff --git a/src/lib/thumbnail.py b/src/lib/thumbnail.py deleted file mode 100644 index e3cd3cf..0000000 --- a/src/lib/thumbnail.py +++ /dev/null @@ -1,119 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - -from tempfile import mkstemp -from hashlib import sha512 -from shutil import move -from os import path -import sys - -from PIL import Image - -from lib import EXIF - - -class Thumbnail(object): - """Class for generate/extract thumbnail from image file""" - - def __init__(self, filename=None, base=''): - self.thumb_x = 160 - self.thumb_y = 160 - self.filename = filename - self.base = base - self.sha512 = sha512(open(filename).read()).hexdigest() - self.thumbnail_path = path.join(self.base, self.sha512 + "_t") - - def save(self): - """Save thumbnail into specific directory structure - return filename base and exif object or None""" - exif = {} - orientations = {2: Image.FLIP_LEFT_RIGHT, # Mirrored horizontal - 3: Image.ROTATE_180, # Rotated 180 - 4: Image.FLIP_TOP_BOTTOM, # Mirrored vertical - 5: Image.ROTATE_90, # Mirrored horizontal then - # rotated 90 CCW - 6: Image.ROTATE_270, # Rotated 90 CW - 7: Image.ROTATE_270, # Mirrored horizontal then - # rotated 90 CW - 8: Image.ROTATE_90} # Rotated 90 CCW - flips = {7: Image.FLIP_LEFT_RIGHT, 5: Image.FLIP_LEFT_RIGHT} - - image_file = open(self.filename, 'rb') - try: - exif = EXIF.process_file(image_file) - except: - if __debug__: - print "exception", sys.exc_info()[0], "raised with file:" - print self.filename - finally: - image_file.close() - - if path.exists(self.thumbnail_path): - if __debug__: - print "file", self.filename, "with hash", self.sha512, "exists" - return self.sha512, exif - - if 'JPEGThumbnail' in exif: - if __debug__: - print self.filename, "exif thumb" - exif_thumbnail = exif['JPEGThumbnail'] - thumb_file = open(self.thumbnail_path, 'wb') - thumb_file.write(exif_thumbnail) - thumb_file.close() - - if 'Image Orientation' in exif: - orient = exif['Image Orientation'].values[0] - if orient > 1 and orient in orientations: - fd, temp_image_path = mkstemp() - os.close(fd) - - thumb_image = Image.open(self.thumbnail_path) - tmp_thumb_img = thumb_image.transpose(orientations[orient]) - - if orient in flips: - tmp_thumb_img = tmp_thumb_img.transpose(flips[orient]) - - if tmp_thumb_img: - tmp_thumb_img.save(temp_image_path, 'JPEG') - move(temp_image_path, self.thumbnail_path) - return self.sha512, exif - else: - if __debug__: - print self.filename, "no exif thumb" - thumb = self.__scale_image() - if thumb: - thumb.save(self.thumbnail_path, "JPEG") - return self.sha512, exif - return None, exif - - def __scale_image(self): - """create thumbnail. returns image object or None""" - try: - image_thumb = Image.open(self.filename).convert('RGB') - except: - return None - it_x, it_y = image_thumb.size - if it_x > self.thumb_x or it_y > self.thumb_y: - image_thumb.thumbnail((self.thumb_x, self.thumb_y), Image.ANTIALIAS) - return image_thumb diff --git a/src/models/__init__.py b/src/models/__init__.py deleted file mode 100644 index 5e9f6f7..0000000 --- a/src/models/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - - diff --git a/src/models/m_config.py b/src/models/m_config.py deleted file mode 100644 index 5e1c41d..0000000 --- a/src/models/m_config.py +++ /dev/null @@ -1,340 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - -from gtkmvc import Model - -import os - -import gtk -import gobject - -from ConfigParser import ConfigParser - -class Ini(object): - - def __init__(self): - self.ini = [] - - def add_section(self, section): - self.ini.append("[%s]" % section) - - def add_key(self, key, value): - self.ini.append("%s=%s" % (key, value)) - - def add_comment(self, comment): - self.ini.append(";%s" % comment) - - def add_verb(self, verb): - self.ini.append(verb) - - def show(self): - return "\n".join(self.ini) - -class ConfigModel(Model): - ini = Ini() - - __properties__ = {} - - filetype_list = ['Images', 'Movies'] - - confd = { - 'savewin': True, - 'savepan': True, - 'wx': 800, - 'wy': 600, - 'h': 200, - 'v': 300, - 'eject': True, - 'compress': True, - - 'exportxls': False, - - 'confirmquit': True, - 'confirmabandon': True, - 'mntwarn': True, - 'delwarn': True, - - 'cd': '/mnt/cdrom', - 'ejectapp': 'eject -r', - - 'imgview': False, - 'imgprog': 'gqview', - - 'retrive': False, - - 'thumbs': True, - 'exif': True, - 'gthumb': False, - - 'extensions': {'bmp':'identify %s', - 'gif':'identify "%s"', - 'jpg':'identify "%s"', - 'jpeg':'identify "%s"', - 'png':'identify "%s"', - 'avi':'midentify "%s"', - 'mkv':'midentify "%s"', - 'mpg':'midentify "%s"', - 'mpeg':'midentify "%s"', - 'wmv':'midentify "%s"', - }, - - 'showtoolbar':True, - 'showstatusbar':True, - } - - dictconf = { - "save main window size": "savewin", - "save panes size": "savepan", - "main window width": "wx", - "main window height": "wy", - "horizontal panes": "h", - "vertical panes":"v", - "export xls":"exportxls", - "cd drive":"cd", - "eject command":"ejectapp", - "eject":"eject", - "image support":"thumbs", - 'confirm quit':'confirmquit', - 'warn mount/umount errors':'mntwarn', - 'warn on delete':'delwarn', - 'confirm abandon current catalog':'confirmabandon', - 'show toolbar':'showtoolbar', - 'show statusbar and progress bar':'showstatusbar', - 'compress catalog':'compress', - 'retrive extra informatin':'retrive', - 'scan exif data':'exif', - 'include gthumb image description':'gthumb', - 'use external image viewer':'imgview', - 'external image viewer program':'imgprog', - } - - dbool = ( - 'exportxls', - 'thumbs', - 'savewin', - 'savepan', - 'eject', - 'gthumb', - 'exif', - 'confirmquit', - 'mntwarn', - 'delwarn', - 'confirmabandon', - 'showtoolbar', - 'showstatusbar', - 'delwarn', - 'compress', - 'retrive', - 'imgview', - ) - - recent = [] - search_history = [] - RECENT_MAX = 10 - HISTORY_MAX = 20 - - dstring = ('cd','ejectapp','imgprog') - - try: - path = os.path.join(os.environ['HOME'], ".pygtktalog") - except KeyError: - raise KeyError, "Cannot stat path for current user home!" - - path = os.path.join(path, "config.ini") - - def __init__(self): - Model.__init__(self) - self.category_tree = gtk.ListStore(gobject.TYPE_STRING) - - self.refresh_ext() - return - - def refresh_ext(self): - self.ext_tree = gtk.ListStore(gobject.TYPE_STRING, - gobject.TYPE_STRING) - keys = sorted(self.confd['extensions'].keys()) - for i in keys: - myiter = self.ext_tree.insert_before(None,None) - self.ext_tree.set_value(myiter, 0, i) - self.ext_tree.set_value(myiter, 1, self.confd['extensions'][i]) - - def save(self): - try: - os.lstat(self.path) - except: - print "Saving preferences to %s." % self.path - if __debug__: - print "m_config.py: save() Saving preferences to", - print "%s" % self.path - newIni = Ini() - - # main section - newIni.add_section("pyGTKtalog conf") - for opt in self.dictconf: - newIni.add_key(opt,self.confd[self.dictconf[opt]]) - - # recent section - newIni.add_section("pyGTKtalog recent") - - count = 1 - max_count = self.RECENT_MAX + 1 - for opt in self.recent: - if count < max_count: - newIni.add_key(count, opt) - else: - break - count+=1 - - # search history section - newIni.add_section("search history") - count = 1 - max_count = self.HISTORY_MAX + 1 - for opt in self.search_history: - if count < max_count: - newIni.add_key(count, opt) - else: - break - count+=1 - - # extensions sections - newIni.add_section("extensions") - count = 1 - for i in self.confd['extensions']: - newIni.add_key(i, self.confd['extensions'][i]) - count+=1 - - # write config - try: - f = open(self.path, "w") - success = True - except: - if __debug__: - print "m_config.py: save() Cannot open config file", - print "%s for writing." % self.path - success = False - f.write(newIni.show()) - f.close() - return success - - def load(self): - try: - # try to read config file - parser = ConfigParser() - parser.read(self.path) - r = {} - h = {} - self.recent = [] - self.search_history = [] - for sec in parser.sections(): - if sec == 'pyGTKtalog conf': - for opt in parser.options(sec): - i = self.dictconf[opt] - try: - if self.dictconf[opt] in self.dbool: - self.confd[i] = parser.getboolean(sec, opt) - elif self.dictconf[opt] in self.dstring: - self.confd[i] = parser.get(sec, opt) - else: - self.confd[i] = parser.getint(sec, opt) - except: - if __debug__: - print "m_config.py: load() failed to parse", - print "option:", opt - pass - elif sec == 'pyGTKtalog recent': - for opt in parser.options(sec): - try: - r[int(opt)] = parser.get(sec, opt) - except: - if __debug__: - print "m_config.py: load() failed to parse", - print "option:", opt - pass - elif sec == 'search history': - for opt in parser.options(sec): - try: - h[int(opt)] = parser.get(sec, opt) - except: - if __debug__: - print "m_config.py: load() failed to parse", - print "option:", opt - pass - elif sec == 'extensions': - self.confd['extensions'] = {} - for opt in parser.options(sec): - try: - self.confd['extensions'][opt] = parser.get(sec, - opt) - except: - if __debug__: - print "m_config.py: load() failed to parse", - print "option:", opt - pass - - for i in range(1, self.RECENT_MAX + 1): - if i in r: - self.recent.append(r[i]) - for i in range(1, self.HISTORY_MAX + 1): - if i in h: - self.search_history.append(h[i]) - - except: - if __debug__: - print "m_config.py: load() load config file failed" - pass - - def add_recent(self, path): - if not path: - return - - if path in self.recent: - self.recent.remove(path) - self.recent.insert(0,path) - return - - self.recent.insert(0,path) - if len(self.recent) > self.RECENT_MAX: - self.recent = self.recent[:self.RECENT_MAX] - return - - def add_search_history(self, text): - if not text: - return - - if text in self.search_history: - self.search_history.remove(text) - self.search_history.insert(0, text) - return - - self.search_history.insert(0, text) - if len(self.search_history) > self.HISTORY_MAX: - self.search_history = self.search_history[:self.HISTORY_MAX] - return - - def __str__(self): - """show prefs in string way""" - string = "[varname]\tvalue\n" - for i in self.confd: - string+="%s\t%s\n" % (i,self.confd[i]) - return string diff --git a/src/models/m_main.py b/src/models/m_main.py deleted file mode 100644 index 8d0e3bb..0000000 --- a/src/models/m_main.py +++ /dev/null @@ -1,2002 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - -import os -import sys -import shutil -import bz2 -import math -import sqlite3 as sqlite -from tempfile import mkstemp -from datetime import datetime -import threading as _threading - -import gtk -import gobject - -from gtkmvc.model_mt import ModelMT -from m_config import ConfigModel - -try: - from lib.thumbnail import Thumbnail - from lib.img import Img -except: - pass -from lib.parse_exif import ParseExif -from lib.gthumb import GthumbCommentParser - -from lib.no_thumb import no_thumb as no_thumb_img -from lib.video import Video - -class MainModel(ModelMT): - """Create, load, save, manipulate db file which is container for data""" - - __properties__ = {'busy': False, - 'statusmsg': '', - 'progress': 0, - # point from search controller - changes while user activate specified - # file on search - 'point': None,} - - # constants instead of dictionary tables - # type of files - LAB = 0 # root of the tree - label/collection name - DIR = 1 # directory - FIL = 2 # file - LIN = 3 # symbolic link - - CD = 1 # sorce: cd/dvd - DR = 2 # source: filesystem - - EXIF_DICT = {0: 'Camera', - 1: 'Date', - 2: 'Aperture', - 3: 'Exposure program', - 4: 'Exposure bias', - 5: 'ISO', - 6: 'Focal length', - 7: 'Subject distance', - 8: 'Metering mode', - 9: 'Flash', - 10: 'Light source', - 11: 'Resolution', - 12: 'Orientation'} - - # images extensions - only for PIL and EXIF - IMG = ['jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff', 'tga', 'pcx', 'bmp', - 'xbm', 'xpm', 'jp2', 'jpx', 'pnm'] - MOV = ['avi', 'mpg', 'mpeg', 'mkv', 'wmv', 'ogm', 'mov'] - - def __init__(self): - """initialize""" - ModelMT.__init__(self) - self.unsaved_project = False - self.filename = None # catalog saved/opened filename - self.internal_dirname = None - self.image_path = None - self.db_connection = None - self.db_cursor = None - self.abort = False - self.source = self.CD - self.config = ConfigModel() - self.config.load() - self.path = None - self.label = None - self.currentid = None - self.thread = None - self.busy = False - self.statusmsg = "Idle" - self.selected_tags = {} - self.search_created = False - self.db_tmp_path = False - - # Directory tree: id, name, icon, type - self.discs_tree = gtk.TreeStore(gobject.TYPE_INT, - gobject.TYPE_STRING, - str, - gobject.TYPE_INT) - # File list of selected directory: id, disc, filename, path, size, - # date, type, icon - self.files_list = gtk.ListStore(gobject.TYPE_INT, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_UINT64, - gobject.TYPE_STRING, - gobject.TYPE_INT, - str) - # Search list. Exactly the same as file list above. - self.search_list = gtk.ListStore(gobject.TYPE_INT, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_UINT64, - gobject.TYPE_STRING, - gobject.TYPE_INT, - str) - # iconview store - id, pixbuffer - self.images_store = gtk.ListStore(gobject.TYPE_INT, gtk.gdk.Pixbuf) - - # exif liststore - id, exif key, exif value - self.exif_list = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING, - gobject.TYPE_STRING) - # search combobox liststore - entry - self.search_history = gtk.ListStore(str) - - # fill it - self.__fill_history_model() - - # tag cloud array element is a dict with 5 keys: - # elem = {'id': str(id), 'name': tagname, 'size': size, - # 'count': cout, 'color': color} - # where color is in one of format: - # - named (i.e. red, blue, black and so on) - # - #rgb - # - #rrggbb - self.tag_cloud = [] - - try: - path = os.path.join(os.environ['HOME'], ".pygtktalog") - imgpath = os.path.join(path, "images") - except KeyError: - raise KeyError, "Cannot stat path for current user home!" - - if os.path.exists(path): - if not os.path.isdir(path): - raise RuntimeError, "There is regular file \"%s\" on the way. Please remove it." % \ - path - else: - os.mkdir(path) - - - if os.path.exists(imgpath): - if not os.path.isdir(imgpath): - print "Warning:", - "There is regular file \"%s\" on the way. Please remove it, otherwise images cannot be used" % imgpath - else: - os.mkdir(imgpath) - - self.image_path = imgpath - - self.new() - return - - def add_search_history(self, txt): - """add txt into search history, update search_history model""" - self.config.add_search_history(txt) - self.__fill_history_model() - return - - def add_tags(self, fid, tags): - """add tag (if not exists) and connect it with file""" - for tag in tags.split(','): - tag = tag.strip() - - # SQL: first, chek if we already have tag in tags table; get id - sql = """SELECT id FROM tags WHERE tag = ?""" - self.db_cursor.execute(sql, (tag, )) - res = self.db_cursor.fetchone() - if not res: - # SQL: insert new tag - sql = """INSERT INTO tags(tag, group_id) - VALUES(?, ?)""" - self.db_cursor.execute(sql, (tag, 1)) - self.db_connection.commit() - # SQL: get tag id - sql = """SELECT id FROM tags WHERE tag = ?""" - self.db_cursor.execute(sql, (tag, )) - res = self.db_cursor.fetchone() - - tag_id = res[0] - - # SQL: then checkout if file have already tag assigned - sql = """SELECT file_id FROM tags_files - WHERE file_id = ? AND tag_id = ?""" - self.db_cursor.execute(sql, (fid, tag_id)) - res = self.db_cursor.fetchone() - if not res: - # SQL: connect tag with file - sql = """INSERT INTO tags_files(file_id, tag_id) - VALUES(?, ?)""" - self.db_cursor.execute(sql, (fid, tag_id)) - self.db_connection.commit() - self.get_tags() - return - - def get_tags_by_file_id(self, file_id_list): - """return dictionary of tags by connected files""" - # SQL: get tags by file_ids - if len(file_id_list) == 1: - sql = "(%d)" % file_id_list[0] - else: - sql = str(tuple(file_id_list)) - sql = """SELECT DISTINCT t.id, t.tag FROM tags_files f - LEFT JOIN tags t on t.id = f.tag_id - WHERE f.file_id in """ + sql + """ - ORDER BY t.tag""" - self.db_cursor.execute(sql) - res = self.db_cursor.fetchall() - - retval = {} - for tag in res: - retval[tag[0]] = tag[1] - return retval - - def get_tag_by_id(self, tag_id): - """get tag (string) by its id""" - # SQL: get tag by id - sql = """SELECT tag FROM tags WHERE id = ?""" - self.db_cursor.execute(sql, (int(tag_id), )) - res = self.db_cursor.fetchone() - if not res: - return None - return res[0] - - def get_file_tags(self, file_id): - """get tags of file""" - - # SQL: get tag by id - sql = """SELECT t.id, t.tag FROM tags t - LEFT JOIN tags_files f ON t.id=f.tag_id - WHERE f.file_id = ? - ORDER BY t.tag""" - self.db_cursor.execute(sql, (int(file_id), )) - res = self.db_cursor.fetchall() - - tmp = {} - if len(res) == 0: - return None - - for row in res: - tmp[row[0]] = row[1] - - return tmp - - def delete_tags(self, file_id_list, tag_id_list): - """remove tags from selected files""" - for file_id in file_id_list: - # SQL: remove tags for selected file - if len(tag_id_list) == 1: - sql = "(%d)" % tag_id_list[0] - else: - sql = str(tuple(tag_id_list)) - sql = """DELETE FROM tags_files WHERE file_id = ? - AND tag_id IN """ + sql - self.db_cursor.execute(sql, (int(file_id), )) - self.db_connection.commit() - - for tag_id in tag_id_list: - sql = """SELECT count(*) FROM tags_files WHERE tag_id=?""" - self.db_cursor.execute(sql, (int(tag_id),)) - res = self.db_cursor.fetchone() - if res[0] == 0: - sql = """DELETE FROM tags WHERE id=?""" - self.db_cursor.execute(sql, (int(tag_id),)) - self.db_connection.commit() - - def get_tags(self): - """fill tags dict with values from db""" - if not self.selected_tags: - sql = """SELECT COUNT(f.file_id), t.id, t.tag FROM tags_files f - LEFT JOIN tags t ON f.tag_id = t.id - GROUP BY f.tag_id - ORDER BY t.tag""" - else: - id_filter = self.__filter() - sql = """SELECT COUNT(f.file_id), t.id, t.tag FROM tags_files f - LEFT JOIN tags t ON f.tag_id = t.id - WHERE f.file_id in """ + str(tuple(id_filter)) + \ - """GROUP BY f.tag_id - ORDER BY t.tag""" - - self.db_cursor.execute(sql) - res = self.db_cursor.fetchall() - - if len(res) > 0: - self.tag_cloud = [] - for row in res: - self.tag_cloud.append({'id': row[1], - 'name': row[2], - 'size': row[0], - 'count': row[0], - 'color':'black'}) - - def tag_weight(initial_value): - """Calculate 'weight' of tag. - Tags can have sizes between 9 to ~40. Upper size is calculated with - logarythm and can take in extereme situation around value 55 like - for 1 milion tagged files.""" - if not initial_value: - initial_value = 1 - return 4 * math.log(initial_value, math.e) - - # correct font sizes with tag_weight function. - count = 0 - for tag0 in self.tag_cloud: - tmp = int(tag_weight(tag0['size'])) - if tmp == 0: - tmp = 1 - self.tag_cloud[count]['size'] = tmp + 8 - count += 1 - - def add_tag_to_path(self, tag_id): - """add tag to filter""" - temp = {} - tag_name = self.get_tag_by_id(tag_id) - for i in self.selected_tags: - temp[i] = self.selected_tags[i] - - temp[int(tag_id)] = tag_name - self.selected_tags = temp - - def add_image(self, image, file_id, only_thumbs=False): - """add single image to file/directory""" - imp = Img(image, self.image_path).save() - if imp: - # check if there is that image already - sql = """SELECT filename FROM images WHERE file_id=? and filename=?""" - self.db_cursor.execute(sql, (file_id, imp)) - res = self.db_cursor.fetchone() - if res and res[0]: - # there is such an image. going back. - if __debug__: - print res[0] - return - - # check if file have have thumbnail. if not, make it with first - # image - sql = """SELECT id FROM thumbnails WHERE file_id=?""" - self.db_cursor.execute(sql, (file_id,)) - res = self.db_cursor.fetchone() - thumb = 1 - if not(res and res[0]): - sql = """INSERT INTO thumbnails(filename, file_id) VALUES(?, ?)""" - self.db_cursor.execute(sql, (imp, file_id)) - - # insert picture into db - sql = """INSERT INTO images(file_id, filename) - VALUES(?, ?)""" - self.db_cursor.execute(sql, (file_id, imp)) - self.db_connection.commit() - - ## check if file have have thumbnail. if not, make it with image - #sql = """SELECT id from thumbnails where file_id=?""" - #self.db_cursor.execute(sql, (file_id,)) - #res = self.db_cursor.fetchone() - #if not res: - # sql = """insert into thumbnails(file_id, filename) - # values (?, ?)""" - # self.db_cursor.execute(sql, (file_id, thp)) - # self.db_connection.commit() - # - self.db_connection.commit() - - def del_images(self, file_id): - """removes images and their thumbnails from selected file/dir""" - ## remove images - #sql = """SELECT filename, thumbnail FROM images WHERE file_id =?""" - #self.db_cursor.execute(sql, (file_id,)) - #res = self.db_cursor.fetchall() - #if len(res) > 0: - # for filen in res: - # if filen[0]: - # os.unlink(os.path.join(self.internal_dirname, filen[0])) - # os.unlink(os.path.join(self.internal_dirname, filen[1])) - - # remove images records - sql = """DELETE FROM images WHERE file_id = ?""" - self.db_cursor.execute(sql, (file_id,)) - self.db_connection.commit() - - def save_image(self, image_id, file_path): - """save image with specified id into file path (directory)""" - sql = """SELECT i.filename, f.filename FROM images i - LEFT JOIN files f on i.file_id=f.id - WHERE i.id=?""" - self.db_cursor.execute(sql, (image_id,)) - res = self.db_cursor.fetchone() - if res and res[0]: - source = os.path.join(self.image_path, res[0]) - count = 1 - dest = os.path.join(file_path, res[1] + "_%04d." % count + 'jpg') - - while os.path.exists(dest): - count += 1 - dest = os.path.join(file_path, res[1] + "_%04d." %\ - count + 'jpg') - if not os.path.exists(source): - return False - shutil.copy(source, dest) - return True - else: - return False - - def delete_images_wth_thumbs(self, image_id): - """removes image (without thumbnail) on specified image id""" - print "method removed" - #sql = """SELECT filename FROM images WHERE id=?""" - #self.db_cursor.execute(sql, (image_id,)) - #res = self.db_cursor.fetchone() - #if res: - # if res[0]: - # os.unlink(os.path.join(self.internal_dirname, res[0])) - # - # if __debug__: - # print "m_main.py: delete_image(): removed images:" - # print res[0] - - # remove images records - #sql = """UPDATE images set filename=NULL WHERE id = ?""" - #self.db_cursor.execute(sql, (image_id,)) - #self.db_connection.commit() - - def delete_all_images_wth_thumbs(self): - """removes all images (without thumbnails) from collection""" - print "method removed" - #sql = """SELECT filename FROM images""" - #self.db_cursor.execute(sql) - #res = self.db_cursor.fetchall() - #for row in res: - # if row[0]: - # os.unlink(os.path.join(self.internal_dirname, row[0])) - # if __debug__: - # print "m_main.py: delete_all_images(): removed image:", - # print row[0] - - # remove images records - #sql = """UPDATE images set filename=NULL""" - #self.db_cursor.execute(sql) - #self.db_connection.commit() - - def delete_image(self, image_id): - """removes image on specified image id""" - #sql = """SELECT filename, thumbnail FROM images WHERE id=?""" - #self.db_cursor.execute(sql, (image_id,)) - #res = self.db_cursor.fetchone() - #if res: - # if res[0]: - # os.unlink(os.path.join(self.internal_dirname, res[0])) - # os.unlink(os.path.join(self.internal_dirname, res[1])) - # - # if __debug__: - # print "m_main.py: delete_image(): removed images:" - # print res[0] - # print res[1] - - # remove images records - sql = """DELETE FROM images WHERE id = ?""" - self.db_cursor.execute(sql, (image_id,)) - self.db_connection.commit() - - def set_image_as_thumbnail(self, image_id): - """set image as file thumbnail""" - sql = """SELECT file_id, filename FROM images WHERE id=?""" - self.db_cursor.execute(sql, (image_id,)) - res = self.db_cursor.fetchone() - if res and res[0]: - sql = """DELETE FROM thumbnails WHERE file_id=?""" - self.db_cursor.execute(sql, (res[0],)) - sql = """INSERT INTO thumbnails(file_id, filename) VALUES(?, ?)""" - self.db_cursor.execute(sql, (res[0], res[1])) - return True - return False - - def delete_all_images(self): - """removes all images from collection""" - # remove images records - sql = """DELETE FROM images""" - self.db_cursor.execute(sql) - self.db_connection.commit() - #try: - # shutil.rmtree(os.path.join(self.internal_dirname, 'images')) - #except: - # pass - - def add_thumbnail(self, img_fn, file_id): - """generate and add thumbnail to selected file/dir""" - if self.config.confd['thumbs']: - self.del_thumbnail(file_id) - im, exif = Thumbnail(img_fn, self.image_path).save() - - sql = """INSERT INTO thumbnails(file_id, filename) values (?, ?)""" - self.db_cursor.execute(sql, (file_id, im)) - self.db_connection.commit() - return True - return False - - def del_thumbnail(self, file_id): - """removes thumbnail from selected file/dir""" - # remove thumbnail files - #sql = """SELECT filename FROM thumbnails WHERE file_id=?""" - #self.db_cursor.execute(sql, (file_id,)) - #res = self.db_cursor.fetchone() - #if res: - # os.unlink(os.path.join(self.internal_dirname, res[0])) - - # remove thumbs records - sql = """DELETE FROM thumbnails WHERE file_id=?""" - self.db_cursor.execute(sql, (file_id,)) - self.db_connection.commit() - - def del_all_thumbnail(self): - """removes thumbnail from selected file/dir""" - # remove thumbs records - sql = """DELETE FROM thumbnails""" - self.db_cursor.execute(sql) - self.db_connection.commit() - #try: - # shutil.rmtree(os.path.join(self.internal_dirname, 'thumbnails')) - #except: - # pass - - def cleanup(self): - """remove temporary directory tree from filesystem""" - self.__close_db_connection() - try: - os.unlink(self.db_tmp_path) - except: - if __debug__: - print "Exception in removing temporary db file!" - pass - - #if self.internal_dirname != None: - # try: - # shutil.rmtree(self.internal_dirname) - # except OSError: - # pass - return - - def new(self): - """create new project""" - self.unsaved_project = False - self.filename = None - self.__create_temporary_db_file() - self.__connect_to_db() - self.__create_database() - self.__clear_trees() - self.clear_search_tree() - self.tag_cloud = [] - self.selected_tags = {} - return - - def save(self, filename=None): - """save tared directory at given catalog fielname""" - - # flush all changes - self.db_connection.commit() - - if not filename and not self.filename: - if __debug__: - return False, "no filename detected!" - return - - if filename: - if not '.sqlite' in filename: - filename += '.sqlite' - else: - filename = filename[:filename.rindex('.sqlite')] + '.sqlite' - - if self.config.confd['compress']: - filename += '.bz2' - - self.filename = filename - val, err = self.__compress_and_save() - if not val: - self.filename = None - return val, err - - def open(self, filename=None): - """try to open db file""" - self.unsaved_project = False - self.__create_temporary_db_file() - self.filename = filename - self.tag_cloud = [] - self.selected_tags = {} - self.clear_search_tree() - - try: - test_file = open(filename).read(15) - except IOError: - self.filename = None - self.internal_dirname = None - return False - - if test_file == "SQLite format 3": - db_tmp = open(self.db_tmp_path, "wb") - db_tmp.write(open(filename).read()) - db_tmp.close() - elif test_file[0:10] == "BZh91AY&SY": - open_file = bz2.BZ2File(filename) - try: - curdb = open(self.db_tmp_path, "w") - curdb.write(open_file.read()) - curdb.close() - open_file.close() - except IOError: - # file is not bz2 - self.filename = None - self.internal_dirname = None - return False - else: - self.filename = None - self.internal_dirname = None - return False - #try: - # tar = tarfile.open(filename, "r:gz") - #except: - # try: - # tar = tarfile.open(filename, "r") - # except: - # self.filename = None - # self.internal_dirname = None - # return - # - #os.chdir(self.internal_dirname) - #try: - # tar.extractall() - # if __debug__: - # print "m_main.py: extracted tarfile into", - # print self.internal_dirname - #except AttributeError: - # # python 2.4 tarfile module lacks of method extractall() - # directories = [] - # for tarinfo in tar: - # if tarinfo.isdir(): - # # Extract directory with a safe mode, so that - # # all files below can be extracted as well. - # try: - # os.makedirs(os.path.join('.', tarinfo.name), 0700) - # except EnvironmentError: - # pass - # directories.append(tarinfo) - # else: - # tar.extract(tarinfo, '.') - # - # # Reverse sort directories. - # directories.sort(lambda a, b: cmp(a.name, b.name)) - # directories.reverse() - # - # # Set correct owner, mtime and filemode on directories. - # for tarinfo in directories: - # try: - # os.chown(os.path.join('.', tarinfo.name), - # tarinfo.uid, tarinfo.gid) - # os.utime(os.path.join('.', tarinfo.name), - # (0, tarinfo.mtime)) - # except OSError: - # if __debug__: - # print "m_main.py: open(): setting corrext owner,", - # print "mtime etc" - #tar.close() - - self.__connect_to_db() - self.__fetch_db_into_treestore() - self.config.add_recent(filename) - self.get_tags() - return True - - def scan(self, path, label, currentid): - """scan files in separated thread""" - - # flush buffer to release db lock. - self.db_connection.commit() - - self.path = path - self.label = label - self.currentid = currentid - - if self.busy: - return - self.thread = _threading.Thread(target=self.__scan) - self.thread.start() - return - - def search(self, string): - """Get all children down from sepcified root""" - self.clear_search_tree() - id_filter = None - sql_con = "" - found = 0 - - if len(string) > 0: - args = self.__postgresize(string) - args = args.split() - for arg in args: - arg = "%" + arg + "%" - sql_con += "AND (LOWER(filename) LIKE LOWER('%s')" % arg - sql_con += " ESCAPE '\\' " - sql_con += "OR LOWER(description) LIKE LOWER('%s')" % arg - sql_con += " ESCAPE '\\' ) " - - # directories - if self.selected_tags: - # we have tags selected, live with that - id_filter = self.__filter2() - if id_filter != None: - if len(id_filter) == 1: - id_filter = "(%d)" % id_filter[0] - else: - id_filter = str(tuple(id_filter)) - sql = """SELECT id, filename, size, date FROM files - WHERE parent_id!=id - AND parent_id!=1 - AND type=1 - AND id in """ + id_filter + sql_con + """ - ORDER BY filename""" - - else: - # alright, search throught all records - sql = """SELECT id, filename, size, date FROM files - WHERE parent_id!=id - AND parent_id!=1 - AND type=1 """ + sql_con + """ - ORDER BY filename""" - - if sql: - self.db_cursor.execute(sql) - data = self.db_cursor.fetchall() - for row in data: - found += 1 - myiter = self.search_list.insert_before(None, None) - self.search_list.set_value(myiter, 0, row[0]) - self.search_list.set_value(myiter, 1, - self.__get_file_root(row[0])) - self.search_list.set_value(myiter, 2, row[1]) - self.search_list.set_value(myiter, 3, - self.__get_file_path(row[0])) - self.search_list.set_value(myiter, 4, row[2]) - self.search_list.set_value(myiter, 5, - datetime.fromtimestamp(row[3])) - self.search_list.set_value(myiter, 6, 1) - self.search_list.set_value(myiter, 7, gtk.STOCK_DIRECTORY) - - # files and links - if self.selected_tags: - if id_filter: - # we have tags selected, live with that - sql = """SELECT f.id, f.filename, f.size, f.date, f.type - FROM files f - WHERE f.type!=1 - AND parent_id!=1 AND id IN """ + id_filter + sql_con + """ - ORDER BY f.filename""" - else: - # alright, search throught all records - sql = """SELECT f.id, f.filename, f.size, f.date, f.type - FROM files f - WHERE f.type!=1 - AND parent_id!=1 """ + sql_con + """ - ORDER BY f.filename""" - - if sql: - self.db_cursor.execute(sql) - - data = self.db_cursor.fetchall() - for row in data: - found += 1 - myiter = self.search_list.insert_before(None, None) - self.search_list.set_value(myiter, 0, row[0]) - self.search_list.set_value(myiter, 1, - self.__get_file_root(row[0])) - self.search_list.set_value(myiter, 2, row[1]) - self.search_list.set_value(myiter, 3, - self.__get_file_path(row[0])) - self.search_list.set_value(myiter, 4, row[2]) - self.search_list.set_value(myiter, 5, - datetime.fromtimestamp(row[3])) - self.search_list.set_value(myiter, 6, row[4]) - if row[4] == self.FIL: - self.search_list.set_value(myiter, 7, gtk.STOCK_FILE) - elif row[4] == self.LIN: - self.search_list.set_value(myiter, 7, gtk.STOCK_INDEX) - - return found - - def rename(self, file_id, new_name=None): - """change name of selected object id""" - if new_name: - # do it in DB - self.db_cursor.execute("update files set filename=? \ - WHERE id=?", (new_name, file_id)) - self.db_connection.commit() - - for row in self.files_list: - if row[0] == file_id: - row[1] = new_name - break - - def foreach_discs_tree(model, path, iterator, data): - if model.get_value(iterator, 0) == data[0]: - model.set_value(iterator, 1, data[1]) - - self.discs_tree.foreach(foreach_discs_tree, (file_id, new_name)) - - #self.__fetch_db_into_treestore() - self.unsaved_project = True - else: - if __debug__: - print "m_main.py: rename(): no label defined" - return - - def refresh_discs_tree(self): - """re-fetch discs tree""" - self.__fetch_db_into_treestore() - - def get_root_entries(self, parent_id=None): - """Get all children down from sepcified root""" - self.__clear_files_tree() - # if we are in "tag" mode, do the boogie - # directories first - if not parent_id and self.selected_tags: - # no parent_id, get all the tagged dirs - id_filter = self.__filter2() - if id_filter != None: - if len(id_filter) == 1: - id_filter = "(%d)" % id_filter[0] - else: - id_filter = str(tuple(id_filter)) - sql = """SELECT id, filename, size, date FROM files - WHERE parent_id!=id AND type=1 AND id in """ + \ - id_filter + """ ORDER BY filename""" - else: - # we have parent_id, get all the tagged dirs with parent_id - if not self.selected_tags: - sql = """SELECT id, filename, size, date FROM files - WHERE parent_id=? AND type=1 - ORDER BY filename""" - else: - id_filter = self.__filter() - if id_filter != None: - sql = """SELECT id, filename, size, date FROM files - WHERE parent_id=? AND type=1 AND id in """ + \ - str(tuple(id_filter)) + """ ORDER BY filename""" - else: - sql="""SELECT id, filename, size, date FROM files - WHERE 1=0""" - - if not parent_id and self.selected_tags: - self.db_cursor.execute(sql) - else: - self.db_cursor.execute(sql, (parent_id,)) - - data = self.db_cursor.fetchall() - for row in data: - myiter = self.files_list.insert_before(None, None) - self.files_list.set_value(myiter, 0, row[0]) - self.files_list.set_value(myiter, 1, self.__get_file_root(row[0])) - self.files_list.set_value(myiter, 2, row[1]) - self.files_list.set_value(myiter, 3, self.__get_file_path(row[0])) - self.files_list.set_value(myiter, 4, row[2]) - self.files_list.set_value(myiter, 5, - datetime.fromtimestamp(row[3])) - self.files_list.set_value(myiter, 6, 1) - self.files_list.set_value(myiter, 7, gtk.STOCK_DIRECTORY) - - # all the rest - if not parent_id and self.selected_tags: - # no parent_id, get all the tagged files - if id_filter != None: - sql = """SELECT f.id, f.filename, f.size, f.date, f.type - FROM files f - WHERE f.type!=1 AND id IN """ + id_filter + \ - """ ORDER BY f.filename""" - else: - # we have parent_id, get all the tagged files with parent_id - if not self.selected_tags: - sql = """SELECT f.id, f.filename, f.size, f.date, f.type - FROM files f - WHERE f.parent_id=? AND f.type!=1 - ORDER BY f.filename""" - else: - if id_filter != None: - sql = """SELECT f.id, f.filename, f.size, f.date, f.type - FROM files f - WHERE f.parent_id=? AND f.type!=1 AND id IN """ + \ - str(tuple(id_filter)) + """ ORDER BY f.filename""" - else: - sql="""SELECT f.id, f.filename, f.size, f.date, f.type - FROM files f - WHERE 1=0""" - - if not parent_id and self.selected_tags: - self.db_cursor.execute(sql) - else: - self.db_cursor.execute(sql, (parent_id,)) - - data = self.db_cursor.fetchall() - for row in data: - myiter = self.files_list.insert_before(None, None) - self.files_list.set_value(myiter, 0, row[0]) - self.files_list.set_value(myiter, 1, self.__get_file_root(row[0])) - self.files_list.set_value(myiter, 2, row[1]) - self.files_list.set_value(myiter, 3, self.__get_file_path(row[0])) - self.files_list.set_value(myiter, 4, row[2]) - self.files_list.set_value(myiter, 5, - datetime.fromtimestamp(row[3])) - self.files_list.set_value(myiter, 6, row[4]) - if row[4] == self.FIL: - self.files_list.set_value(myiter, 7, gtk.STOCK_FILE) - elif row[4] == self.LIN: - self.files_list.set_value(myiter, 7, gtk.STOCK_INDEX) - return - - def get_parent_id(self, child_id): - """get root id from specified child""" - if child_id: - sql = """SELECT parent_id FROM files WHERE id=?""" - self.db_cursor.execute(sql, (child_id,)) - res = self.db_cursor.fetchone() - if res: - return res[0] - return None - - def get_file_info(self, file_id): - """get file info from database""" - retval = {} - sql = """SELECT f.filename, f.date, f.size, f.type, - f.description, f.note, t.filename - FROM files f - LEFT JOIN thumbnails t on f.id = t.file_id - WHERE f.id = ?""" - self.db_cursor.execute(sql, (file_id,)) - res = self.db_cursor.fetchone() - if res: - retval['fileinfo'] = {'id': file_id, - 'date': datetime.fromtimestamp(res[1]), - 'size': res[2], 'type': res[3]} - - retval['fileinfo']['disc'] = self.__get_file_root(file_id) - - retval['filename'] = res[0] - - if res[4]: - retval['description'] = res[4] - - if res[5]: - retval['note'] = res[5] - - if res[6]: - thumbfile = os.path.join(self.image_path, res[6] + "_t") - if os.path.exists(thumbfile): - pix = gtk.gdk.pixbuf_new_from_file(thumbfile) - retval['thumbnail'] = thumbfile - - sql = """SELECT id, filename FROM images - WHERE file_id = ?""" - self.db_cursor.execute(sql, (file_id,)) - res = self.db_cursor.fetchall() - if res: - self.images_store = gtk.ListStore(gobject.TYPE_INT, gtk.gdk.Pixbuf) - for im_id, filename in res: - thumbfile = os.path.join(self.image_path, filename + "_t") - if os.path.exists(thumbfile): - pix = gtk.gdk.pixbuf_new_from_file(thumbfile) - else: - pix = gtk.gdk.pixbuf_new_from_inline(len(no_thumb_img), - no_thumb_img, False) - self.images_store.append([im_id, pix]) - retval['images'] = True - - sql = """SELECT camera, date, aperture, exposure_program, - exposure_bias, iso, focal_length, subject_distance, metering_mode, - flash, light_source, resolution, orientation - FROM exif - WHERE file_id = ?""" - self.db_cursor.execute(sql, (file_id,)) - res = self.db_cursor.fetchone() - - if res: - self.exif_list = gtk.ListStore(gobject.TYPE_STRING, - gobject.TYPE_STRING) - for key in self.EXIF_DICT: - myiter = self.exif_list.insert_before(None, None) - self.exif_list.set_value(myiter, 0, self.EXIF_DICT[key]) - self.exif_list.set_value(myiter, 1, res[key]) - retval['exif'] = True - - # gthumb - sql = """SELECT note, place, date FROM gthumb WHERE file_id = ?""" - self.db_cursor.execute(sql, (file_id,)) - res = self.db_cursor.fetchone() - - if res: - retval['gthumb'] = {'note': res[0], - 'place': res[1], - 'date': res[2]} - - return retval - - def get_source(self, path): - """get source of top level directory""" - bid = self.discs_tree.get_value(self.discs_tree.get_iter(path[0]), 0) - sql = """SELECT source FROM files WHERE id = ?""" - self.db_cursor.execute(sql, - (bid,)) - res = self.db_cursor.fetchone() - if res == None: - return False - return int(res[0]) - - def get_label_and_filepath(self, path): - """get source of top level directory""" - bid = self.discs_tree.get_value(self.discs_tree.get_iter(path), 0) - sql = """SELECT filepath, filename FROM files - WHERE id = ? AND parent_id = 1""" - self.db_cursor.execute(sql, (bid,)) - res = self.db_cursor.fetchone() - if res == None: - return None, None - return res[0], res[1] - - def delete(self, root_id, db_cursor=None, db_connection=None): - """Remove subtree (item and its children) from main tree, remove tags - from database remove all possible data, like thumbnails, images, gthumb - info, exif etc""" - - fids = [] - - if not db_cursor: - db_cursor = self.db_cursor - - if not db_connection: - db_connection = self.db_connection - - sql = """SELECT parent_id FROM files WHERE id = ?""" - db_cursor.execute(sql, (root_id,)) - res = db_cursor.fetchone() - parent_id = res[0] - - def get_children(fid): - """get children of specified id""" - fids.append(fid) - sql = """SELECT id FROM files where parent_id = ?""" - db_cursor.execute(sql, (fid,)) - res = db_cursor.fetchall() - if len(res)>0: - for i in res: - get_children(i[0]) - - get_children(root_id) - - def generator(): - """simple generator for use in executemany() function""" - for field in fids: - yield (field,) - - # remove files records - sql = """DELETE FROM files WHERE id = ?""" - db_cursor.executemany(sql, generator()) - - # remove tags records - sql = """DELETE FROM tags_files WHERE file_id = ?""" - db_cursor.executemany(sql, generator()) - - if __debug__: - print "m_main.py: delete(): deleting:", fids - - if len(fids) == 1: - arg = "(%d)" % fids[0] - else: - arg = str(tuple(fids)) - - # remove thumbnails - #sql = """SELECT filename FROM thumbnails WHERE file_id IN %s""" % arg - #db_cursor.execute(sql) - #res = db_cursor.fetchall() - #if len(res) > 0: - # for row in res: - # os.unlink(os.path.join(self.image_path, row[0])) - - # remove images - #sql = """SELECT filename, thumbnail FROM images - # WHERE file_id IN %s""" % arg - #db_cursor.execute(sql) - #res = db_cursor.fetchall() - #if len(res) > 0: - # for row in res: - # if row[0]: - # os.unlink(os.path.join(self.internal_dirname, row[0])) - # os.unlink(os.path.join(self.internal_dirname, row[1])) - - # remove thumbs records - sql = """DELETE FROM thumbnails WHERE file_id = ?""" - db_cursor.executemany(sql, generator()) - - # remove images records - sql = """DELETE FROM images WHERE file_id = ?""" - db_cursor.executemany(sql, generator()) - - # remove gthumb info - sql = """DELETE FROM gthumb WHERE file_id = ?""" - db_cursor.executemany(sql, generator()) - - # correct parent direcotry sizes - # get size and parent of deleting object - while parent_id: - sql = """UPDATE files SET size = - (SELECT CASE WHEN - SUM(size) IS null - THEN - 0 - ELSE - SUM(size) - END - FROM files WHERE parent_id=?) - WHERE id=?""" - db_cursor.execute(sql, (parent_id, parent_id)) - - sql = """SELECT parent_id FROM files - WHERE id = ? AND parent_id!=id""" - db_cursor.execute(sql, (parent_id,)) - res = db_cursor.fetchone() - if res: - parent_id = res[0] - else: - parent_id = False - - db_connection.commit() - - # part two: remove items from treestore/liststores - def foreach_treestore(model, path, iterator, d): - if d[0] == model.get_value(iterator, 0): - d[1].append(path) - return False - - paths = [] - self.discs_tree.foreach(foreach_treestore, (root_id, paths)) - for path in paths: - self.discs_tree.remove(self.discs_tree.get_iter(path)) - - paths = [] - self.files_list.foreach(foreach_treestore, (root_id, paths)) - for path in paths: - self.files_list.remove(self.files_list.get_iter(path)) - - paths = [] - self.search_list.foreach(foreach_treestore, (root_id, paths)) - for path in paths: - self.search_list.remove(self.search_list.get_iter(path)) - return - - def get_stats(self, selected_id): - """get statistic information""" - retval = {} - if selected_id: - sql = """SELECT id, type, parent_id FROM files WHERE id=?""" - self.db_cursor.execute(sql, (selected_id,)) - res = self.db_cursor.fetchone() - if not res: - return retval - - selected_item = {'id':res[0], 'type':res[1], 'parent': res[2]} - - # collect all parent_id's - parents = [] - - def _recurse(fid): - """recursive gather direcotories ids and store it in list""" - parents.append(fid) - sql = """SELECT id FROM files - WHERE type=? AND parent_id=? AND parent_id!=1""" - self.db_cursor.execute(sql, (self.DIR, fid)) - res = self.db_cursor.fetchall() - if res: - for row in res: - _recurse(row[0]) - _recurse(selected_id) - - if selected_item['parent'] == 1: - parents.pop(0) - retval['discs'] = 1 - retval['dirs'] = len(parents) - - parents.append(selected_id) - - files_count = 0 - - for parent in parents: - sql = """SELECT count(id) FROM files - WHERE type!=0 AND type!=1 AND parent_id=?""" - self.db_cursor.execute(sql, (parent,)) - res = self.db_cursor.fetchone() - if res: - files_count += res[0] - retval['files'] = files_count - sql = """SELECT size FROM files WHERE id=?""" - self.db_cursor.execute(sql, (selected_id,)) - res = self.db_cursor.fetchone() - if res: - retval['size'] = self.__bytes_to_human(res[0]) - else: - sql = """SELECT count(id) FROM files - WHERE parent_id=1 AND type=1""" - self.db_cursor.execute(sql) - res = self.db_cursor.fetchone() - if res: - retval['discs'] = res[0] - - sql = """SELECT count(id) FROM files - WHERE parent_id!=1 AND type=1""" - self.db_cursor.execute(sql) - res = self.db_cursor.fetchone() - if res: - retval['dirs'] = res[0] - - sql = """SELECT count(id) FROM files - WHERE parent_id!=1 AND type!=1""" - self.db_cursor.execute(sql) - res = self.db_cursor.fetchone() - if res: - retval['files'] = res[0] - - sql = """SELECT sum(size) FROM files - WHERE parent_id=1 AND type=1""" - self.db_cursor.execute(sql) - res = self.db_cursor.fetchone() - if res: - retval['size'] = self.__bytes_to_human(res[0]) - return retval - - def get_image_path(self, img_id): - """return image location""" - sql = """SELECT filename FROM images WHERE id=?""" - self.db_cursor.execute(sql, (img_id,)) - res = self.db_cursor.fetchone() - if res and res[0]: - path = os.path.join(self.image_path, res[0]) - if os.path.exists(path): - return path - return None - - def update_desc_and_note(self, file_id, desc='', note=''): - """update note and description""" - sql = """UPDATE files SET description=?, note=? WHERE id=?""" - self.db_cursor.execute(sql, (desc, note, file_id)) - self.db_connection.commit() - return - - def clear_search_tree(self): - """try to clear store for search""" - try: - self.search_list.clear() - except: - pass - - # private class functions - def __fill_history_model(self): - """fill search history model with config dict""" - try: - self.search_history.clear() - except: - pass - - for entry in self.config.search_history: - myiter = self.search_history.insert_before(None, None) - self.search_history.set_value(myiter, 0, entry) - return - - def __get_file_root(self, file_id): - """return string with root (disc name) of selected banch (file_id)""" - sql = """SELECT parent_id FROM files WHERE id=? and parent_id!=1""" - self.db_cursor.execute(sql, (file_id,)) - res = self.db_cursor.fetchone() - - root_id = None - while res: - root_id = res[0] - self.db_cursor.execute(sql, (res[0],)) - res = self.db_cursor.fetchone() - - sql = """SELECT filename FROM files WHERE id=?""" - self.db_cursor.execute(sql, (root_id,)) - res = self.db_cursor.fetchone() - - if res: - return res[0] - else: - return None - - def __get_file_path(self, file_id): - """return string with path from the root of the disc""" - #SQL: get parent id and filename to concatenate path - path = "" - sql = """SELECT parent_id FROM files WHERE id=? AND parent_id!=1""" - self.db_cursor.execute(sql, (file_id,)) - res = self.db_cursor.fetchone() - if not res: - return "/" - - while res: - sql = """SELECT id, filename FROM files - WHERE id=? AND parent_id!=1""" - self.db_cursor.execute(sql, (res[0],)) - res = self.db_cursor.fetchone() - if res: - path = res[1] + "/" + path - sql = """SELECT parent_id FROM files - WHERE id=? AND id!=parent_id""" - self.db_cursor.execute(sql, (res[0],)) - res = self.db_cursor.fetchone() - - return "/" + path - - def __bytes_to_human(self, integer): - """return integer digit in human readable string representation""" - if integer <= 0 or integer < 1024: - return "%d bytes" % integer - - ## convert integer into string with thousands' separator - #for i in range(len(str(integer))/3+1): - # if i == 0: - # s_int = str(integer)[-3:] - # else: - # s_int = str(integer)[-(3*int(i)+3):-(3*int(i))] + " " + s_int - power = None - temp = integer - for power in ['kB', 'MB', 'GB', 'TB']: - temp = temp /1024.0 - if temp < 1 or temp < 1024: - break - return "%0.2f %s (%d bytes)" % (temp, power, integer) - - def __clear_trees(self): - """clears treemodel and treestore of files and discs tree""" - self.__clear_files_tree() - self.__clear_discs_tree() - self.clear_search_tree() - - def __clear_discs_tree(self): - """try to clear model for discs""" - try: - self.discs_tree.clear() - except: - pass - - def __clear_files_tree(self): - """try to clear store for files/directories""" - try: - self.files_list.clear() - except: - pass - - def __connect_to_db(self): - """initialize db connection and store it in class attributes""" - self.db_connection = sqlite.connect(self.db_tmp_path, - detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES) - self.db_cursor = self.db_connection.cursor() - return - - def __close_db_connection(self): - """close db conection""" - - if self.db_cursor != None: - self.db_cursor.close() - self.db_cursor = None - if self.db_connection != None: - self.db_connection.close() - self.db_connection = None - return - - def __create_temporary_db_file(self): - """create temporary db file""" - self.cleanup() - self.db_tmp_path = mkstemp()[1] - return - - def __compress_and_save(self): - """create (and optionaly compress) tar archive from working directory - and write it to specified file""" - - # flush all changes - self.db_connection.commit() - - try: - if self.config.confd['compress']: - output_file = bz2.BZ2File(self.filename, "w") - else: - output_file = open(self.filename, "w") - if __debug__: - print "m_main.py: __compress_and_save(): tar open successed" - - except IOError, (errno, strerror): - return False, strerror - - dbpath = open(self.db_tmp_path) - output_file.write(dbpath.read()) - dbpath.close() - output_file.close() - - self.unsaved_project = False - return True, None - - def __create_database(self): - """make all necessary tables in db file - - - ,------------. ,------------. - |files | |tags | - +------------+ +------------+ - |→|pk id | |pk id | - |_|fk parent_id| |fk group_id | - |filename | |tag | - |filepath | +------------+ - |date | - |size | ,------------ - |source | |tags_files - |note | | - |description | | - +------------+ | - - - - - - - - - - - """ - self.db_cursor.execute("""create table - files(id INTEGER PRIMARY KEY AUTOINCREMENT, - parent_id INTEGER, - filename TEXT, - filepath TEXT, - date datetime, - size INTEGER, - type INTEGER, - source INTEGER, - note TEXT, - description TEXT);""") - self.db_cursor.execute("""create table - tags(id INTEGER PRIMARY KEY AUTOINCREMENT, - group_id INTEGER, - tag TEXT);""") - self.db_cursor.execute("""create table - tags_files(file_id INTEGER, - tag_id INTEGER);""") - self.db_cursor.execute("""create table - groups(id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT, - color TEXT);""") - self.db_cursor.execute("""create table - thumbnails(id INTEGER PRIMARY KEY AUTOINCREMENT, - file_id INTEGER, - filename TEXT);""") - self.db_cursor.execute("""create table - images(id INTEGER PRIMARY KEY AUTOINCREMENT, - file_id INTEGER, - filename TEXT);""") - self.db_cursor.execute("""create table - exif(id INTEGER PRIMARY KEY AUTOINCREMENT, - file_id INTEGER, - camera TEXT, - date TEXT, - aperture TEXT, - exposure_program TEXT, - exposure_bias TEXT, - iso TEXT, - focal_length TEXT, - subject_distance TEXT, - metering_mode TEXT, - flash TEXT, - light_source TEXT, - resolution TEXT, - orientation TEXT);""") - self.db_cursor.execute("""create table - gthumb(id INTEGER PRIMARY KEY AUTOINCREMENT, - file_id INTEGER, - note TEXT, - place TEXT, - date datetime);""") - sql = """INSERT INTO files - VALUES(1, 1, 'root', null, 0, 0, 0, 0, null, null)""" - self.db_cursor.execute(sql) - sql = """INSERT INTO groups VALUES(1, 'default', 'black')""" - self.db_cursor.execute(sql) - self.db_connection.commit() - - def __filter(self): - """return list of ids of files (AND their parent, even if they have no - assigned tags) that corresponds to tags""" - - filtered_ids = [] - count = 0 - for tid in self.selected_tags: - temp1 = [] - sql = """SELECT file_id - FROM tags_files - WHERE tag_id=? """ - self.db_cursor.execute(sql, (tid, )) - data = self.db_cursor.fetchall() - for row in data: - temp1.append(row[0]) - - if count > 0: - filtered_ids = list(set(filtered_ids).intersection(temp1)) - else: - filtered_ids = temp1 - count += 1 - - parents = [] - for i in filtered_ids: - sql = """SELECT parent_id - FROM files - WHERE id = ?""" - self.db_cursor.execute(sql, (i, )) - data = self.db_cursor.fetchone() - if data: - parents.append(data[0]) - while True: - sql = """SELECT parent_id - FROM files - WHERE id = ? and id!=parent_id""" - self.db_cursor.execute(sql, (data[0], )) - data = self.db_cursor.fetchone() - if not data: - break - else: - parents.append(data[0]) - - return list(set(parents).union(filtered_ids)) - - def __filter2(self): - """return list of ids of files (WITHOUT their parent) that - corresponds to tags""" - - filtered_ids = [] - count = 0 - for tid in self.selected_tags: - temp1 = [] - sql = """SELECT file_id - FROM tags_files - WHERE tag_id=? """ - self.db_cursor.execute(sql, (tid, )) - data = self.db_cursor.fetchall() - for row in data: - temp1.append(row[0]) - - if count > 0: - filtered_ids = list(set(filtered_ids).intersection(temp1)) - else: - filtered_ids = temp1 - count += 1 - - return filtered_ids - - def __scan(self): - """scan content of the given path""" - self.busy = True - - # new conection for this task, because it's running in separate thread - db_connection = sqlite.connect(self.db_tmp_path, - detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES, - isolation_level="EXCLUSIVE") - db_cursor = db_connection.cursor() - - timestamp = datetime.now() - - # count files in directory tree - count = 0 - self.statusmsg = "Calculating number of files in directory tree..." - - count = 0 - try: - for root, dirs, files in os.walk(self.path): - count += len(files) - except: - if __debug__: - print 'm_main.py: os.walk in %s' % self.path - pass - - if count > 0: - step = 1.0/count - else: - step = 1.0 - - self.count = 0 - - # guess filesystem encoding - self.fsenc = sys.getfilesystemencoding() - - self.fresh_disk_iter = None - - def __recurse(parent_id, name, path, date, size, filetype, - discs_tree_iter=None): - """recursive scans given path""" - if self.abort: - return -1 - - _size = size - - myit = self.discs_tree.append(discs_tree_iter, None) - - if parent_id == 1: - self.fresh_disk_iter = myit - self.discs_tree.set_value(myit, 2, gtk.STOCK_CDROM) - sql = """INSERT INTO - files(parent_id, filename, filepath, date, - size, type, source) - VALUES(?,?,?,?,?,?,?)""" - db_cursor.execute(sql, (parent_id, name, path, date, size, - filetype, self.source)) - else: - self.discs_tree.set_value(myit, 2, gtk.STOCK_DIRECTORY) - sql = """INSERT INTO - files(parent_id, filename, filepath, date, size, type) - VALUES(?,?,?,?,?,?)""" - db_cursor.execute(sql, (parent_id, name, path, - date, size, filetype)) - - sql = """SELECT seq FROM sqlite_sequence WHERE name='files'""" - db_cursor.execute(sql) - currentid = db_cursor.fetchone()[0] - - self.discs_tree.set_value(myit, 0, currentid) - self.discs_tree.set_value(myit, 1, name) - self.discs_tree.set_value(myit, 3, parent_id) - - try: - root, dirs, files = os.walk(path).next() - except: - if __debug__: - print "m_main.py: cannot access ", path - #return -1 - return 0 - - ############# - # directories - for i in dirs: - j = self.__decode_filename(i) - current_dir = os.path.join(root, i) - - try: - st = os.stat(current_dir) - st_mtime = st.st_mtime - except OSError: - st_mtime = 0 - - # do NOT follow symbolic links - if os.path.islink(current_dir): - l = self.__decode_filename(os.readlink(current_dir)) - - sql = """INSERT INTO - files(parent_id, filename, filepath, date, size, type) - VALUES(?,?,?,?,?,?)""" - db_cursor.execute(sql, (currentid, j + " -> " + l, - current_dir, st_mtime, 0, - self.LIN)) - dirsize = 0 - else: - dirsize = __recurse(currentid, j, current_dir, - st_mtime, 0, self.DIR, myit) - - if dirsize == -1: - break - else: - _size = _size + dirsize - - ######## - # files: - for i in files: - if self.abort: - break - - self.count = self.count + 1 - current_file = os.path.join(root, i) - - try: - st = os.stat(current_file) - st_mtime = st.st_mtime - st_size = st.st_size - except OSError: - st_mtime = 0 - st_size = 0 - - _size = _size + st_size - j = self.__decode_filename(i) - - # do NOT follow symbolic links - if os.path.islink(current_file): - l = self.__decode_filename(os.readlink(current_file)) - sql = """INSERT INTO - files(parent_id, filename, filepath, date, size, type) - VALUES(?,?,?,?,?,?)""" - db_cursor.execute(sql, (currentid, j + " -> " + l, - current_file, st_mtime, 0, - self.LIN)) - else: - sql = """INSERT INTO - files(parent_id, filename, filepath, date, size, type) - VALUES(?,?,?,?,?,?)""" - db_cursor.execute(sql, (currentid, j, current_file, - st_mtime, st_size, self.FIL)) - - if self.count % 32 == 0: - update = True - else: - update = False - - ########################### - # fetch details about files - if self.config.confd['retrive']: - update = True - exif = None - - sql = """SELECT seq FROM sqlite_sequence - WHERE name='files'""" - db_cursor.execute(sql) - fileid = db_cursor.fetchone()[0] - - ext = i.split('.')[-1].lower() - - # Video - if ext in self.MOV: - #import rpdb2; rpdb2.start_embedded_debugger('pass') - v = Video(current_file) - cfn = v.capture() - img = Img(cfn, self.image_path) - th = img.save() - if th: - sql = """INSERT INTO - thumbnails(file_id, filename) - VALUES(?, ?)""" - db_cursor.execute(sql, (fileid, th+"_t")) - sql = """INSERT INTO images(file_id, filename) - VALUES(?, ?)""" - db_cursor.execute(sql, (fileid, th)) - os.unlink(cfn) - - # Images - thumbnails and exif data - if self.config.confd['thumbs'] and ext in self.IMG: - thumb = Thumbnail(current_file, self.image_path) - th, exif = thumb.save() - if th: - sql = """INSERT INTO - thumbnails(file_id, filename) - VALUES(?, ?)""" - db_cursor.execute(sql, (fileid, th)) - - # exif - store data in exif table - jpg = ['jpg', 'jpeg'] - if self.config.confd['exif'] and ext in jpg: - p = None - if self.config.confd['thumbs'] and exif: - p = ParseExif(exif_dict=exif) - else: - p = ParseExif(exif_file=current_file) - if not p.exif_dict: - p = None - if p: - p = p.parse() - p = list(p) - p.insert(0, fileid) - sql = """INSERT INTO exif (file_id, - camera, - date, - aperture, - exposure_program, - exposure_bias, - iso, - focal_length, - subject_distance, - metering_mode, - flash, - light_source, - resolution, - orientation) - values(?,?,?,?,?,?,?,?,?,?,?,?,?,?)""" - db_cursor.execute(sql, (tuple(p))) - - # gthumb - save comments from gThumb program - if self.config.confd['gthumb']: - gt = GthumbCommentParser(root, i) - cmnts = gt.parse() - if cmnts: - sql = """insert into gthumb(file_id, - note, - place, - date) - values(?,?,?,?)""" - db_cursor.execute(sql, (fileid, - cmnts['note'], - cmnts['place'], - cmnts['date'])) - if 'keywords' in cmnts: - # TODO: add gthumb keywords to tags - pass - - # Extensions - user defined actions - if ext in self.config.confd['extensions'].keys(): - cmd = self.config.confd['extensions'][ext] - arg = current_file.replace('"', '\\"') - output = os.popen(cmd % arg).readlines() - desc = '' - for line in output: - desc += line - - sql = """UPDATE files SET description=? - WHERE id=?""" - db_cursor.execute(sql, (desc, fileid)) - - ### end of scan - if update: - self.statusmsg = "Scannig: %s" % current_file - self.progress = step * self.count - - sql = """UPDATE files SET size=? WHERE id=?""" - db_cursor.execute(sql, (_size, currentid)) - if self.abort: - return -1 - else: - return _size - - if __recurse(1, self.label, self.path, 0, 0, self.DIR) == -1: - if __debug__: - print "m_main.py: __scan() __recurse()", - print "interrupted self.abort = True" - self.discs_tree.remove(self.fresh_disk_iter) - db_cursor.close() - db_connection.rollback() - else: - if __debug__: - print "m_main.py: __scan() __recurse() goes without interrupt" - if self.currentid: - if __debug__: - print "m_main.py: __scan() removing old branch" - self.statusmsg = "Removing old branch..." - self.delete(self.currentid, db_cursor, db_connection) - - self.currentid = None - - db_cursor.close() - db_connection.commit() - db_connection.close() - if __debug__: - print "m_main.py: __scan() time: ", (datetime.now() - timestamp) - - self.busy = False - - # refresh discs tree - self.__fetch_db_into_treestore() - self.statusmsg = "Idle" - self.progress = 0 - self.abort = False - - def __fetch_db_into_treestore(self): - """load data from DB to tree model""" - # cleanup treeStore - self.__clear_discs_tree() - - #connect - detect_types = sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES - db_connection = sqlite.connect(self.db_tmp_path, - detect_types = detect_types) - db_cursor = db_connection.cursor() - - # fetch all the directories - if not self.selected_tags: - sql = """SELECT id, parent_id, filename FROM files - WHERE type=1 ORDER BY parent_id, filename""" - else: - id_filter = self.__filter() - if id_filter != None: - sql = """SELECT id, parent_id, filename FROM files - WHERE type=1 and id in """ + str(tuple(id_filter)) \ - + """ ORDER BY parent_id, filename""" - else: - sql="""SELECT id, parent_id, filename FROM files - WHERE 1=0""" - db_cursor.execute(sql) - data = db_cursor.fetchall() - - def get_children(parent_id = 1, iterator = None): - """fetch all children and place them in model""" - for row in data: - if row[1] == parent_id: - myiter = self.discs_tree.insert_before(iterator, None) - self.discs_tree.set_value(myiter, 0, row[0]) # id - self.discs_tree.set_value(myiter, 1, row[2]) # name - self.discs_tree.set_value(myiter, 3, row[1]) # parent_id - get_children(row[0], myiter) - - # isroot? - if iterator == None: - self.discs_tree.set_value(myiter, 2, gtk.STOCK_CDROM) - else: - self.discs_tree.set_value(myiter, 2, - gtk.STOCK_DIRECTORY) - return - - if __debug__: - start_date = datetime.now() - # launch scanning. - get_children() - if __debug__: - print "m_main.py: __fetch_db_into_treestore()", - print "tree generation time: ", (datetime.now() - start_date) - db_connection.close() - return - - def __append_added_volume(self): - """append branch from DB to existing tree model""" - #connect - detect_types = sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES - db_connection = sqlite.connect(self.db_tmp_path, - detect_types = detect_types) - db_cursor = db_connection.cursor() - - sql = """SELECT id, parent_id, filename FROM files - WHERE type=1 ORDER BY parent_id, filename""" - db_cursor.execute(sql) - data = db_cursor.fetchall() - - def get_children(parent_id = 1, iterator = None): - """fetch all children and place them in model""" - for row in data: - if row[1] == parent_id: - myiter = self.discs_tree.insert_before(iterator, None) - self.discs_tree.set_value(myiter, 0, row[0]) - self.discs_tree.set_value(myiter, 1, row[2]) - self.discs_tree.set_value(myiter, 3, row[1]) - get_children(row[0], myiter) - - # isroot? - if iterator == None: - self.discs_tree.set_value(myiter, 2, gtk.STOCK_CDROM) - else: - self.discs_tree.set_value(myiter, 2, - gtk.STOCK_DIRECTORY) - return - - if __debug__: - start_date = datetime.now() - # launch scanning. - get_children() - if __debug__: - print "m_main.py: __append_added_volume() tree generation time: ", - print datetime.now() - start_date - db_connection.close() - return - - def __decode_filename(self, txt): - """decode filename with encoding taken form ENV, returns unicode - string""" - if self.fsenc: - return txt.decode(self.fsenc) - else: - return txt - - def __postgresize(self, string): - """escape sql characters, return escaped string""" - name = string.replace("\\","\\\\") - name = name.replace('%','\%') - name = name.replace('_','\_') - name = name.replace("'","''") - - # special characters ? and * convert to sql sepcial characters _ and % - #name = name.replace('*','%') - #name = name.replace('?','_') - - return name - diff --git a/src/views/__init__.py b/src/views/__init__.py deleted file mode 100644 index 5e9f6f7..0000000 --- a/src/views/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - - diff --git a/src/views/v_config.py b/src/views/v_config.py deleted file mode 100644 index a84653f..0000000 --- a/src/views/v_config.py +++ /dev/null @@ -1,36 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - -from gtkmvc import View -import os.path -import lib.globs - -class ConfigView(View): - """Preferences window from glade file """ - GLADE = os.path.join(lib.globs.GLADE_DIR, "config.glade") - - def __init__(self, ctrl): - View.__init__(self, ctrl, self.GLADE) - return - pass # end of class diff --git a/src/views/v_dialogs.py b/src/views/v_dialogs.py deleted file mode 100644 index 7ca075a..0000000 --- a/src/views/v_dialogs.py +++ /dev/null @@ -1,653 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - -import gtk -import gobject -import os -import lib.globs - -class Qst(object): - """Show simple dialog for questions - if "OK" button pressed, return "True" - "Cancel" button return "False" - """ - - def __init__(self, title="", message="", secondarymsg=""): - self.dialog = gtk.MessageDialog( - flags = gtk.DIALOG_DESTROY_WITH_PARENT, - type = gtk.MESSAGE_QUESTION, - buttons = gtk.BUTTONS_OK_CANCEL, - message_format = message, - ) - self.dialog.set_title(title) - self.dialog.format_secondary_text(secondarymsg) - - def run(self): - retval = self.dialog.run() - self.dialog.destroy() - if retval == gtk.RESPONSE_OK: - return True - return False - -class Inf(object): - """Show simple dialog for notices""" - - def __init__(self, title="", message="", secondarymsg=""): - self.dialog = gtk.MessageDialog( - flags = gtk.DIALOG_DESTROY_WITH_PARENT, - type = gtk.MESSAGE_INFO, - buttons = gtk.BUTTONS_OK, - message_format = message, - ) - self.dialog.set_title(title) - self.dialog.format_secondary_text(secondarymsg) - self.dialog.connect('response', - lambda dialog, response: self.ret(response)) - self.dialog.show() - - def ret(self,result): - self.dialog.destroy() - return True - -class Wrn(object): - """Show simple dialog for warnings""" - - def __init__(self, title="", message="", secondarymsg=""): - self.dialog = gtk.MessageDialog( - flags = gtk.DIALOG_DESTROY_WITH_PARENT, - type = gtk.MESSAGE_WARNING, - buttons = gtk.BUTTONS_CLOSE, - message_format = message, - ) - self.dialog.set_title(title) - self.dialog.format_secondary_text(secondarymsg) - self.dialog.connect('response', - lambda dialog, response: self.ret(response)) - self.dialog.show() - - def ret(self,result): - self.dialog.destroy() - return True - -class Err(object): - """Show simple dialog for errors""" - - def __init__(self, title="", message="", secondarymsg=""): - self.dialog = gtk.MessageDialog( - flags = gtk.DIALOG_DESTROY_WITH_PARENT, - type = gtk.MESSAGE_ERROR, - buttons = gtk.BUTTONS_CLOSE, - message_format = message) - - self.dialog.set_title(title) - self.dialog.format_secondary_text(secondarymsg) - self.dialog.connect('response', - lambda dialog, response: self.ret(response)) - self.dialog.run() - - def ret(self,result): - self.dialog.destroy() - return True - -class Abt(object): - """Show simple about dialog""" - - def __init__(self, name=None, ver="", title="", authors=[],licence=""): - self.dialog = gtk.AboutDialog() - self.dialog.set_title(title) - self.dialog.set_version(ver) - self.dialog.set_license(licence) - self.dialog.set_name(name) - self.dialog.set_authors(authors) - self.dialog.connect('response', - lambda dialog, response: self.dialog.destroy()) - self.dialog.show() - -class InputDiskLabel(object): - """Sepcific dialog for quering user for a disc label""" - - def __init__(self, label=""): - self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade") - self.label = "" - if label!= None: - self.label = label - - def run(self): - gladexml = gtk.glade.XML(self.gladefile, "inputDialog") - dialog = gladexml.get_widget("inputDialog") - entry = gladexml.get_widget("volname") - entry.set_text(self.label) - result = dialog.run() - dialog.destroy() - if result == gtk.RESPONSE_OK: - return entry.get_text() - return None - -class InputNewName(object): - """Sepcific dialog for quering user for a disc label""" - - def __init__(self, name=""): - self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade") - self.label = "" - self.name = name - - def run(self): - gladexml = gtk.glade.XML(self.gladefile, "renameDialog") - dialog = gladexml.get_widget("renameDialog") - entry = gladexml.get_widget("name") - entry.set_text(self.name) - result = dialog.run() - dialog.destroy() - if result == gtk.RESPONSE_OK: - return entry.get_text() - return None - -class PointDirectoryToAdd(object): - """Sepcific dialog for quering user for selecting directory to add""" - - URI="file://"+os.path.abspath(os.path.curdir) - - def __init__(self,volname='',dirname=''): - self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade") - self.gladexml = gtk.glade.XML(self.gladefile, "addDirDialog") - self.volname = self.gladexml.get_widget("dirvolname") - self.volname.set_text(volname) - self.directory = self.gladexml.get_widget("directory") - self.directory.set_text(dirname) - sigs = {"on_browse_activate":self.show_dirchooser, - "on_browse_clicked":self.show_dirchooser} - self.gladexml.signal_autoconnect(sigs) - - def show_dirchooser(self,widget): - """dialog for point the mountpoint""" - dialog = gtk.FileChooserDialog( - title="Choose directory to add", - action=gtk.FILE_CHOOSER_ACTION_OPEN, - buttons=( - gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, - gtk.RESPONSE_OK)) - - dialog.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - dialog.set_default_response(gtk.RESPONSE_OK) - - if self.URI: - dialog.set_current_folder_uri(self.URI) - response = dialog.run() - if response == gtk.RESPONSE_OK: - self.directory.set_text(dialog.get_filename()) - self.__class__.URI = dialog.get_current_folder_uri() - dialog.destroy() - - def run(self): - dialog = self.gladexml.get_widget("addDirDialog") - ch = True - result = dialog.run() - while ch: - if result == gtk.RESPONSE_OK and (self.volname.get_text()=='' or \ - self.directory.get_text() == ''): - a = Err("Error - pyGTKtalog", - "There are fields needed to be filled.", - "Cannot add directory without path and disc label.") - ch = True - result = dialog.run() - else: - ch = False - dialog.destroy() - if result == gtk.RESPONSE_OK: - return self.volname.get_text(),self.directory.get_text() - else: - return None,None - -class SelectDirectory(object): - """Sepcific dialog for quering user for selecting directory to add""" - - URI="file://"+os.path.abspath(os.path.curdir) - - def __init__(self, title=None): - if title: - self.title = title - else: - self.title = "Choose directory" - - def run(self): - """dialog for point the mountpoint""" - dialog = gtk.FileChooserDialog( - title = self.title, - action = gtk.FILE_CHOOSER_ACTION_OPEN, - buttons = ( - gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, - gtk.RESPONSE_OK)) - - dialog.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - dialog.set_default_response(gtk.RESPONSE_OK) - - retval = None - - if self.URI: - dialog.set_current_folder_uri(self.URI) - response = dialog.run() - if response == gtk.RESPONSE_OK: - retval = dialog.get_filename() - self.__class__.URI = dialog.get_current_folder_uri() - dialog.destroy() - return retval - -class ChooseFilename(object): - """Dialog for quering user for selecting filename""" - - URI=None - - def __init__(self, path=None, title=''): - self.path = path - self.dialog = gtk.FileChooserDialog( - title="", - action=gtk.FILE_CHOOSER_ACTION_SAVE, - buttons=( - gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, - gtk.STOCK_SAVE, - gtk.RESPONSE_OK)) - - self.dialog.set_action(gtk.FILE_CHOOSER_ACTION_SAVE) - self.dialog.set_default_response(gtk.RESPONSE_OK) - self.dialog.set_do_overwrite_confirmation(True) - self.dialog.set_title(title) - - f = gtk.FileFilter() - f.set_name("Catalog files") - f.add_pattern("*.sqlite") - f.add_pattern("*.sqlite.bz2") - self.dialog.add_filter(f) - f = gtk.FileFilter() - f.set_name("All files") - f.add_pattern("*.*") - self.dialog.add_filter(f) - - def run(self): - if self.URI: - self.dialog.set_current_folder_uri(self.URI) - elif self.path and os.path.exists(self.path): - self.path = "file://"+os.path.abspath(self.path) - self.dialog.set_current_folder_uri(self.path) - - response = self.dialog.run() - if response == gtk.RESPONSE_OK: - filename = self.dialog.get_filename() - self.__class__.URI = self.dialog.get_current_folder_uri() - self.dialog.destroy() - return filename - else: - self.dialog.destroy() - return None - pass - -class ChooseDBFilename(ChooseFilename): - """Sepcific dialog for quering user for selecting filename for database""" - - URI=None - - def __init__(self, path=None): - ChooseFilename.__init__(self) - self.dialog.set_title('Save catalog as...') - - f = gtk.FileFilter() - f.set_name("Catalog files") - f.add_pattern("*.sqlite") - f.add_pattern("*.sqlite.bz2") - self.dialog.add_filter(f) - f = gtk.FileFilter() - f.set_name("All files") - f.add_pattern("*.*") - self.dialog.add_filter(f) - - def run(self): - if self.URI: - self.dialog.set_current_folder_uri(self.URI) - elif self.path and os.path.exists(self.path): - self.path = "file://"+os.path.abspath(self.path) - self.dialog.set_current_folder_uri(self.path) - - response = self.dialog.run() - if response == gtk.RESPONSE_OK: - filename = self.dialog.get_filename() - self.__class__.URI = self.dialog.get_current_folder_uri() - self.dialog.destroy() - return filename - else: - self.dialog.destroy() - return None - pass - -class LoadDBFile(object): - """Specific class for displaying openFile dialog. It has veryfication - for file existence.""" - - URI = None - - def __init__(self, path=None): - self.path = path - - self.dialog = gtk.FileChooserDialog( - title="Open catalog", - action=gtk.FILE_CHOOSER_ACTION_OPEN, - buttons=( - gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, - gtk.RESPONSE_OK)) - - self.dialog.set_default_response(gtk.RESPONSE_OK) - - f = gtk.FileFilter() - f.set_name("Catalog files") - f.add_pattern("*.sqlite") - f.add_pattern("*.sqlite.bz2") - self.dialog.add_filter(f) - f = gtk.FileFilter() - f.set_name("All files") - f.add_pattern("*.*") - self.dialog.add_filter(f) - - def show_dialog(self): - response = self.dialog.run() - filename = None - if response == gtk.RESPONSE_OK: - try: - filename = self.dialog.get_filename() - except: - pass - #self.dialog.destroy() - return 'ok',filename - else: - return 'cancel',None - - def run(self): - if self.URI: - self.dialog.set_current_folder_uri(self.URI) - elif self.path and os.path.exists(self.path): - self.path = "file://"+os.path.abspath(self.path) - self.dialog.set_current_folder_uri(self.path) - - res,filename = self.show_dialog() - ch = True - while ch: - if res == 'cancel': - self.dialog.destroy() - return None - try: - os.stat(filename) - self.__class__.URI = self.dialog.get_current_folder_uri() - self.dialog.destroy() - return filename - except: - a = Err("Error - pyGTKtalog","File doesn't exist.", - "The file that you choose does not exist." + \ - " Choose another one, or cancel operation.") - ch = True - res, filename = self.show_dialog() - - -class LoadImageFile(object): - """class for displaying openFile dialog. It have possibility of multiple - selection.""" - - URI="file://"+os.path.abspath(os.path.curdir) - - def __init__(self, multiple=False): - self.dialog = gtk.FileChooserDialog( - title="Select image", - action=gtk.FILE_CHOOSER_ACTION_OPEN, - buttons=( - gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, - gtk.RESPONSE_OK)) - self.dialog.set_select_multiple(multiple) - self.dialog.set_default_response(gtk.RESPONSE_OK) - - f = gtk.FileFilter() - f.set_name("All Images") - for i in ['*.jpg', '*.jpeg', '*.gif', '*.png', '*.tif', '*.tiff', - '*.tga', '*.pcx', '*.bmp', '*.xbm', '*.xpm', '*.jp2', - '*.jpx', '*.pnm', '*.JPG', '*.JPEG', '*.GIF', '*.PNG', - '*.TIF', '*.TIFF', '*.TGA', '*.PCX', '*.BMP', '*.XBM', - '*.XPM', '*.JP2', '*.JPX', '*.PNM']: - f.add_pattern(i) - self.dialog.add_filter(f) - f = gtk.FileFilter() - f.set_name("All files") - f.add_pattern("*.*") - self.dialog.add_filter(f) - self.preview = gtk.Image() - - self.dialog.set_preview_widget(self.preview) - self.dialog.connect("update-preview", self.update_preview_cb) - - def run(self): - if self.URI: - self.dialog.set_current_folder_uri(self.URI) - response = self.dialog.run() - filenames = None - only_thumbs = False - - if response == gtk.RESPONSE_OK: - try: - if self.dialog.get_select_multiple(): - filenames = self.dialog.get_filenames() - else: - filenames = self.dialog.get_filename() - - if self.dialog.get_extra_widget().get_active(): - only_thumbs = True - except: - pass - - self.__class__.URI = self.dialog.get_current_folder_uri() - self.dialog.destroy() - return filenames, only_thumbs - - def update_preview_cb(self, widget): - filename = self.dialog.get_preview_filename() - try: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 128, 128) - self.preview.set_from_pixbuf(pixbuf) - have_preview = True - except: - have_preview = False - self.dialog.set_preview_widget_active(have_preview) - return - -class StatsDialog(object): - """Sepcific dialog for display stats""" - - def __init__(self, values={}): - self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade") - self.values = values - - def run(self): - gladexml = gtk.glade.XML(self.gladefile, "statDialog") - dialog = gladexml.get_widget("statDialog") - - if 'discs' in self.values: - entry = gladexml.get_widget("discs_entry") - entry.set_text(str(self.values['discs'])) - else: - label = gladexml.get_widget("discs_label") - entry = gladexml.get_widget("discs_entry") - label.hide() - entry.hide() - - if 'dirs' in self.values: - entry = gladexml.get_widget("dirs_entry") - entry.set_text(str(self.values['dirs'])) - else: - label = gladexml.get_widget("dirs_label") - entry = gladexml.get_widget("dirs_entry") - label.hide() - entry.hide() - - if 'files' in self.values: - entry = gladexml.get_widget("files_entry") - entry.set_text(str(self.values['files'])) - - if 'size' in self.values: - entry = gladexml.get_widget("size_entry") - entry.set_text(str(self.values['size'])) - - result = dialog.run() - dialog.destroy() - if result == gtk.RESPONSE_OK: - return entry.get_text() - return None - -class TagsDialog(object): - """Sepcific dialog for display stats""" - - def __init__(self): - self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade") - - def run(self): - gladexml = gtk.glade.XML(self.gladefile, "tagsDialog") - dialog = gladexml.get_widget("tagsDialog") - - entry = gladexml.get_widget("tag_entry1") - - result = dialog.run() - - dialog.destroy() - if result == gtk.RESPONSE_OK: - return entry.get_text() - return None - -class TagsRemoveDialog(object): - """Sepcific dialog for display stats""" - - def __init__(self, tag_dict=None): - self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade") - self.tag_dict = tag_dict - - def run(self): - if not self.tag_dict: - return None - - gladexml = gtk.glade.XML(self.gladefile, "tagRemove") - dialog = gladexml.get_widget("tagRemove") - - # declare model - model = gtk.ListStore(gobject.TYPE_INT, - gobject.TYPE_STRING, gobject.TYPE_BOOLEAN) - # sort dict - values = self.tag_dict.values() - values.sort() - keys = [] - for val in values: - for d_key, d_value in self.tag_dict.items(): - if d_value == val: - keys.append(d_key) - - # fill model with dict - for count in range(len(keys)): - myiter = model.insert_before(None, None) - model.set_value(myiter, 0, keys[count]) - model.set_value(myiter, 1, values[count]) - model.set_value(myiter, 2, None) - - def toggle(cell, path, model): - model[path][2] = not model[path][2] - - def toggle_all(column, model): - for row in model: - row[2] = not row[2] - - treeview = gladexml.get_widget("treeview1") - treeview.set_model(model) - - renderer = gtk.CellRendererText() - column = gtk.TreeViewColumn("Tag", renderer, text=1) - column.set_property('expand', True) - treeview.append_column(column) - - renderer = gtk.CellRendererToggle() - renderer.set_property('activatable', True) - renderer.connect('toggled', toggle, model) - column = gtk.TreeViewColumn("Toggle", renderer) - column.add_attribute(renderer, "active", 2) - column.set_property('expand', False) - column.set_property("clickable", True) - column.connect("clicked", toggle_all, model) - treeview.append_column(column) - - result = dialog.run() - - dialog.destroy() - if result == gtk.RESPONSE_OK: - ids = [] - for i in model: - if i[2]: - ids.append(i[0]) - return "ok", ids - return None, None - -class EditDialog(object): - """Sepcific dialog for display stats""" - - def __init__(self, values={}): - self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade") - self.values = values - - def run(self): - gladexml = gtk.glade.XML(self.gladefile, "file_editDialog") - dialog = gladexml.get_widget("file_editDialog") - - filename = gladexml.get_widget("filename_entry") - filename.set_text(str(self.values['filename'])) - description = gladexml.get_widget("description_text") - note = gladexml.get_widget("note_text") - - if 'description' in self.values: - buff = gtk.TextBuffer() - buff.set_text(str(self.values['description'])) - description.set_buffer(buff) - - if 'note' in self.values: - buff = gtk.TextBuffer() - buff.set_text(str(self.values['note'])) - note.set_buffer(buff) - - result = dialog.run() - if result == gtk.RESPONSE_OK: - d = description.get_buffer() - n = note.get_buffer() - retval = {'filename': filename.get_text(), - 'description': d.get_text(d.get_start_iter(), - d.get_end_iter()), - 'note': n.get_text(n.get_start_iter(), n.get_end_iter())} - dialog.destroy() - return retval - dialog.destroy() - return None diff --git a/src/views/v_image.py b/src/views/v_image.py deleted file mode 100644 index 1764194..0000000 --- a/src/views/v_image.py +++ /dev/null @@ -1,68 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - -import gtk - -class ImageView(object): - """simple image viewer. no scaling, no zooming, no rotating. - simply show stupid image""" - - def __init__(self, image_filename): - window = gtk.Window(gtk.WINDOW_TOPLEVEL) - - image = gtk.Image() - image.set_from_file(image_filename) - - pixbuf = image.get_pixbuf() - pic_width = pixbuf.get_width() + 23 - pic_height = pixbuf.get_height() + 23 - - screen_width = gtk.gdk.screen_width() - screen_height = gtk.gdk.screen_height() - - need_vieport = False - if pic_height > (screen_height - 128): - height = screen_height - 128 - need_vieport = True - else: - height = screen_height - 128 - - if pic_width > (screen_width - 128): - width = screen_width - 128 - need_vieport = True - else: - width = pic_width - - if need_vieport: - window.resize(width, height) - viewport = gtk.ScrolledWindow() - viewport.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - viewport.add_with_viewport(image) - window.add(viewport) - else: - window.add(image) - window.show_all() - return - - pass # end of class diff --git a/src/views/v_main.py b/src/views/v_main.py deleted file mode 100644 index 799e805..0000000 --- a/src/views/v_main.py +++ /dev/null @@ -1,44 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - -import os.path -import lib.globs -from gtkmvc import View - -class MainView(View): - """This handles only the graphical representation of the - application. The widgets set is loaded from glade file""" - - GLADE = os.path.join(lib.globs.GLADE_DIR, "main.glade") - - def __init__(self, ctrl): - View.__init__(self, ctrl, self.GLADE) - - # hide v2.0 features - self['separatormenuitem4'].hide() - self['list1'].hide() - self['thumbnails1'].hide() - return - - pass # end of class diff --git a/src/views/v_search.py b/src/views/v_search.py deleted file mode 100644 index e7e447f..0000000 --- a/src/views/v_search.py +++ /dev/null @@ -1,38 +0,0 @@ -# This Python file uses the following encoding: utf-8 -# -# Author: Roman 'gryf' Dobosz gryf@elysium.pl -# -# Copyright (C) 2007 by Roman 'gryf' Dobosz -# -# This file is part of pyGTKtalog. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# ------------------------------------------------------------------------- - -from gtkmvc import View -import os.path -import lib.globs - -class SearchView(View): - """Search window from glade file """ - - GLADE = os.path.join(lib.globs.GLADE_DIR, "search.glade") - - def __init__(self, ctrl): - View.__init__(self, ctrl, self.GLADE) - return - pass # end of class - diff --git a/test/unit/__init__.py b/test/unit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/unit/main_view_test.py b/test/unit/main_view_test.py deleted file mode 100644 index 2259955..0000000 --- a/test/unit/main_view_test.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Project: pyGTKtalog -Description: Tests for main view class. -Type: test -Author: Roman 'gryf' Dobosz, gryf73@gmail.com -Created: 2010-03-14 -""" - diff --git a/test/unit/scan_test.py b/test/unit/scan_test.py deleted file mode 100644 index c3d0a0e..0000000 --- a/test/unit/scan_test.py +++ /dev/null @@ -1,124 +0,0 @@ -""" - Project: pyGTKtalog - Description: Tests for scan files. - Type: test - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2011-03-26 -""" -import os -import unittest - -from pygtktalog import scan -from pygtktalog.dbobjects import File -from pygtktalog.dbcommon import connect, Session - - -TEST_DIR = "/home/share/_test_/test_dir" -TEST_DIR_PERMS = "/home/share/_test_/test_dir_permissions/" - -class TestScan(unittest.TestCase): - """ - Test cases for scan functionality - - 1. execution scan function: - 1.1 simple case - should pass - 1.2 non-existent directory passed - 1.3 file passed - 1.4 directory has permission that forbids file listing - - 2. rescan directory; looking for changes - 2.0 don't touch records for changed files (same directories, same - filename, same type and size) - 2.1 search for files of the same type, same size. - 2.2 change parent node for moved files (don't insert new) - - 3. adding new directory tree which contains same files like already stored - in the database - """ - def setUp(self): - connect() - root = File() - root.id = 1 - root.filename = 'root' - root.size = 0 - root.source = 0 - root.type = 0 - root.parent_id = 1 - - sess = Session() - sess.add(root) - sess.commit() - - def test_happy_scenario(self): - """ - make scan, count items - """ - scanob = scan.Scan(os.path.abspath(os.path.join(__file__, - "../../../mocks"))) - scanob = scan.Scan(TEST_DIR) - result_list = scanob.add_files() - self.assertEqual(len(result_list), 143) - self.assertEqual(len(result_list[0].children), 8) - # check soft links - self.assertEqual(len([x for x in result_list if x.type == 3]), 2) - - def test_wrong_and_nonexistent(self): - """ - Check for accessing non existent directory, regular file instead of - the directory, or file.directory with no access to it. - """ - scanobj = scan.Scan('/nonexistent_directory_') - self.assertRaises(OSError, scanobj.add_files) - - scanobj.path = '/root' - self.assertRaises(scan.NoAccessError, scanobj.add_files) - - scanobj.path = '/bin/sh' - self.assertRaises(scan.NoAccessError, scanobj.add_files) - - # dir contains some non accessable items. Should just pass, and on - # logs should be messages about it - scanobj.path = TEST_DIR_PERMS - scanobj.add_files() - - def test_abort_functionality(self): - scanobj = scan.Scan(TEST_DIR) - scanobj.abort = True - self.assertEqual(None, scanobj.add_files()) - - def test_double_scan(self): - """ - Do the scan twice. - """ - ses = Session() - self.assertEqual(len(ses.query(File).all()), 1) - - scanob = scan.Scan(TEST_DIR) - scanob.add_files() - - # note: we have 144 elements in db, because of root element - self.assertEqual(len(ses.query(File).all()), 144) - - scanob2 = scan.Scan(TEST_DIR) - scanob2.add_files() - # it is perfectly ok, since we don't update collection, but just added - # same directory twice. - self.assertEqual(len(ses.query(File).all()), 287) - file_ob = scanob._files[2] - file2_ob = scanob2._files[2] - - # File objects are different - self.assertTrue(file_ob is not file2_ob) - - # While Image objects points to the same file - self.assertTrue(file_ob.images[0].filename == \ - file2_ob.images[0].filename) - - # they are different objects - self.assertTrue(file_ob.images[0] is not file2_ob.images[0]) - - ses.close() - -if __name__ == "__main__": - os.chdir(os.path.join(os.path.abspath(os.path.dirname(__file__)), "../")) - unittest.main() diff --git a/test/unit/video_test.py b/test/unit/video_test.py deleted file mode 100644 index f9df87b..0000000 --- a/test/unit/video_test.py +++ /dev/null @@ -1,133 +0,0 @@ -""" - Project: pyGTKtalog - Description: Tests for Video class. - Type: test - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2008-12-15 -""" -import unittest -import os - -from pygtktalog.video import Video - - -class TestVideo(unittest.TestCase): - """Class for retrive midentify script output and put it in dict. - Usually there is no need for such a detailed movie/clip information. - Video script belongs to mplayer package. - """ - - def test_avi(self): - """test mock avi file, should return dict with expected values""" - avi = Video("mocks/m.avi") - self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0") - self.assertEqual(avi.tags['audio_format'], '85') - self.assertEqual(avi.tags['width'], 128) - self.assertEqual(avi.tags['audio_no_channels'], 2) - self.assertEqual(avi.tags['height'], 96) - self.assertEqual(avi.tags['video_format'], 'xvid') - self.assertEqual(avi.tags['length'], 4) - self.assertEqual(avi.tags['audio_codec'], 'mp3') - self.assertEqual(avi.tags['video_codec'], 'ffodivx') - self.assertEqual(avi.tags['duration'], '00:00:04') - self.assertEqual(avi.tags['container'], 'avi') - - def test_avi2(self): - """test another mock avi file, should return dict with expected - values""" - avi = Video("mocks/m1.avi") - self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0") - self.assertEqual(avi.tags['audio_format'], '85') - self.assertEqual(avi.tags['width'], 128) - self.assertEqual(avi.tags['audio_no_channels'], 2) - self.assertEqual(avi.tags['height'], 96) - self.assertEqual(avi.tags['video_format'], 'h264') - self.assertEqual(avi.tags['length'], 4) - self.assertEqual(avi.tags['audio_codec'], 'mp3') - self.assertEqual(avi.tags['video_codec'], 'ffh264') - self.assertEqual(avi.tags['duration'], '00:00:04') - self.assertEqual(avi.tags['container'], 'avi') - - def test_mkv(self): - """test mock mkv file, should return dict with expected values""" - avi = Video("mocks/m.mkv") - self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0") - self.assertEqual(avi.tags['audio_format'], '8192') - self.assertEqual(avi.tags['width'], 128) - self.assertTrue(avi.tags['audio_no_channels'] in (1, 2)) - self.assertEqual(avi.tags['height'], 96) - self.assertEqual(avi.tags['video_format'], 'mp4v') - self.assertEqual(avi.tags['length'], 4) - self.assertTrue(avi.tags['audio_codec'] in ('a52', 'ffac3')) - self.assertEqual(avi.tags['video_codec'], 'ffodivx') - self.assertEqual(avi.tags['duration'], '00:00:04') - self.assertTrue(avi.tags['container'] in ('mkv', 'lavfpref')) - - def test_mpg(self): - """test mock mpg file, should return dict with expected values""" - avi = Video("mocks/m.mpg") - self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0") - self.assertFalse(avi.tags.has_key('audio_format')) - self.assertEqual(avi.tags['width'], 128) - self.assertFalse(avi.tags.has_key('audio_no_channels')) - self.assertEqual(avi.tags['height'], 96) - self.assertEqual(avi.tags['video_format'], '0x10000001') - self.assertFalse(avi.tags.has_key('lenght')) - self.assertFalse(avi.tags.has_key('audio_codec')) - self.assertEqual(avi.tags['video_codec'], 'ffmpeg1') - self.assertFalse(avi.tags.has_key('duration')) - self.assertEqual(avi.tags['container'], 'mpeges') - - def test_ogm(self): - """test mock ogm file, should return dict with expected values""" - avi = Video("mocks/m.ogm") - self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0") - self.assertEqual(avi.tags['audio_format'], '8192') - self.assertEqual(avi.tags['width'], 160) - self.assertTrue(avi.tags['audio_no_channels'] in (1, 2)) - self.assertEqual(avi.tags['height'], 120) - self.assertEqual(avi.tags['video_format'], 'h264') - self.assertEqual(avi.tags['length'], 4) - self.assertTrue(avi.tags['audio_codec'] in ('a52', 'ffac3')) - self.assertEqual(avi.tags['video_codec'], 'ffh264') - self.assertEqual(avi.tags['duration'], '00:00:04') - self.assertTrue(avi.tags['container'] in ('ogg', 'lavfpref')) - - def test_capture(self): - """test capture with some small movie and play a little with tags""" - avi = Video("mocks/m.avi") - filename = avi.capture() - self.assertTrue(filename != None) - self.assertTrue(os.path.exists(filename)) - file_size = os.stat(filename)[6] - self.assertAlmostEqual(file_size/10000.0, 0.9, 0) - os.unlink(filename) - - for length in (480, 380, 4): - avi.tags['length'] = length - filename = avi.capture() - self.assertTrue(filename is not None) - os.unlink(filename) - - avi.tags['length'] = 3 - self.assertTrue(avi.capture() is None) - - avi.tags['length'] = 4 - - avi.tags['width'] = 0 - self.assertTrue(avi.capture() is None) - - avi.tags['width'] = 1025 - filename = avi.capture() - self.assertTrue(filename is not None) - os.unlink(filename) - - del(avi.tags['length']) - self.assertTrue(avi.capture() is None) - - self.assertTrue(len(str(avi)) > 0) - - -if __name__ == "__main__": - os.chdir(os.path.join(os.path.abspath(os.path.dirname(__file__)), "../")) - unittest.main() diff --git a/pygtktalog/controllers/__init__.py b/tests/__init__.py similarity index 100% rename from pygtktalog/controllers/__init__.py rename to tests/__init__.py diff --git a/test/unit/dbcommon_test.py b/tests/dbcommon_test.py similarity index 100% rename from test/unit/dbcommon_test.py rename to tests/dbcommon_test.py diff --git a/test/unit/dialogs_test.py b/tests/dialogs_test.py similarity index 100% rename from test/unit/dialogs_test.py rename to tests/dialogs_test.py diff --git a/test/unit/misc_test.py b/tests/misc_test.py similarity index 100% rename from test/unit/misc_test.py rename to tests/misc_test.py diff --git a/tests/scan_test.py b/tests/scan_test.py new file mode 100644 index 0000000..86c57d0 --- /dev/null +++ b/tests/scan_test.py @@ -0,0 +1,192 @@ +""" + Project: pyGTKtalog + Description: Tests for scan files. + Type: test + Author: Roman 'gryf' Dobosz, gryf73@gmail.com + Created: 2011-03-26 +""" +import os +import shutil +import tempfile +import unittest + +from pygtktalog import scan +from pygtktalog.dbobjects import File, Config, Image +from pygtktalog.dbcommon import connect, Session + + +def populate_with_mock_files(dir_): + """Make some files under specified directory, return number of files""" + files1 = ['anim.mkv', 'text.txt', 'image.png', 'photoimage.jpg'] + files2 = ['music.mp3', 'loseless.flac'] + + files_no = 0 + for file_ in files1: + with open(os.path.join(dir_, file_), "wb") as fobj: + fobj.write("\xde\xad\xbe\xef" * len(file_)) + files_no += 1 + + os.symlink(os.path.join(dir_, files1[-1]), os.path.join(dir_, 'link.jpg')) + files_no += 1 + + os.mkdir(os.path.join(dir_, 'directory')) + for file_ in files2: + with open(os.path.join(dir_, 'directory', file_), "wb") as fobj: + fobj.write("\xfe\xad\xfa\xce" * len(file_)) + files_no += 1 + + return files_no + + +# TODO: exchange this with mock module +def _fake_video(obj, fobj, filepath): + fobj.images.append(Image()) + fobj.images[0].filename = filepath + ".jpg" + + +def _fake_audio(obj, fobj, filepath): + pass + + +def _fake_image(obj, fobj, filepath): + pass + + +scan.Scan._video = _fake_video +scan.Scan._audio = _fake_audio +scan.Scan._image = _fake_image + + +class TestScan(unittest.TestCase): + """ + Test cases for scan functionality + + 1. execution scan function: + 1.1 simple case - should pass + 1.2 non-existent directory passed + 1.3 file passed + 1.4 directory has permission that forbids file listing + + 2. rescan directory; looking for changes + 2.0 don't touch records for changed files (same directories, same + filename, same type and size) + 2.1 search for files of the same type, same size. + 2.2 change parent node for moved files (don't insert new) + + 3. adding new directory tree which contains same files like already stored + in the database + """ + def setUp(self): + self.image_path = tempfile.mkdtemp() + self.scan_dir = tempfile.mkdtemp() + self.no_of_files = populate_with_mock_files(self.scan_dir) + + connect() + root = File() + root.id = 1 + root.filename = 'root' + root.size = 0 + root.source = 0 + root.type = 0 + root.parent_id = 1 + + config = Config() + config.key = 'image_path' + config.value = self.image_path + + sess = Session() + sess.add(root) + sess.add(config) + sess.commit() + + def tearDown(self): + shutil.rmtree(self.image_path) + shutil.rmtree(self.scan_dir) + + def test_happy_scenario(self): + """ + make scan, count items + """ + scanob = scan.Scan(self.scan_dir) + result_list = scanob.add_files() + + # the number of added objects (files/links only) + "directory" + + # topmost directory (self.scan_dir) + self.assertEqual(len(result_list), self.no_of_files + 2) + + # all of topmost nide children - including "directory", but excluding + # its contents - so it is all_files + 1 (directory) - 2 files from + # subdir contents + self.assertEqual(len(result_list[0].children), self.no_of_files - 1) + # check soft links + self.assertEqual(len([x for x in result_list if x.type == 3]), 1) + + def test_wrong_and_nonexistent(self): + """ + Check for accessing non existent directory, regular file instead of + the directory. + """ + scanobj = scan.Scan('/nonexistent_directory_') + self.assertRaises(OSError, scanobj.add_files) + + scanobj.path = '/root' + self.assertRaises(scan.NoAccessError, scanobj.add_files) + + scanobj.path = '/bin/sh' + self.assertRaises(scan.NoAccessError, scanobj.add_files) + + def test_abort_functionality(self): + scanobj = scan.Scan(self.scan_dir) + scanobj.abort = True + self.assertEqual(None, scanobj.add_files()) + + def test_double_scan(self): + """ + Do the scan twice. + """ + ses = Session() + self.assertEqual(len(ses.query(File).all()), 1) + + scanob = scan.Scan(self.scan_dir) + scanob.add_files() + + # dirs: main one + "directory" subdir + self.assertEqual(len(ses.query(File).filter(File.type == 1).all()), 2) + + # files: '-1' for existing link there, which have it's own type + self.assertEqual(len(ses.query(File).filter(File.type == 2).all()), + self.no_of_files - 1) + # links + self.assertEqual(len(ses.query(File).filter(File.type == 3).all()), 1) + + # all - sum of all of the above + root node + self.assertEqual(len(ses.query(File).all()), self.no_of_files + 2 + 1) + + # it is perfectly ok, since we don't update collection, but just added + # same directory twice. + scanob2 = scan.Scan(self.scan_dir) + scanob2.add_files() + # we have twice as much of files (self.no_of_files), plus 2 * of + # topmost dir and subdir "directory" (means 4) + root element + self.assertEqual(len(ses.query(File).all()), self.no_of_files * 2 + 5) + + # get some movie files to examine + file_ob = [x for x in scanob._files if x.filename == 'anim.mkv'][0] + file2_ob = [x for x in scanob2._files if x.filename == 'anim.mkv'][0] + + # File objects are different + self.assertTrue(file_ob is not file2_ob) + + # While Image objects points to the same file + self.assertTrue(file_ob.images[0].filename == \ + file2_ob.images[0].filename) + + # they are different objects + self.assertTrue(file_ob.images[0] is not file2_ob.images[0]) + + ses.close() + + +if __name__ == "__main__": + os.chdir(os.path.join(os.path.abspath(os.path.dirname(__file__)), "../")) + unittest.main() diff --git a/tests/video_test.py b/tests/video_test.py new file mode 100644 index 0000000..1c385ed --- /dev/null +++ b/tests/video_test.py @@ -0,0 +1,364 @@ +""" + Project: pyGTKtalog + Description: Tests for Video class. + Type: test + Author: Roman 'gryf' Dobosz, gryf73@gmail.com + Created: 2008-12-15 +""" +import os +import unittest + +import PIL + +from pygtktalog.video import Video + + +DATA = {"m1.avi": """ID_VIDEO_ID=0 +ID_AUDIO_ID=1 +ID_FILENAME=m1.avi +ID_DEMUXER=avi +ID_VIDEO_FORMAT=H264 +ID_VIDEO_BITRATE=46184 +ID_VIDEO_WIDTH=128 +ID_VIDEO_HEIGHT=96 +ID_VIDEO_FPS=30.000 +ID_VIDEO_ASPECT=0.0000 +ID_AUDIO_FORMAT=85 +ID_AUDIO_BITRATE=128000 +ID_AUDIO_RATE=0 +ID_AUDIO_NCH=0 +ID_START_TIME=0.00 +ID_LENGTH=4.03 +ID_SEEKABLE=1 +ID_CHAPTERS=0 +ID_VIDEO_CODEC=ffh264 +ID_AUDIO_BITRATE=128000 +ID_AUDIO_RATE=22050 +ID_AUDIO_NCH=2 +ID_AUDIO_CODEC=mpg123 +ID_EXIT=EOF +""", + "m.avi": """ID_VIDEO_ID=0 +ID_AUDIO_ID=1 +ID_FILENAME=m.avi +ID_DEMUXER=avi +ID_VIDEO_FORMAT=XVID +ID_VIDEO_BITRATE=313536 +ID_VIDEO_WIDTH=128 +ID_VIDEO_HEIGHT=96 +ID_VIDEO_FPS=30.000 +ID_VIDEO_ASPECT=0.0000 +ID_AUDIO_FORMAT=85 +ID_AUDIO_BITRATE=128000 +ID_AUDIO_RATE=0 +ID_AUDIO_NCH=0 +ID_START_TIME=0.00 +ID_LENGTH=4.03 +ID_SEEKABLE=1 +ID_CHAPTERS=0 +ID_VIDEO_CODEC=ffodivx +ID_AUDIO_BITRATE=128000 +ID_AUDIO_RATE=22050 +ID_AUDIO_NCH=2 +ID_AUDIO_CODEC=mpg123 +ID_EXIT=EOF""", + "m.mkv": """ID_VIDEO_ID=0 +ID_AUDIO_ID=0 +ID_CLIP_INFO_NAME0=title +ID_CLIP_INFO_VALUE0=Avidemux +ID_CLIP_INFO_NAME1=encoder +ID_CLIP_INFO_VALUE1=Lavf51.12.1 +ID_CLIP_INFO_N=2 +ID_FILENAME=m.mkv +ID_DEMUXER=lavfpref +ID_VIDEO_FORMAT=MP4V +ID_VIDEO_BITRATE=0 +ID_VIDEO_WIDTH=128 +ID_VIDEO_HEIGHT=96 +ID_VIDEO_FPS=30.000 +ID_VIDEO_ASPECT=0.0000 +ID_AUDIO_FORMAT=8192 +ID_AUDIO_BITRATE=128000 +ID_AUDIO_RATE=22050 +ID_AUDIO_NCH=1 +ID_START_TIME=0.00 +ID_LENGTH=4.07 +ID_SEEKABLE=1 +ID_CHAPTERS=0 +ID_VIDEO_CODEC=ffodivx +ID_AUDIO_BITRATE=128000 +ID_AUDIO_RATE=22050 +ID_AUDIO_NCH=1 +ID_AUDIO_CODEC=ffac3 +ID_EXIT=EOF""", + "m.mpg": """ID_VIDEO_ID=0 +ID_FILENAME=m.mpg +ID_DEMUXER=mpeges +ID_VIDEO_FORMAT=0x10000001 +ID_VIDEO_BITRATE=2200000 +ID_VIDEO_WIDTH=128 +ID_VIDEO_HEIGHT=96 +ID_VIDEO_FPS=30.000 +ID_VIDEO_ASPECT=0.0000 +ID_START_TIME=0.00 +ID_LENGTH=0.97 +ID_SEEKABLE=1 +ID_CHAPTERS=0 +ID_VIDEO_CODEC=ffmpeg1 +ID_EXIT=EOF""", + "m.ogm": """ID_VIDEO_ID=0 +ID_AUDIO_ID=0 +ID_FILENAME=m.ogm +ID_DEMUXER=lavfpref +ID_VIDEO_FORMAT=H264 +ID_VIDEO_BITRATE=0 +ID_VIDEO_WIDTH=160 +ID_VIDEO_HEIGHT=120 +ID_VIDEO_FPS=30.000 +ID_VIDEO_ASPECT=0.0000 +ID_AUDIO_FORMAT=8192 +ID_AUDIO_BITRATE=128000 +ID_AUDIO_RATE=22050 +ID_AUDIO_NCH=1 +ID_START_TIME=0.00 +ID_LENGTH=4.00 +ID_SEEKABLE=1 +ID_CHAPTERS=0 +ID_VIDEO_CODEC=ffh264 +ID_AUDIO_BITRATE=128000 +ID_AUDIO_RATE=22050 +ID_AUDIO_NCH=1 +ID_AUDIO_CODEC=ffac3 +ID_EXIT=EOF""", + "m.wmv":"""ID_AUDIO_ID=1 +ID_VIDEO_ID=2 +ID_FILENAME=m.wmv +ID_DEMUXER=asf +ID_VIDEO_FORMAT=WMV3 +ID_VIDEO_BITRATE=1177000 +ID_VIDEO_WIDTH=852 +ID_VIDEO_HEIGHT=480 +ID_VIDEO_FPS=1000.000 +ID_VIDEO_ASPECT=0.0000 +ID_AUDIO_FORMAT=353 +ID_AUDIO_BITRATE=0 +ID_AUDIO_RATE=0 +ID_AUDIO_NCH=0 +ID_START_TIME=4.00 +ID_LENGTH=4656.93 +ID_SEEKABLE=1 +ID_CHAPTERS=0 +ID_VIDEO_CODEC=ffwmv3 +ID_AUDIO_BITRATE=64028 +ID_AUDIO_RATE=48000 +ID_AUDIO_NCH=2 +ID_AUDIO_CODEC=ffwmav2 +ID_EXIT=EOF""", + "m.mp4": """ID_VIDEO_ID=0 +ID_AUDIO_ID=0 +ID_AID_0_LANG=unk +ID_CLIP_INFO_NAME0=major_brand +ID_CLIP_INFO_VALUE0=isom +ID_CLIP_INFO_NAME1=minor_version +ID_CLIP_INFO_VALUE1=512 +ID_CLIP_INFO_NAME2=compatible_brands +ID_CLIP_INFO_VALUE2=isomiso2avc1mp41 +ID_CLIP_INFO_NAME3=encoder +ID_CLIP_INFO_VALUE3=Lavf56.25.101 +ID_CLIP_INFO_N=4 +ID_FILENAME=m.mp4 +ID_DEMUXER=lavfpref +ID_VIDEO_FORMAT=H264 +ID_VIDEO_BITRATE=1263573 +ID_VIDEO_WIDTH=720 +ID_VIDEO_HEIGHT=404 +ID_VIDEO_FPS=25.000 +ID_VIDEO_ASPECT=0.0000 +ID_AUDIO_FORMAT=MP4A +ID_AUDIO_BITRATE=155088 +ID_AUDIO_RATE=44100 +ID_AUDIO_NCH=2 +ID_START_TIME=0.00 +ID_LENGTH=69.18 +ID_SEEKABLE=1 +ID_CHAPTERS=0 +ID_VIDEO_CODEC=ffh264 +ID_AUDIO_BITRATE=155082 +ID_AUDIO_RATE=44100 +ID_AUDIO_NCH=2 +ID_AUDIO_CODEC=ffaac +ID_EXIT=EOF"""} + + +# TODO: exchange this with mock +class Readlines(object): + def __init__(self, key=None): + self.data = DATA.get(key, "") + + def readlines(self): + return self.data.split('\n') + +def mock_popen(command): + key = None + if 'midentify' in command: + key = command.split('"')[1] + elif 'jpeg:outdir' in command: + # simulate capture for mplayer + img_dir = command.split('"')[-2] + img = PIL.Image.new('RGBA', (320, 200)) + with open(os.path.join(img_dir, "00000001.jpg"), "wb") as fobj: + img.save(fobj) + + return Readlines(key) + + +os.popen = mock_popen + + +class TestVideo(unittest.TestCase): + """test class for retrive midentify script output""" + + def test_avi(self): + """test mock avi file, should return dict with expected values""" + avi = Video("m.avi") + self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0") + self.assertEqual(avi.tags['audio_format'], '85') + self.assertEqual(avi.tags['width'], 128) + self.assertEqual(avi.tags['audio_no_channels'], 2) + self.assertEqual(avi.tags['height'], 96) + self.assertEqual(avi.tags['video_format'], 'xvid') + self.assertEqual(avi.tags['length'], 4) + self.assertEqual(avi.tags['audio_codec'], 'mpg123') + self.assertEqual(avi.tags['video_codec'], 'ffodivx') + self.assertEqual(avi.tags['duration'], '00:00:04') + self.assertEqual(avi.tags['container'], 'avi') + + def test_avi2(self): + """test another mock avi file, should return dict with expected + values""" + avi = Video("m1.avi") + self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0") + self.assertEqual(avi.tags['audio_format'], '85') + self.assertEqual(avi.tags['width'], 128) + self.assertEqual(avi.tags['audio_no_channels'], 2) + self.assertEqual(avi.tags['height'], 96) + self.assertEqual(avi.tags['video_format'], 'h264') + self.assertEqual(avi.tags['length'], 4) + self.assertEqual(avi.tags['audio_codec'], 'mpg123') + self.assertEqual(avi.tags['video_codec'], 'ffh264') + self.assertEqual(avi.tags['duration'], '00:00:04') + self.assertEqual(avi.tags['container'], 'avi') + + def test_mkv(self): + """test mock mkv file, should return dict with expected values""" + mkv = Video("m.mkv") + self.assertTrue(len(mkv.tags) != 0, "result should have lenght > 0") + self.assertEqual(mkv.tags['audio_format'], '8192') + self.assertEqual(mkv.tags['width'], 128) + self.assertTrue(mkv.tags['audio_no_channels'] in (1, 2)) + self.assertEqual(mkv.tags['height'], 96) + self.assertEqual(mkv.tags['video_format'], 'mp4v') + self.assertEqual(mkv.tags['length'], 4) + self.assertTrue(mkv.tags['audio_codec'] in ('a52', 'ffac3')) + self.assertEqual(mkv.tags['video_codec'], 'ffodivx') + self.assertEqual(mkv.tags['duration'], '00:00:04') + self.assertTrue(mkv.tags['container'] in ('mkv', 'lavfpref')) + + def test_mpg(self): + """test mock mpg file, should return dict with expected values""" + mpg = Video("m.mpg") + self.assertTrue(len(mpg.tags) != 0, "result should have lenght > 0") + self.assertFalse(mpg.tags.has_key('audio_format')) + self.assertEqual(mpg.tags['width'], 128) + self.assertFalse(mpg.tags.has_key('audio_no_channels')) + self.assertEqual(mpg.tags['height'], 96) + self.assertEqual(mpg.tags['video_format'], '0x10000001') + self.assertFalse(mpg.tags.has_key('lenght')) + self.assertFalse(mpg.tags.has_key('audio_codec')) + self.assertEqual(mpg.tags['video_codec'], 'ffmpeg1') + self.assertFalse(mpg.tags.has_key('duration')) + self.assertEqual(mpg.tags['container'], 'mpeges') + + def test_ogm(self): + """test mock ogm file, should return dict with expected values""" + ogm = Video("m.ogm") + self.assertTrue(len(ogm.tags) != 0, "result should have lenght > 0") + self.assertEqual(ogm.tags['audio_format'], '8192') + self.assertEqual(ogm.tags['width'], 160) + self.assertTrue(ogm.tags['audio_no_channels'] in (1, 2)) + self.assertEqual(ogm.tags['height'], 120) + self.assertEqual(ogm.tags['video_format'], 'h264') + self.assertEqual(ogm.tags['length'], 4) + self.assertTrue(ogm.tags['audio_codec'] in ('a52', 'ffac3')) + self.assertEqual(ogm.tags['video_codec'], 'ffh264') + self.assertEqual(ogm.tags['duration'], '00:00:04') + self.assertTrue(ogm.tags['container'] in ('ogg', 'lavfpref')) + + def test_wmv(self): + """test mock wmv file, should return dict with expected values""" + wmv = Video("m.wmv") + self.assertTrue(len(wmv.tags) != 0, "result should have lenght > 0") + self.assertEqual(wmv.tags['audio_format'], '353') + self.assertEqual(wmv.tags['width'], 852) + self.assertEqual(wmv.tags['audio_no_channels'], 2) + self.assertEqual(wmv.tags['height'], 480) + self.assertEqual(wmv.tags['video_format'], 'wmv3') + self.assertEqual(wmv.tags['length'], 4656) + self.assertEqual(wmv.tags['audio_codec'], 'ffwmav2') + self.assertEqual(wmv.tags['video_codec'], 'ffwmv3') + self.assertEqual(wmv.tags['duration'], '01:17:32') + self.assertEqual(wmv.tags['container'], 'asf') + + def test_mp4(self): + """test mock mp4 file, should return dict with expected values""" + mp4 = Video("m.mp4") + self.assertTrue(len(mp4.tags) != 0, "result should have lenght > 0") + self.assertEqual(mp4.tags['audio_format'], 'mp4a') + self.assertEqual(mp4.tags['width'], 720) + self.assertEqual(mp4.tags['audio_no_channels'], 2) + self.assertEqual(mp4.tags['height'], 404) + self.assertEqual(mp4.tags['video_format'], 'h264') + self.assertEqual(mp4.tags['length'], 69) + self.assertEqual(mp4.tags['audio_codec'], 'ffaac') + self.assertEqual(mp4.tags['video_codec'], 'ffh264') + self.assertEqual(mp4.tags['duration'], '00:01:09') + self.assertEqual(mp4.tags['container'], 'lavfpref') + + def test_capture(self): + """test capture with some small movie and play a little with tags""" + avi = Video("m.avi") + filename = avi.capture() + self.assertTrue(filename != None) + self.assertTrue(os.path.exists(filename)) + file_size = os.stat(filename)[6] + self.assertAlmostEqual(file_size/10000.0, 0.151, 0) + os.unlink(filename) + + for length in (480, 380, 4): + avi.tags['length'] = length + filename = avi.capture() + self.assertTrue(filename is not None) + os.unlink(filename) + + avi.tags['length'] = 3 + self.assertTrue(avi.capture() is None) + + avi.tags['length'] = 4 + + avi.tags['width'] = 0 + self.assertTrue(avi.capture() is None) + + avi.tags['width'] = 1025 + filename = avi.capture() + self.assertTrue(filename is not None) + os.unlink(filename) + + del(avi.tags['length']) + self.assertTrue(avi.capture() is None) + + self.assertTrue(len(str(avi)) > 0) + + +if __name__ == "__main__": + os.chdir(os.path.join(os.path.abspath(os.path.dirname(__file__)), "../")) + unittest.main()