1
0
mirror of https://github.com/gryf/pygtktalog.git synced 2025-12-17 11:30:19 +01:00

* Added support for "menu" key on keyboard.

* Small changes in behaviour of individual popup menus.
This commit is contained in:
2008-05-06 19:17:10 +00:00
parent aea871b30e
commit 33b5e76f99
6 changed files with 328 additions and 192 deletions

21
README
View File

@@ -1,4 +1,4 @@
pyGTKtalog 0.8
pyGTKtalog 0.9
==============
pyGTKtalog Linux/FreeBSD program for indexing CD/DVD or directories on
@@ -24,6 +24,7 @@ REQUIREMENTS
pyGTKtalog is written in python with following dependencies:
- python 2.4 or higher
- pygtk <http://www.pygtk.org>
- pysqlite2 <http://pysqlite.org/> (unnecessary, if python 2.5 is used)
@@ -37,7 +38,7 @@ Additional pyGTKtalog uses EXIF module by Gene Cash which is included in
sources.
pyGTKtalog extensivly uses external programs in unix spirit, however there is
small possibility of using it Windows (probably with liitations) and quite big
small possibility of using it Windows (probably with limitations) and quite big
possiblity to run it on other sofisticated unix-like systems (i.e.
BeOS/ZETA/Haiku, QNX or MacOSX).
@@ -57,11 +58,14 @@ Then, just run pyGTKtalog script.
TODO
====
For version 1.0 following aims have to be done:
PyGTKtalog is still under heavy development, however there is small chance to
change structure of catalogs (and if it'll change, there will be transparent
function to update DB schema).
For version 1.0 following major aims have to be done:
- searching database
- tagging files (90%)
- user definied group of tags (represented by color in cloud tag)
x tagging files
x remove nasty bug in redraw of tag cloud
x file details:
x files properties
@@ -75,6 +79,10 @@ x generating/saving thumbnails
x moving hardcoded files extensions into config
x statistics
There are still minor aims for versions 1.x to be done:
- consolidate popup-menus with edit menu
- add popup menu for directly removing tag from tag cloud
Legend: [-] not done, [x] done.
For version 2.0:
@@ -82,6 +90,9 @@ For version 2.0:
- command line support: query, adding media to collection etc
- internationalization support
- export to XLS
- user definied group of tags (represented by color in cloud tag)
- hiding specified files - configurable, like dot prefixed, cfg and manualy
selected
Removed:
- filetypes handling (movies, images, archives, documents etc). Now it have

View File

@@ -93,35 +93,9 @@
<property name="visible">True</property>
<property name="label" translatable="yes">_Edit</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_edit1_activate"/>
<child>
<widget class="GtkMenu" id="edit1_menu">
<child>
<widget class="GtkImageMenuItem" id="cut1">
<property name="visible">True</property>
<property name="label">gtk-cut</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_cut1_activate"/>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="copy1">
<property name="visible">True</property>
<property name="label">gtk-copy</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_copy1_activate"/>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="paste1">
<property name="visible">True</property>
<property name="label">gtk-paste</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_paste1_activate"/>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="delete1">
<property name="visible">True</property>
@@ -129,6 +103,7 @@
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_delete1_activate"/>
<accelerator key="Delete" modifiers="" signal="activate"/>
</widget>
</child>
<child>
@@ -177,7 +152,7 @@
<property name="label" translatable="yes">Add _CD/DVD</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_add_cd_activate"/>
<accelerator key="a" modifiers="GDK_CONTROL_MASK" signal="activate"/>
<accelerator key="c" modifiers="GDK_CONTROL_MASK" signal="activate"/>
</widget>
</child>
<child>
@@ -185,7 +160,7 @@
<property name="visible">True</property>
<property name="label" translatable="yes">Add _Directory</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_add_directory1_activate"/>
<signal name="activate" handler="on_add_directory_activate"/>
<accelerator key="d" modifiers="GDK_CONTROL_MASK" signal="activate"/>
</widget>
</child>
@@ -371,6 +346,18 @@
<property name="expand">False</property>
</packing>
</child>
<child>
<widget class="GtkToolButton" id="tb_adddir">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Add Dir</property>
<property name="stock_id">gtk-directory</property>
<signal name="clicked" handler="on_add_directory_activate"/>
</widget>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<widget class="GtkToolButton" id="tb_find">
<property name="visible">True</property>
@@ -501,6 +488,7 @@
<signal name="button_press_event" handler="on_discs_button_press_event"/>
<signal name="row_activated" handler="on_discs_row_activated"/>
<signal name="cursor_changed" handler="on_discs_cursor_changed"/>
<signal name="key_release_event" handler="on_discs_key_release_event"/>
</widget>
</child>
</widget>
@@ -672,6 +660,7 @@
<property name="tooltip" translatable="yes">Double click to open image</property>
<signal name="button_press_event" handler="on_images_button_press_event"/>
<signal name="item_activated" handler="on_images_item_activated"/>
<signal name="key_release_event" handler="on_images_key_release_event"/>
</widget>
</child>
</widget>
@@ -1025,4 +1014,17 @@
</widget>
</child>
</widget>
<widget class="GtkMenu" id="tag_popup">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<widget class="GtkMenuItem" id="delete_tag2">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Delete tag</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_delete_tag2_activate"/>
</widget>
</child>
</widget>
</glade-interface>

