From be230a4e9fa9b56e37dd5ed2d9050d3639bf7ea4 Mon Sep 17 00:00:00 2001 From: gryf Date: Fri, 28 Mar 2008 18:18:14 +0000 Subject: [PATCH] * Added another part of tag cloud. --- resources/glade/main.glade | 82 +++++------ src/ctrls/c_main.py | 97 ++++++++++--- src/models/m_config.py | 2 +- src/models/m_main.py | 269 +++++++++---------------------------- 4 files changed, 179 insertions(+), 271 deletions(-) diff --git a/resources/glade/main.glade b/resources/glade/main.glade index 274fec3..3255e6f 100644 --- a/resources/glade/main.glade +++ b/resources/glade/main.glade @@ -434,6 +434,47 @@ 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 + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + GTK_WRAP_WORD + False + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Tag cloud + + + label_item + + + + + True + True + + True @@ -458,46 +499,6 @@ True - - - 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 - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - GTK_WRAP_WORD - False - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Keywords - - - label_item - - - - - False - False - - @@ -524,6 +525,7 @@ True + diff --git a/src/ctrls/c_main.py b/src/ctrls/c_main.py index 3756362..70c1bbf 100644 --- a/src/ctrls/c_main.py +++ b/src/ctrls/c_main.py @@ -49,13 +49,13 @@ class MainController(Controller): widgets = ( "discs","files", 'save1','save_as1','cut1','copy1','paste1','delete1','add_cd','add_directory1', - 'tb_save','tb_addcd','tb_find','keywords','description', + 'tb_save','tb_addcd','tb_find','tag_cloud_ex','description', ) widgets_all = ( "discs","files", 'file1','edit1','add_cd','add_directory1','help1', 'tb_save','tb_addcd','tb_find','tb_new','tb_open','tb_quit', - 'keywords','description', + 'tag_cloud_ex','description', ) widgets_cancel = ('cancel','cancel1') @@ -103,18 +103,20 @@ class MainController(Controller): self.view['vpaned1'].set_position(self.model.config.confd['v']) self.view['main'].resize(self.model.config.confd['wx'],self.model.config.confd['wy']) - # zainicjalizuj statusbar + # initialize statusbar self.context_id = self.view['mainStatus'].get_context_id('detailed res') self.statusbar_id = self.view['mainStatus'].push(self.context_id, "Idle") - # inicjalizacja drzew + # initialize treeviews self.__setup_disc_treeview() self.__setup_files_treeview() - # w przypadku podania jako argument z linii komend bazy, odblokuj cały ten staff + # in case passing catalog filename in command line, unlock gui if self.model.filename != None: self.__activateUI(self.model.filename) + self.view['vpaned2'].set_position(18) + print self.view['tag_cloud_textview'].get_window(gtk.TEXT_WINDOW_TEXT) # generate recent menu self.__generate_recent_menu() @@ -124,6 +126,23 @@ class MainController(Controller): ######################################################################### # Connect signals from GUI, like menu objects, toolbar buttons and so on. + def on_tag_cloud_textview_motion_notify_event(self, widget): + print 'e' + w = self.view['tag_cloud_textview'].get_window(gtk.TEXT_WINDOW_TEXT) + if w: + w.set_cursor(None) + + def on_tag_cloud_ex_activate(self, widget): + # TODO: change this fsckin amatourish positioning! + if widget.get_expanded(): + self.view['vpaned2'].set_position(18) + else: + + w = self.view['tag_cloud_textview'].get_window(gtk.TEXT_WINDOW_TEXT) + if w: + w.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) + self.view['vpaned2'].set_position(200) + def on_main_destroy_event(self, window, event): self.__doQuit() return True @@ -196,8 +215,6 @@ class MainController(Controller): """Show files on right treeview, after clicking the left disc treeview.""" model = self.view['discs'].get_model() selected_item = self.model.discs_tree.get_value(self.model.discs_tree.get_iter(self.view['discs'].get_cursor()[0]),0) - if __debug__: - print "c_main.py, on_discs_cursor_changed()",selected_item self.model.get_root_entries(selected_item) self.__get_item_info(selected_item) @@ -271,23 +288,42 @@ class MainController(Controller): buf = self.view['details'].get_buffer() buf.set_text('') self.view['details'].set_buffer(buf)''' - if __debug__: - print "c_main.py: on_files_cursor_changed() directory selected" else: #file, show what you got. #self.details.get_top_widget() selected_item = self.model.files_list.get_value(model.get_iter(treeview.get_cursor()[0]),0) self.__get_item_info(selected_item) - if __debug__: - print "c_main.py: on_files_cursor_changed() some other thing selected" except: if __debug__: print "c_main.py: on_files_cursor_changed() insufficient iterator" return - + + def on_files_key_release_event(self, a, event): + 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 = self.view['files'].get_model() + first_child_value = f_model.get_value(f_model.get_iter_first(), 0) + # get two steps up + parent_value = self.model.get_parent_discs_value(self.model.get_parent_discs_value(first_child_value)) + iter = self.model.discs_tree.get_iter_first() + while iter: + if self.model.discs_tree.get_value(iter,0) == parent_value: + self.view['discs'].set_cursor(self.model.discs_tree.get_path(iter)) + iter = None + else: + iter = self.model.discs_tree.iter_next() + def on_files_row_activated(self, files_obj, row, column): """On directory doubleclick in files listview dive into desired branch.""" - # TODO: map backspace key for moving to upper level of directiories f_iter = self.model.files_list.get_iter(row) current_id = self.model.files_list.get_value(f_iter,0) @@ -441,9 +477,12 @@ class MainController(Controller): """Save database to file under different filename.""" path = Dialogs.ChooseDBFilename().show_dialog() if path: - self.__setTitle(filepath=path) - self.model.save(path) - self.model.config.add_recent(path) + ret, err = self.model.save(path) + if ret: + self.model.config.add_recent(path) + self.__setTitle(filepath=path) + else: + Dialogs.Err("Error writing file - pyGTKtalog","Cannot write file %s." % path, "%s" % err) pass def __addCD(self, label=None): @@ -499,6 +538,9 @@ class MainController(Controller): return False def __newDB(self): + + self.__tag_cloud() + """Create new database file""" if self.model.unsaved_project: if not Dialogs.Qst('Unsaved data - pyGTKtalog', @@ -515,6 +557,9 @@ class MainController(Controller): self.__activateUI() + self.view['tag_cloud_ex'].set_sensitive(True) + rect = self.view['tag_cloud_ex'].allocation + print rect.width, rect.height, rect.x, rect.y return def __setup_disc_treeview(self): @@ -677,22 +722,32 @@ class MainController(Controller): """react on click on connected tag items""" if event.type == gtk.gdk.BUTTON_RELEASE: print tag.get_property('name') + elif event.type == gtk.gdk.MOTION_NOTIFY: + w = self.view['tag_cloud_textview'].get_window(gtk.TEXT_WINDOW_TEXT) + if w: + w.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) + else: + w = self.view['tag_cloud_textview'].get_window(gtk.TEXT_WINDOW_TEXT) + if w: + w.set_cursor(None) + def insert_blank(b, iter): - if b.is_end() and b.is_start(): - iter = b.get_end_iter() + if iter.is_end() and iter.is_start(): + return iter else: b.insert(iter, " ") iter = b.get_end_iter() return iter if len(self.model.tag_cloud) > 0: - buff = self.view['keyword_textview'].get_buffer() + buff = gtk.TextBuffer() for cloud in self.model.tag_cloud: iter = insert_blank(buff, buff.get_end_iter()) tag = buff.create_tag(cloud['id']) tag.set_property('size-points', cloud['size']) - tag.connect('event', foo, tag) + tag.set_property('foreground', cloud['color']) + tag.connect('event', tag_cloud_click, tag) buff.insert_with_tags(iter, cloud['name'], tag) - self.view['keyword_textview'].set_buffer(buff) + self.view['tag_cloud_textview'].set_buffer(buff) pass # end of class diff --git a/src/models/m_config.py b/src/models/m_config.py index fa40065..f5b859c 100644 --- a/src/models/m_config.py +++ b/src/models/m_config.py @@ -109,7 +109,7 @@ class ConfigModel(Model): 'confirm abandon current catalog':'confirmabandon', 'show toolbar':'showtoolbar', 'show statusbar and progress bar':'showstatusbar', - 'compress collection':'compress', + 'compress catalog':'compress', 'retrive extra informatin':'retrive', 'scan exif data':'exif', 'include gthumb image description':'gthumb', diff --git a/src/models/m_main.py b/src/models/m_main.py index 5876b80..6930daa 100644 --- a/src/models/m_main.py +++ b/src/models/m_main.py @@ -27,6 +27,7 @@ import sys import base64 import shutil import tarfile +import tempfile import gtk import gobject @@ -35,184 +36,15 @@ from gtkmvc.model_mt import ModelMT from pysqlite2 import dbapi2 as sqlite from datetime import datetime -#import mx.DateTime + try: import threading as _threading except ImportError: - if __debug__: - print "m_main.py: import exception: _threading" import dummy_threading as _threading -try: - import Image, ImageEnhance -except: - if __debug__: - print "m_main.py: import exception: Image|ImageEnhance" - pass - -from utils import EXIF from m_config import ConfigModel from m_details import DetailsModel - -class Thumbnail(object): - def __init__(self, filename=None, x=160, y=120, root='thumbnails', base=''): - self.root = root - self.x = x - self.y = y - self.filename = filename - self.base = base - - def save(self, image_id): - """Save thumbnail into specific directory structure - return full path to the file and exif object or None""" - filepath = os.path.join(self.base, self.__get_and_make_path(image_id)) - f = open(self.filename, 'rb') - exif = None - returncode = -1 - try: - exif = EXIF.process_file(f) - f.close() - if exif.has_key('JPEGThumbnail'): - thumbnail = exif['JPEGThumbnail'] - f = open(filepath,'wb') - f.write(thumbnail) - f.close() - if exif.has_key('Image Orientation'): - orientation = exif['Image Orientation'].values[0] - if orientation > 1: - t = "/tmp/thumb%d.jpg" % datetime.now().microsecond - im_in = Image.open(filepath) - im_out = None - if orientation == 8: - # Rotated 90 CCW - im_out = im_in.transpose(Image.ROTATE_90) - elif orientation == 6: - # Rotated 90 CW - im_out = im_in.transpose(Image.ROTATE_270) - elif orientation == 3: - # Rotated 180 - im_out = im_in.transpose(Image.ROTATE_180) - elif orientation == 2: - # Mirrored horizontal - im_out = im_in.transpose(Image.FLIP_LEFT_RIGHT) - elif orientation == 4: - # Mirrored vertical - im_out = im_in.transpose(Image.FLIP_TOP_BOTTOM) - elif orientation == 5: - # Mirrored horizontal then rotated 90 CCW - im_out = im_in.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90) - elif orientation == 7: - # Mirrored horizontal then rotated 90 CW - im_out = im_in.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270) - - if im_out: - im_out.save(t, 'JPEG') - shutil.move(t, filepath) - else: - f.close() - returncode = 0 - else: - im = self.__scale_image(True) - if im: - im.save(filepath, "JPEG") - returncode = 1 - except: - f.close() - im = self.__scale_image(True) - if im: - im.save(filepath, "JPEG") - returncode = 2 - return filepath, exif, returncode - - # private class functions - def __get_and_make_path(self, img_id): - """Make directory structure regards of id - and return filepath WITHOUT extension""" - t = os.path.join(self.base, self.root) - try: os.mkdir(t) - except: pass - h = hex(img_id) - if len(h[2:])>6: - try: os.mkdir(os.path.join(t, h[2:4])) - except: pass - try: os.mkdir(os.path.join(t, h[2:4], h[4:6])) - except: pass - path = os.path.join(t, h[2:4], h[4:6], h[6:8]) - try: os.mkdir(path) - except: pass - img = "%s.%s" % (h[8:], 'jpg') - elif len(h[2:])>4: - try: os.mkdir(os.path.join(t, h[2:4])) - except: pass - path = os.path.join(t, h[2:4], h[4:6]) - try: os.mkdir(path) - except: pass - img = "%s.%s" % (h[6:], 'jpg') - elif len(h[2:])>2: - path = os.path.join(t, h[2:4]) - try: os.mkdir(path) - except: pass - img = "%s.%s" %(h[4:], 'jpg') - else: - path = t - img = "%s.%s" %(h[2:], 'jpg') - return(os.path.join(t, img)) - - def __scale_image(self, factor=False): - """generate scaled Image object for given file - args: - factor - if False, adjust height into self.y - if True, use self.x for scale portrait pictures height. - returns Image object, or False - """ - try: - im = Image.open(self.filename).convert('RGB') - except: - return False - x, y = im.size - - if x > self.x or y > self.y: - if x==y: - # square - imt = im.resize((self.y, self.y), Image.ANTIALIAS) - elif x > y: - # landscape - if int(y/(x/float(self.x))) > self.y: - # landscape image: height is non standard - self.x1 = int(float(self.y) * self.y / self.x) - if float(self.y) * self.y / self.x - self.x1 > 0.49: - self.x1 += 1 - imt = im.resize(((int(x/(y/float(self.y))),self.y)),Image.ANTIALIAS) - elif x/self.x==y/self.y: - # aspect ratio ok - imt = im.resize((self.x, self.y), Image.ANTIALIAS) - else: - imt = im.resize((self.x,int(y/(x/float(self.x)))), 1) - else: - # portrait - if factor: - if y>self.x: - imt = im.resize(((int(x/(y/float(self.x))),self.x)),Image.ANTIALIAS) - else: - imt = im - else: - self.x1 = int(float(self.y) * self.y / self.x) - if float(self.y) * self.y / self.x - self.x1 > 0.49: - self.x1 += 1 - - if x/self.x1==y/self.y: - # aspect ratio ok - imt = im.resize((self.x1,self.y),Image.ANTIALIAS) - else: - imt = im.resize(((int(x/(y/float(self.y))),self.y)),Image.ANTIALIAS) - return imt - else: - return im - -class Picture(object): - def __init__(self, *args): - self.x = None - self.y = None +from utils.thumbnail import Thumbnail class MainModel(ModelMT): """Create, load, save, manipulate db file which is container for data""" @@ -241,7 +73,7 @@ class MainModel(ModelMT): ModelMT.__init__(self) self.config = ConfigModel() self.unsaved_project = False - self.filename = None # collection saved/opened filename + self.filename = None # catalog saved/opened filename self.internal_dirname = None self.db_connection = None self.db_cursor = None @@ -259,6 +91,13 @@ class MainModel(ModelMT): gobject.TYPE_UINT64, gobject.TYPE_STRING, gobject.TYPE_INT, gobject.TYPE_STRING, str) + + # tag cloud array element is a dict with 4 keys: + # elem = {'id': str(id), 'name': tagname, 'size': size, 'color': color} + # where color is in one of format: + # - named (i.e. red, blue, black and so on) + # - #rgb + # - #rrggbb self.tag_cloud = [] return @@ -268,8 +107,6 @@ class MainModel(ModelMT): try: shutil.rmtree(self.internal_dirname) except: - if __debug__: - print "m_main.py: cleanup()", self.internal_dirname pass return @@ -287,10 +124,13 @@ class MainModel(ModelMT): return def save(self, filename=None): + """save tared directory at given catalog fielname""" if filename: self.filename = filename - self.__compress_and_save() - return + 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""" @@ -304,7 +144,6 @@ class MainModel(ModelMT): try: tar = tarfile.open(filename, "r") except: - print "%s: file cannot be read!" % filename self.filename = None self.internal_dirname = None return @@ -312,6 +151,7 @@ class MainModel(ModelMT): os.chdir(self.internal_dirname) try: tar.extractall() + print "m_main.py: extracted tarfile into", self.internal_dirname except AttributeError: # python's 2.4 tarfile module lacks of method extractall() directories = [] @@ -372,14 +212,7 @@ class MainModel(ModelMT): self.files_list.clear() except: pass - # parent for virtual '..' dir - #myiter = self.filemodel.insert_before(None,None) - #self.cur.execute("SELECT parent FROM files_connect WHERE child=? AND depth = 1",(id,)) - #self.filemodel.set_value(myiter,0,self.cur.fetchone()[0]) - #self.filemodel.set_value(myiter,1,'..') - #if __debug__: - # print datetime.fromtimestamp(ch[3]) - + # directories first self.db_cursor.execute("SELECT id, filename, size, date FROM files \ WHERE parent_id=? AND type=1 \ @@ -413,32 +246,38 @@ class MainModel(ModelMT): self.files_list.set_value(myiter, 4, ch[4]) self.files_list.set_value(myiter, 5, 'kategoria srategoria') if ch[4] == self.FIL: - if ch[5] == self.F_IMG and ch[6] != None: + if ch[6] != None: + # TODO: change icon to thumbnail self.files_list.set_value(myiter, 6, gtk.STOCK_FILE) else: self.files_list.set_value(myiter, 6, gtk.STOCK_FILE) elif ch[4] == self.LIN: self.files_list.set_value(myiter, 6, gtk.STOCK_INDEX) return - + def get_parent_discs_value(self, child_id): + if child_id: + self.db_cursor.execute("SELECT parent_id FROM files where id=?", (child_id,)) + set = self.db_cursor.fetchone() + if set: + return set[0] + return None + def get_file_info(self, id): """get file info from database""" retval = {} - self.db_cursor.execute("SELECT filename, date, size, type, filetype, \ - id FROM files WHERE id = ?", (id,)) + self.db_cursor.execute("SELECT f.filename, f.date, f.size, f.type, \ + t.filename \ + FROM files f \ + LEFT JOIN thumbnails t ON t.file_id = f.id \ + WHERE f.id = ?", (id,)) set = self.db_cursor.fetchone() if set: - string = "Filename: %s\nDate: %s\nSize: %s\ntype: %s" % \ - (set[0], datetime.fromtimestamp(set[1]), set[2], set[3]) + string = "ID: %d\nFilename: %s\nDate: %s\nSize: %s\ntype: %s" % \ + (id, set[0], datetime.fromtimestamp(set[1]), set[2], set[3]) retval['description'] = string - if set[4] == self.F_IMG: - self.db_cursor.execute("SELECT filename FROM thumbnails \ - WHERE file_id = ?", - (id,)) - set = self.db_cursor.fetchone() - if set: - retval['thumbnail'] = os.path.join(self.internal_dirname, set[0]) + if set[4]: + retval['thumbnail'] = os.path.join(self.internal_dirname, set[4]) return retval def get_source(self, path): @@ -505,28 +344,31 @@ class MainModel(ModelMT): def __create_internal_dirname(self): self.cleanup() - self.internal_dirname = "/tmp/pygtktalog%d" % datetime.now().microsecond + self.internal_dirname = "/tm/pygtktalog%d" % datetime.now().microsecond try: os.mkdir(self.internal_dirname) - except: - if __debug__: - print "m_main.py: __create_internal_dirname(): cannot create \ - temporary directory, or directory exists" - pass + except IOError, (errno, strerror): + print "m_main.py: __create_internal_dirname(): ", strerror return def __compress_and_save(self): - if self.config.confd['compress']: - tar = tarfile.open(self.filename, "w:gz") - else: - tar = tarfile.open(self.filename, "w") + try: + if self.config.confd['compress']: + tar = tarfile.open(self.filename, "w:gz") + else: + tar = tarfile.open(self.filename, "w") + if __debug__: + print "m_main.py: __compress_and_save(): tar open successed" + + except IOError, (errno, strerror): + return False, strerror os.chdir(self.internal_dirname) tar.add('.') tar.close() self.unsaved_project = False - return + return True, None def __create_database(self): """make all necessary tables in db file""" @@ -778,7 +620,16 @@ class MainModel(ModelMT): detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES) db_cursor = db_connection.cursor() + print "%s" % \ + (self.internal_dirname + '/db.sqlite') + # fetch all the directories + sql = """ + SELECT id, parent_id, filename FROM files + WHERE type=1 ORDER BY parent_id, filename + """ + db_cursor.execute(sql) + data = db_cursor.fetchall() try: sql = """ SELECT id, parent_id, filename FROM files