#!/usr/bin/env python # -*- python -*- # Time-stamp: <2004-05-24 17:19:53 rozen> ############################################################################## # # Python Photo Album - A program to manage digital photographs. # # Copyright (C) 2004 Stewart Allen # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ############################################################################## from Tkinter import * import Tix, sys import Image, ImageTk import MySQLdb import copy import cPickle import os import os.path import random import shutil import string import sys import tkMessageBox import tkFileDialog # Support modules in current directory import exif import sparse # For pretty printing of debugging statements. import pprint pp = pprint.PrettyPrinter() WIDTH = 200 HEIGHT = 200 HOME = os.getenv('HOME') # my home directory ORIG_LOCATION = os.path.join(HOME, 'dp') # Where I store images from the camera. ARCHIVE = os.path.join(HOME, 'pictures') # Location of my archives. exif_string = '' last_attribues = [] # Following code is part of the exif package. # Photo Name: %(PhotoName)s # Camera: %(Make)s %(Model)s TEXT_FORMAT = """ Camera: %(Model)s Date: %(DateTime)s Exposure Time: %(ExposureTime)s sec Aperture: f/%(FNumber)s Exposure Program: %(ExposureProgram)s Exposure Exposure Bias: %(ExposureBiasValue)s Exposure Metering Mode: %(MeteringMode)s Flash: %(Flash)s FocalLength: %(FocalLength)s mm """ # For converting month to quarter. quarter = {'01':'q1', '02':'q1', '03':'q1', '04':'q2', '05':'q2', '06':'2', '07':'q3', '08':'q3', '09':'q3', '10':'q4', '11':'q4', '12':'q4'} display_dir_name = '' c = None d = None e = None f = None p = None def main(): '''I always start with main because I use gdb and my gdb init file knows about main. ''' vp_start_gui() def vp_start_gui(): '''Starting point for the program. It opens the main top level window.''' global w global root root = Tix.Tk() # Here is example of how to read a file into the tk option data base. # I do this to get a bigger font. root.option_readfile("/home/rozen/python/TkoptionDB") root.title('New_Toplevel_1') root.geometry('650x500+144+29') w = New_Toplevel_1 (root) init() root.mainloop() def init(): ''' Connect to the local mysql server. Quit if there is a problem. ''' global db global db_cursor try: db=MySQLdb.connect(db="pictures") db_cursor = db.cursor() except: tkMessageBox.showerror(title="MySql Error", message="Can't connect to local MySQL server.") quit() def quit(): sys.exit() def process_criteria_text(): global c global criteria_text criteria_text = criteria_text.lower() if len(criteria_text.strip()) == 0: return if w.caller == 'add_to_archive': w.add_to_archive_II() elif w.caller == 'view_archive': w.view_archive_II() else: print "error determining exit from process_criteria_text" def build_attribute_list(picts): global att att = [] for i in range(len(picts)): att.append(picts[i][0]) class New_Toplevel_1: def __init__(self, master=None): self.win = Tix.ScrolledWindow(master, scrollbar='auto') # I wanted the Scrolled window to fill the toplevel window # so I use the following pack statement instead of a place. self.win.pack(side=LEFT, expand=YES, fill=BOTH) self.frame = self.win.window # Accesses internal frame of ScrolledWindow # Here I build a menu in the toplevel window. self.m23 = Menu(master) master['menu'] = self.m23 # Since I want a simple menu I do not use drop down items except for # the help menu. # Help menu tobe added when the help features are completed. ## Example of putting everything in a drop down menu. ## self.m23_men24 = Menu(master,tearoff=1) ## self.m23.add_cascade(label="Action",underline="0",menu=self.m23_men24) ## self.m23_men24.add_command(label="View Archive",underline="0", ## command=self.view_archive) ## self.m23_men24.add_command(label="Display Directory",underline="0", ## command=self.select_and_display) ## self.m23_men24.add_command(label="Show all Attributes",underline="0", ## command=self.display_attributes) ## self.m23_men24.add_command(label="Exit",underline="0",command=quit) self.m23.add_command(label="Search Archive", command=self.view_archive) self.m23.add_command(label="Incoming Directory", command=self.select_and_display_incoming) self.m23.add_command(label="Archive Directory", command=self.select_and_display_archive) self.m23.add_command(label="Show All Attributes", command=self.display_attributes) # Quit self.m23.add_command(label="Quit", command=quit) # Help Memu ## self.m23_men28 = Menu(master,tearoff=1) ## self.m23.add_cascade(label="Help",underline="0",menu=self.m23_men28) ## self.m23_men28.add_command(label="Contents",underline="0", ## command=lambda : greeting("Contents")) ## self.m23_men28.add_separator() ## self.m23_men28.add_command(label="About",underline="0", ## command=lambda : greeting("About")) # End of Menu self.num = 0 self.last_attribues = [] self.export_dir = None def popup(self, event): ''' Create and post dynamic popup menu.''' # Creation. self.menu = Menu(root, tearoff=0) self.menu.add_command(label="Display using XV", command=self.call_xv) if self.caller != 'view_archive': self.menu.add_command(label="Add to Archive", command=self.add_to_archive) self.menu.add_command(label="Display Exif", command=self.show_exif) if self.caller == 'view_archive': # Depending on caller we get # additional menu iems. self.menu.add_command(label="Modify Attributes", command=self.modify_attributes) self.menu.add_command(label="Duplicate", command=self.duplicate_image) self.menu.add_command(label="Export", command=self.export) self.menu.add_command(label="Modify using Gimp", command=self.call_gimp) self.menu.add_command(label="Use Image Editor", command=self.call_image_ed) self.menu.add_command(label="Delete", command=self.delete_image) self.menu.add_command(label="Quit", command=quit) self.menu.add_command(label="Null") # It just closes the popup # Post the popup memu # event.widget.winfo_id() is the widget underneath the mouse at # the button press. Self.id is a dictionary maintaing an index for # each widget. self.selected = self.id[event.widget.winfo_id()] self.image_selected = self.images[self.selected] self.menu.post(event.x_root, event.y_root) # Actual post at mouse. def export(self): '''Export the selected photo to a directory chosen by the user.''' if self.export_dir: initialdir = self.export_dir else: initialdir = os.path.join(ORIG_LOCATION, 'export') filetypes = [('jpg', '*.jpg'), ('All files', '*')] dest_filename = \ tkFileDialog.asksaveasfilename(filetypes=filetypes, initialdir=initialdir) if dest_filename == '': tkMessageBox.showerror(title="File select error", message="No filename given.") return self.export_dir = os.path.dirname(dest_filename) dest_file = open(dest_filename, 'w') shutil.copy(self.image_selected,dest_filename) dest_file.close() def select_and_display_incoming(self): global d if d: d.destroy() d = None self.create_directory_display('in') #self.display_dir() self.caller = '' def select_and_display_archive(self): global d if d: d.destroy() d = None print 'select_and_display_archive - d.distroy' self.create_directory_display('arc') #self.display_dir() self.caller = '' def get_picture_critera(self): ''' When a thumnail is selected, this routine will return a list of all the attributes of that picture.''' global att sql = '''SELECT DISTINCT phrase FROM attribute WHERE attribute.file="%s" ORDER BY phrase''' % self.image_selected db_cursor.execute(sql) picts = db_cursor.fetchall() build_attribute_list(picts) def modify_attributes(self): '''Modifies the attributes for a window. It obtains the attributes of the selected photograph and opens the attribute window which can lead to the changing of the attributes associated with the selected photograph.''' self.get_picture_critera() self.create_attribute_window('mod') def duplicate_image(self): ''' Copy the picture including the thumbnail and the exif file. Also copy appropriate data base entries.''' source_file = self.image_selected new_name = self.get_new_name(self.image_selected) # Get exif data. It is a dictionary. self.tags = self.get_exif_tags(source_file) # Select data for inclusion in db. # Want date, time, exposure, aperture, flash, camera self.date = self.tags['DateTime'] self.exposure = self.tags['ExposureTime'] self.aperture = self.tags['FNumber'] self.flash = self.tags['Flash'] self.camera = self.tags['Model'] self.get_picture_critera() # Insert data base info. f_name = new_name + '.jpg' # (file, date, exposure, aperture, flash, camera) sql = '''INSERT picture (file, date, exposure, aperture, flash, camera) VALUES ("%s", "%s", "%s", "%s", "%s", "%s")''' % \ (f_name, self.date, self.exposure, self.aperture, self.flash, self.camera) db_cursor.execute(sql) attributes = att for i in range(len(attributes)): if attributes[i] != '': sql = 'INSERT attribute (file, phrase) VALUES ("%s", "%s")' % \ (f_name, attributes[i].strip()) db_cursor.execute(sql) # Actually copy the photo self.copy_photo(source_file, new_name) self.display_dir() def get_new_name(self, image): '''Generate an unique name for the photograph in the current directory. Since it goes in the same directory as the current photo, I will just get the root of the base name and add a random number to it. It returns a name without without an extension.''' (dir_name, base_name) = os.path.split(image) (root, ext) = os.path.splitext(base_name) repeat = 1 while repeat: # Be sure that the name is not in use. candidate = '%s-%s' % (root, random.randint(1,9999)) print 'get_new_name: candidate =', candidate name = os.path.join(dir_name, candidate) if not os.path.exists(name): repeat = 0 # name goes not have extension. return name def display_attributes(self): '''Creates an attribute window which will display all of the attributes found in the data base.''' self.create_attribute_window('all') def create_attribute_window(self, action): '''This is code borrowed from the module created by PAGE. The added parameter is passed to allow attribute window to be used for different purposes, i.e., its configuration is dynamic.''' global e_win global e # Test for existance of top level - NEEDS WORK. e = Toplevel(root) e.geometry('207x470+0+200') e.title('Attributes') self.e_win = Attributes(master=e, action=action) def create_Progress_Bar(self): global p global p_win if p: # So we have only one instance of window. return p = Toplevel (root) p.title('Progress Bar') p.geometry('211x40+411+714') p_win = Progress_Bar (p) def display_image_list(self, image_list): '''Function which will display a list of image files. The main feature is to create a button for each photograph and place on the button a thumbnail image of the photograph. The button is located in the frame of the scrolled window using the grid method. Under each button is placed a label containg the date the photograph was taken.''' # Many of the varibles used in this function need to presist after # exiting the function, hence, they are turned into attributes by # prepending 'self'. 'row' and 'i' are local variables. self.images = image_list # Initializations self.clean_image_field() # Be sure to remove any photos being displayed self.num = len(self.images) self.image = {} self.original = {} self.pict = {} self.label = {} self.id = {} row = 0 self.create_Progress_Bar() for i in range(self.num): # Loop over image list planting a button with an image for each # member of the image list. The thumbnail is put into self.image[i]. thumbnail = self.get_thumbnail(self.images[i], 1) row, col = divmod(i,3) # Put thumbnail in even rows. # Think of picking up the next four(?) lines to update a display - NEEDS WORK # changed by xv or gimp. self.original[i] = ImageTk.PhotoImage(thumbnail) self.pict[i] = Button(self.frame, image=self.original[i],) self.pict[i].grid(row=2*row, column=col, sticky=W+E+S+N) self.id[self.pict[i].winfo_id()] = i # ---------------- self.pict[i].bind('', self.name_pict) self.pict[i].bind('', self.popup) # Put label contianing date in odd rows provide that the # tag info is available. tags = self.get_exif_tags(self.images[i]) # Two try's because there are two forms (at least) of exif data! if self.tags.has_key('DateTime'): date =self.date_fr(self.tags['DateTime']) elif self.tags.has_key('DateTimeOriginal'): date =self.date_fr(self.tags['DateTimeOriginal']) else: date = '' if date != '': self.label[i] = Label(self.frame, text=date) self.label[i].grid(row=2*row+1, column=col, sticky=W+E+S+N) p_win.update_bar(str(float(i)/self.num)) p_win.close() def clean_image_field(self): '''Remove any existing picture being displayed.''' for i in range(self.num): try: self.pict[i].destroy() self.label[i].destroy() except: pass def display_dir(self): '''Open the GUI and allows user to select the directory and then builds a list of images from that directory. It then calls display_image_list to put them up on the screen.''' self.clean_image_field() if display_dir_name == '': return if not os.path.exists(display_dir_name): return # Build the image list self.chosen_dir = display_dir_name file_list = os.listdir(self.chosen_dir) image_list = [] for i in file_list: if i[-4:] == '.jpg': image_list.append(i) if len(image_list) == 0: tkMessageBox.showerror(title="MySql Error", message="Directory '%s' empty!" % display_dir_name) self.chosen_dir = '' # Probably should remove the directory??? return # Change the title of the main window to show directory name being # displayed. root.title('Image Album ' + self.chosen_dir) for i in range(len(image_list)): image_list[i] = os.path.join(self.chosen_dir, image_list[i]) leave = 0 while 1: # I want to repeat this loop as long as any member of # self.image_list is a directory. In other words, the process # removes all entries which are directories. # NEEDS WORK - revisit to handle case where there may be an image # subdirectory. for i in range(len(image_list)): leave = 0 if os.path.isdir(image_list[i]): del image_list[i] leave = 1 break if image_list[i][-4:] != '.jpg': del image_list[i] leave = 1 break if leave: continue break self.image_list = image_list self.display_image_list(image_list) def add_to_archive(self): '''This function adds a photograph from the incoming directory to the data base. It gets information about the photograph and opens the attribute window in a state to receive attributes for the db.''' global last_attributes image = self.image_selected # Get exif data self.tags = self.get_exif_tags(image) #print 'add_to_archive: self.tags =', self.tags # Select data for inclusion in db. # Want date, time, exposure, aperture, flash, camera self.date = self.tags.get('DateTime', 'unknown') self.exposure = self.tags.get('ExposureTime', 'unknown') self.aperture = self.tags.get('FNumber', 'unknown') self.flash = self.tags.get('Flash', 'unknown') self.camera = self.tags.get('Model', 'unknown') # Determine destination name of photo. Note it does not have the # extension self.base_name = self.get_archive_name(self.date) # Launch GUI to get attributes. self.caller = 'add_to_archive' self.create_attribute_window('add') def add_to_archive_II(self): '''We come back here after the criteia window has been closed. Build and execute sql statements for inserting data into db. The criteria will be in the string 'criteria_text'.''' if self.date == 'unknown': date = 'unknown' else: date_pieces = self.date.split() #self.date from preceeding function. date = date_pieces[0] date = date.replace(':', '-') time = date_pieces[1] date = date + " " + time # DATATIME format for use in the data base. attributes = criteria_text.split('\n') f_name = self.base_name + '.jpg' sql = '''INSERT picture (file, date, exposure, aperture, flash, camera) VALUES ("%s", "%s", "%s", "%s", "%s", "%s")''' % \ (f_name, date, self.exposure, self.aperture, self.flash, self.camera) db_cursor.execute(sql) for i in range(len(attributes)): if attributes[i] != '': sql = 'INSERT attribute (file, phrase) VALUES ("%s", "%s")' % \ (f_name, attributes[i].strip()) db_cursor.execute(sql) # Move photo, thumbnail, exif. self.move_photo(self.image_selected, self.base_name) # Erase original photo, thumbnail, exif. image = self.image_selected self.delete_image() #self.display_dir() # Redisplay the source directory. # Destroy button and label self.destroy_button(self.selected) self.destroy_label(self.selected) self.last_attributes = copy.copy(attributes) # Save attributes. def move_photo(self, source, dest): '''Does the copying of the photograph and associated files using the move command.''' (dir1, base1) = os.path.split(source) (r1, ext1) = os.path.splitext(base1) (dir2, base2) = os.path.split(dest) r2 = base2 # because base2 is just the root part of the filename. shutil.move(source, os.path.join(dir2, r2+'.jpg')) # photo shutil.move(os.path.join(dir1,'.thumb',base1), os.path.join(dir2,'.thumb',r2+'.jpg')) # thumbnail shutil.move(os.path.join(dir1,'.exif', r1+'.exif'), os.path.join(dir2,'.exif', r2+'.exif')) #exif file def copy_photo(self, source, dest): '''Does the copying of the photograph and associated files using the copy command.''' (dir1, base1) = os.path.split(source) (r1, ext1) = os.path.splitext(base1) (dir2, base2) = os.path.split(dest) r2 = base2 # because base2 is just the root part of the filename. shutil.copyfile(source, os.path.join(dir2, r2+'.jpg')) # photo shutil.copyfile(os.path.join(dir1,'.thumb',base1), os.path.join(dir2,'.thumb',r2+'.jpg')) # thumbnail shutil.copyfile(os.path.join(dir1,'.exif', r1+'.exif'), os.path.join(dir2,'.exif', r2+'.exif')) #exif file def get_archive_name(self, date): '''Need to get a unique new name in a directory based on the date. Top directory for the archived pictures is ARCHIVE, the first subdirectory is yr-quarter. The name does not include an extension. This function is very similar to 'get_new_name'. This function creates the archive directory if it does not exist.''' date = date if date == 'unknown': print 'a' year = 'unknown' month = 'unknown' q = 'unknown' else: print 'b' date_pieces = date.split(':', 3) year = date_pieces[0] month = date_pieces[1] q = quarter[month] dir_name = os.path.join(ARCHIVE, year, q) # Create the directory for the photo, if it doesn't exist. if not os.path.exists(dir_name): os.makedirs(os.path.join(dir_name, '.thumb')) os.makedirs(os.path.join(dir_name, '.exif')) repeat = 1 while repeat: # Be sure that the name is not in use. candidate = 'p-%s-%04d' % (month, random.randint(1,9999)) name = os.path.join(dir_name, candidate) if not os.path.exists(name): repeat = 0 # name goes not have extension. return name def get_thumbnail(self, image, look_for_existing): '''Function returns a thumbnail for the photograph selected. An optimization is to store the thumbnail in a subdirectory so that it need not be calculated anew each time.''' # Find if we already have a thumbnail lying around. image_name = os.path.basename(image) dirname, basename = os.path.split(image) thumb = os.path.join(dirname, '.thumb', image_name) if look_for_existing: if os.path.exists(thumb): thumbnail = Image.open(thumb) return thumbnail # If we get here we build and store the thumbnail image. try: self.masterImg = Image.open(image) except: print 'Exception opening image: %s' % (image_name) sys.exit() self.masterImg.thumbnail((WIDTH,HEIGHT)) thumbnail = self.masterImg.copy() thumb_path = os.path.join(dirname, '.thumb') if not os.path.exists(thumb_path): os.mkdir(thumb_path) self.masterImg.save(thumb) return thumbnail def name_pict(self, event): ''' Call xv in response to Button-1.''' self.selected = self.id[event.widget.winfo_id()] self.image_selected = self.images[self.selected] self.call_xv() def call_xv(self): '''Actually calls 'xv'. Since xv can be used to modify the photograph, I must set up the current directory to the one containing the photograph rather than the one containing this program.''' t_orig = os.path.getmtime(self.image_selected) cmd = "xv %s" % self.image_selected # A little trickiness so 'xv' will try storing in the right directory. # I am changing the current working directory to that containing the # image before calling 'xv' and then restoring it after 'xv' returns. cwd = os.getcwd() os.chdir(os.path.dirname(self.image_selected)) os.system(cmd) os.chdir(cwd) t_now = os.path.getmtime(self.image_selected) if t_now - t_orig: self.delete_thumbnail() # Renew thumbnail. self.get_thumbnail(self.image_selected, 0) self.display_image_list(self.image_list) def call_gimp(self): '''Calls gimp passing the photograph.''' t_orig = os.path.getmtime(self.image_selected) cmd = "gimp %s" % self.image_selected os.system(cmd) t_now = os.path.getmtime(self.image_selected) if t_now - t_orig: self.delete_thumbnail() # Renew thumbnail. self.get_thumbnail(self.image_selected, 0) self.display_image_list(self.image_list) def call_image_ed(self): cmd ="python imageEditor.py %s" % self.image_selected os.system(cmd) def exif_file_name(self, full_name): ''' Takes the full path name and then separates the dirname form the filename, then separates the extension from the filename and puts it all back together with a different extension - '.exif' ''' dirname, image_name = os.path.split(full_name) (name, ext) = os.path.splitext(image_name) exif_file = os.path.join(dirname, '.exif', name + '.exif') return exif_file def get_exif_tags(self, image): '''Sets self.tags a dictionary containing the exif information. An attempt is made to see if the data has already been processed and if so use it.''' # Find if we already have a exif lying around. dirname, base = os.path.split(image) (name, ext) = os.path.splitext(base) ex = self.exif_file_name(image) if os.path.exists(ex): try: in_file = open(ex) self.tags = cPickle.load(in_file) in_file.close() return self.tags except: pass self.tags = exif.parse(image, 0, 0) exif_path = os.path.join(dirname, '.exif') if not os.path.exists(exif_path): os.mkdir(exif_path) out_name = os.path.join(exif_path, name + '.exif') out_file = open(out_name, 'w') cPickle.dump(self.tags, out_file) out_file.close() return self.tags def show_exif(self): self.get_exif_tags(self.image_selected) if self.tags.has_key('DateTime'): self.tags['DateTime'] =self.date_fr(self.tags['DateTime']) elif self.tags.has_key('DateTimeOriginal'): self.tags['DateTime'] =self.date_fr(self.tags['DateTimeOriginal']) else: self.tags['DateTime'] = '' print_tags = PrintMap(self.tags) self.exif_string = TEXT_FORMAT % print_tags self.create_exif_display(self.exif_string) def create_exif_display(self, exif_string): global f_win global f if f: return f = Toplevel(root) f.geometry('288x339+105+233') f.title('Exif_Data') self.f_win = Vrex_Info(exif_string, master=f) def delete_thumbnail(self): '''Function to delete the thumbnail image. If we go into either gimp or xv, then we may end up modifying the image and so I want to be sure we have an up to date thumbnail image. Occaisionally, I will needlessly throw away a thumbnail and have to rebuild it. This duplicates some of the code in delete_image.''' image = self.image_selected (dir, base) = os.path.split(image) thumb_path = os.path.join(dir, '.thumb',base) try: os.remove(thumb_path) except: print "remove %s failed" % thumb_path def delete_image(self): '''Delete the selected image as well as the thumbnail and exif files. Since xv may create a thumbnail in the directory remove it as well. ''' image = self.image_selected (dir, base) = os.path.split(image) thumb_path = os.path.join(dir, '.thumb') xvpics_path = os.path.join(dir, '.xvpics') # Remove image, remove thumbnail, remove exif file, remove # thumbnail directory if empty, remove exif directory if empty, # remove the image directory if empty.os.path.basename(image)] # Later remove any data base entries. for f in [self.exif_file_name(image), os.path.join(thumb_path,base), os.path.join(xvpics_path,base), image]: try: os.remove(f) except: pass self.image_selected = '' for p in [os.path.join(dir, '.exif'), thumb_path, xvpics_path, dir]: try: os.rmdir(p) except: pass # Clear out data base entry try: sql = '''DELETE FROM attribute WHERE attribute.file="%s"''' % image db_cursor.execute(sql) sql = '''DELETE FROM picture WHERE picture.file="%s"''' % image db_cursor.execute(sql) except: pass # Finally redisplay current directory #self.display_dir() # Finally destroy the button and the label. self.destroy_button(self.selected) self.destroy_label(self.selected) def destroy_button(self, i): self.pict[i].destroy() pass def destroy_label(self, i): self.label[i].destroy() pass def create_directory_display(self, type): '''Puts the Directory Selection window up and then just returns. Things dont really begin to happen until the user hits the display button on that window.''' global d_win global d if d: return d = Toplevel(root) # The -0-0 below anchors window to lower right corner of display. d.geometry('300x200-0-0') d.title('Directory Selection') self.d_win = Select_Dir(master=d, which=type) def date_fr(self,date_str): """This function formats the date/time like we prefer them.""" p = date_str.split() p[0] = p[0].replace(':', '-') new_date = string.join(p) return new_date #return date_str[:4] +'-'+ date_str[5:7] +'-'+ date_str[8:10] +' '+\ # date_str[11:13] +':'+ date_str[14:16] +':'+ date_str[17:19] def view_archive(self): ''' This is the entry point for pulling out a list of entries from the archive. Basically what it does is to get from the user a a list of selection criteria, forms proper data base queries to get a list of file names, and finally displays thumbnails of those files. ''' # Pull up the criteia window. # This routine does not hang until the criteria window closes. self.caller = 'view_archive' #self.criteria_window() self.create_attribute_window('sql') class PrintMap: '''In order to print the exif header with a % format string, even when some of the tags are missing, we use a PrintMap which returns 'unknown' for all missing tags. This class is part of the exif support. ''' def __init__(self, map): self.map = map def __getitem__(self, key): val = self.map.get(key) if val == None: val = 'unknown' return val class Select_Dir: def __init__(self, master=None, which='in'): '''The directory selection window. It displays either the incoming directory or the archive directory based on the value of which.''' self.tix24 = Tix.DirList(master) self.tix24.place(in_=master,x=5,y=5) self.tix24.configure(height="150") self.tix24.configure(width="290") if which=='in': self.tix24.configure(value=ORIG_LOCATION) elif which=='arc': self.tix24.configure(value=ARCHIVE) self.tix24.configure(browsecmd=self.select_dir) self.tix24.configure(command=self.select_dir) self.but24 = Button (master) self.but24.place(in_=master,x=10,y=160) self.but24.configure(command=self.display) self.but24.configure(text="Display") self.but25 = Button (master) self.but25.place(in_=master,x=240,y=160) self.but25.configure(command=quit) self.but25.configure(text="Quit") self.but26 = Button (master) self.but26.place(in_=master,x=140,y=160) self.but26.configure(command=self.close) self.but26.configure(text="Close") def close(self): global d d.destroy() d = None def select_dir(self,name): global display_dir_name display_dir_name = name def display(self): w.display_dir() class Attributes: def __init__(self, master=None, action='all'): '''Window for working with picture criteria. It is a dynamic window based on the value of action. action can have the values: all - just show all attributes mod - modify the attributes for the picture sql - build the attribute expression for a query. add - collect attributes for insertion into archive.''' self.tix24 = Tix.ScrolledListBox(master) self.tix24.place(in_=master,x=5,y=65) self.tix24.configure(height="155") self.tix24.configure(width="195") self.tix24_listbox = self.tix24.subwidget_list["listbox"] self.but25 = Button (master) self.but25.place(in_=master,x=5,y=440) self.but25.configure(command=self.close) self.but25.configure(text="Close") self.but26 = Button (master) self.but26.place(in_=master,x=150,y=440) self.but26.configure(command=quit) self.but26.configure(text="Quit") self.tix23 = Tix.LabelEntry(master) self.tix23.place(in_=master,x=10,y=10) self.tix23.configure(label="Root:") self.tix23_frame_entry = self.tix23.subwidget_list["entry"] self.tix23_label = self.tix23.subwidget_list["label"] self.tix23_label.configure(text="Root:") self.but24 = Button (master) self.but24.place(in_=master,x=115,y=10) self.but24.configure(text="Complete") self.tix25 = Tix.ScrolledText(master) self.tix25.place(in_=master,x=5,y=250) self.tix25.configure(height="165") self.tix25.configure(width="195") self.tix25_text = self.tix25.subwidget_list["text"] self.but27 = Button (master) self.but27.place(in_=master,x=70,y=440) self.but27.configure(command=self.commit) self.but27.configure(text="Commit") self.lab28 = Label (master) self.lab28.place(in_=master,x=5,y=227) self.lab28.configure(borderwidth="1") self.lab28.configure(text="Attributes") self.lab29 = Label (master) self.lab29.place(in_=master,x=5,y=40) self.lab29.configure(borderwidth="1") self.lab29.configure(text="Completions") self.but24.configure(command=self.return_completion) # Here is the dynamic stuff. Basically, I change labels, button # text, button commands, and may even add a button. if action=='all': self.get_all_attributes() self.load_attributes() self.lab29.configure(text="All Attributes in Data Base:") self.but27.configure(state="disabled") self.but27.configure(text=" ") elif action=='mod': self.lab29.configure(text="Attributes for Picture:") self.mod() elif action=='sql': self.lab28.configure(text="Build SQL expression below:") self.but27.configure(text="Display") self.but27.configure(command=self.view_archive) self.but23 = Button (master) self.but23.place(in_=master,x=115,y=37) self.but23.configure(width=8) # Width is in character widths. self.but23.configure(text="Show All") self.but23.configure(command=self.show_all) #self.show_last_attributes() elif action=='add': self.lab28.configure(text="List Attributes Below:") self.but27.configure(text="Add to DB") self.but27.configure(command=self.add) self.but23 = Button (master) # Add a button self.but23.place(in_=master,x=115,y=37) self.but23.configure(width=8) # Width is in character widths. self.but23.configure(text="Show All") self.but23.configure(command=self.show_all) self.show_last_attributes() def close(self): global e e.destroy() e = None def add(self): global criteria_text criteria_text = self.tix25_text.get(1.0, END) process_criteria_text() self.close() def commit(self): ''' This is where I update the data base with the new set of attributes. ''' # Read the criteria text string criteria_text = self.tix25_text.get(1.0, END) criteria_text = criteria_text.lower() if len(criteria_text.strip()) == 0: return # Delete all the current ones from data base. f_name = w.image_selected sql = '''DELETE FROM attribute Where attribute.file="%s"''' % f_name db_cursor.execute(sql) # Borrowed from add_to_archive_II to put in the attributes. attributes = criteria_text.split('\n') # (file, date, exposure, aperture, flash, camera) for i in range(len(attributes)): if attributes[i] != '': sql = 'INSERT attribute (file, phrase) VALUES ("%s", "%s")' % \ (f_name, attributes[i].strip()) db_cursor.execute(sql) self.close() def mod(self): w.get_picture_critera() self.clear_listbox() self.load_attributes_II() def show_all(self): self.clear_listbox() self.get_all_attributes() self.load_attributes() def load_attributes(self): for i in range(len(att)): self.tix24_listbox.insert(END, att[i]) def load_attributes_II(self): for i in range(len(att)): self.tix25_text.insert(END, att[i]+'\n') def show_last_attributes(self): #global last_attributes try: self.tix25_text.delete(0, END) except: pass try: # Only here till I can get the following 'if' test to work. x = w.last_attributes[0] except: return if len(w.last_attributes) == 0: return for i in range(len(w.last_attributes)): self.tix25_text.insert(END, w.last_attributes[i]+'\n') def clear_listbox(self): self.tix24_listbox.delete(0, END) def attribute_completion(self, str): '''Returns a list of attributes which complete the str.''' sql = '''SELECT DISTINCT phrase FROM attribute WHERE phrase LIKE "%s%s" ORDER BY phrase''' % (str,'%') db_cursor.execute(sql) picts = db_cursor.fetchall() build_attribute_list(picts) def return_completion(self): x = self.tix23_frame_entry.get() self.clear_listbox() self.attribute_completion(x) self.load_attributes() def view_archive(self): '''Here is the bulk of the view_archive work; program returns here.''' criteria_text = self.tix25_text.get(1.0, END) pieces = criteria_text.split() chunk = string.join(pieces) success, picts = sparse.parse(chunk, db_cursor) no_picts = len(picts) image_list = [] for i in range(len(picts)): image_list.append(picts[i][0]) w.image_list = copy.copy(image_list) # For redisplaying archive. w.display_image_list(image_list) #self.close() def get_all_attributes(self): sql = 'SELECT DISTINCT phrase FROM attribute ORDER BY phrase' db_cursor.execute(sql) picts = db_cursor.fetchall() build_attribute_list(picts) class Vrex_Info: def __init__(self, string, master=None): self.tix24 = Tix.ScrolledText(master) self.tix24.place(in_=master,x=10,y=10) self.tix24.configure(scrollbar="y") self.tix24.configure(height="245") self.tix24.configure(highlightbackground="#f4ddb2") self.tix24.configure(width="265") self.tix24_text = self.tix24.subwidget_list["text"] self.tix24_text.configure(cursor="fleur") self.tix24_text.configure(highlightbackground="#f4ddb2") self.but23 = Button (master) self.but23.place(in_=master,x=120,y=290) self.but23.configure(command=self.close) self.but23.configure(highlightbackground="#f4ddb2") self.but23.configure(text="Close") self.but23.configure(underline="0") self.load_exif(string) def load_exif(self, text): self.tix24_text.insert(END, text) def close(self): global f f.destroy() f = None class Progress_Bar: def __init__(self, master=None): self.tix26 = Tix.Meter(master) self.tix26.place(in_=master,x=5,y=5) self.tix26.configure(value="0") self.tix26.configure(width="188") def update_bar(self, v): self.tix26.configure(value=v) root.update() def close(self): global p p.destroy() p = None if __name__ == '__main__': main()