View File

@@ -47,9 +47,9 @@ import pango
class MainController(Controller):
"""Controller for main application window"""
scan_cd = False
widgets = ("discs", "files", 'save1', 'save_as1', 'cut1', 'copy1',
'paste1', 'delete1', 'add_cd', 'add_directory1', 'tb_save',
'tb_addcd', 'tb_find', 'nb_dirs', 'description', 'stat1')
widgets = ("discs", "files", 'save1', 'save_as1', 'delete1', 'add_cd',
'add_directory1', 'tb_save', 'tb_addcd', 'tb_find', 'nb_dirs',
'description', 'stat1')
widgets_all = ("discs", "files", 'file1', 'edit1', 'add_cd',
'add_directory1', 'help1', 'tb_save', 'tb_addcd', 'tb_find',
'tb_new', 'tb_open', 'tb_quit', 'nb_dirs', 'description',
@@ -72,7 +72,7 @@ class MainController(Controller):
for widget in self.widgets:
self.view[widget].set_sensitive(False)
# Make not active "Cancel" button and menuitem
# Make not active "Cancel" button and menu_item
for widget in self.widgets_cancel:
self.view[widget].set_sensitive(False)
@@ -124,6 +124,9 @@ class MainController(Controller):
# generate recent menu
self.__generate_recent_menu()
self.view['tag_cloud_textview'].connect("populate-popup",
self.on_tag_cloud_textview_popup)
# in case model has opened file, register tags
if self.model.internal_dirname:
self.__tag_cloud()
@@ -173,6 +176,10 @@ class MainController(Controller):
string = str(tuple(ids)).replace(",)", ")")
selection.set(selection.target, 8, string)
def on_tag_cloud_textview_popup(self, textview, menu):
menu = None
return True
def on_tag_cloud_textview_event_after(self, textview, event):
if event.type != gtk.gdk.BUTTON_RELEASE:
return False
@@ -220,7 +227,7 @@ class MainController(Controller):
def on_tag_cloud_textview_drag_data_received(self, widget, context, x, y,
selection, targetType, time):
"""recive data from source TV"""
"""recive data from source TreeView"""
if targetType == self.DND_TARGETS[0][2]:
iter = widget.get_iter_at_location(x, y)
ids = selection.data.rstrip(")").lstrip("(").split(",")
@@ -556,7 +563,7 @@ class MainController(Controller):
"Last mount message:\n%s" % mount)
return False
def on_add_directory1_activate(self, widget, path=None, label=None,
def on_add_directory_activate(self, widget, path=None, label=None,
current_id=None):
"""Show dialog for choose drectory to add from filesystem."""
if not label or not path:
@@ -694,30 +701,53 @@ class MainController(Controller):
treeview.expand_row(path, False)
return
def on_discs_key_release_event(self, treeview, event):
if gtk.gdk.keyval_name(event.keyval) == 'Menu':
ids = self.__get_tv_selection_ids(treeview)
menu_items = ['update1','rename1','delete2', 'statistics1']
for menu_item in menu_items:
self.view[menu_item].set_sensitive(not not ids)
self.__popup_menu(event, 'discs_popup')
return True
return False
def on_images_key_release_event(self, iconview, event):
if gtk.gdk.keyval_name(event.keyval) == 'Menu':
self.__popup_menu(event, 'img_popup')
return True
return False
def on_images_button_press_event(self, iconview, event):
try:
path_and_cell = iconview.get_item_at_pos(int(event.x),
int(event.y))
except TypeError:
return False
#try:
# path_and_cell = iconview.get_item_at_pos(int(event.x),
# int(event.y))
#except TypeError:
# return False
if event.button == 3: # Right mouse button. Show context menu.
try:
iconview.select_path(path_and_cell[0])
except TypeError:
return False
#try:
# iconview.select_path(path_and_cell[0])
#except TypeError:
# return False
self.__popup_menu(event, 'img_popup')
return True
return False
def on_img_delete_activate(self, menu_item):
"""delete selected images (with thumbnails)"""
list_of_paths = self.view['images'].get_selected_items()
if not list_of_paths:
Dialogs.Inf("Delete images", "No images selected",
"You have to select at least one image to delete.")
return
if self.model.config.confd['delwarn']:
obj = Dialogs.Qst('Delete images', 'Delete selected images?',
'Selected images will be permanently removed from catalog.')
if not obj.run():
return
list_of_paths = self.view['images'].get_selected_items()
model = self.view['images'].get_model()
for path in list_of_paths:
iter = model.get_iter(path)
@@ -751,24 +781,39 @@ class MainController(Controller):
count = 0
for path in list_of_paths:
icon_iter = model.get_iter(path)
img_id = model.get_value(icon_iter, 0)
if len(list_of_paths) == 0:
# no picture was selected. default to save all of them
for image in model:
if self.model.save_image(image[0], filepath):
count += 1
else:
# some pictures was selected, save them
for path in list_of_paths:
icon_iter = model.get_iter(path)
img_id = model.get_value(icon_iter, 0)
if self.model.save_image(img_id, filepath):
count += 1
if len(list_of_paths) > 0:
if count > 0:
Dialogs.Inf("Save images",
"%d images was succsefully saved." % count,
"Images are placed in directory:\n%s." % filepath)
else:
Dialogs.Inf("Save images",
"No images was saved.",
"Images probably don't have real images - only" + \
" thumbnails.")
if count > 0:
Dialogs.Inf("Save images",
"%d images was succsefully saved." % count,
"Images are placed in directory:\n%s." % filepath)
else:
Dialogs.Inf("Save images",
"No images was saved.",
"Images probably don't have real images - only" + \
" thumbnails.")
return
def on_img_delete2_activate(self, menu_item):
"""remove images, but keep thumbnails"""
list_of_paths = self.view['images'].get_selected_items()
if not list_of_paths:
Dialogs.Inf("Delete images", "No images selected",
"You have to select at least one image to delete.")
return
if self.model.config.confd['delwarn']:
obj = Dialogs.Qst('Delete images', 'Delete selected images?',
'Selected images will be permanently removed from ' + \
@@ -776,7 +821,6 @@ class MainController(Controller):
if not obj.run():
return
list_of_paths = self.view['images'].get_selected_items()
model = self.view['images'].get_model()
for path in list_of_paths:
iter = model.get_iter(path)
@@ -822,31 +866,29 @@ class MainController(Controller):
if path not in list_of_paths:
treeview.get_selection().unselect_all()
treeview.get_selection().select_path(path)
# setup menu
ids = self.__get_tv_selection_ids(treeview)
menu_items = ['update1','rename1','delete2', 'statistics1']
for menu_item in menu_items:
self.view[menu_item].set_sensitive(not not ids)
# checkout, if we dealing with disc or directory
# if ancestor is 'root', then activate "update" menu item
treeiter = self.model.discs_tree.get_iter(path)
ancestor = self.model.discs_tree.get_value(treeiter, 3) == 1
self.view['update1'].set_sensitive(ancestor)
iter = self.model.discs_tree.get_iter(path)
if self.model.discs_tree.get_value(iter, 3) == 1:
# if ancestor is 'root', then activate "update" menu item
self.view['update1'].set_sensitive(True)
else:
self.view['update1'].set_sensitive(False)
self.__popup_menu(event)
def on_expand_all1_activate(self, menuitem):
def on_expand_all1_activate(self, menu_item):
self.view['discs'].expand_all()
return
def on_collapse_all1_activate(self, menuitem):
def on_collapse_all1_activate(self, menu_item):
self.view['discs'].collapse_all()
return
def on_files_button_press_event(self, tree, event):
try:
path, column, x, y = tree.get_path_at_pos(int(event.x),
int(event.y))
except TypeError:
tree.get_selection().unselect_all()
return False
if event.button == 3: # Right mouse button. Show context menu.
try:
selection = tree.get_selection()
@@ -855,6 +897,14 @@ class MainController(Controller):
list_of_paths = []
if len(list_of_paths) == 0:
# try to select item under cursor
try:
path, column, x, y = tree.get_path_at_pos(int(event.x),
int(event.y))
except TypeError:
# failed, do not show any popup and return
tree.get_selection().unselect_all()
return False
selection.select_path(path[0])
if len(list_of_paths) > 1:
@@ -867,8 +917,8 @@ class MainController(Controller):
self.view['edit2'].set_sensitive(True)
self.__popup_menu(event, 'files_popup')
return True
if event.button == 1:
return False
#if event.button == 1:
# return False
def on_files_cursor_changed(self, treeview):
"""Show details of selected file/directory"""
@@ -877,6 +927,17 @@ class MainController(Controller):
return
def on_files_key_release_event(self, treeview, event):
"""do something with pressed keys"""
if gtk.gdk.keyval_name(event.keyval) == 'Menu':
try:
selection = treeview.get_selection()
model, list_of_paths = selection.get_selected_rows()
if not list_of_paths:
return
except TypeError:
return
self.__popup_menu(event, 'files_popup')
if gtk.gdk.keyval_name(event.keyval) == 'BackSpace':
d_path, d_column = self.view['discs'].get_cursor()
if d_path and d_column:
@@ -903,9 +964,9 @@ class MainController(Controller):
iter = None
else:
iter = self.model.discs_tree.iter_next(iter)
if gtk.gdk.keyval_name(event.keyval) == 'Delete':
for file_id in self.__get_tv_selection_ids(treeview):
self.main.delete(file_id)
#if gtk.gdk.keyval_name(event.keyval) == 'Delete':
# for file_id in self.__get_tv_selection_ids(treeview):
# self.main.delete(file_id)
ids = self.__get_tv_selection_ids(self.view['files'])
@@ -951,6 +1012,9 @@ class MainController(Controller):
return
# NOTE: add tags / images
def on_delete_tag2_activate(self, menu_item):
pass
def on_delete_tag_activate(self, menu_item):
ids = self.__get_tv_selection_ids(self.view['files'])
if not ids:
@@ -996,8 +1060,8 @@ class MainController(Controller):
def on_add_image1_activate(self, menu_item):
dialog = Dialogs.LoadImageFile(True)
toggle = gtk.CheckButton("Don't copy images. \
Generate only thumbnails.")
toggle = gtk.CheckButton("Don't copy images. " + \
"Generate only thumbnails.")
toggle.show()
dialog.dialog.set_extra_widget(toggle)
@@ -1039,10 +1103,19 @@ class MainController(Controller):
if self.model.get_source(path) == self.model.CD:
self.on_add_cd_activate(menu_item, label, fid)
elif self.model.get_source(path) == self.model.DR:
self.on_add_directory1_activate(menu_item, filepath, label, fid)
self.on_add_directory_activate(menu_item, filepath, label, fid)
return
def on_delete1_activate(self, menu_item):
"""Main menu delete dispatcher"""
if self.view['files'].is_focus():
self.on_delete3_activate(menu_item)
if self.view['discs'].is_focus():
self.on_delete2_activate(menu_item)
if self.view['images'].is_focus():
self.on_img_delete_activate(menu_item)
def on_delete2_activate(self, menu_item):
try:
selection = self.view['discs'].get_selection()
@@ -1050,6 +1123,12 @@ class MainController(Controller):
except:
return
if not selected_iter:
Dialogs.Inf("Delete disc", "No disc selected",
"You have to select disc first before you " +\
"can delete it")
return
if self.model.config.confd['delwarn']:
name = model.get_value(selected_iter, 1)
obj = Dialogs.Qst('Delete %s' % name, 'Delete %s?' % name,
@@ -1094,10 +1173,15 @@ class MainController(Controller):
except TypeError:
return
if not list_of_paths:
Dialogs.Inf("Delete files", "No files selected",
"You have to select at least one file to delete.")
return
if self.model.config.confd['delwarn']:
obj = Dialogs.Qst("Delete elements", "Delete items?",
"Items will be permanently \
removed from catalog.")
obj = Dialogs.Qst("Delete files", "Delete files?",
"Selected files and directories will be" + \
" permanently\nremoved from catalog.")
if not obj.run():
return
@@ -1156,6 +1240,14 @@ class MainController(Controller):
self.__set_title(filepath=self.model.filename, modified=True)
return
def on_edit1_activate(self, menu_item):
"""Make sufficient menu items sensitive in right cases"""
# TODO: consolidate popup-menus with edit menu
if self.view['tag_cloud_textview'].is_focus():
self.view['delete1'].set_sensitive(False)
else:
self.view['delete1'].set_sensitive(True)
def on_debugbtn_clicked(self, widget):
"""Debug. To remove in stable version, including button in GUI"""
if __debug__:
@@ -1168,6 +1260,9 @@ 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
print "files have focus", self.view['files'].is_focus()
print "discs have focus", self.view['discs'].is_focus()
print "images have focus", self.view['images'].is_focus()
#####################
@@ -1360,8 +1455,9 @@ class MainController(Controller):
def __popup_menu(self, event, menu='discs_popup'):
"""Popoup desired menu"""
self.view[menu].popup(None, None, None, event.button,
event.time)
self.view[menu].popup(None, None, None, 0, 0)
#self.view[menu].popup(None, None, None, event.button,
# event.time)
self.view[menu].show_all()
return
@@ -1490,7 +1586,8 @@ class MainController(Controller):
tag.set_property('size-points', cloud['size'])
#tag.connect('event', self.on_tag_cloud_click, tag)
buff.insert_with_tags(buff_iter,
cloud['name'] + "(%d)" % cloud['count'],
cloud['name'] + "(%d)" % \
cloud['count'],
tag)
except:
if __debug__:

View File

@@ -222,6 +222,16 @@ class MainModel(ModelMT):
sql = """DELETE FROM tags_files WHERE file_id = ?
AND tag_id IN """ + sql
self.db_cursor.execute(sql, (int(file_id), ))
self.db_connection.commit()
for tag_id in tag_id_list:
sql = """SELECT count(*) FROM tags_files WHERE tag_id=?"""
self.db_cursor.execute(sql, (int(tag_id),))
res = self.db_cursor.fetchone()
if res[0] == 0:
sql = """DELETE FROM tags WHERE id=?"""
self.db_cursor.execute(sql, (int(tag_id),))
self.db_connection.commit()
def get_tags(self):
"""fill tags dict with values from db"""
@@ -793,7 +803,13 @@ class MainModel(ModelMT):
sql = """DELETE FROM tags_files WHERE file_id = ?"""
db_cursor.executemany(sql, generator())
arg = str(tuple(fids))
if __debug__:
print "m_main.py: delete(): deleting:", fids
if len(fids) == 1:
arg = "(%d)" % fids[0]
else:
arg = str(tuple(fids))
# remove thumbnails
sql = """SELECT filename FROM thumbnails WHERE file_id IN %s""" % arg
@@ -946,6 +962,7 @@ class MainModel(ModelMT):
"""update note and description"""
sql = """UPDATE files SET description=?, note=? WHERE id=?"""
self.db_cursor.execute(sql, (desc, note, file_id))
self.db_connection.commit()
return
# private class functions

View File

@@ -513,13 +513,23 @@ class TagsRemoveDialog(object):
gladexml = gtk.glade.XML(self.gladefile, "tagRemove")
dialog = gladexml.get_widget("tagRemove")
# fill model with dict
# declare model
model = gtk.ListStore(gobject.TYPE_INT,
gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
for tag in self.tag_dict:
# sort dict
values = self.tag_dict.values()
values.sort()
keys = []
for val in values:
for d_key, d_value in self.tag_dict.items():
if d_value == val:
keys.append(d_key)
# fill model with dict
for count in range(len(keys)):
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, 0, keys[count])
model.set_value(myiter, 1, values[count])
model.set_value(myiter, 2, None)
def toggle(cell, path, model):

View File

@@ -39,7 +39,6 @@ class MainView(View):
self['separatormenuitem4'].hide()
self['list1'].hide()
self['thumbnails1'].hide()
#self['tag_cloud_textview'].drag_dest_set(0, [], 0)
return
pass # end of class