Skip to content

Commit

Permalink
Fix lots of problems related to using multiple categories at once.
Browse files Browse the repository at this point in the history
  • Loading branch information
akkana committed Mar 3, 2017
1 parent 42c051b commit 0b7047e
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 38 deletions.
5 changes: 4 additions & 1 deletion metapho/gtkpho/ImageViewer.py
Expand Up @@ -324,7 +324,7 @@ def key_press_event(self, widget, event):

return False

if __name__ == '__main__':
def main():
import sys

if len(sys.argv) <= 1:
Expand All @@ -333,3 +333,6 @@ def key_press_event(self, widget, event):
ivw = ImageViewerWindow(*sys.argv[1:], quit=gtk.main_quit)

gtk.main()

if __name__ == '__main__':
main()
59 changes: 41 additions & 18 deletions metapho/gtkpho/gtkpho.py
Expand Up @@ -76,10 +76,10 @@ def __init__(self, parentwin):

self.show()

def change_tag(self, tagno, newstr):
def change_tag(self, entryno, newstr):
'''Update a tag: called on focus_out from one of the text entries'''
if tagno < len(self.categories[self.current_category]):
self.tag_list[self.categories[self.current_category][tagno]] \
if entryno < len(self.categories[self.current_category]):
self.tag_list[self.categories[self.current_category][entryno]] \
= newstr
else:
self.add_tag(newstr, self.cur_img)
Expand Down Expand Up @@ -159,20 +159,29 @@ def focus_out(self, entry, event, entryno):
self.sync_entry(entry, entryno)
return True

def toggled(self, button, tagno):
def toggled(self, button, btnno):
# We'll get a recursion loop if we don't block events here --
# adding and removing tags update the GUI state, which
# changes the toggle button state, which calls toggled() again.

if self.ignore_events:
return

tagno = self.categories[self.current_category][btnno] or None

# get_active() is the state *after* the button has been pressed.
if button.get_active():
# Was off, now on, so add the tag.
self.add_tag(tagno, self.cur_img)
# But not if the tag doesn't exist yet.
if tagno:
self.add_tag(tagno, self.cur_img)
self.highlight_tag(btnno, True)
else:
# It's already on, so toggle it off.
self.remove_tag(tagno, self.cur_img)
# But not if the tag doesn't exist yet.
if tagno:
self.remove_tag(tagno, self.cur_img)
self.highlight_tag(btnno, False)

# Often when the user clicks on a button it's because
# focus was in a text field. We definitely don't want it
Expand Down Expand Up @@ -208,6 +217,9 @@ def display_tags(self):
self.display_tags_for_category(self.current_category)

def display_tags_for_category(self, catname):
'''Display the tag names in a new category,
and reset the mapping.
'''
# Is this a new category, not in the list?
if catname not in self.categories.keys():
for i in range(len(self.entries)):
Expand Down Expand Up @@ -243,6 +255,7 @@ def display_tags_for_category(self, catname):
def highlight_categories(self):
'''Highlight the button for any category that includes tags
set in this image.
XXX This is broken.
'''
self.catviewer.unhighlight_all()
for tag in self.cur_img.tags:
Expand Down Expand Up @@ -425,23 +438,34 @@ def remove_tag(self, tag, img):

metapho.Tagger.remove_tag(self, tag, img)

self.highlight_tag(tag, False)

def toggle_tag(self, tagno, img):
def toggle_tag(self, btnno, img):
'''Toggle tag number tagno for the given img.'''
metapho.Tagger.toggle_tag(self, tagno, img)
if tagno < len(self.categories[self.current_category]):
self.highlight_tag(tagno, not self.buttons[tagno].get_active())
if btnno < len(self.categories[self.current_category]):
tagno = self.categories[self.current_category][btnno]

if tagno >= len(self.tag_list):
# I think this shouldn't happen given the btnno check,
# so print a warning if it does.
print "Eek: tagno is", tagno, "len tag_list is", \
len(self.tag_list)
return

