removed commented code
[marvin.git] / marvin / view.py
blobabaa5a3821056f7ac1f4c4b58ddd1133e0ee56b9
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 ('File', None, _("_File"), None, None, None),
120 ('ImportPhoto', gtk.STOCK_ADD, _("Import Photos"), "<Control>i", None,
121 self._on_import_clicked),
122 ("Quit", gtk.STOCK_QUIT, None, "<Control>q", None,
123 self._on_quit_clicked),
124 ("Image", None, _("_Image"), None, None, None),
125 ('SetAsWallpaper', None, _("Set as wallpaper"), None, None,
126 self._on_set_wallpaper_clicked),
127 ("Help", None, _("Help"), None, None, None),
128 ("About", gtk.STOCK_ABOUT, None, None, None,
129 self._on_about_clicked),
132 self._uimanager.insert_action_group(action_group, 0)
133 self._uimanager.add_ui_from_file(uidef)
134 box = wTree.get_widget("vbox1")
135 menubar = self._uimanager.get_widget("/menubar")
136 box.pack_start(menubar, False)
137 box.reorder_child(menubar, 0)
138 toolbar = self._uimanager.get_widget("/toolbar")
139 box.pack_start(toolbar, False)
140 box.reorder_child(toolbar, 1)
142 config = ConfigurationManager()
143 self._win.set_default_size(config.window_default_size[0],
144 config.window_default_size[1])
145 self._win.show_all()
148 # prepare and launch the thread that fills the iconview model
149 self._thread = PhotoListThread(list_photos)
150 self._thread.start()
152 def _show_popup(self, widget, event):
153 if event.button != 3:
154 return
156 iconview = wTree.get_widget("iconview1")
157 selected_items = iconview.get_selected_items()
158 item_under_mouse = iconview.get_item_at_pos (int(event.x), int(event.y))[0]
160 if item_under_mouse not in selected_items:
161 iconview.unselect_all()
162 iconview.select_path(item_under_mouse)
163 selected_items = iconview.get_selected_items()
165 item = self._uimanager.get_widget("/popup/SetAsWallpaper")
166 if len(selected_items) != 1:
167 item.set_sensitive(False)
168 else:
169 item.set_sensitive(True)
171 menu_widget = self._uimanager.get_widget("/popup");
173 menu_widget.popup(None, None, None, event.button, event.time);
174 menu_widget.show_all()
176 def _on_about_clicked(self, menuitem):
177 AboutDialog()
179 def _on_quit_clicked (self, menuitem):
180 self.quit()
182 def _on_set_wallpaper_clicked(self, menuitem):
184 iconview = wTree.get_widget("iconview1")
186 selected_items = iconview.get_selected_items()
188 if len(selected_items) != 1:
189 return
191 model = iconview.get_model()
192 iter = model.get_iter(selected_items[0])
193 photo = model.get(iter, 0)[0]
195 ConfigurationManager.set_wallpaper(photo.path)
197 def _on_import_clicked(self, menuitem):
198 ImportWindow()
200 def _on_sql_filter_activate(self, entry):
201 button = wTree.get_widget("btn_search_with_filter")
202 button.clicked()
204 def _on_search_clicked(self, button):
207 Arguments:
208 - `self`:
209 - `button`:
211 model = wTree.get_widget("iconview1").get_model()
213 entry = wTree.get_widget("entry_sql_filter")
214 sql_filter = entry.get_text()
216 query = "select id, uri from photos " + sql_filter
218 if not self._thread.stop.isSet():
219 self._thread.stop.set()
220 thread = PhotoListThread(model, query)
221 thread.start()
223 def _on_selection_changed(self, iconview):
226 Arguments:
227 - `self`:
228 - `iconview`:
230 num_selec_items = len(iconview.get_selected_items())
231 if num_selec_items == 1:
233 lbl = wTree.get_widget("lbl_original_date")
235 model = iconview.get_model()
236 iter = model.get_iter(iconview.get_selected_items()[0])
237 photo = model.get(iter, 0)[0]
238 str_date = photo.original_date.strftime("%x")
239 lbl.set_text(_("Original Date %s" % str_date))
241 lbl = wTree.get_widget("lbl_tags")
243 lbl.set_text(str(photo.tags))
244 elif num_selec_items == 0:
245 wTree.get_widget("lbl_original_date").set_text("")
246 wTree.get_widget("lbl_tags").set_text("")
248 def _item_activated(self, iconview, path):
250 store = iconview.get_model()
251 iter = store.get_iter(path)
252 photo = store.get_value (iter, 0)
254 ImageWindow(photo, iconview)
256 def _on_delete_event (self, widget, event):
257 self.quit()
259 def quit(self):
260 config = ConfigurationManager()
261 win = wTree.get_widget("wnd_main")
262 config.window_default_size = win.get_size()
263 gtk.main_quit()
265 class ImageWindow:
267 def __init__(self, photo, iconview):
269 self.fullscreen = False
271 self._wTree = gtk.glade.XML(resource_filename("marvin.ui", "marvin.glade"), "wnd_display_img")
273 signals = {
274 "on_wnd_display_img_window_state_event": self._on_window_state_event,
275 "on_wnd_display_img_delete_event" : self._on_delete_event,
276 "on_wnd_display_img_key_press_event" : self._on_key_press,
278 self._wTree.signal_autoconnect (signals)
280 self.img_widget = self._wTree.get_widget("img")
282 self.iconview = iconview
283 self.photo = photo
284 self.path = gnomevfs.get_local_path_from_uri (self.photo.uri)
286 win = self._wTree.get_widget("wnd_display_img")
287 win.set_size_request (800, 600)
288 win.show_all()
290 self.update_image()
292 def _on_delete_event(self, widget, event):
295 Arguments:
296 - `self`:
297 - `widget`:
298 - `event`:
300 pass
302 def _on_window_state_event (self, widget, event):
303 "This method should be used for the fullscreen functinality"
304 pass
307 def _on_key_press(self, widget, event):
308 """Handle the key press event of the image window
310 Arguments:
311 - `self`:
312 - `widget`:
313 - `event`:
315 model = self.iconview.get_model()
317 iter = model.get_iter(self.iconview.get_selected_items()[0])
319 str_key = gtk.gdk.keyval_name(event.keyval)
321 if str_key == 'Right' or str_key == 'Down':
322 iter_next = model.iter_next(iter)
324 if iter_next == None:
325 iter_next = model.get_iter_first()
327 self.update_iconview(iter_next)
329 elif str_key == 'Left' or str_key == 'Up':
330 int_path = int(model.get_string_from_iter(iter)) - 1
331 if int_path < 0:
332 int_path = 0
333 str_path = str("%d" % (int_path))
335 iter_next = model.get_iter_from_string(str_path)
337 self.update_iconview(iter_next)
339 elif str_key == 'plus':
340 self.zoom(1.1)
342 elif str_key == 'minus':
343 self.zoom(0.9)
345 elif str_key == '1':
346 self.update_image(False)
348 elif str_key == 'F11':
349 win = self._wTree.get_widget("wnd_display_img")
350 if self.fullscreen:
351 win.unfullscreen()
352 self.fullscreen =False
353 else:
354 win.fullscreen()
355 self.fullscreen = True
357 elif str_key.lower() == 's':
358 self.update_image()
360 elif str_key == 'Escape':
361 win = self._wTree.get_widget("wnd_display_img")
362 win.destroy()
363 return
364 else:
365 print str_key
366 return
369 def zoom(self, factor):
371 curr_width = self.pixbuf.get_width()
372 curr_height = self.pixbuf.get_height()
373 scaled_pixbuf = self.pixbuf.scale_simple(int(curr_width*factor), int(curr_height*factor), gtk.gdk.INTERP_BILINEAR)
375 self.pixbuf = scaled_pixbuf
376 self.img_widget.set_from_pixbuf(self.pixbuf)
379 def update_iconview(self, iter_next):
381 self.photo = model.get(iter_next, 0)[0]
382 self.path = gnomevfs.get_local_path_from_uri (self.photo.uri)
384 self.update_image()
385 str_path = model.get_string_from_iter(iter_next)
386 self.iconview.select_path(str_path)
388 def update_image(self, to_wnd_size=True):
390 if (to_wnd_size):
391 win = self._wTree.get_widget("wnd_display_img")
392 size = win.get_size()
393 self.pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.path, size[0], size[1])
394 else:
395 self.pixbuf = gtk.gdk.pixbuf_new_from_file(self.path)
397 self.img_widget.set_from_pixbuf(self.pixbuf)
399 class PhotoListThread (threading.Thread):
401 def __init__ (self, model, query=None, use_progressbar=False):
402 super(PhotoListThread, self).__init__()
404 if query is not None:
405 self._query = query
406 else:
407 self._query = "select id, uri from photos order by time desc limit 10"
409 self._model = model
411 def run (self):
413 inicio = datetime.datetime.now()
415 global wTree
417 gtk.gdk.threads_enter()
418 progressbar = wTree.get_widget("progressbar1")
419 progressbar.show()
420 self._model.clear()
421 gtk.gdk.threads_leave()
423 conn = connect_to_db()
424 c = conn.cursor()
425 c.execute (self._query)
427 for row in c:
429 gtk.gdk.threads_enter()
430 i += 1
431 try:
432 p = Photo(row[0], row[1])
433 self._model.append([p,
434 p.pixbuf,
435 p.original_date.date().strftime("%x")])
436 except:
437 pass
439 progressbar.pulse()
440 gtk.gdk.threads_leave()
442 gtk.gdk.threads_enter()
443 Progressbar.push_message(_("%d pictures found.") % (i))
444 conn.close()
445 progressbar.hide()
446 gtk.gdk.threads_leave()
448 fin = datetime.datetime.now()
449 print "Took %s load the pictures" % (fin-inicio)
451 class Progressbar(object):
453 @classmethod
454 def push_message(cls, msg, context="marvin"):
455 statusbar = wTree.get_widget("statusbar1")
456 context_id = statusbar.get_context_id(context)
457 msg_id = statusbar.push(context_id, msg)
458 gobject.timeout_add(10000,Progressbar.remove_from_statusbar,context_id, msg_id)
460 @classmethod
461 def remove_from_statusbar(cls, context_id, msg_id):
462 statusbar = wTree.get_widget("statusbar1")
463 statusbar.remove(context_id, msg_id)
465 return False #stop being called by gobject.timeout_add
468 class ImportWindow(object):
472 def __init__(self):
475 self._thread = None
476 glade_file = resource_filename("marvin.ui", "marvin.glade")
477 self._wTree = gtk.glade.XML(glade_file, "wnd_photo_import")
479 signals = {
480 "on_checkbtn_include_subdirs_toggled" : self._on_include_subdir_toggled,
481 "on_chooserbtn_dir_to_import_current_folder_changed" : self._on_folder_changed,
482 "on_btn_accept_clicked" : self._on_accept_clicked,
485 self._wTree.signal_autoconnect(signals)
487 list_photos = gtk.ListStore(Photo, gtk.gdk.Pixbuf, str)
488 list_photos.clear()
489 iconview = self._wTree.get_widget("iconview_preview")
490 iconview.set_model(list_photos)
491 iconview.set_pixbuf_column(1)
492 iconview.set_text_column(2)
494 win = self._wTree.get_widget("wnd_photo_import")
495 win.show_all()
497 def _on_folder_changed(self, widget):
498 self.refresh_iconview()
500 def _on_include_subdir_toggled(self, togglebutton):
501 self.refresh_iconview()
503 def refresh_iconview(self):
505 uris_list = list()
507 uri = self._wTree.get_widget("chooserbtn_dir_to_import").get_current_folder_uri()
508 selected_path= gnomevfs.get_local_path_from_uri(uri)
509 model = self._wTree.get_widget("iconview_preview").get_model()
511 recursive = self._wTree.get_widget("checkbtn_include_subdirs").get_active()
513 if recursive:
514 for root, dirs, files in os.walk(selected_path):
515 for name in files:
516 uri = "file://" + str(root) + "/" + str(name)
517 if gnomevfs.get_mime_type(uri) == "image/jpeg":
518 uris_list.append(uri)
519 else:
520 for name in os.listdir(selected_path):
521 uri = "file://" + str(selected_path) + "/" + str(name)
522 if gnomevfs.get_mime_type(uri) == "image/jpeg":
523 uris_list.append(uri)
525 if (self._thread is not None) and (not self._thread.is_stopped()):
526 self._thread.stop()
528 self._thread = PhotoPreviewThread(model, uris_list)
529 self._thread.start()
531 def _on_accept_clicked(self, button):
533 self._wTree.get_widget("wnd_photo_import").set_sensitive(False)
535 iconview = self._wTree.get_widget("iconview_preview")
536 model = iconview.get_model()
538 list_of_photos = list()
540 if len(iconview.get_selected_items()) == 0:
541 iter = model.get_iter_first()
543 while (iter is not None):
544 list_of_photos.append(model.get(iter, 0)[0])
545 iter = model.iter_next(iter)
547 else:
548 selected_items = iconview.get_selected_items()
549 for path in selected_items:
550 iter = model.get_iter(path)
551 list_of_photos.append(model.get(iter, 0)[0])
553 copy_to_photodir = self._wTree.get_widget("checkbtn_copy").get_active()
554 progressbar = self._wTree.get_widget("progressbar_import")
555 thread = ImportPhotoThread(list_of_photos, copy_to_photodir, progressbar)
556 thread.start()
558 class AboutDialog(object):
559 "Control the about dialog"
561 def __init__(self):
563 glade_file = resource_filename("marvin.ui", "marvin.glade")
564 self._wTree = gtk.glade.XML(glade_file)
566 signals = {
567 "on_aboutdialog_response" : self._on_response,
570 self._wTree.signal_autoconnect(signals)
572 win = self._wTree.get_widget("aboutdialog")
573 win.show_all()
575 def _on_response (self, dialog, response_id):
576 "Destroy the dialog when the user click in the close button"
577 self._wTree.get_widget("aboutdialog").destroy()
579 class ImportPhotoThread(threading.Thread):
580 "Thread that import the photos to the collection"
582 def __init__(self, list_of_photos, copy_to_photodir, progressbar):
583 super(ImportPhotoThread, self).__init__()
585 self._photos = list_of_photos
586 self._copy_to_photodir = copy_to_photodir
587 self._progressbar = progressbar
589 def run(self):
590 self._conn = connect_to_db()
591 self._c = self._conn.cursor()
593 self._progressbar.show()
594 i = 0
595 total = float(len(self._photos))
596 for photo in self._photos:
597 i += 1
598 self._import (photo, self._copy_to_photodir)
599 self._progressbar.set_fraction(i/total)
600 self._progressbar.set_text(_("Imported %d of %d") % (i, int(total)))
602 self._conn.commit()
603 self._conn.close()
605 win = self._progressbar.get_toplevel()
606 win.destroy()
608 def _import (self, photo, copy_to_photodir):
610 if copy_to_photodir:
611 new_uri = self._copy_photo(photo.uri)
612 else:
613 new_uri = photo.uri
615 self._c.execute("""insert into photos(uri, time, description, roll_id, default_version_id, rating)
616 values (?, ?, '', 0, 1, 0)""", (new_uri, time.mktime(photo.original_date.timetuple()),))
618 def _copy_photo(self, uri):
620 base = "/apps/f-spot/import"
621 client = gconf.client_get_default()
622 client.add_dir(base, gconf.CLIENT_PRELOAD_NONE)
623 root_photo_dir = client.get_string(base + "/storage_path")
625 src_path = gnomevfs.get_local_path_from_uri(uri)
626 f = open(src_path, 'rb')
627 tags = EXIF.process_file(f, stop_tag='DateTimeOriginal')
629 if tags.has_key('EXIF DateTimeOriginal'):
630 (aux_date, aux_time) = str(tags['EXIF DateTimeOriginal']).split(' ')
631 (year, month, day) = aux_date.split(':')
633 else:
634 mtime = time.localtime(os.path.getmtime(src_path))
635 year = str(mtime[0])
636 moth = str(mtime[1])
637 day = str(mtime[2])
640 destdir = os.path.join(root_photo_dir, year, month, day)
641 list_dir = (os.path.join(root_photo_dir, year),
642 os.path.join(root_photo_dir, year, month),
643 os.path.join(root_photo_dir, year, month, day))
644 for aux_path in list_dir:
645 if not os.path.exists(aux_path):
646 os.mkdir(aux_path)
648 if os.path.exists(os.path.join(destdir, os.path.basename(src_path))):
649 aux = os.path.basename(src_path).rsplit('.', 1)
650 suff = str(time.localtime()[3]) + str(time.localtime()[4])
651 destdir = os.path.join(destdir, aux[0] + suff + aux[1])
652 else:
653 destdir = os.path.join(destdir, os.path.basename(src_path))
655 shutil.copyfile(src_path, destdir)
657 return "file://" + destdir #returns the destination uri
659 class PhotoPreviewThread(threading.Thread):
660 """Thread that loads the list store associated to the iconview that shows
661 the preview of the photos that will be loaded
665 def __init__(self, model, uris):
666 super(PhotoPreviewThread, self).__init__()
668 self._model = model
669 self._uris = uris
670 self._stop = False
671 self._stopped = True
673 def run(self):
674 self._stopped = False
675 self._model.clear()
677 for uri in self._uris:
678 if self._stop:
679 self._stopped = True
680 self._stop = False
681 return
683 photo = Photo(-1, uri, 150)
684 self._model.append([photo,
685 photo.pixbuf,
686 photo.original_date.date().strftime("%x")])
688 def stop(self):
689 self._stop = True
691 def is_stopped(self):
692 return self._stopped
694 def setup_log():
695 "Configure the log system"
696 # code adapted from
697 # http://docs.python.org/lib/multiple-destinations.html example
698 # set up logging to file - see previous section for more details
699 logging.basicConfig(level=logging.DEBUG,
700 #format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
701 format='%(levelname)-8s %(message)s',
702 datefmt='%m-%d %H:%M')
703 # define a Handler which writes INFO messages or higher to the sys.stderr
704 console = logging.StreamHandler()
705 console.setLevel(logging.INFO)
706 # set a format which is simpler for console use
707 formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
708 # tell the handler to use this format
709 console.setFormatter(formatter)
710 # add the handler to the root logger
711 logging.getLogger('').addHandler(console)
714 def start_import(run_main_loop=True):
715 "Initialize the import dialog"
717 ImportWindow()
719 if run_main_loop:
720 setup_log()
722 gobject.set_application_name(_("Photo collection manager"))
723 gobject.set_prgname("marvin")
725 gtk.main()
727 def start():
728 "Entry point to start the application"
730 setup_log()
731 log = logging.getLogger('view')
733 gobject.set_application_name(_("Photo collection manager"))
734 gobject.set_prgname("marvin")
736 wnd = MainWindow()
738 gtk.gdk.threads_enter()
739 gtk.main()
740 gtk.gdk.threads_leave()
743 if __name__ == '__main__':
744 start()