diff --git a/resources/glade/dialogs.glade b/resources/glade/dialogs.glade index 76c4ba9..bd7a587 100644 --- a/resources/glade/dialogs.glade +++ b/resources/glade/dialogs.glade @@ -759,4 +759,80 @@ + + 600 + 400 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + pyGTKtalog - remove tags + True + GTK_WIN_POS_CENTER_ON_PARENT + GDK_WINDOW_TYPE_HINT_DIALOG + False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + True + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + -6 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-remove + True + -5 + + + 1 + + + + + False + GTK_PACK_END + + + + + diff --git a/resources/glade/main.glade b/resources/glade/main.glade index 8b80040..1d7a58a 100644 --- a/resources/glade/main.glade +++ b/resources/glade/main.glade @@ -861,6 +861,20 @@ + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Remo_ve tag + True + + + + + + True + + True @@ -967,7 +981,7 @@ True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Delete images True diff --git a/src/ctrls/c_main.py b/src/ctrls/c_main.py index 27a9c5f..9cbb383 100644 --- a/src/ctrls/c_main.py +++ b/src/ctrls/c_main.py @@ -193,7 +193,6 @@ class MainController(Controller): int(event.x), int(event.y)) iterator = textview.get_iter_at_location(x, y) - # call open_url if an URL is assigned to the iter tags = iterator.get_tags() if len(tags) == 1: @@ -215,8 +214,10 @@ class MainController(Controller): else: self.view['tag_path'].set_text(txt + ", " +tag1) self.__tag_cloud() - - + self.model.get_root_entries() + self.view['files'].set_model(self.model.files_list) + self.__hide_details() + def on_tag_cloud_textview_drag_data_received(self, widget, context, x, y, selection, targetType, time): """recive data from source TV""" @@ -401,6 +402,13 @@ class MainController(Controller): self.view['tag_path_box'].hide() self.model.selected_tags = [] self.model.refresh_discs_tree() + + # cleanup files and detiles + try: + self.model.files_list.clear() + except: + pass + self.__hide_details() self.on_discs_cursor_changed(w) self.__tag_cloud() @@ -471,6 +479,7 @@ class MainController(Controller): else: self.view['tag_path'].set_text(txt + ", " +tag1) self.__tag_cloud() + #elif event.type == gtk.gdk.MOTION_NOTIFY: # window = tag_cloud.get_window(gtk.TEXT_WINDOW_TEXT) @@ -511,10 +520,13 @@ class MainController(Controller): return self.model.new() - # clear "details" buffer - buf = self.view['description'].get_buffer() - buf.set_text("") - self.view['description'].set_buffer(buf) + # cleanup files and details + try: + self.model.files_list.clear() + except: + pass + self.__hide_details() + self.view['tag_path_box'].hide() self.__activate_ui() self.__tag_cloud() @@ -639,7 +651,18 @@ class MainController(Controller): if not path: path = Dialogs.LoadDBFile().run() - + + # cleanup files and details + try: + self.model.files_list.clear() + except: + pass + self.__hide_details() + self.view['tag_path_box'].hide() + buf = self.view['tag_cloud_textview'].get_buffer() + buf.set_text('') + self.view['tag_cloud_textview'].set_buffer(buf) + if path: if not self.model.open(path): Dialogs.Err("Error opening file - pyGTKtalog", @@ -928,6 +951,28 @@ class MainController(Controller): return # NOTE: add tags / images + def on_delete_tag_activate(self, menu_item): + ids = self.__get_tv_selection_ids(self.view['files']) + if not ids: + Dialogs.Inf("Remove tags", "No files selected", + "You have to select some files first.") + return + + tags = self.model.get_tags_by_file_id(ids) + if tags: + d = Dialogs.TagsRemoveDialog(tags) + retcode, retval = d.run() + if retcode=="ok" and not retval: + Dialogs.Inf("Remove tags", "No tags selected", + "You have to select any tag to remove from files.") + return + elif retcode == "ok" and retval: + self.model.delete_tags(ids, retval) + self.model.get_root_entries() + self.view['files'].set_model(self.model.files_list) + self.__tag_cloud() + return + def on_add_tag1_activate(self, menu_item): #try: tags = Dialogs.TagsDialog().run() @@ -1123,7 +1168,7 @@ class MainController(Controller): print "abort = %s" % self.model.abort print "self.model.config.recent = %s" % self.model.config.recent print "source: %s" % self.model.source - self.__tag_cloud() + ##################### # observed properetis @@ -1335,11 +1380,7 @@ class MainController(Controller): buf = gtk.TextBuffer() if not file_id: - buf.set_text('') - self.view['img_container'].hide() - self.view['exifinfo'].hide() - self.view['thumb_box'].hide() - self.view['description'].set_buffer(buf) + self.__hide_details() return #self.view['description'].show() set = self.model.get_file_info(file_id) @@ -1452,6 +1493,16 @@ class MainController(Controller): cloud['name'] + "(%d)" % cloud['count'], tag) except: - print "fuckup", cloud + if __debug__: + print "c_main.py: __tag_cloud: error on tag:", cloud + + def __hide_details(self): + """hide details and "reset" tabs visibility""" + buf = self.view['description'].get_buffer() + buf.set_text('') + self.view['img_container'].hide() + self.view['exifinfo'].hide() + self.view['thumb_box'].hide() + self.view['description'].set_buffer(buf) pass # end of class diff --git a/src/models/m_main.py b/src/models/m_main.py index 7b78634..26e4b7c 100644 --- a/src/models/m_main.py +++ b/src/models/m_main.py @@ -162,6 +162,25 @@ class MainModel(ModelMT): self.get_tags() return + def get_tags_by_file_id(self, file_id_list): + """return dictionary of tags by connected files""" + # SQL: get tags by file_ids + if len(file_id_list) == 1: + sql = "(%d)" % file_id_list[0] + else: + sql = str(tuple(file_id_list)) + sql = """SELECT DISTINCT t.id, t.tag FROM tags_files f + LEFT JOIN tags t on t.id = f.tag_id + WHERE f.file_id in """ + sql + """ + ORDER BY t.tag""" + self.db_cursor.execute(sql) + res = self.db_cursor.fetchall() + + retval = {} + for tag in res: + retval[tag[0]] = tag[1] + return retval + def get_tag_by_id(self, tag_id): """get tag (string) by its id""" # SQL: get tag by id @@ -192,6 +211,18 @@ class MainModel(ModelMT): return tmp + def delete_tags(self, file_id_list, tag_id_list): + """remove tags from selected files""" + for file_id in file_id_list: + # SQL: remove tags for selected file + if len(tag_id_list) == 1: + sql = "(%d)" % tag_id_list[0] + else: + sql = str(tuple(tag_id_list)) + sql = """DELETE FROM tags_files WHERE file_id = ? + AND tag_id IN """ + sql + self.db_cursor.execute(sql, (int(file_id), )) + def get_tags(self): """fill tags dict with values from db""" if not self.selected_tags: @@ -398,7 +429,7 @@ class MainModel(ModelMT): self.__create_database() self.__clear_trees() self.tag_cloud = [] - self.selected_tags = None + self.selected_tags = {} return def save(self, filename=None): @@ -420,6 +451,8 @@ class MainModel(ModelMT): self.unsaved_project = False self.__create_internal_dirname() self.filename = filename + self.tag_cloud = [] + self.selected_tags = {} try: tar = tarfile.open(filename, "r:gz") @@ -501,7 +534,6 @@ class MainModel(ModelMT): for row in self.files_list: if row[0] == file_id: - print row[0], row[1], row[2] row[1] = new_name break @@ -522,27 +554,43 @@ class MainModel(ModelMT): """re-fetch discs tree""" self.__fetch_db_into_treestore() - def get_root_entries(self, parent_id): + def get_root_entries(self, parent_id=None): """Get all children down from sepcified root""" self.__clear_files_tree() - + # if we are in "tag" mode, do the boogie # directories first - if not self.selected_tags: - sql = """SELECT id, filename, size, date FROM files - WHERE parent_id=? AND type=1 - ORDER BY filename""" - else: - id_filter = self.__filter() + if not parent_id and self.selected_tags: + # no parent_id, get all the tagged dirs + id_filter = self.__filter2() if id_filter != None: + if len(id_filter) == 1: + id_filter = "(%d)" % id_filter[0] + else: + id_filter = str(tuple(id_filter)) sql = """SELECT id, filename, size, date FROM files - WHERE parent_id=? AND type=1 AND id in """ + \ - str(tuple(id_filter)) + """ ORDER BY filename""" + WHERE parent_id!=id AND type=1 AND id in """ + \ + id_filter + """ ORDER BY filename""" + else: + # we have parent_id, get all the tagged dirs with parent_id + if not self.selected_tags: + sql = """SELECT id, filename, size, date FROM files + WHERE parent_id=? AND type=1 + ORDER BY filename""" else: - sql="""SELECT id, filename, size, date FROM files - WHERE 1=0""" - + id_filter = self.__filter() + if id_filter != None: + sql = """SELECT id, filename, size, date FROM files + WHERE parent_id=? AND type=1 AND id in """ + \ + str(tuple(id_filter)) + """ ORDER BY filename""" + else: + sql="""SELECT id, filename, size, date FROM files + WHERE 1=0""" - self.db_cursor.execute(sql, (parent_id,)) + if not parent_id and self.selected_tags: + self.db_cursor.execute(sql) + else: + self.db_cursor.execute(sql, (parent_id,)) + data = self.db_cursor.fetchall() for row in data: myiter = self.files_list.insert_before(None, None) @@ -556,24 +604,36 @@ class MainModel(ModelMT): self.files_list.set_value(myiter, 6, gtk.STOCK_DIRECTORY) # all the rest - if not self.selected_tags: - sql = """SELECT f.id, f.filename, f.size, f.date, f.type - FROM files f - WHERE f.parent_id=? AND f.type!=1 - ORDER BY f.filename""" - else: + if not parent_id and self.selected_tags: + # no parent_id, get all the tagged files if id_filter != None: sql = """SELECT f.id, f.filename, f.size, f.date, f.type FROM files f - WHERE f.parent_id=? AND f.type!=1 AND id IN """ + \ - str(tuple(id_filter)) + """ ORDER BY f.filename""" - else: - sql="""SELECT f.id, f.filename, f.size, f.date, f.type + WHERE f.type!=1 AND id IN """ + id_filter + \ + """ ORDER BY f.filename""" + else: + # we have parent_id, get all the tagged files with parent_id + if not self.selected_tags: + sql = """SELECT f.id, f.filename, f.size, f.date, f.type FROM files f - WHERE 1=0""" - - - self.db_cursor.execute(sql, (parent_id,)) + WHERE f.parent_id=? AND f.type!=1 + ORDER BY f.filename""" + else: + if id_filter != None: + sql = """SELECT f.id, f.filename, f.size, f.date, f.type + FROM files f + WHERE f.parent_id=? AND f.type!=1 AND id IN """ + \ + str(tuple(id_filter)) + """ ORDER BY f.filename""" + else: + sql="""SELECT f.id, f.filename, f.size, f.date, f.type + FROM files f + WHERE 1=0""" + + if not parent_id and self.selected_tags: + self.db_cursor.execute(sql) + else: + self.db_cursor.execute(sql, (parent_id,)) + data = self.db_cursor.fetchall() for row in data: myiter = self.files_list.insert_before(None, None) @@ -948,6 +1008,7 @@ class MainModel(ModelMT): def __create_internal_dirname(self): """create temporary directory for working thumb/image files and database""" + # TODO: change this stupid rutine into tempfile mkdtemp method self.cleanup() self.internal_dirname = "/tmp/pygtktalog%d" % \ datetime.now().microsecond @@ -1085,6 +1146,30 @@ class MainModel(ModelMT): return list(set(parents).union(filtered_ids)) + def __filter2(self): + """return list of ids of files (WITHOUT their parent) that + corresponds to tags""" + + filtered_ids = [] + count = 0 + for tid in self.selected_tags: + temp1 = [] + sql = """SELECT file_id + FROM tags_files + WHERE tag_id=? """ + self.db_cursor.execute(sql, (tid, )) + data = self.db_cursor.fetchall() + for row in data: + temp1.append(row[0]) + + if count > 0: + filtered_ids = list(set(filtered_ids).intersection(temp1)) + else: + filtered_ids = temp1 + count += 1 + + return filtered_ids + def __scan(self): """scan content of the given path""" self.busy = True diff --git a/src/views/v_dialogs.py b/src/views/v_dialogs.py index c4f36ac..a7a8553 100644 --- a/src/views/v_dialogs.py +++ b/src/views/v_dialogs.py @@ -23,6 +23,7 @@ # ------------------------------------------------------------------------- import gtk +import gobject import os import utils.globals @@ -497,6 +498,65 @@ class TagsDialog(object): if result == gtk.RESPONSE_OK: return entry.get_text() return None + +class TagsRemoveDialog(object): + """Sepcific dialog for display stats""" + + def __init__(self, tag_dict=None): + self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade") + self.tag_dict = tag_dict + + def run(self): + if not self.tag_dict: + return None + + gladexml = gtk.glade.XML(self.gladefile, "tagRemove") + dialog = gladexml.get_widget("tagRemove") + + # fill model with dict + model = gtk.ListStore(gobject.TYPE_INT, + gobject.TYPE_STRING, gobject.TYPE_BOOLEAN) + for tag in self.tag_dict: + myiter = model.insert_before(None, None) + model.set_value(myiter, 0, tag) + model.set_value(myiter, 1, self.tag_dict[tag]) + model.set_value(myiter, 2, None) + + def toggle(cell, path, model): + model[path][2] = not model[path][2] + + def toggle_all(column, model): + for row in model: + row[2] = not row[2] + + treeview = gladexml.get_widget("treeview1") + treeview.set_model(model) + + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn("Tag", renderer, text=1) + column.set_property('expand', True) + treeview.append_column(column) + + renderer = gtk.CellRendererToggle() + renderer.set_property('activatable', True) + renderer.connect('toggled', toggle, model) + column = gtk.TreeViewColumn("Toggle", renderer) + column.add_attribute(renderer, "active", 2) + column.set_property('expand', False) + column.set_property("clickable", True) + column.connect("clicked", toggle_all, model) + treeview.append_column(column) + + result = dialog.run() + + dialog.destroy() + if result == gtk.RESPONSE_OK: + ids = [] + for i in model: + if i[2]: + ids.append(i[0]) + return "ok", ids + return None, None class EditDialog(object): """Sepcific dialog for display stats"""