deleted some print statements used to debug
[marvin.git] / marvin / view.py
blob4aad397bf9d0fdd486ef1c6e3759fe0d4e5c5a5d
1 ##
2 ## view.py
3 ## Login : <freyes@yoda>
4 ## Started on Tue Jul 1 20:51:33 2008 Felipe Reyes
5 ## $Id$
6 ##
7 ## Copyright (C) 2008 Felipe Reyes
8 ##
9 ## This file is part of Marvin.
11 ## Marvin is free software: you can redistribute it and/or modify
12 ## it under the terms of the GNU General Public License as published by
13 ## the Free Software Foundation, either version 3 of the License, or
14 ## (at your option) any later version.
16 ## Marvin is distributed in the hope that it will be useful,
17 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ## GNU General Public License for more details.
21 ## You should have received a copy of the GNU General Public License
22 ## along with this program. If not, see <http://www.gnu.org/licenses/>.
24 from marvin.model import Photo, PhotoListStore, connect_to_db, ConfigurationManager
26 import locale
27 import gettext
28 import gobject
29 import gtk
30 import gtk.glade
31 import gtk.gdk
32 import gnomevfs
33 import gconf
34 import threading
35 import sqlite3
36 import os
37 import sys
38 import shutil
39 import time
40 import datetime
41 try:
42 import EXIF
43 except:
44 curr_dir = os.path.abspath(".")
45 sys.path.append(curr_dir)
46 import EXIF
48 ##logging system
49 import logging
50 log = None
52 ### l18n
53 APP_NAME = "marvin"
54 #Get the local directory since we are not installing anything
55 local_path = os.path.realpath(os.path.dirname(sys.argv[0]))
56 # Init the list of languages to support
57 langs = []
58 #Check the default locale
59 lc, encoding = locale.getdefaultlocale()
60 if (lc):
61 #If we have a default, it's the first in the list
62 langs = [lc]
63 # Now lets get all of the supported languages on the system
64 language = os.environ.get('LANGUAGE', None)
65 if (language):
66 langs += language.split(":")
68 langs += ["es_CL"]
70 gettext.bindtextdomain(APP_NAME, local_path)
71 gettext.textdomain(APP_NAME)
72 # Get the language to use
73 lang = gettext.translation(APP_NAME, local_path, languages=langs, fallback = True)
75 _ = lang.gettext
76 ###
78 gtk.gdk.threads_init()
79 wTree = None
81 from pkg_resources import resource_filename
84 class MainWindow (object):
86 def __init__ (self, **kwargs):
88 self._gladefile = resource_filename("marvin.ui", "marvin.glade")
89 global wTree
90 wTree = gtk.glade.XML(self._gladefile, "wnd_main")
92 signals = {
93 "on_entry_sql_filter_activate" : self._on_sql_filter_activate,
94 "on_btn_search_with_filter_clicked" : self._on_search_clicked,
95 "on_iconview1_selection_changed" : self._on_selection_changed,
96 "on_wnd_main_delete_event" : self._on_delete_event,
97 "on_iconview1_item_activated" : self._item_activated,
98 "on_iconview1_button_press_event" : self._show_popup,
101 wTree.signal_autoconnect(signals)
103 list_photos = gtk.ListStore(Photo, gtk.gdk.Pixbuf, str)
104 list_photos.clear()
106 #setup the iconview and set the model
107 iconview = wTree.get_widget("iconview1")
108 iconview.set_model(list_photos)
109 iconview.set_pixbuf_column(1)
110 iconview.set_text_column(2)
112 self._win = wTree.get_widget("wnd_main")
113 uidef = resource_filename("marvin.ui", "bar.xml")
114 self._uimanager = gtk.UIManager()
115 accel_group = self._uimanager.get_accel_group()
116 self._win.add_accel_group(accel_group)
117 action_group = gtk.ActionGroup('my_actions')
118 action_group.add_actions([#TODO:finish actions
119 ('ImportPhoto', gtk.STOCK_ADD, _("Import Photos"), "<Control>i", None,
120 self._on_import_clicked),
121 ('SetAsWallpaper', None, _("Set as wallpaper"), None, None,
122 self._on_set_wallpaper_clicked),
125 self._uimanager.insert_action_group(action_group, 0)
126 self._uimanager.add_ui_from_file(uidef)
127 box = wTree.get_widget("vbox1")
128 toolbar = self._uimanager.get_widget("/toolbar")
129 box.pack_start(toolbar, False)
130 box.reorder_child(toolbar, 0)
132 config = ConfigurationManager()
133 self._win.set_default_size(config.window_default_size[0],
134 config.window_default_size[1])
135 self._win.show_all()
138 # prepare and launch the thread that fills the iconview model
139 self._thread = PhotoListThread(list_photos)
140 self._thread.start()
142 def _show_popup(self, widget, event):
143 if event.button != 3:
144 return
146 iconview = wTree.get_widget("iconview1")
147 selected_items = iconview.get_selected_items()
148 item_under_mouse = iconview.get_item_at_pos (int(event.x), int(event.y))[0]
150 if item_under_mouse not in selected_items:
151 iconview.unselect_all()
152 iconview.select_path(item_under_mouse)
153 selected_items = iconview.get_selected_items()
155 item = self._uimanager.get_widget("/popup/SetAsWallpaper")
156 if len(selected_items) != 1:
157 item.set_sensitive(False)
158 else:
159 item.set_sensitive(True)
161 menu_widget = self._uimanager.get_widget("/popup");
163 menu_widget.popup(None, None, None, event.button, event.time);
164 menu_widget.show_all()
166 def _on_set_wallpaper_clicked(self, menuitem):
168 iconview = wTree.get_widget("iconview1")
170 selected_items = iconview.get_selected_items()
172 if len(selected_items) != 1:
173 return
175 model = iconview.get_model()
176 iter = model.get_iter(selected_items[0])
177 photo = model.get(iter, 0)[0]
179 ConfigurationManager.set_wallpaper(photo.path)
181 def _on_import_clicked(self, menuitem):
182 ImportWindow()
184 def _on_sql_filter_activate(self, entry):
185 button = wTree.get_widget("btn_search_with_filter")
186 button.clicked()
188 def _on_search_clicked(self, button):
191 Arguments:
192 - `self`:
193 - `button`:
195 model = wTree.get_widget("iconview1").get_model()
197 entry = wTree.get_widget("entry_sql_filter")
198 sql_filter = entry.get_text()
200 query = "select id, uri from photos " + sql_filter
202 if not self._thread.stop.isSet():
203 self._thread.stop.set()
204 thread = PhotoListThread(model, query)
205 thread.start()
207 def _on_selection_changed(self, iconview):
210 Arguments:
211 - `self`:
212 - `iconview`:
214 num_selec_items = len(iconview.get_selected_items())
215 if num_selec_items == 1:
217 lbl = wTree.get_widget("lbl_original_date")
219 model = iconview.get_model()
220 iter = model.get_iter(iconview.get_selected_items()[0])
221 photo = model.get(iter, 0)[0]
222 str_date = photo.original_date.strftime("%x")
223 lbl.set_text(_("Original Date %s" % str_date))
225 lbl = wTree.get_widget("lbl_tags")
227 lbl.set_text(str(photo.tags))
228 elif num_selec_items == 0:
229 wTree.get_widget("lbl_original_date").set_text("")
230 wTree.get_widget("lbl_tags").set_text("")
232 def _item_activated(self, iconview, path):
234 store = iconview.get_model()
235 iter = store.get_iter(path)
236 photo = store.get_value (iter, 0)
238 ImageWindow(photo, iconview)
240 def _on_delete_event (self, widget, event):
242 config = ConfigurationManager()
243 win = wTree.get_widget("wnd_main")
244 config.window_default_size = win.get_size()
245 gtk.main_quit()
247 class ImageWindow:
249 def __init__(self, photo, iconview):
251 self.fullscreen = False
253 self._wTree = gtk.glade.XML(resource_filename("marvin.ui", "marvin.glade"), "wnd_display_img")
255 signals = {
256 "on_wnd_display_img_window_state_event": self._on_window_state_event,
257 "on_wnd_display_img_delete_event" : self._on_delete_event,
258 "on_wnd_display_img_key_press_event" : self._on_key_press,
260 self._wTree.signal_autoconnect (signals)
262 self.img_widget = self._wTree.get_widget("img")
264 self.iconview = iconview
265 self.photo = photo
266 self.path = gnomevfs.get_local_path_from_uri (self.photo.uri)
268 win = self._wTree.get_widget("wnd_display_img")
269 win.set_size_request (800, 600)
270 win.show_all()
272 self.update_image()
274 def _on_delete_event(self, widget, event):
277 Arguments:
278 - `self`:
279 - `widget`:
280 - `event`:
282 pass
284 def _on_window_state_event (self, widget, event):
285 "This method should be used for the fullscreen functinality"
286 pass
289 def _on_key_press(self, widget, event):
290 """Handle the key press event of the image window
292 Arguments:
293 - `self`:
294 - `widget`:
295 - `event`:
297 model = self.iconview.get_model()
299 iter = model.get_iter(self.iconview.get_selected_items()[0])
301 str_key = gtk.gdk.keyval_name(event.keyval)
303 if str_key == 'Right' or str_key == 'Down':
304 iter_next = model.iter_next(iter)
306 if iter_next == None:
307 iter_next = model.get_iter_first()
309 self.update_iconview(iter_next)
311 elif str_key == 'Left' or str_key == 'Up':
312 int_path = int(model.get_string_from_iter(iter)) - 1
313 if int_path < 0:
314 int_path = 0
315 str_path = str("%d" % (int_path))
317 iter_next = model.get_iter_from_string(str_path)
319 self.update_iconview(iter_next)
321 elif str_key == 'plus':
322 self.zoom(1.1)
324 elif str_key == 'minus':
325 self.zoom(0.9)
327 elif str_key == '1':
328 self.update_image(False)
330 elif str_key == 'F11':
331 win = self._wTree.get_widget("wnd_display_img")
332 if self.fullscreen:
333 win.unfullscreen()
334 self.fullscreen =False
335 else:
336 win.fullscreen()
337 self.fullscreen = True
339 elif str_key.lower() == 's':
340 self.update_image()
342 elif str_key == 'Escape':
343 win = self._wTree.get_widget("wnd_display_img")
344 win.destroy()
345 return
346 else:
347 print str_key
348 return
351 def zoom(self, factor):
353 curr_width = self.pixbuf.get_width()
354 curr_height = self.pixbuf.get_height()
355 scaled_pixbuf = self.pixbuf.scale_simple(int(curr_width*factor), int(curr_height*factor), gtk.gdk.INTERP_BILINEAR)
357 self.pixbuf = scaled_pixbuf
358 self.img_widget.set_from_pixbuf(self.pixbuf)
361 def update_iconview(self, iter_next):
363 self.photo = model.get(iter_next, 0)[0]
364 self.path = gnomevfs.get_local_path_from_uri (self.photo.uri)
366 self.update_image()
367 str_path = model.get_string_from_iter(iter_next)
368 self.iconview.select_path(str_path)
370 def update_image(self, to_wnd_size=True):
372 if (to_wnd_size):
373 win = self._wTree.get_widget("wnd_display_img")
374 size = win.get_size()
375 self.pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.path, size[0], size[1])
376 else:
377 self.pixbuf = gtk.gdk.pixbuf_new_from_file(self.path)
379 self.img_widget.set_from_pixbuf(self.pixbuf)
381 class PhotoListThread (threading.Thread):
383 def __init__ (self, model, query=None, use_progressbar=False):
384 super(PhotoListThread, self).__init__()
386 if query is not None:
387 self._query = query
388 else:
389 self._query = "select id, uri from photos order by time desc limit 10"
391 self._model = model
393 def run (self):
395 inicio = datetime.datetime.now()
397 global wTree
399 gtk.gdk.threads_enter()
400 progressbar = wTree.get_widget("progressbar1")
401 progressbar.show()
402 self._model.clear()
403 gtk.gdk.threads_leave()
405 conn = connect_to_db()
406 c = conn.cursor()
407 c.execute (self._query)
409 for row in c:
411 gtk.gdk.threads_enter()
412 i += 1
413 try:
414 p = Photo(row[0], row[1])
415 self._model.append([p,
416 p.pixbuf,
417 p.original_date.date().strftime("%x")])
418 except:
419 pass
421 progressbar.pulse()
422 gtk.gdk.threads_leave()
424 gtk.gdk.threads_enter()
425 Progressbar.push_message(_("%d pictures found.") % (i))
426 conn.close()
427 progressbar.hide()
428 gtk.gdk.threads_leave()
430 fin = datetime.datetime.now()
431 print "Took %s load the pictures" % (fin-inicio)
433 class Progressbar(object):
435 @classmethod
436 def push_message(cls, msg, context="marvin"):
437 statusbar = wTree.get_widget("statusbar1")
438 context_id = statusbar.get_context_id(context)
439 msg_id = statusbar.push(context_id, msg)
440 gobject.timeout_add(10000,Progressbar.remove_from_statusbar,context_id, msg_id)
442 @classmethod
443 def remove_from_statusbar(cls, context_id, msg_id):
444 statusbar = wTree.get_widget("statusbar1")
445 statusbar.remove(context_id, msg_id)
447 return False #stop being called by gobject.timeout_add
450 class ImportWindow(object):
454 def __init__(self):
457 self._thread = None
458 glade_file = resource_filename("marvin.ui", "marvin.glade")
459 self._wTree = gtk.glade.XML(glade_file, "wnd_photo_import")
461 signals = {
462 "on_checkbtn_include_subdirs_toggled" : self._on_include_subdir_toggled,
463 "on_chooserbtn_dir_to_import_current_folder_changed" : self._on_folder_changed,
464 "on_btn_accept_clicked" : self._on_accept_clicked,
467 self._wTree.signal_autoconnect(signals)
469 list_photos = gtk.ListStore(Photo, gtk.gdk.Pixbuf, str)
470 list_photos.clear()
471 iconview = self._wTree.get_widget("iconview_preview")
472 iconview.set_model(list_photos)
473 iconview.set_pixbuf_column(1)
474 iconview.set_text_column(2)
476 win = self._wTree.get_widget("wnd_photo_import")
477 win.show_all()
479 def _on_folder_changed(self, widget):
480 self.refresh_iconview()
482 def _on_include_subdir_toggled(self, togglebutton):
483 self.refresh_iconview()
485 def refresh_iconview(self):
487 uris_list = list()
489 uri = self._wTree.get_widget("chooserbtn_dir_to_import").get_current_folder_uri()
490 selected_path= gnomevfs.get_local_path_from_uri(uri)
491 model = self._wTree.get_widget("iconview_preview").get_model()
493 recursive = self._wTree.get_widget("checkbtn_include_subdirs").get_active()
495 if recursive:
496 for root, dirs, files in os.walk(selected_path):
497 for name in files:
498 uri = "file://" + str(root) + "/" + str(name)
499 if gnomevfs.get_mime_type(uri) == "image/jpeg":
500 uris_list.append(uri)
501 else:
502 for name in os.listdir(selected_path):
503 uri = "file://" + str(selected_path) + "/" + str(name)
504 if gnomevfs.get_mime_type(uri) == "image/jpeg":
505 uris_list.append(uri)
507 if (self._thread is not None) and (not self._thread.is_stopped()):
508 self._thread.stop()
510 self._thread = PhotoPreviewThread(model, uris_list)
511 self._thread.start()
513 def _on_accept_clicked(self, button):
515 self._wTree.get_widget("wnd_photo_import").set_sensitive(False)
517 iconview = self._wTree.get_widget("iconview_preview")
518 model = iconview.get_model()
520 list_of_photos = list()
522 if len(iconview.get_selected_items()) == 0:
523 iter = model.get_iter_first()
525 while (iter is not None):
526 list_of_photos.append(model.get(iter, 0)[0])
527 iter = model.iter_next(iter)
529 else:
530 selected_items = iconview.get_selected_items()
531 for path in selected_items:
532 iter = model.get_iter(path)
533 list_of_photos.append(model.get(iter, 0)[0])
535 copy_to_photodir = self._wTree.get_widget("checkbtn_copy").get_active()
536 progressbar = self._wTree.get_widget("progressbar_import")
537 thread = ImportPhotoThread(list_of_photos, copy_to_photodir, progressbar)
538 thread.start()
540 class ImportPhotoThread(threading.Thread):
542 def __init__(self, list_of_photos, copy_to_photodir, progressbar):
543 super(ImportPhotoThread, self).__init__()
545 self._photos = list_of_photos
546 self._copy_to_photodir = copy_to_photodir
547 self._progressbar = progressbar
549 def run(self):
550 self._conn = connect_to_db()
551 self._c = self._conn.cursor()
553 self._progressbar.show()
554 i = 0
555 total = float(len(self._photos))
556 for photo in self._photos:
557 i += 1
558 self._import (photo, self._copy_to_photodir)
559 self._progressbar.set_fraction(i/total)
560 self._progressbar.set_text(_("Imported %d of %d") % (i, int(total)))
562 self._conn.commit()
563 self._conn.close()
565 win = self._progressbar.get_toplevel()
566 win.destroy()
568 def _import (self, photo, copy_to_photodir):
570 if copy_to_photodir:
571 new_uri = self._copy_photo(photo.uri)
572 else:
573 new_uri = photo.uri
575 self._c.execute("""insert into photos(uri, time, description, roll_id, default_version_id, rating)
576 values (?, ?, '', 0, 1, 0)""", (new_uri, time.mktime(photo.original_date.timetuple()),))
578 def _copy_photo(self, uri):
580 base = "/apps/f-spot/import"
581 client = gconf.client_get_default()
582 client.add_dir(base, gconf.CLIENT_PRELOAD_NONE)
583 root_photo_dir = client.get_string(base + "/storage_path")
585 src_path = gnomevfs.get_local_path_from_uri(uri)
586 f = open(src_path, 'rb')
587 tags = EXIF.process_file(f, stop_tag='DateTimeOriginal')
589 if tags.has_key('EXIF DateTimeOriginal'):
590 (aux_date, aux_time) = str(tags['EXIF DateTimeOriginal']).split(' ')
591 (year, month, day) = aux_date.split(':')
593 else:
594 mtime = time.localtime(os.path.getmtime(src_path))
595 year = str(mtime[0])
596 moth = str(mtime[1])
597 day = str(mtime[2])
600 destdir = os.path.join(root_photo_dir, year, month, day)
601 list_dir = (os.path.join(root_photo_dir, year),
602 os.path.join(root_photo_dir, year, month),
603 os.path.join(root_photo_dir, year, month, day))
604 for aux_path in list_dir:
605 if not os.path.exists(aux_path):
606 os.mkdir(aux_path)
608 if os.path.exists(os.path.join(destdir, os.path.basename(src_path))):
609 aux = os.path.basename(src_path).rsplit('.', 1)
610 suff = str(time.localtime()[3]) + str(time.localtime()[4])
611 destdir = os.path.join(destdir, aux[0] + suff + aux[1])
612 else:
613 destdir = os.path.join(destdir, os.path.basename(src_path))
615 shutil.copyfile(src_path, destdir)
617 return "file://" + destdir #returns the destination uri
619 class PhotoPreviewThread(threading.Thread):
621 def __init__(self, model, uris):
622 super(PhotoPreviewThread, self).__init__()
624 self._model = model
625 self._uris = uris
626 self._stop = False
627 self._stopped = True
629 def run(self):
630 self._stopped = False
631 self._model.clear()
633 for uri in self._uris:
634 if self._stop:
635 self._stopped = True
636 self._stop = False
637 return
639 photo = Photo(-1, uri, 150)
640 self._model.append([photo,
641 photo.pixbuf,
642 photo.original_date.date().strftime("%x")])
644 def stop(self):
645 self._stop = True
647 def is_stopped(self):
648 return self._stopped
650 def setup_log():
651 # code adapted from
652 # http://docs.python.org/lib/multiple-destinations.html example
653 # set up logging to file - see previous section for more details
654 logging.basicConfig(level=logging.DEBUG,
655 #format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
656 format='%(levelname)-8s %(message)s',
657 datefmt='%m-%d %H:%M')
658 # define a Handler which writes INFO messages or higher to the sys.stderr
659 console = logging.StreamHandler()
660 console.setLevel(logging.INFO)
661 # set a format which is simpler for console use
662 formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
663 # tell the handler to use this format
664 console.setFormatter(formatter)
665 # add the handler to the root logger
666 logging.getLogger('').addHandler(console)
669 def start_import(run_main_loop=True):
671 ImportWindow()
673 if run_main_loop:
674 setup_log()
676 gobject.set_application_name(_("Photo collection manager"))
677 gobject.set_prgname("marvin")
679 gtk.main()
681 def start():
684 setup_log()
685 log = logging.getLogger('view')
687 gobject.set_application_name(_("Photo collection manager"))
688 gobject.set_prgname("marvin")
690 wnd = MainWindow()
692 gtk.gdk.threads_enter()
693 gtk.main()
694 gtk.gdk.threads_leave()
697 if __name__ == '__main__':
698 start()