From 3fd3b2ec196a5da0bd84369480829600a84d497b Mon Sep 17 00:00:00 2001 From: gryf Date: Mon, 14 Apr 2008 13:29:50 +0000 Subject: [PATCH] * Added exteranl image viewer. * Added edit for descriptions and notes for files. * Added possibility for make thumbnail for any directory/file. * More improvements and bufixes. --- README | 32 +++++--- src/ctrls/c_config.py | 32 +++++--- src/ctrls/c_main.py | 174 ++++++++++++++++++++++++++++++++++------- src/models/m_config.py | 8 +- src/models/m_main.py | 165 ++++++++++++++++++++++++++++---------- src/utils/gthumb.py | 23 ++++++ src/utils/img.py | 75 +++--------------- src/views/v_dialogs.py | 53 +++++++++++-- 8 files changed, 400 insertions(+), 162 deletions(-) diff --git a/README b/README index 626533a..d01e8e9 100644 --- a/README +++ b/README @@ -10,9 +10,14 @@ which seems to be dead project for years. FEATURES ======== -- scanning for files in selected media -- generating thumbnails +- scan for files in selected media +- get/generate thumbnails from exif and other images +- most important exif tags +- add/edit description and notes +- fetch comments for images made in gThumb +- add/remove unlimited images to any file or directory - tagging files +- and more :) REQUIREMENTS ============ @@ -57,13 +62,13 @@ For version 1.0 following aims have to be done: - searching database - tagging files - user definied group of tags (represented by color in cloud tag) -- file details: - - files properties +x file details: + x files properties x thumbnail x description + x edit note and description x exif information - - keywords (tags) - - gthumb integration + x gthumb integration x adding images x generating/saving thumbnails x moving hardcoded files extensions into config @@ -75,11 +80,12 @@ For version 2.0: - Icon grid in files view - command line support: query, adding media to collection etc - internationalization support +- export to XLS Removed: - filetypes handling (movies, images, archives, documents etc). Now it have - common, unified external "plugin" system - simple output from command line - programs. + common, unified external "plugin" system - simple text output from command + line programs. - anime/movie - title - alt title @@ -90,16 +96,16 @@ Removed: - sub lang - release date (from - to) - anidb link/imdb link - Maybe in future versions. Now text file descriptions and tags have to be - enough for good and fast information search. + Maybe in future versions. Now text file descriptions/notes and tags have to + be enough for good and fast information search. - file information (date, size, etc) (50%) (no need for?) NOTES ===== -Catalog file is tared and optionaly compressed sqlite database and directory -with thumbnails. If there are more images, the size of catalog file will grow. -So be carefull with adding big images in your catalog file! +Catalog file is tared and optionaly compressed sqlite database and directories +with images and thumbnails. If there are more images, the size of catalog file +will grow. So be carefull with adding big images in your catalog file! BUGS ==== diff --git a/src/ctrls/c_config.py b/src/ctrls/c_config.py index a30e7ba..c302a0a 100644 --- a/src/ctrls/c_config.py +++ b/src/ctrls/c_config.py @@ -59,6 +59,8 @@ class ConfigController(Controller): 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() @@ -118,21 +120,30 @@ class ConfigController(Controller): 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() - return def on_button_ejt_clicked(self, button): - self.__show_filechooser() - return + fn = self.__show_filechooser("Choose eject program") + self.view['ejt_entryentry_imv'].set_text(fn) def on_button_mnt_clicked(self, button): - self.__show_dirchooser() - return + 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() - return + + 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() @@ -220,10 +231,11 @@ class ConfigController(Controller): column.set_resizable(True) category_tree.append_column(column) - def __show_filechooser(self): + def __show_filechooser(self, title): """dialog for choose eject""" + fn = None dialog = gtk.FileChooserDialog( - title="Choose eject program", + title=title, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=( gtk.STOCK_CANCEL, @@ -239,9 +251,9 @@ class ConfigController(Controller): if response == gtk.RESPONSE_OK: if __debug__: print "c_config.py: __show_filechooser()", dialog.get_filename() - self.view['ejt_entry'].set_text(dialog.get_filename()) - + fn = dialog.get_filename() dialog.destroy() + return fn def __show_dirchooser(self): """dialog for point the mountpoint""" diff --git a/src/ctrls/c_main.py b/src/ctrls/c_main.py index 6a16cb2..24165e5 100644 --- a/src/ctrls/c_main.py +++ b/src/ctrls/c_main.py @@ -30,6 +30,7 @@ http://www.gnu.org/licenses/gpl.txt """ import os.path +from os import popen from utils import deviceHelper from gtkmvc import Controller @@ -126,11 +127,83 @@ class MainController(Controller): ######################################################################### # Connect signals from GUI, like menu objects, toolbar buttons and so on. + 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 zaznaczonych wierszy" + 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) + + def on_add_thumb1_activate(self, menu_item): + image = Dialogs.LoadImageFile().run() + if not image: + 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.add_thumbnail(image, id) + except: + if __debug__: print "c_main.py: on_add_thumb1_activate(): error on getting selected items or creating thumbnails" + return + 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 getting selected items or removing thumbnails" + return + 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 getting selected items or removing thumbnails" + return + 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) - ImageView(self.model.get_image_path(id)) + img = self.model.get_image_path(id) + 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) def on_rename1_activate(self, widget): model, iter = self.view['discs'].get_selection().get_selected() @@ -138,8 +211,7 @@ class MainController(Controller): 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 __debug__: print "c_main.py: on_rename1_activate(): label:", new_name if new_name != None and new_name != name: self.model.rename(id, new_name) @@ -159,8 +231,7 @@ class MainController(Controller): 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 __debug__: print "c_main.py: on_rename1_activate(): label:", new_name if new_name != None and new_name != name: self.model.rename(fid, new_name) @@ -176,8 +247,7 @@ class MainController(Controller): return def on_tag_cloud_textview_motion_notify_event(self, widget): - if __debug__: - print "c_main.py: on_tag_cloud_textview_motion_notify_event():" + 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) @@ -322,6 +392,10 @@ class MainController(Controller): 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), @@ -379,9 +453,11 @@ class MainController(Controller): 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 @@ -394,8 +470,7 @@ class MainController(Controller): selected_item = self.model.files_list.get_value(iter, 0) self.__get_item_info(selected_item) except: - if __debug__: - print "c_main.py: on_files_cursor_changed() insufficient iterator" + if __debug__: print "c_main.py: on_files_cursor_changed() insufficient iterator" return def on_files_key_release_event(self, a, event): @@ -470,9 +545,10 @@ class MainController(Controller): print self.view['files'].get_cursor() def on_add_image1_activate(self, menu_item): - images = Dialogs.LoadImageFile().run() + images = Dialogs.LoadImageFile(True).run() if not images: return + for image in images: try: selection = self.view['files'].get_selection() @@ -561,7 +637,7 @@ class MainController(Controller): if self.model.config.confd['delwarn']: obj = Dialogs.Qst('Delete elements', 'Delete items?', - 'Items will be permanently removed.') + 'Items will be permanently removed from catalog.') if not obj.run(): return @@ -590,11 +666,27 @@ class MainController(Controller): self.model.get_root_entries(model.get_value(model.get_iter(list_of_paths[0]),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): + 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) + return + def on_debugbtn_clicked(self, widget): """Debug. To remove in stable version, including button in GUI""" if __debug__: @@ -762,8 +854,6 @@ class MainController(Controller): buf = self.view['description'].get_buffer() buf.set_text("") self.view['description'].set_buffer(buf) - self.view['thumb'].hide() - self.__activate_ui() return @@ -903,18 +993,47 @@ class MainController(Controller): def __get_item_info(self, item): self.view['description'].show() set = self.model.get_file_info(item) - buf = self.view['description'].get_buffer() + buf = gtk.TextBuffer() - if set.has_key('file_info'): - buf.set_text(set['file_info']) - if set.has_key('description'): - 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']) - else: - buf.set_text('') + if __debug__ and set.has_key('debug'): + tag = buf.create_tag() + tag.set_property('weight', pango.WEIGHT_BOLD) + buf.insert_with_tags(buf.get_end_iter(), "ID: ", tag) + buf.insert(buf.get_end_iter(), str(set['debug']['id']) + "\n") + 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['debug']['date']) + "\n") + buf.insert_with_tags(buf.get_end_iter(), "Size: ", tag) + buf.insert(buf.get_end_iter(), str(set['debug']['size']) + "\n") + buf.insert_with_tags(buf.get_end_iter(), "Type: ", tag) + buf.insert(buf.get_end_iter(), str(set['debug']['type']) + "\n\n") + + if set.has_key('gthumb'): + tag = buf.create_tag() + tag.set_property('weight', pango.WEIGHT_BOLD) + buf.insert_with_tags(buf.get_end_iter(), "gThumb 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") + buf.insert(buf.get_end_iter(), "\n") + + if set.has_key('description'): + tag = buf.create_tag() + tag.set_property('weight', pango.WEIGHT_BOLD) + buf.insert_with_tags(buf.get_end_iter(), "Details:\n", tag) + buf.insert(buf.get_end_iter(), set['description']) + buf.insert(buf.get_end_iter(), "\n") + if set.has_key('note'): + tag = buf.create_tag() + tag.set_property('weight', pango.WEIGHT_BOLD) + buf.insert_with_tags(buf.get_end_iter(), "Note:\n", tag) + buf.insert(buf.get_end_iter(), set['note']) + self.view['description'].set_buffer(buf) if set.has_key('images'): @@ -931,9 +1050,11 @@ class MainController(Controller): if set.has_key('thumbnail'): self.view['thumb'].set_from_file(set['thumbnail']) - self.view['thumb'].show() + self.view['thumb_box'].show() + #self.view['thumb'].show() else: - self.view['thumb'].hide() + #self.view['thumb'].hide() + self.view['thumb_box'].hide() return def __tag_cloud(self): @@ -953,7 +1074,6 @@ class MainController(Controller): self.view['tag_cloud_textview'].get_window(gtk.TEXT_WINDOW_TEXT) if w: w.set_cursor(None) - def insert_blank(b, iter): if iter.is_end() and iter.is_start(): diff --git a/src/models/m_config.py b/src/models/m_config.py index 8a198c7..8943bbc 100644 --- a/src/models/m_config.py +++ b/src/models/m_config.py @@ -78,6 +78,9 @@ class ConfigModel(Model): 'cd': '/mnt/cdrom', 'ejectapp': 'eject -r', + 'imgview': False, + 'imgprog': 'gqview', + 'retrive': False, 'thumbs': True, @@ -122,6 +125,8 @@ class ConfigModel(Model): 'retrive extra informatin':'retrive', 'scan exif data':'exif', 'include gthumb image description':'gthumb', + 'use external image viewer':'imgview', + 'external image viewer program':'imgprog', } dbool = ( @@ -141,12 +146,13 @@ class ConfigModel(Model): 'delwarn', 'compress', 'retrive', + 'imgview', ) recent = [] RECENT_MAX = 10 - dstring = ('cd','ejectapp') + dstring = ('cd','ejectapp','imgprog') try: path = os.environ['HOME'] diff --git a/src/models/m_main.py b/src/models/m_main.py index 8335151..d1b4a04 100644 --- a/src/models/m_main.py +++ b/src/models/m_main.py @@ -45,8 +45,11 @@ except ImportError: from m_config import ConfigModel from m_details import DetailsModel -from utils.thumbnail import Thumbnail -from utils.img import Img +try: + from utils.thumbnail import Thumbnail + from utils.img import Img +except: + pass from utils.parse_exif import ParseExif from utils.gthumb import GthumbCommentParser @@ -131,7 +134,9 @@ class MainModel(ModelMT): {'id': str(10), 'name': "windows", 'size': 18, 'color': '#333'}, ]''' return + def add_image(self, image, id): + """add single image to file/directory""" sql = """insert into images(file_id, thumbnail, filename) values(?, null, null)""" self.db_cursor.execute(sql, (id,)) @@ -148,8 +153,72 @@ class MainModel(ModelMT): (ip.split(self.internal_dirname)[1][1:], tp.split(self.internal_dirname)[1][1:], res[0])) - self.db_connection.commit() + self.db_connection.commit() + + def del_images(self, 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, (id,)) + res = self.db_cursor.fetchall() + if len(res) > 0: + for fn in res: + os.unlink(os.path.join(self.internal_dirname, fn[0])) + os.unlink(os.path.join(self.internal_dirname, fn[1])) + + # remove images records + sql = """delete from images where file_id = ?""" + self.db_cursor.execute(sql, (id,)) + self.db_connection.commit() + + def delete_image(self, id): + """removes image on specified image id""" + sql = """select filename, thumbnail from images where id=?""" + self.db_cursor.execute(sql, (id,)) + res = self.db_cursor.fetchone() + 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, (id,)) + self.db_connection.commit() + + def add_thumbnail(self, img_fn, id): + """generate and add thumbnail to selected file/dir""" + if self.config.confd['thumbs']: + self.del_thumbnail(id) + p, e, ret_code = Thumbnail(img_fn, + base=self.internal_dirname).save(id) + if ret_code != -1: + sql = """insert into thumbnails(file_id, filename) values (?, ?)""" + self.db_cursor.execute(sql, + (id, + p.split(self.internal_dirname)[1][1:])) + self.db_connection.commit() + return True + return False + + def del_thumbnail(self, id): + """removes thumbnail from selected file/dir""" + + # remove thumbnail files + sql = """select filename from thumbnails where file_id=?""" + self.db_cursor.execute(sql, (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, (id,)) + self.db_connection.commit() + def cleanup(self): self.__close_db_connection() if self.internal_dirname != None: @@ -202,7 +271,8 @@ class MainModel(ModelMT): os.chdir(self.internal_dirname) try: tar.extractall() - print "m_main.py: extracted tarfile into", self.internal_dirname + if __debug__: + print "m_main.py: extracted tarfile into", self.internal_dirname except AttributeError: # python's 2.4 tarfile module lacks of method extractall() directories = [] @@ -328,18 +398,24 @@ class MainModel(ModelMT): """get file info from database""" retval = {} sql = """SELECT f.filename, f.date, f.size, f.type, - t.filename, f.description + t.filename, f.description, f.note FROM files f LEFT JOIN thumbnails t ON t.file_id = f.id WHERE f.id = ?""" self.db_cursor.execute(sql, (id,)) set = self.db_cursor.fetchone() if set: - string = "ID: %d\nFilename: %s\nDate: %s\nSize: %s\ntype: %s" % \ - (id, set[0], datetime.fromtimestamp(set[1]), set[2], set[3]) - retval['file_info'] = string + retval['debug'] = {'id': id, + 'date': datetime.fromtimestamp(set[1]), + 'size': set[2], 'type': set[3]} + + retval['filename'] = set[0] + if set[5]: retval['description'] = set[5] + + if set[6]: + retval['note'] = set[6] if set[4]: retval['thumbnail'] = os.path.join(self.internal_dirname, set[4]) @@ -370,8 +446,16 @@ class MainModel(ModelMT): 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, set[key]) - retval['exif'] = True + + # gthumb + sql = """SELECT note, place, date from gthumb WHERE file_id = ?""" + self.db_cursor.execute(sql, (id,)) + set = self.db_cursor.fetchone() + + if set: + retval['gthumb'] = {'note': set[0], 'place': set[1], 'date': set[2]} + return retval def get_source(self, path): @@ -396,27 +480,10 @@ class MainModel(ModelMT): return None, None return res[0], res[1] - def delete_image(self, id): - """removes image on specified id""" - sql = """select filename, thumbnail from images where id=?""" - self.db_cursor.execute(sql, (id,)) - res = self.db_cursor.fetchone() - 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, (id,)) - self.db_connection.commit() - def delete(self, root_id, db_cursor=None, db_connection=None): """Remove subtree from main tree, remove tags from database - remove all possible data, like thumbnails""" + remove all possible data, like thumbnails, images, gthumb info, exif + etc""" fids = [] @@ -429,9 +496,7 @@ class MainModel(ModelMT): sql = """select parent_id from files where id = ?""" db_cursor.execute(sql, (root_id,)) res = db_cursor.fetchone() - if res: - parent_id = res[0] - + parent_id = res[0] def get_children(fid): fids.append(fid) @@ -456,13 +521,9 @@ class MainModel(ModelMT): sql = """delete from tags_files where file_id = ?""" db_cursor.executemany(sql, generator()) + arg = self.__list_to_string(fids) + # remove thumbnails - arg ='' - for c in fids: - if len(arg) > 0: - arg+=", %d" % c - else: - arg = "%d" % c sql = """select filename from thumbnails where file_id in (%s)""" % arg db_cursor.execute(sql) res = db_cursor.fetchall() @@ -477,6 +538,7 @@ class MainModel(ModelMT): if len(res) > 0: for fn in res: os.unlink(os.path.join(self.internal_dirname, fn[0])) + os.unlink(os.path.join(self.internal_dirname, fn[1])) # remove thumbs records sql = """delete from thumbnails where file_id = ?""" @@ -486,6 +548,10 @@ class MainModel(ModelMT): 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: @@ -500,8 +566,6 @@ class MainModel(ModelMT): 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() @@ -590,9 +654,15 @@ class MainModel(ModelMT): self.db_cursor.execute(sql, (img_id,)) res = self.db_cursor.fetchone() if res: - return res[0] + return os.path.join(self.internal_dirname, res[0]) return None - + + def update_desc_and_note(self, id, desc='', note=''): + """update note and description""" + sql = """UPDATE files SET description=?, note=? WHERE id=?""" + self.db_cursor.execute(sql, (desc, note, id)) + return + # private class functions def __bytes_to_human(self, integer): if integer <= 0 or integer < 1024: @@ -729,6 +799,7 @@ class MainModel(ModelMT): date datetime);""") self.db_cursor.execute("insert into files values(1, 1, 'root', null, 0, 0, 0, 0, null, null);") self.db_cursor.execute("insert into groups values(1, 'default', 'black');") + self.db_connection.commit() def __scan(self): """scan content of the given path""" @@ -952,7 +1023,7 @@ class MainModel(ModelMT): cmnts['place'], cmnts['date'] )) - if cmnts['keywords']: + if cmnts.has_key('keywords'): # TODO: add gthumb keywords to tags and group 'gthumb' pass @@ -997,7 +1068,9 @@ class MainModel(ModelMT): 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 else: print "new directory/cd" @@ -1120,4 +1193,12 @@ class MainModel(ModelMT): else: return txt + def __list_to_string(self, array): + arg ='' + for c in array: + if len(arg) > 0: + arg+=", %d" % c + else: + arg = "%d" % c + return arg pass # end of class diff --git a/src/utils/gthumb.py b/src/utils/gthumb.py index df26764..216427d 100644 --- a/src/utils/gthumb.py +++ b/src/utils/gthumb.py @@ -1,3 +1,26 @@ +# 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.minidom import Node from xml.dom import minidom import gzip diff --git a/src/utils/img.py b/src/utils/img.py index fa90aea..80048f2 100644 --- a/src/utils/img.py +++ b/src/utils/img.py @@ -27,7 +27,6 @@ from shutil import move, copy from os import path, mkdir from datetime import datetime -from utils import EXIF import Image class Img(object): @@ -40,76 +39,26 @@ class Img(object): def save(self, image_id): """Save image and asociated thumbnail into specific directory structure - return full path to the file and thumbnail None""" + return full path to the file and thumbnail or None""" base_path = self.__get_and_make_path(image_id) ext = self.filename.split('.')[-1].lower() - image_filename = path.join(self.base, base_path + "_im." + ext) + image_filename = path.join(self.base, base_path + "." + ext) + + thumbnail = path.join(self.base, base_path + "_t.jpg") - # make and save image - filepath = path.join(self.base, base_path + ".jpg") - 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: - # TODO: replace silly datetime function with tempfile - t = path.join(gettempdir(), "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') - move(t, filepath) - else: - f.close() - returncode = 0 - else: - im = self.__scale_image() - if im: - im.save(filepath, "JPEG") - returncode = 1 - except: - f.close() - im = self.__scale_image() - if im: - im.save(filepath, "JPEG") - returncode = 2 + + im = self.__scale_image() + if im: + im.save(thumbnail, "JPEG") + returncode = 1 if returncode != -1: # copy image copy(self.filename, image_filename) - return filepath, image_filename, returncode + + return thumbnail, image_filename, returncode # private class functions def __get_and_make_path(self, img_id): @@ -146,7 +95,7 @@ class Img(object): img = "%s" % h[2:] return(path.join(t, fpath, img)) - def __scale_image(self, factor=True): + def __scale_image(self): """create thumbnail. returns image object or None""" try: im = Image.open(self.filename).convert('RGB') diff --git a/src/views/v_dialogs.py b/src/views/v_dialogs.py index dccee36..5ba304d 100644 --- a/src/views/v_dialogs.py +++ b/src/views/v_dialogs.py @@ -177,14 +177,15 @@ class PointDirectoryToAdd(object): 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): - if self.URI: - self.dialog.set_current_folder_uri(self.URI) dialog = self.gladexml.get_widget("addDirDialog") ch = True result = dialog.run() @@ -195,7 +196,6 @@ class PointDirectoryToAdd(object): result = dialog.run() else: ch = False - self.__class__.URI = self.dialog.get_current_folder_uri() dialog.destroy() if result == gtk.RESPONSE_OK: return self.volname.get_text(),self.directory.get_text() @@ -321,7 +321,7 @@ class LoadImageFile(object): URI="file://"+os.path.abspath(os.path.curdir) - def __init__(self): + def __init__(self, multiple=False): self.dialog = gtk.FileChooserDialog( title="Select image", action=gtk.FILE_CHOOSER_ACTION_OPEN, @@ -332,7 +332,7 @@ class LoadImageFile(object): gtk.RESPONSE_OK ) ) - self.dialog.set_select_multiple(True) + self.dialog.set_select_multiple(multiple) self.dialog.set_default_response(gtk.RESPONSE_OK) f = gtk.FileFilter() @@ -357,7 +357,10 @@ class LoadImageFile(object): if response == gtk.RESPONSE_OK: try: - filenames = self.dialog.get_filenames() + if self.dialog.get_select_multiple(): + filenames = self.dialog.get_filenames() + else: + filenames = self.dialog.get_filename() except: pass @@ -417,3 +420,41 @@ class StatsDialog(object): if result == gtk.RESPONSE_OK: return entry.get_text() return None + +class EditDialog(object): + """Sepcific dialog for display stats""" + def __init__(self, values={}): + self.gladefile = os.path.join(utils.globals.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 self.values.has_key('description'): + buff = gtk.TextBuffer() + buff.set_text(str(self.values['description'])) + description.set_buffer(buff) + + if self.values.has_key('note'): + 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