diff --git a/convert_1.x_to_2.x.py b/convert_1.x_to_2.x.py index e24da91..6ab7848 100755 --- a/convert_1.x_to_2.x.py +++ b/convert_1.x_to_2.x.py @@ -96,7 +96,7 @@ if __name__ == "__main__": for id, date in dst_c.execute(sql).fetchall(): sql = "update files set date=? where id=?" - if int(date) > 0: + if date and int(date) > 0: dst_c.execute(sql, (datetime.fromtimestamp(int(date)), id)) else: dst_c.execute(sql, (None, id)) @@ -105,10 +105,13 @@ if __name__ == "__main__": for id, date in dst_c.execute(sql).fetchall(): sql = "update gthumb set date=? where id=?" - if int(date) > 0: - dst_c.execute(sql, (datetime.fromtimestamp(int(date)), id)) - else: - dst_c.execute(sql, (None, id)) + try: + if int(date) > 0: + dst_c.execute(sql, (datetime.fromtimestamp(int(date)), id)) + else: + dst_c.execute(sql, (None, id)) + except: + print id, date dst_con.commit() dst_c.close() diff --git a/pygtktalog/__init__.py b/pygtktalog/__init__.py index c34f2bb..6e94479 100644 --- a/pygtktalog/__init__.py +++ b/pygtktalog/__init__.py @@ -13,6 +13,16 @@ import __builtin__ import gtk.glade +__all__ = ['controllers', + 'models', + 'views', + 'EXIF', + 'dbcommon', + 'dbobjects', + 'dialogs', + 'logger', + 'misc'] + GETTEXT_DOMAIN = 'pygtktalog' # There should be message catalogs in "locale" directory placed by setup.py # script. If there is no such directory, let's assume that message catalogs are diff --git a/pygtktalog/controllers/discs.py b/pygtktalog/controllers/discs.py index 48ec87f..7824d64 100644 --- a/pygtktalog/controllers/discs.py +++ b/pygtktalog/controllers/discs.py @@ -52,6 +52,8 @@ class DiscsController(Controller): # make cell text editabe cell.set_property('editable', True) cell.connect('edited', self.on_editing_done, self.model.discs) + # TODO: find a way how to disable default return keypress on editable + # fields col.pack_start(cellpb, False) col.pack_start(cell, True) @@ -70,30 +72,37 @@ class DiscsController(Controller): LOG.debug(self.on_discs_button_press_event.__doc__.strip()) pathinfo = treeview.get_path_at_pos(int(event.x), int(event.y)) - if event.button == 3 and pathinfo: - path = pathinfo[0] + if event.button == 3: + if pathinfo: + path = pathinfo[0] - # Make sure, that there is selected row - sel = treeview.get_selection() - sel.unselect_all() - sel.select_path(path) + # Make sure, that there is selected row + sel = treeview.get_selection() + sel.unselect_all() + sel.select_path(path) - self._popup_menu(sel, event, event.button) + self._popup_menu(sel, event, event.button) + else: + self._popup_menu(None, event, event.button) return True - def on_discs_cursor_changed(self, widget): + def on_discs_cursor_changed(self, treeview): """ Show files on right treeview, after clicking the left disc treeview. """ LOG.debug(self.on_discs_cursor_changed.__doc__.strip()) - raise NotImplementedError + selection = treeview.get_selection() + path = selection.get_selected_rows()[1][0] + self.model.update_files(self.model.discs.get_value(\ + self.model.discs.get_iter(path), 0)) def on_discs_key_release_event(self, treeview, event): """ - Trigger popup menu by pressing 'menu' key + Watch for specific keys """ LOG.debug(self.on_discs_key_release_event.__doc__.strip()) if gtk.gdk.keyval_name(event.keyval) == 'Menu': + LOG.debug('Menu key pressed') self._popup_menu(treeview.get_selection(), event, 0) return True return False @@ -170,11 +179,15 @@ class DiscsController(Controller): and trigger menu popup. """ LOG.debug(self._popup_menu.__doc__.strip()) - model, list_of_paths = selection.get_selected_rows() + if selection is None: + self.view.menu.set_menu_items_sensitivity(False) + else: + model, list_of_paths = selection.get_selected_rows() - for path in list_of_paths: - self.view.menu.set_update_sensitivity(not model.get_value(\ - model.get_iter(path), 0).parent_id == 1) + for path in list_of_paths: + self.view.menu.set_menu_items_sensitivity(True) + self.view.menu.set_update_sensitivity(model.get_value(\ + model.get_iter(path), 0).parent_id == 1) self.view.menu['discs_popup'].popup(None, None, None, button, event.time) diff --git a/pygtktalog/controllers/files.py b/pygtktalog/controllers/files.py index 22d41c0..e57465e 100644 --- a/pygtktalog/controllers/files.py +++ b/pygtktalog/controllers/files.py @@ -15,19 +15,57 @@ class FilesController(Controller): Controller for files TreeView list. """ + def __init__(self, model, view): + """ + FilesController initialization + """ + Controller.__init__(self, model, view) + self.DND_TARGETS = [('files_tags', 0, 69)] + + def register_view(self, view): - """Default view registration stuff""" - self.view['files'].set_model(self.model.discs) + """ + Register view, and setup columns for files treeview + """ + view['files'].set_model(self.model.files) + view['files'].get_selection().set_mode(gtk.SELECTION_MULTIPLE) - col = gtk.TreeViewColumn('kolumna2') + col = gtk.TreeViewColumn(_('Disc'), gtk.CellRendererText(), text=1) + col.set_sort_column_id(1) + col.set_resizable(True) + col.set_visible(False) + view['files'].append_column(col) + col = gtk.TreeViewColumn(_('Filename')) cellpb = gtk.CellRendererPixbuf() cell = gtk.CellRendererText() - col.pack_start(cellpb, False) col.pack_start(cell, True) - - col.set_attributes(cellpb, stock_id=0) - col.set_attributes(cell, text=1) + col.set_attributes(cellpb, stock_id=7) + col.set_attributes(cell, text=2) + col.set_sort_column_id(2) + col.set_resizable(True) self.view['files'].append_column(col) + col = gtk.TreeViewColumn(_('Path'), gtk.CellRendererText(), text=3) + col.set_sort_column_id(3) + col.set_resizable(True) + col.set_visible(False) + self.view['files'].append_column(col) + + col = gtk.TreeViewColumn(_('Size'), gtk.CellRendererText(), text=4) + col.set_sort_column_id(4) + col.set_resizable(True) + self.view['files'].append_column(col) + + col = gtk.TreeViewColumn(_('Date'), gtk.CellRendererText(), text=5) + col.set_sort_column_id(5) + col.set_resizable(True) + self.view['files'].append_column(col) + self.view['files'].set_search_column(2) + + # setup d'n'd support + view['files'].drag_source_set(gtk.gdk.BUTTON1_MASK, + self.DND_TARGETS, + gtk.gdk.ACTION_COPY) + diff --git a/pygtktalog/controllers/main.py b/pygtktalog/controllers/main.py index c085149..88fc3a9 100644 --- a/pygtktalog/controllers/main.py +++ b/pygtktalog/controllers/main.py @@ -11,7 +11,7 @@ from gtkmvc import Controller #from pygtktalog.dialogs import yesno from pygtktalog.controllers.discs import DiscsController -#from pygtktalog.controllers.files import FilesController +from pygtktalog.controllers.files import FilesController #from pygtktalog.controllers.details import DetailsController #from pygtktalog.controllers.tags import TagcloudController #from pygtktalog.dialogs import yesno, okcancel, info, warn, error @@ -25,36 +25,50 @@ class MainController(Controller): """ def __init__(self, model, view): - """Initialize main controller""" + """ + Initialize MainController, add controllers for trees and details. + """ + LOG.debug(self.__init__.__doc__.strip()) Controller.__init__(self, model, view) - # add controllers for files/tags components + # add controllers for files/tags/details components self.discs = DiscsController(model, view.discs) - #self.files = FilesController(model, view.files) + self.files = FilesController(model, view.files) #self.details = DetailsController(model, view.details) #self.tags = TagcloudController(model, view.tags) def register_view(self, view): - """Default view registration stuff""" - # one row contains image and text + """ + Registration view for MainController class + """ + LOG.debug(self.register_view.__doc__.strip()) + LOG.debug("replace hardcoded defaults with configured!") + view['main'].set_default_size(800, 600) + view['hpaned1'].set_position(200) view['main'].show() def register_adapters(self): """ progress bar/status bar adapters goes here """ + LOG.debug(self.register_adapters.__doc__.strip()) pass # signals def on_main_destroy_event(self, widget, event): - """Quit""" + """ + Window destroyed. Cleanup before quit. + """ + LOG.debug(self.on_main_destroy_event.__doc__.strip()) self.on_quit_activate(widget) return True def on_quit_activate(self, widget): - """Quit and save window parameters to config file""" - + """ + Quit and save window parameters to config file + """ + LOG.debug(self.on_quit_activate.__doc__.strip()) #if yesno(_("Do you really want to quit?"), # _("Current database is not saved, any changes will be " # "lost."), _("Quit application") + " - pyGTKtalog", 0): diff --git a/pygtktalog/models/main.py b/pygtktalog/models/main.py index 698a955..e5e2816 100644 --- a/pygtktalog/models/main.py +++ b/pygtktalog/models/main.py @@ -45,6 +45,8 @@ class MainModel(ModelMT): self.cat_fname = filename # Temporary (usually in /tmp) working database. self.tmp_filename = None + # SQLAlchemy session object for internal use + self._session = None # Flag indicates, that db was compressed # TODO: make it depend on configuration self.compressed = False @@ -54,7 +56,14 @@ class MainModel(ModelMT): self.discs = gtk.TreeStore(gobject.TYPE_PYOBJECT, gobject.TYPE_STRING, str) - + self.files = gtk.ListStore(gobject.TYPE_PYOBJECT, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_UINT64, + gobject.TYPE_STRING, + gobject.TYPE_INT, + str) if self.cat_fname: self.open(self.cat_fname) @@ -82,12 +91,18 @@ class MainModel(ModelMT): """ Create new catalog """ - self._cleanup_and_create_temp_db_file() + self.cleanup() + self._create_temp_db_file() def cleanup(self): """ Remove temporary directory tree from filesystem """ + + if self._session: + self._session.close() + self._session = None + if self.tmp_filename is None: return @@ -105,7 +120,8 @@ class MainModel(ModelMT): """ Create new DB """ - self._cleanup_and_create_temp_db_file() + self.cleanup() + self._create_temp_db_file() def _examine_file(self, filename): """ @@ -143,7 +159,10 @@ class MainModel(ModelMT): filename = os.path.abspath(self.cat_fname) LOG.info("catalog file: %s", filename) - self._cleanup_and_create_temp_db_file() + if self._session: + self.cleanup() + + self._create_temp_db_file() LOG.debug("tmp database file: %s", str(self.tmp_filename)) examine = self._examine_file(filename) @@ -181,10 +200,10 @@ class MainModel(ModelMT): return False connect(os.path.abspath(self.tmp_filename)) + self._session = Session() return True - def _cleanup_and_create_temp_db_file(self): - self.cleanup() + def _create_temp_db_file(self): fd, self.tmp_filename = mkstemp() # close file descriptor, otherwise it can be source of app crash! # http://www.logilab.org/blogentry/17873 @@ -195,8 +214,7 @@ class MainModel(ModelMT): Read objects from database, fill TreeStore model with discs information """ - session = Session() - dirs = session.query(File).filter(File.type == 1) + dirs = self._session.query(File).filter(File.type == 1) dirs = dirs.order_by(File.filename).all() def get_children(parent_id=1, iterator=None): @@ -218,9 +236,33 @@ class MainModel(ModelMT): get_children(fileob.id, myiter) return get_children() - session.close() return True - def get_root_entries(self, id): - LOG.debug("not implemented!, get_root_entries, id: %s", str(id)) + def update_files(self, fileob): + """ + Update files ListStore + Arguments: + fileob - File object + """ + files = self._session.query(File).filter(File.parent_id==fileob.id)\ + .order_by(File.type, File.filename).all() + files = [] + LOG.info("found %d files for root id %s" %(len(files), str(fileob))) + + self.files.clear() + + for fob in files: + myiter = self.files.insert_before(None, None) + self.files.set_value(myiter, 0, fob.id) + self.files.set_value(myiter, 1, fob.parent_id if fob.parent_id!=1 else None) + self.files.set_value(myiter, 2, fob.filename) + self.files.set_value(myiter, 3, fob.filepath) + self.files.set_value(myiter, 4, fob.size) + self.files.set_value(myiter, 5, fob.date) + self.files.set_value(myiter, 6, 1) + self.files.set_value(myiter, 7, gtk.STOCK_DIRECTORY \ + if fob.type==1 else gtk.STOCK_FILE) + + + diff --git a/pygtktalog/views/glade/files.glade b/pygtktalog/views/glade/files.glade index 53e5b96..e0e0d89 100644 --- a/pygtktalog/views/glade/files.glade +++ b/pygtktalog/views/glade/files.glade @@ -1,19 +1,122 @@ - - - + + + True True True - - + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Add tag + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Remo_ve tag + True + + + + + + True + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Add _Thumbnail + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Re_move Thumbnail + True + + + + + + True + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + Add images to file. If file have no thumbnail, +thumbnail from first image will be generated. + Add _Images + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Rem_ove All Images + True + + + + + + True + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Edit + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Delete + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Rename + True + + + + diff --git a/pygtktalog/views/glade/main.glade b/pygtktalog/views/glade/main.glade index c486922..ee224bb 100644 --- a/pygtktalog/views/glade/main.glade +++ b/pygtktalog/views/glade/main.glade @@ -495,7 +495,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -556,7 +556,7 @@ True vertical - + True True automatic @@ -639,108 +639,6 @@ - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Add tag - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Remo_ve tag - True - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Add _Thumbnail - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Re_move Thumbnail - True - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Add images to file. If file have no thumbnail, -thumbnail from first image will be generated. - Add _Images - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Rem_ove All Images - True - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Edit - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Delete - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Rename - True - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK pyGTKtalog - Image diff --git a/pygtktalog/views/main.py b/pygtktalog/views/main.py index a29a83d..ea48be2 100644 --- a/pygtktalog/views/main.py +++ b/pygtktalog/views/main.py @@ -31,8 +31,13 @@ class MainView(View): """ View.__init__(self) self['tag_path_box'].hide() + self.discs = DiscsView() - self['scrolledwindow1'].add_with_viewport(self.discs.get_top_widget()) + #self['scrolledwindow_discs'].add_with_viewport(self.discs.get_top_widget()) + self['scrolledwindow_discs'].add(self.discs.get_top_widget()) + + self.files = FilesView() + self['scrolledwindow_files'].add_with_viewport(self.files.get_top_widget()) def set_widgets_scan_sensitivity(self, sensitive=True): """ @@ -71,11 +76,23 @@ class DiscsPopupView(View): def set_update_sensitivity(self, state): """ - Update sensitivity for 'update' popup menu item - + Set sensitivity for 'update' popup menu item + Arguments: + @state - Bool, if True update menu item will be sensitive, + otherwise not """ - self['update'].set_sensitive(not state) + self['update'].set_sensitive(state) + def set_menu_items_sensitivity(self, state): + """ + Set sensitivity for couple of popup menu items, which should be + disabled if user right-clicks on no item in treeview. + Arguments: + @state - Bool, if True update menu item will be sensitive, + otherwise not + """ + for item in ['update', 'rename', 'delete', 'statistics']: + self[item].set_sensitive(state) class FilesView(View): """ @@ -90,6 +107,18 @@ class FilesView(View): """ View.__init__(self) +class FilesPopupView(View): + """ + Separate Files PopUp subview. + """ + glade = get_glade("files.glade") + top = 'files_popup' + + def __init__(self): + """ + Initialize view + """ + View.__init__(self) class TagcloudView(View): """