fill-form accepts dot as a placeholder type-nothing parameter
[hband-tools.git] / xgui-tools / gexifcomment
blob996512640a0b206c1b92c3544646a38f579100e0
1 #!/usr/bin/env python
3 import gtk
4 import os
5 import sys
6 import imagemetadata
7 import sqlite3
8 import glib
9 import traceback
10 gtk.threads_init()
13 def add_key_binding(widget, keyname, callback):
14 accelgroup = gtk.AccelGroup()
15 key, modifier = gtk.accelerator_parse(keyname)
16 accelgroup.connect_group(key, modifier, gtk.ACCEL_VISIBLE, callback)
17 widget.add_accel_group(accelgroup)
19 class StockButton(gtk.Button):
20 def __init__(self, label=None, stock=None, use_underline=True):
21 gtk.Button.__init__(self, label, stock, use_underline)
22 if label is not None:
23 self.set_markup(label)
24 def set_markup(self, label):
25 a = self.get_children()[0]
26 a = a.get_children()[0]
27 a, lbl = a.get_children()
28 lbl.set_markup(label)
29 def set_pixbuf(self, data):
30 a = self.get_children()[0]
31 a = a.get_children()[0]
32 img, a = a.get_children()
33 if data[0] is None:
34 img.set_from_icon_name(data[1], data[2])
35 else:
36 img.set_from_pixbuf(data[0])
37 img.props.visible = True
40 def get_comment_text():
41 tb = txtv.get_buffer()
42 s, e = tb.get_start_iter(), tb.get_end_iter()
43 return tb.get_text(s, e)
45 def save_comment():
46 txt = get_comment_text()
47 Metadata.comment = txt
48 update_index(SubjectFile, txt)
50 def save_comment_and_exit(*a):
51 save_comment()
52 gtk.main_quit()
54 def tag_entered(entry):
55 tagstr = entry.get_text()
56 if tagstr not in get_comment_text().split("\n"):
57 tb = txtv.get_buffer()
58 end = tb.get_end_iter()
59 lastchar = tb.get_text(tb.get_iter_at_offset(tb.get_char_count() - 1), end)
60 tb.insert(end, ("\n" if lastchar not in ["\n", ''] else '') + tagstr + "\n")
61 entry.set_text('')
63 def db_open(dbfile):
64 dbconn = sqlite3.connect(dbfile)
65 dbconn.text_factory = str
66 dbcur = dbconn.cursor()
67 dbcur.execute("PRAGMA foreign_keys = ON")
68 return (dbconn, dbcur)
70 def db_oper(dbfile, relpath, fname, valtext):
71 values = filter(lambda x: x.strip() != '', valtext.split('\n'))
72 (dbconn, dbcur) = db_open(dbfile)
73 try:
74 dbcur.execute("CREATE TABLE IF NOT EXISTS folder (folderid INTEGER PRIMARY KEY, relpath TEXT UNIQUE)")
75 dbcur.execute("CREATE TABLE IF NOT EXISTS tag (tagid INTEGER PRIMARY KEY, tag TEXT UNIQUE)")
76 dbcur.execute("CREATE TABLE IF NOT EXISTS comment (folder REFERENCES folder (folderid), filename TEXT, tag REFERENCES tag (tagid), UNIQUE (folder, filename, tag))")
78 dbcur.execute("INSERT OR IGNORE INTO folder (relpath) VALUES (?)", [relpath])
79 dbcur.executemany("INSERT OR IGNORE INTO tag (tag) VALUES (?)", map(lambda x: [x], values))
81 dbcur.execute("DELETE FROM comment WHERE folder = (SELECT folderid FROM folder WHERE relpath=?) AND filename = ? AND tag NOT IN (SELECT tagid FROM tag WHERE tag IN (" + ','.join(map(lambda x: '?', values)) + "))", [relpath, fname] + values)
82 dbcur.executemany("INSERT OR IGNORE INTO comment (folder, filename, tag) VALUES ((SELECT folderid FROM folder WHERE relpath=?), ?, (SELECT tagid FROM tag WHERE tag=?))", map(lambda x: [relpath, fname, x], values))
83 dbconn.commit()
84 except sqlite3.OperationalError, exc:
85 print "SQLite Error:", exc.message
86 dbconn.close()
87 return False
88 dbconn.close()
89 return True
91 def update_index(key, val):
92 (dbfilepath, relpath) = find_dbfile()
93 if dbfilepath is None:
94 return False
95 name = os.path.basename(key)
96 return db_oper(dbfilepath, relpath, name, val)
98 def find_dbfile():
99 path = os.path.realpath(os.path.dirname(SubjectFile))
100 relpath = '.'
101 while True:
102 dbfilepath = os.path.join(path, 'exifcomment.db')
103 print "checking DB in", dbfilepath
104 if os.path.exists(dbfilepath):
105 print "found DB"
106 return (dbfilepath, relpath)
107 if path == '/':
108 break
109 if relpath == '.':
110 relpath = os.path.basename(path)
111 else:
112 relpath = os.path.join(os.path.basename(path), relpath)
113 path = os.path.dirname(path)
114 print "no DB found"
115 return (None, None)
117 def db_load_tags(store):
118 (dbfile, unused) = find_dbfile()
119 if dbfile is None: return False
120 (dbconn, dbcur) = db_open(dbfile)
121 try:
122 dbcur.execute("SELECT tag FROM tag")
123 while True:
124 tag = dbcur.fetchone()
125 if tag is None: break
126 store.append([tag[0], -1])
127 except sqlite3.OperationalError, exc:
128 print "SQLite Error:", exc.message
129 dbconn.close()
130 return False
131 dbconn.close()
132 return True
134 def display_error(msg):
135 dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, msg)
136 dlg.run()
137 dlg.destroy()
139 def load_thumbnail():
140 try:
141 thumbnail_loaded = False
142 thumbnail = Metadata.thumbnail
143 if thumbnail is not None:
144 try:
145 ldr = gtk.gdk.PixbufLoader('jpeg')
146 ldr.write(thumbnail)
147 ldr.close()
148 thnl.set_from_pixbuf(ldr.get_pixbuf())
149 thumbnail_loaded = True
150 except Exception, e:
151 traceback.print_exc(e)
152 display_error("Can not load thumbnail.\n"+str(e))
154 if not thumbnail_loaded:
155 pb = gtk.gdk.pixbuf_new_from_file(SubjectFile)
156 w0, h0 = pb.get_width(), pb.get_height()
157 w = 200
158 h = h0 * w / w0
159 pb = pb.scale_simple(w, h, gtk.gdk.INTERP_BILINEAR)
160 thnl.set_from_pixbuf(pb)
161 thumbnail_loaded = True
162 except Exception, e:
163 traceback.print_exc(e)
164 display_error("Can not load thumbnail.\n"+str(e))
165 lbl_load_thnl.hide()
167 def open_externally(*X):
168 import subprocess
169 subprocess.Popen(['gpicview', SubjectFile], stdout=sys.stdout, stderr=sys.stderr)
171 def completion_matcher(completion, userinput, treeiter, userdata):
172 column_text, column_rank = userdata[:]
173 model = completion.get_model()
174 return model[treeiter][column_text].lower().find(userinput) > -1
176 def periodic_sorter():
177 completion = cmpl
178 model = completion.get_model()
179 userinput = entr.get_text()
180 last_userinput = completion.get_data('last-userinput')
181 if last_userinput is None or last_userinput != userinput:
182 completion.set_data('last-userinput', userinput)
183 for item in model:
184 item[column_rank] = item[column_text].lower().find(userinput)
185 # continue scheduling:
186 return True
188 def sorter_cb(model, iter_a, iter_b, userdata):
189 column_text, column_rank = userdata[:]
190 c = cmp(model[iter_a][column_rank], model[iter_b][column_rank])
191 if c == 0:
192 c = cmp(model[iter_a][column_text].lower(), model[iter_b][column_text].lower())
193 return c
195 def program_start(widget, event):
196 widget.disconnect(start_event_handle)
198 text = Metadata.tags.get('DateTimeOriginal') or Metadata.tags.get('DateTime', '')
199 text += "\n"
200 text += os.path.splitext(SubjectFile)[0].replace('/', '\n').strip()
201 lbl_annotation.set_text(text)
203 txtv.get_buffer().set_text(Metadata.comment)
204 db_load_tags(lst0)
205 load_thumbnail()
209 win = gtk.Window(gtk.WINDOW_TOPLEVEL)
210 win.connect('delete-event', lambda a, b: gtk.main_quit())
211 win.set_default_size(400, 300)
212 add_key_binding(win, 'Escape', gtk.main_quit)
213 add_key_binding(win, 'F2', save_comment_and_exit)
214 add_key_binding(win, '<Control>O', open_externally)
216 box0 = gtk.VBox()
217 box11 = gtk.HBox()
218 box111 = gtk.VBox()
219 box112 = gtk.VBox()
220 box12 = gtk.HBox()
221 lbl_load_thnl = gtk.Label("Loading thumbnail...")
222 thnl = gtk.Image()
223 lbl_annotation = gtk.Label()
224 scrl = gtk.ScrolledWindow()
225 txtv = gtk.TextView()
226 cmpl = gtk.EntryCompletion()
227 entr = gtk.Entry()
228 lst0 = gtk.ListStore(str, int)
229 column_text = 0
230 column_rank = 1
231 btn0 = StockButton("Save &amp; Close (F2)", gtk.STOCK_SAVE)
233 box111.set_border_width(2)
234 box112.set_border_width(2)
235 lbl_annotation.set_selectable(True)
236 scrl.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
237 btn0.connect('clicked', save_comment_and_exit)
238 entr.connect('activate', tag_entered)
239 entr.set_completion(cmpl)
240 cmpl.set_match_func(completion_matcher, (column_text, column_rank))
241 cmpl.set_model(lst0)
242 cmpl.set_text_column(column_text)
243 lst0.set_sort_column_id(column_rank, gtk.SORT_ASCENDING)
244 lst0.set_sort_func(column_rank, sorter_cb, (column_text, column_rank))
245 glib.timeout_add(1000, periodic_sorter)
248 win.add(box0)
249 box0.pack_start(box11, True, True, 0)
250 box0.pack_start(box12, False, True, 0)
251 box11.pack_start(box111, True, True, 0)
252 box11.pack_start(box112, False, True, 0)
253 box111.pack_start(scrl, True, True, 0)
254 scrl.add(txtv)
255 box111.pack_start(entr, False, True, 0)
256 box112.pack_start(lbl_load_thnl, False, False, 0)
257 box112.pack_start(thnl, False, False, 0)
258 box112.pack_start(lbl_annotation, False, False, 0)
259 box12.pack_start(btn0, True, False, 0)
261 if len(sys.argv) < 2:
262 sys.stderr.write("Usage: %s <filepath>\n" % sys.argv[0])
263 sys.exit(1)
264 SubjectFile = sys.argv[1]
265 Metadata = imagemetadata.Image(open(SubjectFile, 'r').read())
267 start_event_handle = win.connect('map-event', program_start)
270 win.show_all()
271 win.set_focus(entr)
272 gtk.main()