diff --git a/img.py b/img.py new file mode 100644 index 0000000..6f59e2a --- /dev/null +++ b/img.py @@ -0,0 +1,202 @@ +import pygtk; pygtk.require('2.0') +import gtk + +import EXIF +import Image +import os +import shutil +from datetime import datetime + +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: + im_out = im_in.transpose(Image.ROTATE_90) + elif orientation == 6: + im_out = im_in.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""" + try: os.mkdir(self.root) + except: pass + h = hex(img_id) + if len(h[2:])>6: + try: os.mkdir(os.path.join(self.root, h[2:4])) + except: pass + try: os.mkdir(os.path.join(self.root, h[2:4], h[4:6])) + except: pass + path = os.path.join(self.root, 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(self.root, h[2:4])) + except: pass + path = os.path.join(self.root, 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(self.root, h[2:4]) + try: os.mkdir(path) + except: pass + img = "%s.%s" %(h[4:], 'jpg') + else: + path = self.root + img = "%s.%s" %(h[2:], 'jpg') + return(os.path.join(self.root, 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 Image_Example(object): + + def pressButton(self, widget, data=None): + print "Pressed" + + def delete_event(self, widget, event, data=None): + print "delete event occured" + + return False + + def destroy(self, widget, data=None): + gtk.main_quit() + + def __init__(self): + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.connect("delete_event", self.delete_event) + self.window.connect("destroy", self.destroy) + self.window.set_border_width(10) + + self.button = gtk.Button() + self.button.connect("clicked", self.pressButton, None) + self.button.connect_object("clicked", gtk.Widget.destroy, self.window) + + + + root, dirs, files = os.walk('/home/gryf/t/t').next() + count = 0 + for i in files: + count+=1 + path, exif, success = Thumbnail(os.path.join(root, i), base='/home/gryf/t/t').save(count) + if exif: + print path, len(exif), success + if success != -1: + p = path + + self.image = gtk.Image() + self.image.set_from_file(os.path.join(root, path)) + self.image.show() + + pb = self.image.get_pixbuf() + print pb.get_width(), pb.get_height() + + self.button.add(self.image) + self.window.add(self.button) + self.button.show() + self.window.show() + + + + def main(self): + gtk.main() + + +if __name__ == '__main__': + + Image_Example().main() + diff --git a/resources/glade/dialogs.glade b/resources/glade/dialogs.glade index 1966cb1..a381fd9 100644 --- a/resources/glade/dialogs.glade +++ b/resources/glade/dialogs.glade @@ -395,4 +395,86 @@ + + 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 + + + + + diff --git a/resources/glade/main.glade b/resources/glade/main.glade index 625c9bb..1b287cb 100644 --- a/resources/glade/main.glade +++ b/resources/glade/main.glade @@ -650,98 +650,24 @@ - + 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 - 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 - - + False + GTK_WRAP_WORD + 2 + 2 + False - - 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 - 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 - 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 - GTK_SHADOW_IN - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - - - False - True - - - - - - - - 1 - - - - - - - - True - True - @@ -761,6 +687,113 @@ 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 + + + + + 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 + True + + + + + False + 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 + True + + + + + 1 + + + + + + + + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + page 3 + + + tab + 2 + False + + True @@ -854,6 +887,7 @@ True _Rename True + @@ -890,7 +924,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Add tag True @@ -898,11 +932,78 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Add _thumbnail + Add _Images True + + + + + + True + + + + + False + 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 + + + + + + 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 + _Delete image + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Add images + True + diff --git a/src/ctrls/c_main.py b/src/ctrls/c_main.py index 1caf3f6..27b83f1 100644 --- a/src/ctrls/c_main.py +++ b/src/ctrls/c_main.py @@ -39,6 +39,8 @@ from models.m_config import ConfigModel import views.v_dialogs as Dialogs +from views.v_image import ImageView + import gtk import pango @@ -123,18 +125,57 @@ class MainController(Controller): ######################################################################### # Connect signals from GUI, like menu objects, toolbar buttons and so on. + 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)) + def on_rename1_activate(self, widget): model, iter = self.view['discs'].get_selection().get_selected() - label_old = model.get_value(iter, 1) + name = model.get_value(iter, 1) id = model.get_value(iter, 0) - label = Dialogs.InputDiskLabel(label_old).run() + new_name = Dialogs.InputNewName(name).run() + if __debug__: - print "c_main.py: on_rename1_activate(): label:", label - if label != None and label !=label_old: - self.model.set_label(id, label) - self.model.unsaved_project = True + print "c_main.py: on_rename1_activate(): label:", new_name + + if new_name != None and new_name != name: + self.model.rename(id, new_name) self.__set_title(filepath=self.model.filename, modified=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 != None and new_name != name: + self.model.rename(fid, new_name) + self.__set_title(filepath=self.model.filename, modified=True) + + try: + path, column = self.view['discs'].get_cursor() + print path + iter = model.get_iter(path) + self.model.get_root_entries(model.get_value(iter, 0)) + except TypeError: + print "zuo" + 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():" @@ -246,7 +287,42 @@ class MainController(Controller): else: treeview.expand_row(path,False) return - + + 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): + list_of_paths = self.view['images'].get_selected_items() + model = self.view['images'].get_model() + iter = model.get_iter(list_of_paths[0]) + id = model.get_value(iter, 0) + self.model.delete_image(id) + + 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) + self.__get_item_info(id) + except: + pass + + def on_img_add_activate(self, menu_item): + self.on_add_image1_activate(menu_item) + def on_discs_button_press_event(self, treeview, event): try: path, column, x, y = treeview.get_path_at_pos(int(event.x), @@ -271,11 +347,9 @@ class MainController(Controller): if self.model.discs_tree.get_value(iter, 3) == 1: # if ancestor is 'root', then activate "update" menu item self.view['update1'].set_sensitive(True) - self.view['rename1'].set_sensitive(True) else: self.view['update1'].set_sensitive(False) - self.view['rename1'].set_sensitive(False) - self.__popup_discs_menu(event) + self.__popup_menu(event) # elif event.button == 1: # Left click # """Show files on right treeview, after clicking the left disc treeview.""" @@ -314,8 +388,17 @@ class MainController(Controller): model, list_of_paths = selection.get_selected_rows() except TypeError: list_of_paths = [] - - self.__popup_files_menu(event) + + if len(list_of_paths) == 0: + selection.select_path(path[0]) + + if len(list_of_paths) > 1: + self.view['add_image1'].set_sensitive(False) + self.view['rename2'].set_sensitive(False) + else: + self.view['add_image1'].set_sensitive(True) + self.view['rename2'].set_sensitive(True) + self.__popup_menu(event, 'files_popup') return True def on_files_cursor_changed(self, treeview): @@ -323,18 +406,18 @@ class MainController(Controller): model, paths = treeview.get_selection().get_selected_rows() try: itera = model.get_iter(paths[0]) - if model.get_value(itera,4) == 1: - #directory, do nothin', just turn off view - '''self.view['details'].hide() - buf = self.view['details'].get_buffer() - buf.set_text('') - self.view['details'].set_buffer(buf)''' - else: + #if model.get_value(itera,4) == 1: + # #directory, do nothin', just turn off view + # '''self.view['details'].hide() + # buf = self.view['details'].get_buffer() + # buf.set_text('') + # self.view['details'].set_buffer(buf)''' + #else: #file, show what you got. #self.details.get_top_widget() - iter = model.get_iter(treeview.get_cursor()[0]) - selected_item = self.model.files_list.get_value(iter, 0) - self.__get_item_info(selected_item) + iter = model.get_iter(treeview.get_cursor()[0]) + 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" @@ -409,39 +492,130 @@ class MainController(Controller): return def on_add_tag1_activate(self, menu_item): - print self.view['discs'].get_cursor() + print self.view['files'].get_cursor() + + def on_add_image1_activate(self, menu_item): + images = Dialogs.LoadImageFile().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) + 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 - path = self.view['discs'].get_cursor() 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: - if self.__add_cd(label): - self.model.delete(self.model.discs_tree.get_iter(path[0],0)) - pass + self.__add_cd(label, fid) + elif self.model.get_source(path) == self.model.DR: - if self.__add_directory(filepath, label): - self.model.delete(self.model.discs_tree.get_iter(path[0])) - pass + self.__add_directory(filepath, label, fid) + return def on_delete2_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) + #path, column = self.view['discs'].get_cursor() + #selected_iter = model.get_iter(path) + selection = self.view['discs'].get_selection() + model, selected_iter = selection.get_selected() except: return + if self.model.config.confd['delwarn']: - name = self.model.discs_tree.get_value(selected_iter,1) + 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 - self.model.delete(selected_iter) + + # remove from model + path = model.get_path(selected_iter) + current_id = 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 + print current_id + self.model.delete(current_id) + + # refresh files treeview + current_id = model.get_value(model.get_iter(path), 0) + self.model.get_root_entries(current_id) + + # refresh file info view + self.__get_item_info(current_id) + + self.model.unsaved_project = True + self.__set_title(filepath=self.model.filename, modified=True) + return + + def on_delete3_activate(self, menu_item): + dmodel = self.model.discs_tree + try: + selection = self.view['files'].get_selection() + model, list_of_paths = selection.get_selected_rows() + except TypeError: + return + + if self.model.config.confd['delwarn']: + obj = Dialogs.Qst('Delete elements', 'Delete items?', + 'Items will be permanently removed.') + 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 + + for p in list_of_paths: + val = model.get_value(model.get_iter(p), 0) + if model.get_value(model.get_iter(p), 4) == self.model.DIR: + # remove from disctree model aswell + dpath = [] + dmodel.foreach(foreach_disctree, (val, dpath)) + for dp in dpath: + dmodel.remove(dmodel.get_iter(dp)) + + # delete from db + self.model.delete(val) + + try: + selection = self.view['discs'].get_selection() + model, list_of_paths = selection.get_selected_rows() + if not list_of_paths: + list_of_paths = [1] + self.model.get_root_entries(model.get_value(model.get_iter(list_of_paths[0]),0)) + except TypeError: + return + self.model.unsaved_project = True self.__set_title(filepath=self.model.filename, modified=True) return @@ -505,8 +679,7 @@ class MainController(Controller): """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.') + obj = Dialogs.Qst('Unsaved data - pyGTKtalog','There is not saved database','Pressing "Ok" will abandon catalog.') if not obj.run(): return @@ -543,7 +716,7 @@ class MainController(Controller): Dialogs.Err("Error writing file - pyGTKtalog","Cannot write \ file %s." % path, "%s" % err) - def __add_cd(self, label=None): + def __add_cd(self, label=None, current_id=None): """Add directory structure from cd/dvd disc""" mount = deviceHelper.volmount(self.model.config.confd['cd']) if mount == 'ok': @@ -555,7 +728,8 @@ class MainController(Controller): 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) + 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) return True @@ -566,7 +740,7 @@ class MainController(Controller): "Last mount message:\n%s" % mount) return False - def __add_directory(self, path=None, label=None): + def __add_directory(self, path=None, label=None, current_id=None): if not label or not path: res = Dialogs.PointDirectoryToAdd().run() if res !=(None,None): @@ -577,7 +751,7 @@ class MainController(Controller): self.scan_cd = False self.model.source = self.model.DR - self.model.scan(path, label) + self.model.scan(path, label, current_id) self.model.unsaved_project = True self.__set_title(filepath=self.model.filename, modified=True) return True @@ -639,6 +813,12 @@ class MainController(Controller): 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) + return + def __setup_tags_treeview(self): """Setup TreeView discs widget as tree.""" self.view['tags'].set_model(self.model.tagsTree) @@ -687,10 +867,10 @@ class MainController(Controller): c.set_resizable(True) self.view['files'].append_column(c) - c = gtk.TreeViewColumn('Category',gtk.CellRendererText(), text=5) - c.set_sort_column_id(5) - c.set_resizable(True) - self.view['files'].append_column(c) + #c = gtk.TreeViewColumn('Category',gtk.CellRendererText(), text=5) + #c.set_sort_column_id(5) + #c.set_resizable(True) + #self.view['files'].append_column(c) # registration of treeview signals: @@ -742,16 +922,10 @@ class MainController(Controller): self.model.config.save() return - def __popup_discs_menu(self, event): - self.view['discs_popup'].popup(None, None, None, event.button, + def __popup_menu(self, event, menu='discs_popup'): + self.view[menu].popup(None, None, None, event.button, event.time) - self.view['discs_popup'].show_all() - return - - def __popup_files_menu(self, event): - self.view['files_popup'].popup(None, None, None, event.button, - event.time) - self.view['files_popup'].show_all() + self.view[menu].show_all() return def __generate_recent_menu(self): @@ -777,11 +951,17 @@ class MainController(Controller): 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('') + self.view['description'].set_buffer(buf) + if set.has_key('images'): + self.__setup_iconview() + self.view['img_container'].show() + else: + self.view['img_container'].hide() + if set.has_key('thumbnail'): self.view['thumb'].set_from_file(set['thumbnail']) self.view['thumb'].show() diff --git a/src/models/m_main.py b/src/models/m_main.py index e7fbed4..85545d3 100644 --- a/src/models/m_main.py +++ b/src/models/m_main.py @@ -46,6 +46,7 @@ except ImportError: from m_config import ConfigModel from m_details import DetailsModel from utils.thumbnail import Thumbnail +from utils.img import Img class MainModel(ModelMT): """Create, load, save, manipulate db file which is container for data""" @@ -79,7 +80,7 @@ class MainModel(ModelMT): self.config.load() self.details = DetailsModel() - # Directory tree: id, nazwa, ikonka, typ + # 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: child_id(?), filename, size, @@ -88,6 +89,8 @@ class MainModel(ModelMT): gobject.TYPE_UINT64, gobject.TYPE_STRING, gobject.TYPE_INT, gobject.TYPE_STRING, str) + # iconview store - image id, pixbuffer + self.images_store = gtk.ListStore(gobject.TYPE_INT, gtk.gdk.Pixbuf) # tag cloud array element is a dict with 4 keys: # elem = {'id': str(id), 'name': tagname, 'size': size, 'color': color} @@ -108,7 +111,25 @@ class MainModel(ModelMT): {'id': str(10), 'name': "windows", 'size': 18, 'color': '#333'}, ]''' return + def add_image(self, image, id): + sql = """insert into images(file_id, thumbnail, filename) + values(?, null, null)""" + self.db_cursor.execute(sql, (id,)) + self.db_connection.commit() + sql = """select id from images where thumbnail is null and filename is null and file_id=?""" + self.db_cursor.execute(sql, (id,)) + res = self.db_cursor.fetchone() + if res: + tp, ip, rc = Img(image, self.internal_dirname).save(res[0]) + if rc != -1: + sql = """update images set filename=?, thumbnail=? where id=?""" + self.db_cursor.execute(sql, + (ip.split(self.internal_dirname)[1][1:], + tp.split(self.internal_dirname)[1][1:], + res[0])) + self.db_connection.commit() + def cleanup(self): self.__close_db_connection() if self.internal_dirname != None: @@ -201,7 +222,7 @@ class MainModel(ModelMT): return True - def scan(self, path, label): + def scan(self, path, label, currentid): """scan files in separated thread""" # flush buffer to release db lock. @@ -209,6 +230,7 @@ class MainModel(ModelMT): self.path = path self.label = label + self.currentid = currentid if self.busy: return @@ -216,17 +238,21 @@ class MainModel(ModelMT): self.thread.start() return - def set_label(self, id, label=None): - if label: + def rename(self, id, new_name=None): + if new_name: self.db_cursor.execute("update files set filename=? \ - where id=? and parent_id=1", (label, id)) + where id=?", (new_name, id)) self.db_connection.commit() self.__fetch_db_into_treestore() + self.unsaved_project = True else: if __debug__: - print "m_main.py: set_label(): no label defined" + print "m_main.py: rename(): no label defined" return + def refresh_discs_tree(self): + self.__fetch_db_into_treestore() + def get_root_entries(self, id=None): """Get all children down from sepcified root""" try: @@ -281,11 +307,12 @@ class MainModel(ModelMT): def get_file_info(self, id): """get file info from database""" retval = {} - self.db_cursor.execute("SELECT f.filename, f.date, f.size, f.type, \ - t.filename, f.description \ - FROM files f \ - LEFT JOIN thumbnails t ON t.file_id = f.id \ - WHERE f.id = ?", (id,)) + sql = """SELECT f.filename, f.date, f.size, f.type, + t.filename, f.description + 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" % \ @@ -296,6 +323,17 @@ class MainModel(ModelMT): if set[4]: retval['thumbnail'] = os.path.join(self.internal_dirname, set[4]) + + sql = """SELECT id, filename, thumbnail from images WHERE file_id = ?""" + self.db_cursor.execute(sql, (id,)) + set = self.db_cursor.fetchall() + if set: + self.images_store = gtk.ListStore(gobject.TYPE_INT, gtk.gdk.Pixbuf) + for id, img, thb in set: + im = os.path.join(self.internal_dirname,thb) + pix = gtk.gdk.pixbuf_new_from_file(im) + self.images_store.append([id, pix]) + retval['images'] = True return retval def get_source(self, path): @@ -311,7 +349,7 @@ class MainModel(ModelMT): 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]), + bid = self.discs_tree.get_value(self.discs_tree.get_iter(path), 0) self.db_cursor.execute("select filepath, filename from files \ where id = ? and parent_id = 1", (bid,)) @@ -320,11 +358,92 @@ class MainModel(ModelMT): return None, None return res[0], res[1] - def delete(self, branch_iter): - if not branch_iter: - return - self.__remove_branch_form_db(self.discs_tree.get_value(branch_iter,0)) - self.discs_tree.remove(branch_iter) + 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""" + + # TODO: opanowac syf zwiazany z tym, ze katalogi teraz przechowuja dane nieprawdziwe + + fids = [] + + if not db_cursor: + db_cursor = self.db_cursor + + if not db_connection: + db_connection = self.db_connection + + def get_children(fid): + 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(): + for c in fids: + yield (c,) + + # 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()) + + # 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() + if len(res) > 0: + for fn in res: + os.unlink(os.path.join(self.internal_dirname, fn[0])) + + # remove images + sql = """select filename, thumbnail from images where file_id in (%s)""" % arg + db_cursor.execute(sql) + res = db_cursor.fetchall() + if res[0][0]: + for fn in res: + os.unlink(os.path.join(self.internal_dirname, fn[0])) + + # 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()) + + db_connection.commit() return def get_stats(self, selected_id): @@ -398,25 +517,33 @@ class MainModel(ModelMT): 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: + return res[0] + return None + # private class functions def __bytes_to_human(self, integer): if integer <= 0 or integer < 1024: return "%d bytes" % integer - t = integer /1024.0 - if t < 1 or t < 1024: - return "%d bytes (%d kB)" % (integer, t) - - t = t /1024.0 - if t < 1 or t < 1024: - return "%d bytes (%d MB)" % (integer, t) + ## 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 - t = t /1024.0 - if t < 1 or t < 1024: - return "%d bytes (%d GB)" % (integer, t) - - t = t /1024.0 - return "%d bytes (%d TB)" % (integer, t) + t = integer + for power in ['kB', 'MB', 'GB', 'TB']: + t = t /1024.0 + if t < 1 or t < 1024: + break + return "%0.2f %s (%d bytes)" % (t, power, integer) def __clear_trees(self): self.__clear_files_tree() @@ -506,7 +633,12 @@ class MainModel(ModelMT): thumbnails(id INTEGER PRIMARY KEY AUTOINCREMENT, file_id INTEGER, filename TEXT);""") - self.db_cursor.execute("insert into files values(1, 1, 'root', null, 0, 0, 0, 0, null, null, null, null);") + self.db_cursor.execute("""create table + images(id INTEGER PRIMARY KEY AUTOINCREMENT, + file_id INTEGER, + thumbnail TEXT, + filename TEXT);""") + 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');") def __scan(self): @@ -678,11 +810,11 @@ class MainModel(ModelMT): # Images - thumbnails and exif data if self.config.confd['thumbs'] and ext in self.IMG: - path, exif, ret_code = Thumbnail(current_file, base=self.internal_dirname).save(fileid) + tpath, exif, ret_code = Thumbnail(current_file, base=self.internal_dirname).save(fileid) if ret_code != -1: sql = """insert into thumbnails(file_id, filename) values (?, ?)""" db_cursor.execute(sql, (fileid, - path.split(self.internal_dirname)[1][1:])) + tpath.split(self.internal_dirname)[1][1:])) if self.config.confd['exif']: # TODO: exif implementation @@ -717,14 +849,21 @@ class MainModel(ModelMT): if __recurse(1, self.label, self.path, 0, 0, self.DIR) == -1: if __debug__: - print "m_main.py: __scan() __recurse() \ - interrupted self.abort = True" + 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.delete(self.currentid, db_cursor, db_connection) + self.currentid = None + else: + print "new directory/cd" db_cursor.close() db_connection.commit() db_connection.close() @@ -733,6 +872,8 @@ class MainModel(ModelMT): self.busy = False + # refresh discs tree + self.__fetch_db_into_treestore() self.statusmsg = "Idle" self.progress = 0 self.abort = False @@ -797,54 +938,6 @@ class MainModel(ModelMT): db_connection.close() return - def __remove_branch_form_db(self, root_id): - """Remove subtree from main tree, remove tags from database - remove all possible data, like thumbnails""" - fids = [] - - def get_children(fid): - fids.append(fid) - sql = """select id from files where parent_id = ?""" - self.db_cursor.execute(sql, (fid,)) - res = self.db_cursor.fetchall() - if len(res)>0: - for i in res: - get_children(i[0]) - - get_children(root_id) - - def generator(): - for c in fids: - yield (c,) - - # remove files records - sql = """delete from files where id = ?""" - self.db_cursor.executemany(sql, generator()) - - # remove tags records - sql = """delete from tags_files where file_id = ?""" - self.db_cursor.executemany(sql, generator()) - - # 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 - self.db_cursor.execute(sql) - res = self.db_cursor.fetchall() - if len(res) > 0: - for fn in res: - os.unlink(os.path.join(self.internal_dirname, fn[0])) - - # remove thumbs records - sql = """delete from thumbnails where file_id = ?""" - self.db_cursor.executemany(sql, generator()) - self.db_connection.commit() - return - def __append_added_volume(self): """append branch from DB to existing tree model""" #connect diff --git a/src/utils/img.py b/src/utils/img.py new file mode 100644 index 0000000..12cfc4b --- /dev/null +++ b/src/utils/img.py @@ -0,0 +1,156 @@ +# 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 gettempdir +from shutil import move, copy +from os import path, mkdir +from datetime import datetime + +import EXIF +import Image + +class Img(object): + def __init__(self, filename=None, base=''): + self.root = 'images' + self.x = 160 + self.y = 160 + self.filename = filename + self.base = base + + def save(self, image_id): + """Save image and asociated thumbnail into specific directory structure + return full path to the file and thumbnail 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) + + # 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 + + if returncode != -1: + # copy image + copy(self.filename, image_filename) + return filepath, image_filename, returncode + + # private class functions + def __get_and_make_path(self, img_id): + """Make directory structure regards of id + and return filepath and img filename WITHOUT extension""" + t = path.join(self.base, self.root) + try: mkdir(t) + except: pass + + h = hex(img_id) + if len(h[2:])>6: + try: mkdir(path.join(t, h[2:4])) + except: pass + try: mkdir(path.join(t, h[2:4], h[4:6])) + except: pass + fpath = path.join(t, h[2:4], h[4:6], h[6:8]) + try: mkdir(fpath) + except: pass + img = "%s" % h[8:] + elif len(h[2:])>4: + try: mkdir(path.join(t, h[2:4])) + except: pass + fpath = path.join(t, h[2:4], h[4:6]) + try: mkdir(fpath) + except: pass + img = "%s" % h[6:] + elif len(h[2:])>2: + fpath = path.join(t, h[2:4]) + try: mkdir(fpath) + except: pass + img = "%s" % h[4:] + else: + fpath = '' + img = "%s" % h[2:] + return(path.join(t, fpath, img)) + + def __scale_image(self, factor=True): + """create thumbnail. returns image object or None""" + try: + im = Image.open(self.filename).convert('RGB') + except: + return None + im.thumbnail((self.x, self.y), Image.ANTIALIAS) + return im diff --git a/src/views/v_dialogs.py b/src/views/v_dialogs.py index fb20d98..fdc66d5 100644 --- a/src/views/v_dialogs.py +++ b/src/views/v_dialogs.py @@ -128,6 +128,24 @@ class InputDiskLabel(object): 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(utils.globals.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""" @@ -276,7 +294,47 @@ class LoadDBFile(object): 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.""" + def __init__(self): + 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(True) + 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']: + 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) + + def run(self): + response = self.dialog.run() + filenames = None + + if response == gtk.RESPONSE_OK: + try: + filenames = self.dialog.get_filenames() + except: + pass + self.dialog.destroy() + return filenames + class StatsDialog(object): """Sepcific dialog for display stats""" def __init__(self, values={}): diff --git a/src/views/v_image.py b/src/views/v_image.py new file mode 100644 index 0000000..c792c2c --- /dev/null +++ b/src/views/v_image.py @@ -0,0 +1,43 @@ +# 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 gtk + +import utils.globals + +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) + window.add(image) + image.show() + window.show() + return + + pass # end of class