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
43 curr_dir
= os
.path
.abspath(".")
44 sys
.path
.append(curr_dir
)
53 #Get the local directory since we are not installing anything
54 local_path
= os
.path
.realpath(os
.path
.dirname(sys
.argv
[0]))
55 # Init the list of languages to support
57 #Check the default locale
58 lc
, encoding
= locale
.getdefaultlocale()
60 #If we have a default, it's the first in the list
62 # Now lets get all of the supported languages on the system
63 language
= os
.environ
.get('LANGUAGE', None)
65 langs
+= language
.split(":")
69 gettext
.bindtextdomain(APP_NAME
, local_path
)
70 gettext
.textdomain(APP_NAME
)
71 # Get the language to use
72 lang
= gettext
.translation(APP_NAME
, local_path
, languages
=langs
, fallback
= True)
77 gtk
.gdk
.threads_init()
80 from pkg_resources
import resource_filename
83 class MainWindow (object):
85 def __init__ (self
, **kwargs
):
87 self
._gladefile
= resource_filename("marvin.ui", "marvin.glade")
89 wTree
= gtk
.glade
.XML(self
._gladefile
, "wnd_main")
92 "on_entry_sql_filter_activate" : self
._on
_sql
_filter
_activate
,
93 "on_btn_search_with_filter_clicked" : self
._on
_search
_clicked
,
94 "on_iconview1_selection_changed" : self
._on
_selection
_changed
,
95 "on_wnd_main_delete_event" : self
._on
_delete
_event
,
96 "on_iconview1_item_activated" : self
._item
_activated
,
99 wTree
.signal_autoconnect(signals
)
101 list_photos
= gtk
.ListStore(Photo
, gtk
.gdk
.Pixbuf
, str)
104 #setup the iconview and set the model
105 iconview
= wTree
.get_widget("iconview1")
106 iconview
.set_model(list_photos
)
107 iconview
.set_pixbuf_column(1)
108 iconview
.set_text_column(2)
110 self
._win
= wTree
.get_widget("wnd_main")
111 uidef
= resource_filename("marvin.ui", "bar.xml")
112 self
._uimanager
= gtk
.UIManager()
113 accel_group
= self
._uimanager
.get_accel_group()
114 self
._win
.add_accel_group(accel_group
)
115 action_group
= gtk
.ActionGroup('my_actions')
116 action_group
.add_actions([#TODO:finish actions
117 ('ImportPhoto', gtk
.STOCK_ADD
, _("Import Photos"), "<Control>i", None,
118 self
._on
_import
_clicked
),
121 self
._uimanager
.insert_action_group(action_group
, 0)
122 self
._uimanager
.add_ui_from_file(uidef
)
123 box
= wTree
.get_widget("vbox1")
124 toolbar
= self
._uimanager
.get_widget("/toolbar")
125 box
.pack_start(toolbar
, False)
126 box
.reorder_child(toolbar
, 0)
131 # prepare and launch the thread that fills the iconview model
132 thread
= PhotoListThread(list_photos
)
135 def _on_import_clicked(self
, menuitem
):
138 def _on_sql_filter_activate(self
, entry
):
139 button
= wTree
.get_widget("btn_search_with_filter")
142 def _on_search_clicked(self
, button
):
149 model
= wTree
.get_widget("iconview1").get_model()
151 entry
= wTree
.get_widget("entry_sql_filter")
152 sql_filter
= entry
.get_text()
154 query
= "select id, uri from photos " + sql_filter
155 thread
= PhotoListThread(model
, query
)
158 def _on_selection_changed(self
, iconview
):
165 num_selec_items
= len(iconview
.get_selected_items())
166 if num_selec_items
== 1:
168 lbl
= wTree
.get_widget("lbl_original_date")
170 model
= iconview
.get_model()
171 iter = model
.get_iter(iconview
.get_selected_items()[0])
172 photo
= model
.get(iter, 0)[0]
173 str_date
= photo
.original_date
.strftime("%x")
174 lbl
.set_text(_("Original Date %s" % str_date
))
176 lbl
= wTree
.get_widget("lbl_tags")
178 lbl
.set_text(str(photo
.tags
))
179 elif num_selec_items
== 0:
180 wTree
.get_widget("lbl_original_date").set_text("")
181 wTree
.get_widget("lbl_tags").set_text("")
183 def _item_activated(self
, iconview
, path
):
185 store
= iconview
.get_model()
186 iter = store
.get_iter(path
)
187 photo
= store
.get_value (iter, 0)
189 ImageWindow(photo
, iconview
)
191 def _on_delete_event (self
, widget
, event
):
196 def __init__(self
, photo
, iconview
):
198 self
.fullscreen
= False
200 self
._wTree
= gtk
.glade
.XML(resource_filename("marvin.ui", "marvin.glade"), "wnd_display_img")
203 "on_wnd_display_img_window_state_event": self
._on
_window
_state
_event
,
204 "on_wnd_display_img_delete_event" : self
._on
_delete
_event
,
205 "on_wnd_display_img_key_press_event" : self
._on
_key
_press
,
207 self
._wTree
.signal_autoconnect (signals
)
209 self
.img_widget
= self
._wTree
.get_widget("img")
211 self
.iconview
= iconview
213 self
.path
= gnomevfs
.get_local_path_from_uri (self
.photo
.uri
)
215 win
= self
._wTree
.get_widget("wnd_display_img")
216 win
.set_size_request (800, 600)
221 def _on_delete_event(self
, widget
, event
):
231 def _on_window_state_event (self
, widget
, event
):
232 "This method should be used for the fullscreen functinality"
236 def _on_key_press(self
, widget
, event
):
237 """Handle the key press event of the image window
244 model
= self
.iconview
.get_model()
246 iter = model
.get_iter(self
.iconview
.get_selected_items()[0])
248 str_key
= gtk
.gdk
.keyval_name(event
.keyval
)
250 if str_key
== 'Right' or str_key
== 'Down':
251 iter_next
= model
.iter_next(iter)
253 if iter_next
== None:
254 iter_next
= model
.get_iter_first()
256 self
.update_iconview(iter_next
)
258 elif str_key
== 'Left' or str_key
== 'Up':
259 int_path
= int(model
.get_string_from_iter(iter)) - 1
262 str_path
= str("%d" % (int_path
))
264 iter_next
= model
.get_iter_from_string(str_path
)
266 self
.update_iconview(iter_next
)
268 elif str_key
== 'plus':
271 elif str_key
== 'minus':
275 self
.update_image(False)
277 elif str_key
== 'F11':
278 win
= self
._wTree
.get_widget("wnd_display_img")
281 self
.fullscreen
=False
284 self
.fullscreen
= True
286 elif str_key
.lower() == 's':
289 elif str_key
== 'Escape':
290 win
= self
._wTree
.get_widget("wnd_display_img")
298 def zoom(self
, factor
):
300 curr_width
= self
.pixbuf
.get_width()
301 curr_height
= self
.pixbuf
.get_height()
302 scaled_pixbuf
= self
.pixbuf
.scale_simple(int(curr_width
*factor
), int(curr_height
*factor
), gtk
.gdk
.INTERP_BILINEAR
)
304 self
.pixbuf
= scaled_pixbuf
305 self
.img_widget
.set_from_pixbuf(self
.pixbuf
)
308 def update_iconview(self
, iter_next
):
310 self
.photo
= model
.get(iter_next
, 0)[0]
311 self
.path
= gnomevfs
.get_local_path_from_uri (self
.photo
.uri
)
314 str_path
= model
.get_string_from_iter(iter_next
)
315 self
.iconview
.select_path(str_path
)
317 def update_image(self
, to_wnd_size
=True):
320 win
= self
._wTree
.get_widget("wnd_display_img")
321 size
= win
.get_size()
322 self
.pixbuf
= gtk
.gdk
.pixbuf_new_from_file_at_size(self
.path
, size
[0], size
[1])
324 self
.pixbuf
= gtk
.gdk
.pixbuf_new_from_file(self
.path
)
326 self
.img_widget
.set_from_pixbuf(self
.pixbuf
)
328 class PhotoListThread (threading
.Thread
):
330 def __init__ (self
, model
, query
=None, use_progressbar
=False):
331 super(PhotoListThread
, self
).__init
__()
332 if query
is not None:
335 self
._query
= "select id, uri from photos order by time desc limit 10"
343 progressbar
= wTree
.get_widget("progressbar1")
346 conn
= connect_to_db()
348 c
.execute (self
._query
)
353 p
= Photo(row
[0], row
[1])
354 self
._model
.append([p
,
356 p
.original_date
.date().strftime("%x")])
362 Progressbar
.push_message(_("%d pictures found.") % (i
))
366 class Progressbar(object):
369 def push_message(cls
, msg
, context
="marvin"):
370 statusbar
= wTree
.get_widget("statusbar1")
371 context_id
= statusbar
.get_context_id(context
)
372 msg_id
= statusbar
.push(context_id
, msg
)
373 gobject
.timeout_add(10000,Progressbar
.remove_from_statusbar
,context_id
, msg_id
)
376 def remove_from_statusbar(cls
, context_id
, msg_id
):
377 statusbar
= wTree
.get_widget("statusbar1")
378 statusbar
.remove(context_id
, msg_id
)
380 return False #stop being called by gobject.timeout_add
383 class ImportWindow(object):
391 glade_file
= resource_filename("marvin.ui", "marvin.glade")
392 self
._wTree
= gtk
.glade
.XML(glade_file
, "wnd_photo_import")
395 "on_checkbtn_include_subdirs_toggled" : self
._on
_include
_subdir
_toggled
,
396 "on_chooserbtn_dir_to_import_current_folder_changed" : self
._on
_folder
_changed
,
397 "on_btn_accept_clicked" : self
._on
_accept
_clicked
,
400 self
._wTree
.signal_autoconnect(signals
)
402 list_photos
= gtk
.ListStore(Photo
, gtk
.gdk
.Pixbuf
, str)
404 iconview
= self
._wTree
.get_widget("iconview_preview")
405 iconview
.set_model(list_photos
)
406 iconview
.set_pixbuf_column(1)
407 iconview
.set_text_column(2)
409 win
= self
._wTree
.get_widget("wnd_photo_import")
412 def _on_folder_changed(self
, widget
):
413 self
.refresh_iconview()
415 def _on_include_subdir_toggled(self
, togglebutton
):
416 self
.refresh_iconview()
418 def refresh_iconview(self
):
422 uri
= self
._wTree
.get_widget("chooserbtn_dir_to_import").get_current_folder_uri()
423 selected_path
= gnomevfs
.get_local_path_from_uri(uri
)
424 model
= self
._wTree
.get_widget("iconview_preview").get_model()
426 recursive
= self
._wTree
.get_widget("checkbtn_include_subdirs").get_active()
429 for root
, dirs
, files
in os
.walk(selected_path
):
431 uri
= "file://" + str(root
) + "/" + str(name
)
432 if gnomevfs
.get_mime_type(uri
) == "image/jpeg":
433 uris_list
.append(uri
)
435 for name
in os
.listdir(selected_path
):
436 uri
= "file://" + str(selected_path
) + "/" + str(name
)
437 if gnomevfs
.get_mime_type(uri
) == "image/jpeg":
438 uris_list
.append(uri
)
440 if (self
._thread
is not None) and (not self
._thread
.is_stopped()):
443 self
._thread
= PhotoPreviewThread(model
, uris_list
)
446 def _on_accept_clicked(self
, button
):
448 self
._wTree
.get_widget("wnd_photo_import").set_sensitive(False)
450 iconview
= self
._wTree
.get_widget("iconview_preview")
451 model
= iconview
.get_model()
453 list_of_photos
= list()
455 if len(iconview
.get_selected_items()) == 0:
456 iter = model
.get_iter_first()
458 while (iter is not None):
459 list_of_photos
.append(model
.get(iter, 0)[0])
460 iter = model
.iter_next(iter)
463 selected_items
= iconview
.get_selected_items()
464 for path
in selected_items
:
465 iter = model
.get_iter(path
)
466 list_of_photos
.append(model
.get(iter, 0)[0])
468 copy_to_photodir
= self
._wTree
.get_widget("checkbtn_copy").get_active()
469 progressbar
= self
._wTree
.get_widget("progressbar_import")
470 thread
= ImportPhotoThread(list_of_photos
, copy_to_photodir
, progressbar
)
473 class ImportPhotoThread(threading
.Thread
):
475 def __init__(self
, list_of_photos
, copy_to_photodir
, progressbar
):
476 super(ImportPhotoThread
, self
).__init
__()
478 self
._photos
= list_of_photos
479 self
._copy
_to
_photodir
= copy_to_photodir
480 self
._progressbar
= progressbar
483 self
._conn
= connect_to_db()
484 self
._c
= self
._conn
.cursor()
486 self
._progressbar
.show()
488 total
= float(len(self
._photos
))
489 for photo
in self
._photos
:
491 self
._import
(photo
, self
._copy
_to
_photodir
)
492 self
._progressbar
.set_fraction(i
/total
)
493 self
._progressbar
.set_text(_("Imported %d of %d") % (i
, int(total
)))
498 win
= self
._progressbar
.get_toplevel()
501 def _import (self
, photo
, copy_to_photodir
):
504 new_uri
= self
._copy
_photo
(photo
.uri
)
508 self
._c
.execute("""insert into photos(uri, time, description, roll_id, default_version_id, rating)
509 values (?, ?, '', 0, 1, 0)""", (new_uri
, time
.mktime(photo
.original_date
.timetuple()),))
511 def _copy_photo(self
, uri
):
513 base
= "/apps/f-spot/import"
514 client
= gconf
.client_get_default()
515 client
.add_dir(base
, gconf
.CLIENT_PRELOAD_NONE
)
516 root_photo_dir
= client
.get_string(base
+ "/storage_path")
518 src_path
= gnomevfs
.get_local_path_from_uri(uri
)
519 f
= open(src_path
, 'rb')
520 tags
= EXIF
.process_file(f
, stop_tag
='DateTimeOriginal')
522 if tags
.has_key('EXIF DateTimeOriginal'):
523 (aux_date
, aux_time
) = str(tags
['EXIF DateTimeOriginal']).split(' ')
524 (year
, month
, day
) = aux_date
.split(':')
527 mtime
= time
.localtime(os
.path
.getmtime(src_path
))
533 destdir
= os
.path
.join(root_photo_dir
, year
, month
, day
)
534 list_dir
= (os
.path
.join(root_photo_dir
, year
),
535 os
.path
.join(root_photo_dir
, year
, month
),
536 os
.path
.join(root_photo_dir
, year
, month
, day
))
537 for aux_path
in list_dir
:
538 if not os
.path
.exists(aux_path
):
541 if os
.path
.exists(os
.path
.join(destdir
, os
.path
.basename(src_path
))):
542 aux
= os
.path
.basename(src_path
).rsplit('.', 1)
543 suff
= str(time
.localtime()[3]) + str(time
.localtime()[4])
544 destdir
= os
.path
.join(destdir
, aux
[0] + suff
+ aux
[1])
546 destdir
= os
.path
.join(destdir
, os
.path
.basename(src_path
))
548 shutil
.copyfile(src_path
, destdir
)
550 return "file://" + destdir
#returns the destination uri
552 class PhotoPreviewThread(threading
.Thread
):
554 def __init__(self
, model
, uris
):
555 super(PhotoPreviewThread
, self
).__init
__()
563 self
._stopped
= False
566 for uri
in self
._uris
:
572 photo
= Photo(-1, uri
, 150)
573 self
._model
.append([photo
,
575 photo
.original_date
.date().strftime("%x")])
580 def is_stopped(self
):
585 # http://docs.python.org/lib/multiple-destinations.html example
586 # set up logging to file - see previous section for more details
587 logging
.basicConfig(level
=logging
.DEBUG
,
588 #format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
589 format
='%(levelname)-8s %(message)s',
590 datefmt
='%m-%d %H:%M')
591 # define a Handler which writes INFO messages or higher to the sys.stderr
592 console
= logging
.StreamHandler()
593 console
.setLevel(logging
.INFO
)
594 # set a format which is simpler for console use
595 formatter
= logging
.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
596 # tell the handler to use this format
597 console
.setFormatter(formatter
)
598 # add the handler to the root logger
599 logging
.getLogger('').addHandler(console
)
602 def start_import(run_main_loop
=True):
609 gobject
.set_application_name(_("Photo collection manager"))
610 gobject
.set_prgname("marvin")
618 log
= logging
.getLogger('view')
620 gobject
.set_application_name(_("Photo collection manager"))
621 gobject
.set_prgname("marvin")
628 if __name__
== '__main__':