metapho.Tagger.toggle_tag(self, tagno, img)

if btnno < len(self.buttons) and \
btnno <= len(self.categories[self.current_category]):
# Note <= comparison where previously we looked for <.
# We'll highlight the tag if it's an existing tag or if
# it's the first new tag, but not arbitrary higher new tags.
self.highlight_tag(btnno, not self.buttons[btnno].get_active())

def toggle_tag_by_letter(self, tagchar, img):
'''Toggle the tag corresponding to the letter typed by the user'''
if tagchar.islower():
tagno = ord(tagchar) - ord('a')
btnno = ord(tagchar) - ord('a')
else:
tagno = ord(tagchar) - ord('A') + self.num_rows
if tagno >= len(self.tag_list):
return
self.toggle_tag(tagno, img)
btnno = ord(tagchar) - ord('A') + self.num_rows
self.toggle_tag(btnno, img)

def focus_next_entry(self):
'''Set focus to the next available entry.
Expand Down Expand Up @@ -533,7 +557,6 @@ def add_category(self, newcat):
def button_cb(self, w, which):
if self.updating:
return
# print "Clicked on", which
self.set_active(which)
if self.change_cat_cb:
self.change_cat_cb(self.categories[which])
Expand Down
12 changes: 1 addition & 11 deletions metapho/gtkpho/main.py
Expand Up @@ -67,8 +67,6 @@ def quit(self, widget=None, data=None):
if type(self.win.get_focus()) is gtk.Entry:
self.tagger.check_entry_tag(self.win.get_focus())

print "==========="
print self.tagger
self.tagger.write_tag_file()

# Can't call main_quit here: RuntimeError: called outside of a mainloop
Expand All @@ -85,11 +83,7 @@ def read_all_tags(self):
dirs.add(dirname)
self.tagger.read_tags(dirname)

nonexistent = metapho.Image.find_nonexistent_files()
if nonexistent:
print "Warning: these files don't exist:"
for f in nonexistent:
print " ", f
metapho.Image.clean_up_nonexistent_files(self.tagger.commondir)

self.tagger.display_tags()

Expand All @@ -116,7 +110,6 @@ def next_image(self):
oldtags = metapho.Image.g_image_list[self.imgno].tags
except:
print "Couldn't load image #", self.imgno
print "Tags:", metapho.Image.g_image_list[self.imgno].tags
pass

while self.imgno < len(metapho.Image.g_image_list)-1 and not loaded:
Expand All @@ -127,7 +120,6 @@ def next_image(self):
if loaded:
self.viewer.show_image()
else:
print "next_image: couldn't show", img.filename
img.displayed = False
# Should arguably delete it from the list
# so we don't continue to save tags for a
Expand All @@ -149,7 +141,6 @@ def next_image(self):
self.tagger.set_image(metapho.Image.g_image_list[self.imgno])

else : # couldn't load anything in the list
print "No more images"
dialog = gtk.MessageDialog(self.win,
gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_QUESTION,
Expand All @@ -170,7 +161,6 @@ def prev_image(self):
loaded = self.viewer.load_image(img)
self.viewer.show_image()
if not loaded:
print "prev_image: couldn't show", img.filename
img.displayed = False
# See comment in next_image
#del(metapho.Image.g_image_list[self.imgno])
Expand Down
62 changes: 54 additions & 8 deletions metapho/metapho.py
Expand Up @@ -40,13 +40,13 @@ def __init__(self, filename, displayed=True):
self.rot = None

def __repr__(self):
str = "Image %s" % self.filename
str = "Image '%s'" % self.filename

if self.rot:
str += " (rotation %s)" % self.rot

if self.tags:
str += " Tags: " + self.tags.__repr__()
str += ": Tags: " + self.tags.__repr__()

# str += '\n'

Expand All @@ -61,18 +61,55 @@ def delete(self):
os.unlink(self.filename)
Image.g_image_list.remove(self)

@classmethod
def image_index(cls, filename):
'''Find a name in the global image list. Return index, or None.'''
for i, img in enumerate(cls.g_image_list):
if img.filename == filename:
return i
return None

@classmethod
def find_nonexistent_files(cls):
'''Returns a list of images in the imagelist that don't exist on disk.
'''
not_on_disk = set()
for im in Image.g_image_list:
for im in cls.g_image_list:
if not os.path.exists(im.filename):
not_on_disk.add(im.filename)
not_on_disk = list(not_on_disk)
not_on_disk.sort()
return not_on_disk

@classmethod
def clean_up_nonexistent_files(cls, topdir):
'''For any file that was referenced in a tag file but doesn't
exist on disk, see if perhaps it's been moved to a different
subdirectory under topdir. If so, adjust file path appropriately.
'''
nefbases = set()
nefdict = {}
for f in cls.find_nonexistent_files():
fbase = os.path.basename(f)
nefbases.add(fbase)
if fbase in nefdict:
print "Warning: multiple files named", fbase
else:
nefdict[fbase] = f

for root, dirs, files in os.walk(topdir):
root = os.path.normpath(root)
for f in files:
if f in nefbases:
try:
i = cls.image_index(nefdict[f])
cls.g_image_list[i].filename = os.path.join(root, f)
except ValueError:
print "Eek!", nefdict[f], \
"has vanished from the global image list"

nefbases.remove(f)

import shlex

class Tagger(object):
Expand All @@ -82,17 +119,20 @@ class Tagger(object):
def __init__(self):
'''tagger: an object to manage metapho image tags'''

# The actual per-image lists of tags live in the Image class.
# Each image has img.tags, which is a list of tag indices.

# The category list is a list of lists:
# [ [ "First category", 3, 5, 11 ] ]
# means category 0 has the name "First category" and includes
# tags 3, 5 and 11 from the tag_list.
self.categories = collections.OrderedDict()

# The tag list is just a list of all tags we know about.
# The tag list is just a list of all tags we know about (strings).
# A tag may be in several categories.
self.tag_list = []

# Files from which we've read tags
# Files from which we've read tags (named Tags or Keywords)
self.tagfiles = []
# the directory common to them, where we'll try to store tags
self.commondir = None
Expand All @@ -102,7 +142,7 @@ def __init__(self):
self.changed = False

# What category are we currently processing? Default is Tags.
self.current_category = "Tags"
self.current_category = None

def __repr__(self):
'''Returns a string summarizing all known images and tags,
Expand All @@ -111,6 +151,7 @@ def __repr__(self):
outstr = ''
for cat in self.categories:
outstr += '\ncategory ' + cat + '\n\n'
outstr += str(self.categories[cat]) + '\n'

for tagno in self.categories[cat]:
tagstr = self.tag_list[tagno]
Expand Down Expand Up @@ -180,14 +221,17 @@ def read_tags(self, dirname):
tag squirrels: img_001.jpg img_030.jpg
tag horses: img_042.jpg
tag penguins: img 008.jpg
category Places
tag New Mexico: img_020.jpg img_042.jpg
tag Bruny Island: img 008.jpg
Extra whitespace is fine; category lines are optional;
"tag " at beginning of line is optional.
'''
self.current_category = "Tags"
self.categories[self.current_category] = []
# The default category name is Tags.
if not self.current_category:
self.current_category = "Tags"
self.categories[self.current_category] = []

try:
pathname = os.path.join(dirname, "Tags")
Expand All @@ -203,6 +247,7 @@ def read_tags(self, dirname):
print "No Tags or Keywords file in", dirname
return

print "Reading tags from", pathname
for line in fp:
# The one line type that doesn't need a colon is a cat name.
if line.startswith('category '):
Expand Down Expand Up @@ -350,3 +395,4 @@ def toggle_tag(self, tagno, img):
def match_tag(self, pattern):
'''Return a list of tags matching the pattern.'''
return None

0 comments on commit 0b7047e

Please sign in to comment.