3 ## Login : <freyes@yoda>
4 ## Started on Tue Jul 1 20:51:33 2008 Felipe Reyes
7 ## Copyright (C) 2008 Felipe Reyes
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
44 curr_dir
= os
.path
.abspath(".")
45 sys
.path
.append(curr_dir
)
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
58 #Check the default locale
59 lc
, encoding
= locale
.getdefaultlocale()
61 #If we have a default, it's the first in the list
63 # Now lets get all of the supported languages on the system
64 language
= os
.environ
.get('LANGUAGE', None)
66 langs
+= language
.split(":")
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)
78 gtk
.gdk
.threads_init()
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")
90 wTree
= gtk
.glade
.XML(self
._gladefile
, "wnd_main")
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)
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])
148 # prepare and launch the thread that fills the iconview model
149 self
._thread
= PhotoListThread(list_photos
)
152 def _show_popup(self
, widget
, event
):
153 if event
.button
!= 3:
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)
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
):
179 def _on_quit_clicked (self
, menuitem
):
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:
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
):
200 def _on_sql_filter_activate(self
, entry
):
201 button
= wTree
.get_widget("btn_search_with_filter")
204 def _on_search_clicked(self
, 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
)
223 def _on_selection_changed(self
, 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
):
260 config
= ConfigurationManager()
261 win
= wTree
.get_widget("wnd_main")
262 config
.window_default_size
= win
.get_size()
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")
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
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)
292 def _on_delete_event(self
, widget
, event
):
302 def _on_window_state_event (self
, widget
, event
):
303 "This method should be used for the fullscreen functinality"
307 def _on_key_press(self
, widget
, event
):
308 """Handle the key press event of the image window
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
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':
342 elif str_key
== 'minus':
346 self
.update_image(False)
348 elif str_key
== 'F11':
349 win
= self
._wTree
.get_widget("wnd_display_img")
352 self
.fullscreen
=False
355 self
.fullscreen
= True
357 elif str_key
.lower() == 's':
360 elif str_key
== 'Escape':
361 win
= self
._wTree
.get_widget("wnd_display_img")
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
)
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):
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])
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:
407 self
._query
= "select id, uri from photos order by time desc limit 10"
413 inicio
= datetime
.datetime
.now()
417 gtk
.gdk
.threads_enter()
418 progressbar
= wTree
.get_widget("progressbar1")
421 gtk
.gdk
.threads_leave()
423 conn
= connect_to_db()
425 c
.execute (self
._query
)
429 gtk
.gdk
.threads_enter()
432 p
= Photo(row
[0], row
[1])
433 self
._model
.append([p
,
435 p
.original_date
.date().strftime("%x")])
440 gtk
.gdk
.threads_leave()
442 gtk
.gdk
.threads_enter()
443 Progressbar
.push_message(_("%d pictures found.") % (i
))
446 gtk
.gdk
.threads_leave()
448 fin
= datetime
.datetime
.now()
449 print "Took %s load the pictures" % (fin
-inicio
)
451 class Progressbar(object):
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
)
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):
476 glade_file
= resource_filename("marvin.ui", "marvin.glade")
477 self
._wTree
= gtk
.glade
.XML(glade_file
, "wnd_photo_import")
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)
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")
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
):
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()
514 for root
, dirs
, files
in os
.walk(selected_path
):
516 uri
= "file://" + str(root
) + "/" + str(name
)
517 if gnomevfs
.get_mime_type(uri
) == "image/jpeg":
518 uris_list
.append(uri
)
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()):
528 self
._thread
= PhotoPreviewThread(model
, uris_list
)
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)
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
)
558 class AboutDialog(object):
559 "Control the about dialog"
563 glade_file
= resource_filename("marvin.ui", "marvin.glade")
564 self
._wTree
= gtk
.glade
.XML(glade_file
)
567 "on_aboutdialog_response" : self
._on
_response
,
570 self
._wTree
.signal_autoconnect(signals
)
572 win
= self
._wTree
.get_widget("aboutdialog")
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
590 self
._conn
= connect_to_db()
591 self
._c
= self
._conn
.cursor()
593 self
._progressbar
.show()
595 total
= float(len(self
._photos
))
596 for photo
in self
._photos
:
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
)))
605 win
= self
._progressbar
.get_toplevel()
608 def _import (self
, photo
, copy_to_photodir
):
611 new_uri
= self
._copy
_photo
(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(':')
634 mtime
= time
.localtime(os
.path
.getmtime(src_path
))
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
):
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])
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
__()
674 self
._stopped
= False
677 for uri
in self
._uris
:
683 photo
= Photo(-1, uri
, 150)
684 self
._model
.append([photo
,
686 photo
.original_date
.date().strftime("%x")])
691 def is_stopped(self
):
695 "Configure the log system"
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"
722 gobject
.set_application_name(_("Photo collection manager"))
723 gobject
.set_prgname("marvin")
728 "Entry point to start the application"
731 log
= logging
.getLogger('view')
733 gobject
.set_application_name(_("Photo collection manager"))
734 gobject
.set_prgname("marvin")
738 gtk
.gdk
.threads_enter()
740 gtk
.gdk
.threads_leave()
743 if __name__
== '__main__':