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