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
),
129 self
._uimanager
.insert_action_group(action_group
, 0)
130 self
._uimanager
.add_ui_from_file(uidef
)
131 box
= wTree
.get_widget("vbox1")
132 menubar
= self
._uimanager
.get_widget("/menubar")
133 box
.pack_start(menubar
, False)
134 box
.reorder_child(menubar
, 0)
135 toolbar
= self
._uimanager
.get_widget("/toolbar")
136 box
.pack_start(toolbar
, False)
137 box
.reorder_child(toolbar
, 1)
139 config
= ConfigurationManager()
140 self
._win
.set_default_size(config
.window_default_size
[0],
141 config
.window_default_size
[1])
145 # prepare and launch the thread that fills the iconview model
146 self
._thread
= PhotoListThread(list_photos
)
149 def _show_popup(self
, widget
, event
):
150 if event
.button
!= 3:
153 iconview
= wTree
.get_widget("iconview1")
154 selected_items
= iconview
.get_selected_items()
155 item_under_mouse
= iconview
.get_item_at_pos (int(event
.x
), int(event
.y
))[0]
157 if item_under_mouse
not in selected_items
:
158 iconview
.unselect_all()
159 iconview
.select_path(item_under_mouse
)
160 selected_items
= iconview
.get_selected_items()
162 item
= self
._uimanager
.get_widget("/popup/SetAsWallpaper")
163 if len(selected_items
) != 1:
164 item
.set_sensitive(False)
166 item
.set_sensitive(True)
168 menu_widget
= self
._uimanager
.get_widget("/popup");
170 menu_widget
.popup(None, None, None, event
.button
, event
.time
);
171 menu_widget
.show_all()
173 def _on_quit_clicked (self
, menuitem
):
176 def _on_set_wallpaper_clicked(self
, menuitem
):
178 iconview
= wTree
.get_widget("iconview1")
180 selected_items
= iconview
.get_selected_items()
182 if len(selected_items
) != 1:
185 model
= iconview
.get_model()
186 iter = model
.get_iter(selected_items
[0])
187 photo
= model
.get(iter, 0)[0]
189 ConfigurationManager
.set_wallpaper(photo
.path
)
191 def _on_import_clicked(self
, menuitem
):
194 def _on_sql_filter_activate(self
, entry
):
195 button
= wTree
.get_widget("btn_search_with_filter")
198 def _on_search_clicked(self
, button
):
205 model
= wTree
.get_widget("iconview1").get_model()
207 entry
= wTree
.get_widget("entry_sql_filter")
208 sql_filter
= entry
.get_text()
210 query
= "select id, uri from photos " + sql_filter
212 if not self
._thread
.stop
.isSet():
213 self
._thread
.stop
.set()
214 thread
= PhotoListThread(model
, query
)
217 def _on_selection_changed(self
, iconview
):
224 num_selec_items
= len(iconview
.get_selected_items())
225 if num_selec_items
== 1:
227 lbl
= wTree
.get_widget("lbl_original_date")
229 model
= iconview
.get_model()
230 iter = model
.get_iter(iconview
.get_selected_items()[0])
231 photo
= model
.get(iter, 0)[0]
232 str_date
= photo
.original_date
.strftime("%x")
233 lbl
.set_text(_("Original Date %s" % str_date
))
235 lbl
= wTree
.get_widget("lbl_tags")
237 lbl
.set_text(str(photo
.tags
))
238 elif num_selec_items
== 0:
239 wTree
.get_widget("lbl_original_date").set_text("")
240 wTree
.get_widget("lbl_tags").set_text("")
242 def _item_activated(self
, iconview
, path
):
244 store
= iconview
.get_model()
245 iter = store
.get_iter(path
)
246 photo
= store
.get_value (iter, 0)
248 ImageWindow(photo
, iconview
)
250 def _on_delete_event (self
, widget
, event
):
254 config
= ConfigurationManager()
255 win
= wTree
.get_widget("wnd_main")
256 config
.window_default_size
= win
.get_size()
261 def __init__(self
, photo
, iconview
):
263 self
.fullscreen
= False
265 self
._wTree
= gtk
.glade
.XML(resource_filename("marvin.ui", "marvin.glade"), "wnd_display_img")
268 "on_wnd_display_img_window_state_event": self
._on
_window
_state
_event
,
269 "on_wnd_display_img_delete_event" : self
._on
_delete
_event
,
270 "on_wnd_display_img_key_press_event" : self
._on
_key
_press
,
272 self
._wTree
.signal_autoconnect (signals
)
274 self
.img_widget
= self
._wTree
.get_widget("img")
276 self
.iconview
= iconview
278 self
.path
= gnomevfs
.get_local_path_from_uri (self
.photo
.uri
)
280 win
= self
._wTree
.get_widget("wnd_display_img")
281 win
.set_size_request (800, 600)
286 def _on_delete_event(self
, widget
, event
):
296 def _on_window_state_event (self
, widget
, event
):
297 "This method should be used for the fullscreen functinality"
301 def _on_key_press(self
, widget
, event
):
302 """Handle the key press event of the image window
309 model
= self
.iconview
.get_model()
311 iter = model
.get_iter(self
.iconview
.get_selected_items()[0])
313 str_key
= gtk
.gdk
.keyval_name(event
.keyval
)
315 if str_key
== 'Right' or str_key
== 'Down':
316 iter_next
= model
.iter_next(iter)
318 if iter_next
== None:
319 iter_next
= model
.get_iter_first()
321 self
.update_iconview(iter_next
)
323 elif str_key
== 'Left' or str_key
== 'Up':
324 int_path
= int(model
.get_string_from_iter(iter)) - 1
327 str_path
= str("%d" % (int_path
))
329 iter_next
= model
.get_iter_from_string(str_path
)
331 self
.update_iconview(iter_next
)
333 elif str_key
== 'plus':
336 elif str_key
== 'minus':
340 self
.update_image(False)
342 elif str_key
== 'F11':
343 win
= self
._wTree
.get_widget("wnd_display_img")
346 self
.fullscreen
=False
349 self
.fullscreen
= True
351 elif str_key
.lower() == 's':
354 elif str_key
== 'Escape':
355 win
= self
._wTree
.get_widget("wnd_display_img")
363 def zoom(self
, factor
):
365 curr_width
= self
.pixbuf
.get_width()
366 curr_height
= self
.pixbuf
.get_height()
367 scaled_pixbuf
= self
.pixbuf
.scale_simple(int(curr_width
*factor
), int(curr_height
*factor
), gtk
.gdk
.INTERP_BILINEAR
)
369 self
.pixbuf
= scaled_pixbuf
370 self
.img_widget
.set_from_pixbuf(self
.pixbuf
)
373 def update_iconview(self
, iter_next
):
375 self
.photo
= model
.get(iter_next
, 0)[0]
376 self
.path
= gnomevfs
.get_local_path_from_uri (self
.photo
.uri
)
379 str_path
= model
.get_string_from_iter(iter_next
)
380 self
.iconview
.select_path(str_path
)
382 def update_image(self
, to_wnd_size
=True):
385 win
= self
._wTree
.get_widget("wnd_display_img")
386 size
= win
.get_size()
387 self
.pixbuf
= gtk
.gdk
.pixbuf_new_from_file_at_size(self
.path
, size
[0], size
[1])
389 self
.pixbuf
= gtk
.gdk
.pixbuf_new_from_file(self
.path
)
391 self
.img_widget
.set_from_pixbuf(self
.pixbuf
)
393 class PhotoListThread (threading
.Thread
):
395 def __init__ (self
, model
, query
=None, use_progressbar
=False):
396 super(PhotoListThread
, self
).__init
__()
398 if query
is not None:
401 self
._query
= "select id, uri from photos order by time desc limit 10"
407 inicio
= datetime
.datetime
.now()
411 gtk
.gdk
.threads_enter()
412 progressbar
= wTree
.get_widget("progressbar1")
415 gtk
.gdk
.threads_leave()
417 conn
= connect_to_db()
419 c
.execute (self
._query
)
423 gtk
.gdk
.threads_enter()
426 p
= Photo(row
[0], row
[1])
427 self
._model
.append([p
,
429 p
.original_date
.date().strftime("%x")])
434 gtk
.gdk
.threads_leave()
436 gtk
.gdk
.threads_enter()
437 Progressbar
.push_message(_("%d pictures found.") % (i
))
440 gtk
.gdk
.threads_leave()
442 fin
= datetime
.datetime
.now()
443 print "Took %s load the pictures" % (fin
-inicio
)
445 class Progressbar(object):
448 def push_message(cls
, msg
, context
="marvin"):
449 statusbar
= wTree
.get_widget("statusbar1")
450 context_id
= statusbar
.get_context_id(context
)
451 msg_id
= statusbar
.push(context_id
, msg
)
452 gobject
.timeout_add(10000,Progressbar
.remove_from_statusbar
,context_id
, msg_id
)
455 def remove_from_statusbar(cls
, context_id
, msg_id
):
456 statusbar
= wTree
.get_widget("statusbar1")
457 statusbar
.remove(context_id
, msg_id
)
459 return False #stop being called by gobject.timeout_add
462 class ImportWindow(object):
470 glade_file
= resource_filename("marvin.ui", "marvin.glade")
471 self
._wTree
= gtk
.glade
.XML(glade_file
, "wnd_photo_import")
474 "on_checkbtn_include_subdirs_toggled" : self
._on
_include
_subdir
_toggled
,
475 "on_chooserbtn_dir_to_import_current_folder_changed" : self
._on
_folder
_changed
,
476 "on_btn_accept_clicked" : self
._on
_accept
_clicked
,
479 self
._wTree
.signal_autoconnect(signals
)
481 list_photos
= gtk
.ListStore(Photo
, gtk
.gdk
.Pixbuf
, str)
483 iconview
= self
._wTree
.get_widget("iconview_preview")
484 iconview
.set_model(list_photos
)
485 iconview
.set_pixbuf_column(1)
486 iconview
.set_text_column(2)
488 win
= self
._wTree
.get_widget("wnd_photo_import")
491 def _on_folder_changed(self
, widget
):
492 self
.refresh_iconview()
494 def _on_include_subdir_toggled(self
, togglebutton
):
495 self
.refresh_iconview()
497 def refresh_iconview(self
):
501 uri
= self
._wTree
.get_widget("chooserbtn_dir_to_import").get_current_folder_uri()
502 selected_path
= gnomevfs
.get_local_path_from_uri(uri
)
503 model
= self
._wTree
.get_widget("iconview_preview").get_model()
505 recursive
= self
._wTree
.get_widget("checkbtn_include_subdirs").get_active()
508 for root
, dirs
, files
in os
.walk(selected_path
):
510 uri
= "file://" + str(root
) + "/" + str(name
)
511 if gnomevfs
.get_mime_type(uri
) == "image/jpeg":
512 uris_list
.append(uri
)
514 for name
in os
.listdir(selected_path
):
515 uri
= "file://" + str(selected_path
) + "/" + str(name
)
516 if gnomevfs
.get_mime_type(uri
) == "image/jpeg":
517 uris_list
.append(uri
)
519 if (self
._thread
is not None) and (not self
._thread
.is_stopped()):
522 self
._thread
= PhotoPreviewThread(model
, uris_list
)
525 def _on_accept_clicked(self
, button
):
527 self
._wTree
.get_widget("wnd_photo_import").set_sensitive(False)
529 iconview
= self
._wTree
.get_widget("iconview_preview")
530 model
= iconview
.get_model()
532 list_of_photos
= list()
534 if len(iconview
.get_selected_items()) == 0:
535 iter = model
.get_iter_first()
537 while (iter is not None):
538 list_of_photos
.append(model
.get(iter, 0)[0])
539 iter = model
.iter_next(iter)
542 selected_items
= iconview
.get_selected_items()
543 for path
in selected_items
:
544 iter = model
.get_iter(path
)
545 list_of_photos
.append(model
.get(iter, 0)[0])
547 copy_to_photodir
= self
._wTree
.get_widget("checkbtn_copy").get_active()
548 progressbar
= self
._wTree
.get_widget("progressbar_import")
549 thread
= ImportPhotoThread(list_of_photos
, copy_to_photodir
, progressbar
)
552 class ImportPhotoThread(threading
.Thread
):
554 def __init__(self
, list_of_photos
, copy_to_photodir
, progressbar
):
555 super(ImportPhotoThread
, self
).__init
__()
557 self
._photos
= list_of_photos
558 self
._copy
_to
_photodir
= copy_to_photodir
559 self
._progressbar
= progressbar
562 self
._conn
= connect_to_db()
563 self
._c
= self
._conn
.cursor()
565 self
._progressbar
.show()
567 total
= float(len(self
._photos
))
568 for photo
in self
._photos
:
570 self
._import
(photo
, self
._copy
_to
_photodir
)
571 self
._progressbar
.set_fraction(i
/total
)
572 self
._progressbar
.set_text(_("Imported %d of %d") % (i
, int(total
)))
577 win
= self
._progressbar
.get_toplevel()
580 def _import (self
, photo
, copy_to_photodir
):
583 new_uri
= self
._copy
_photo
(photo
.uri
)
587 self
._c
.execute("""insert into photos(uri, time, description, roll_id, default_version_id, rating)
588 values (?, ?, '', 0, 1, 0)""", (new_uri
, time
.mktime(photo
.original_date
.timetuple()),))
590 def _copy_photo(self
, uri
):
592 base
= "/apps/f-spot/import"
593 client
= gconf
.client_get_default()
594 client
.add_dir(base
, gconf
.CLIENT_PRELOAD_NONE
)
595 root_photo_dir
= client
.get_string(base
+ "/storage_path")
597 src_path
= gnomevfs
.get_local_path_from_uri(uri
)
598 f
= open(src_path
, 'rb')
599 tags
= EXIF
.process_file(f
, stop_tag
='DateTimeOriginal')
601 if tags
.has_key('EXIF DateTimeOriginal'):
602 (aux_date
, aux_time
) = str(tags
['EXIF DateTimeOriginal']).split(' ')
603 (year
, month
, day
) = aux_date
.split(':')
606 mtime
= time
.localtime(os
.path
.getmtime(src_path
))
612 destdir
= os
.path
.join(root_photo_dir
, year
, month
, day
)
613 list_dir
= (os
.path
.join(root_photo_dir
, year
),
614 os
.path
.join(root_photo_dir
, year
, month
),
615 os
.path
.join(root_photo_dir
, year
, month
, day
))
616 for aux_path
in list_dir
:
617 if not os
.path
.exists(aux_path
):
620 if os
.path
.exists(os
.path
.join(destdir
, os
.path
.basename(src_path
))):
621 aux
= os
.path
.basename(src_path
).rsplit('.', 1)
622 suff
= str(time
.localtime()[3]) + str(time
.localtime()[4])
623 destdir
= os
.path
.join(destdir
, aux
[0] + suff
+ aux
[1])
625 destdir
= os
.path
.join(destdir
, os
.path
.basename(src_path
))
627 shutil
.copyfile(src_path
, destdir
)
629 return "file://" + destdir
#returns the destination uri
631 class PhotoPreviewThread(threading
.Thread
):
633 def __init__(self
, model
, uris
):
634 super(PhotoPreviewThread
, self
).__init
__()
642 self
._stopped
= False
645 for uri
in self
._uris
:
651 photo
= Photo(-1, uri
, 150)
652 self
._model
.append([photo
,
654 photo
.original_date
.date().strftime("%x")])
659 def is_stopped(self
):
664 # http://docs.python.org/lib/multiple-destinations.html example
665 # set up logging to file - see previous section for more details
666 logging
.basicConfig(level
=logging
.DEBUG
,
667 #format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
668 format
='%(levelname)-8s %(message)s',
669 datefmt
='%m-%d %H:%M')
670 # define a Handler which writes INFO messages or higher to the sys.stderr
671 console
= logging
.StreamHandler()
672 console
.setLevel(logging
.INFO
)
673 # set a format which is simpler for console use
674 formatter
= logging
.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
675 # tell the handler to use this format
676 console
.setFormatter(formatter
)
677 # add the handler to the root logger
678 logging
.getLogger('').addHandler(console
)
681 def start_import(run_main_loop
=True):
688 gobject
.set_application_name(_("Photo collection manager"))
689 gobject
.set_prgname("marvin")
697 log
= logging
.getLogger('view')
699 gobject
.set_application_name(_("Photo collection manager"))
700 gobject
.set_prgname("marvin")
704 gtk
.gdk
.threads_enter()
706 gtk
.gdk
.threads_leave()
709 if __name__
== '__main__':