import sys import os import glob import tempfile import re import string from functools import cmp_to_key from tkinter import * from tkinter.messagebox import * from tkinter.simpledialog import * from tkinter.filedialog import * from tkinter.scrolledtext import * import imp #from Canvas import * # Contact the environemnt variable (if it exists) and the extension, and see # if the result is an existing directory. If so, return the result. Else, # return False. def seeif(evar, exten, addme=False): if evar not in os.environ: return False path = os.environ[evar] if exten: path = path + os.sep + exten if os.path.isdir(path): if addme: return path + os.sep + addme else: return path else: return False # The directory where the plugins go. bdirn = 'MIMP2' if sys.platform == 'win32': plugdir = False for p in [ ( 'APPDATA', '\\' + bdirn, '' ), ( 'USERPROFILE', '\\' + bdirn, '' ), ( 'HOMEPATH', '\\' + bdirn, '' ), ( 'SystemDrive', bdirn, '' ), ( 'APPDATA', '', bdirn ), ( 'USERPROFILE', '', bdirn ), ( 'HOMEPATH', '', bdirn ), ( 'SystemDrive', '', bdirn ) ]: plugdir = seeif(p[0], p[1], p[2]) if plugdir: break if not plugdir: plugdir = 'C:\\' + bdirn else: plugdir = os.environ['HOME'] + '/' + bdirn if not os.path.isdir(plugdir): try: os.mkdir(plugdir) except Exception as e: root = Tk() showerror('Cannot create plugins directory: ' + str(e)) sys.exit(1) # Background colors. bgcolor='#99FFDD' abgcolor='#BBFFEE' # Represent a pixel. Takes a #hhhhhh for the initial value. class Pixel: def __init__(self, color): self.r = int(color[1:3],16) self.g = int(color[3:5],16) self.b = int(color[5:7],16) def color(self): return '#%02x%02x%02x' % (self.r, self.g, self.b) #return '#%02x%02x%02x' % (bound(self.r), bound(self.g), bound(self.b)) #return '#%02x%02x%02x' % (self.r&0xff, self.g&0xff, self.b&0xff) # Fix up any bounds or (fixable) type errors in the pixel values. def bound(self): def cbound(c): if c < 0: return 0 if c > 255: return 255 return int(c) self.r = cbound(self.r) self.g = cbound(self.g) self.b = cbound(self.b) class ImageSyncError(Exception): def __init__(self, msg): self.message = msg # This is added to the plug-in modules. Just displays whatever you want. shush = False def show(*args): global shush res = '' for a in args: res = res + ' ' + str(a) if not shush: if not askyesno("Modifier Message", res[1:] + "\n\nContinue Showing Messages?"): shush = True # Image class extending PhotoImage from Tkinter, adding convenient # methods for getting and setting individual pixels. class ImageBender(PhotoImage): # Last file name associated with the image. #var lastfn def fn(self): return self.lastfn # Initialize the image. This is presumably not as generaly, but I # don't know enough to fix that. Works for what I need. def __init__(self, file=''): if file == '': PhotoImage.__init__(self, width=200, height=150) self.put(('{' + (bgcolor + ' ') * 200 + '} ' )*150, (0, 0)) self.lastfn = '' else: PhotoImage.__init__(self, file=file) self.lastfn = file self.cnys() # This is a Tk method omitted in Python. Perl has it! Nah nah nah nah # nah nah! I have patterned it after the write method in Tkinter.py # which sends similar arguments to Tk. It should be portable, but if # it doesn't work on Windows I'll probably want to invent some new curse # words. def data(self, background=None, format=None, from_coords=None): """Exract image data from the image in FORMAT optionally selecting the region in FROM_COORDS replacing transparent pixels with BACKGROUND. from_coords is (x.y) starting loc, or (x1,y1,x2,y2) region, excluding the limit""" args = (self.name, 'data') if background: args = args + ('-background', background) if format: args = args + ('-format', format) if from_coords: args = args + ('-from',) + tuple(from_coords) return self.tk.call(args) # Load the image data into a Python list structure. Eccch. But # going through Tk a pixel at a time is tooooo sssssllllllooooooowwwwwww # Data is loaded in (and out) a row at a time. That may turn out to have # just barely acceptable performance. def cnys(self): try: self.idat = [] for row in range(0,self.height()): # For each row, we get the row data from Tk which is a string # { #hhhhhh .... }. In the last stmt, the { } are removed, # the pixels are split, and each is converted into a an # integer. This list of integers for the row is appended to # self.idat, a list of the row lists. Some newer tk gives # a tuple, and we deal with that here, too. rowdat = self.data(background=bgcolor, from_coords=(0, row, self.width(), row + 1)) if type(rowdat) is str: left = rowdat.find('{') right = rowdat.find('}') if left < 0 or right < 0: raise ImageSyncError("Format: Missing { or }") #self.idat.append(map((lambda x: Pixel(x)), # rowdat[left+1:right].split())) # The above breaks in 3. Stackoverflow 1303347 suggests. How about # not breaking something fundamental? self.idat.append([Pixel(x) for x in rowdat[left+1:right].split()]) else: # Assuming the tuple format with unicode string. rowdat = rowdat[0].encode('utf-8') #self.idat.append(list(map((lambda x: Pixel(x)),rowdat.split()))) self.idat.append([Pixel(x) for x in rowdat.split()]) except Exception as e: showerror('Internal Error', 'Image sync failed: ' + str(e)) raise # Load changes made to the Python image data into the image itself. This # is a duzey, which packs the entire modified image data as a string and # sends it to self.put, which updates the image with the new data. def sync(self): self.put(' '.join(map((lambda row: '{'+ ' '.join (map((lambda x: x.color()),row)) + '}'), self.idat))) def write(self, fn, format='GIF'): PhotoImage.write(fn, format=format) self.lastfn = fn # File open. fmenu = 0 def getfile(): global pic, fmenu fn = askopenfilename(filetypes=[("GIF files", '.gif')]) root.update() if fn: try: lpic = ImageBender(file=fn) except: showwarning("Open Failed", 'Unable to open ' + fn) return pic = lpic ilab.configure(image=pic) fmenu.entryconfigure(2, state='normal') # File save def savefile(): global pic, fmenu fn = asksaveasfilename(filetypes=[("GIF files", '.gif')]) root.update() if fn: try: pic.write(fn, format='GIF') fmenu.entryconfigure(2, state='normal') except: showwarning("Save Failed", 'Unable to save to ' + fn) return # Clear the image area. def clearimg(): global pic pic = ImageBender() ilab.configure(image=pic) fmenu.entryconfigure(2, state='disabled') # File reload def relfile(): global pic ofn = pic.fn() if ofn != '': root.update() try: lpic = ImageBender(file=ofn) except: showwarning("Reload Failed", 'Unable to reopen ' + ofn) return pic = lpic ilab.configure(image=pic) # Exit def quit(): root.quit() # This shouldn't be necessary, but a library bug seems to keep me from # doing this without creating a file. (fh, upbmp) = tempfile.mkstemp('.bmp'); os.write(fh,b"""#define up_width 13 #define up_height 4 static unsigned char up_bits[] = { 0x40, 0x00, 0xf0, 0x01, 0xfc, 0x07, 0xff, 0x1f}; """) os.close(fh) atupbmp = '@' + upbmp root = Tk() root.title('Pluggable Image Manipulation') bgframe = Frame(root, bg=bgcolor) #bgframe.grid(row=0,column=0) bgframe.pack() # Create the standard file menu (though we don't fill it up 'till later). top = Menu(root, bg=bgcolor, activebackground=abgcolor) root.config(menu=top) # Create the empty image. pic = ImageBender() # Initial modifier. imod = '''# Modifier skeleton # Modify this function to perform some operation on each pixel. The # initial version just sets each component to its original value. def each_pixel(pix, row, col): pix.r = pix.r pix.g = pix.g pix.b = pix.b # The variables height and width always contain the total height and width # of the image. # Any of the following functions which you define are called in the following # order. # def init() Called first, just once. # def inspect(red, green, blue, row, col) # Called second, for each pixel. Allows you to create # totals or count things before making modifications. # def summarize() Called third, just once. # def each_pixel(pixel, row, col) Called fourth, once for each pixel # def whole(pic) # This is called once, and the entire image is sent. This # is useful for transforms which require access to more than # one pixel at a time. Use pic.idat[row][col] for pixels. # The provided function show() can be used for debugging. It will show # whatever argument you send. Each time it does, you choose whether to # continue showing values. ''' marker = "# ======= START USER CODE ======\n" # Editor. class Editor(Toplevel): def __init__(self, fn, but): # Set up the window. Toplevel.__init__(self) self.title(but['text']) mframe = Frame(self, bg=bgcolor) mframe.pack() tmen = Menu(mframe, bg=bgcolor, activebackground=abgcolor) self.config(menu=tmen) fmenu = Menu(tmen, bg=bgcolor, activebackground=abgcolor) fmenu.add_command(label='Save', command=self.save, underline=0, accelerator="Ctrl+S") fmenu.add_command(label='Reload', command=self.load, underline=0) fmenu.add_command(label='Check', command=self.check, underline=0) fmenu.add_separator() fmenu.add_command(label='Exit', command=self.onexit, underline=0) fmenu.add_command(label='Save+Exit', command=self.snex, underline=0) fmenu.add_command(label='Discard+Exit', command=self.cancel, underline=0) tmen.add_cascade(label='File', menu=fmenu, underline=0) # Edit incantations suggested by Jeff Epler # http://mail.python.org/pipermail/python-list/2005-August/334669.html emenu = Menu(tmen, bg=bgcolor, activebackground=abgcolor) emenu.add_command(label='Cut', underline=2, accelerator="Ctrl+X", command=lambda: self.text.event_generate("<>")) emenu.add_command(label='Copy', underline=0, accelerator="Ctrl+C", command=lambda: self.text.event_generate("<>")) emenu.add_command(label='Paste', underline=0, accelerator="Ctrl+V", command=lambda: self.text.event_generate("<>")) tmen.add_cascade(label='Edit', menu=emenu, underline=0) # Text widget. sc = ScrolledText(self, font=('Courier', 14, 'normal'), bg=bgcolor) # Set the tabs to four characters. Tab setting code swiped from # Idle's editor code. I don't claim to understand it at any depth, # except that it's criminal for it to be this hard to make a tab # setting of four characters. Geeeesh. pixels = sc.tk.call("font", "measure", sc["font"], "-displayof", sc.master, "n" * 4) sc.configure(tabs=pixels) if sys.platform == 'win32': # This works around a bug in how the Windows port of the # text window displays tabs. sc.bind("", lambda e: self.win_snafu()) # I'm not sure about this, but it seems to appear differently on # the different systems. Very odd. if sys.platform != 'win32': sc.bind("", lambda e: self.text.event_generate("<>")) sc.bind("", lambda e: self.text.event_generate("<>")) sc.bind("", lambda e: self.text.event_generate("<>")) sc.bind("", lambda e: self.save()) sc.pack(fill=BOTH,expand=YES) self.protocol('WM_DELETE_WINDOW', self.onexit) self.fn = fn self.but = but self.text = sc # There is a reported bug the edit_modified() method of the Text (and # apparently ScrolledText) objects in tkinter. Here is a work-around # derived from the bug report. # https://sourceforge.net/tracker/ # ?func=detail&atid=105470&aid=1362475&group_id=5470 # http://mail.python.org/pipermail/ # python-bugs-list/2007-January/036927.html def is_modified(self): return self.tk.call(self.text._w, 'edit', 'modified') def set_unmodified(self): self.tk.call(self.text._w, 'edit', 'modified', 0) # The Windows port has a nasty bug in displaying tabs. This is really # bad with a language like Python where indenting is important. This # function standardizes the line before the cursor, and should make # it work okay in most cases. def win_snafu(self): # Get the contents of the line to the cursor stuff = self.text.get('insert linestart','insert') # If it's not all spaces, don't sweat it. if not stuff.isspace(): return # Replace with a string of tabs back to the start. The tab # being inserted will bring us to the right place. #tlen = len(string.expandtabs(stuff,4)) tlen = len(stuff.expandtabs(4)) stuff = "\t" * int(tlen/4) # Replace the existing text. self.text.delete('insert linestart','insert') self.text.insert('insert', stuff) # Save and exit the editor. def snex(self): if (not self.is_modified()) or self.save(): self.but.doneedit() self.destroy() # Save the file. def save(self): while True: try: fout = open(self.fn, "w") fout.write("name = '" + self.but["text"] + "'\n") fout.write(marker) cont = self.text.get('1.0','end') #fout.write(string.expandtabs(cont,4)) fout.write(cont.expandtabs(4)) fout.close() self.set_unmodified() return True except Exception as e: try: fout.close() except: None resp = askretrycancel('Save Failed', 'Unable to save: ' + str(e)) if resp: continue else: return False # Exit, but ask about saving if needed. def onexit(self): #if self.text.edit_modified(): if self.is_modified(): resp = askyesno('Save?', 'Do you want to save changes\nto ' + self.but.name + '?') if resp: self.save() self.but.doneedit() self.destroy() # Load the file. def load(self): fin = open(self.fn, "r") while True: line = fin.readline() if not line: break if line == marker: break self.text.delete('1.0','end') while True: line = fin.readline() if not line: break self.text.insert('end', line) fin.close() self.set_unmodified() # Check the module. def check(self): if(self.is_modified()): resp = askyesno('Save and Check?', "You must save your changes\nbefore " + "you can check.\nSave and check now?") if resp: self.save() else: return if self.but.reload("Check"): showinfo("Ok", "Syntax is okay") def cancel(self): self.but.doneedit() self.destroy() # Get a proper operator name. def getname(msg): res = None okay = re.compile("[^a-zA-Z0-9\s]") spaces = re.compile("^\s*$") while True: res = askstring("Button Name", msg) if res == None: return None if spaces.search(res) != None: showerror('Bad Name', "Please use some non-space characters") continue if okay.search(res) != None: showerror('Bad Name', "Please make your name of\nletters, " + "digits and spaces.") continue return res # Modifier button. class ModifierButton(Button): # RE for switching line numbers linefinder = re.compile(r'line\s(\d+)') # These are all the buttons. all_of_us = [ ] next_but_seq = 0 plugdir = None ncol = 3 def numcol(): return ModifierButton.ncol numcol = staticmethod(numcol) # Load from the directory. def loadall(par, dir): ModifierButton.plugdir = dir # Get rid of the oldies oldus = ModifierButton.all_of_us for x in oldus: x.destroy() # Scan the directory for m in glob.glob(dir + os.sep + 'immod_*.py'): loc = m.rfind(os.sep) + 7 n = int(m[loc:-3]) if n > ModifierButton.next_but_seq: ModifierButton.next_but_seq = n try: ModifierButton(par,m) except Exception as e: showerror('Load Failed', 'Pre-existing module failed to load: ' + str(e)) # Place them. ModifierButton.arrange() loadall = staticmethod(loadall) # Close all the editors. def closeall(): # Get rid of the oldies for x in ModifierButton.all_of_us: if x.editor: x.editor.onexit() closeall = staticmethod(closeall) # Generate the next base file name. def nextfn(): fn = "immod_" + str(ModifierButton.next_but_seq) ModifierButton.next_but_seq = ModifierButton.next_but_seq + 1 return fn nextfn = staticmethod(nextfn) # Create a full filename from a base fn. def fullify(basefn): return ModifierButton.plugdir + os.sep + basefn + ".py" fullify = staticmethod(fullify) # Find the name = line and return the name. def extrname(self, name = 'Unknown'): try: modfh = open(self.fn, "r") while True: line = modfh.readline() if not line: break if line == marker: break if line[0:8] == "name = '": pos = line.rfind("'") if pos >= 0: name = line[8:pos] break except Exception as e: modfh.close() else: modfh.close() return name # This throws several exceptions, and should be run within a try block. def __init__(self, par, fn = None, name = None, external=False): if fn == None and name == None: raise Exception("Internal error: Button created w/o name.") self.editor = False # If we're importing, we need to choose a local file name and copy # it in. if external: self.fn = fn name = self.extrname('Imported') newfn = ModifierButton.fullify(ModifierButton.nextfn()) self.docopy(fn, newfn, name) fn = newfn # Without a file name, we must create a new file. if fn == None: # Create blank modifier file. self.basefn = basefn = ModifierButton.nextfn() self.fn = fn = ModifierButton.fullify(basefn) wf = open(fn, "w") wf.write('name = "' + name + '"\n') wf.write(marker) wf.write(imod) wf.close() else: # File name parms. self.fn = fn seploc = fn.rfind(os.sep) if seploc < 0: basefn = fn else: basefn = fn[seploc+1:] self.basefn = basefn = basefn[:basefn.rfind('.py')] name = self.basefn # Make sure to set counter so we don't reuse this name. pos = basefn.rfind('_') if pos >= 0: n = int(basefn[pos+1:]) if n >= ModifierButton.next_but_seq: ModifierButton.next_but_seq = n + 1 # Okay. Take some care for our base class. Button.__init__(self, par, text=name, command=self.pushed, bg=bgcolor, activebackground=abgcolor) # Create our right-click menu. self.menu = menu = Menu(self, bg=bgcolor, activebackground=abgcolor, tearoff=False) #global upbmp #menu.add_command(bitmap=upbmp, command=lambda:self.menu.unpost(), menu.add_command(bitmap=atupbmp, command=lambda:self.menu.unpost(), label="[close]", underline=1) menu.add_command(label='Edit...', command=self.runedit,underline=0) menu.add_command(label='Delete...', command=self.delbut,underline=0) menu.add_command(label='Rename...', command=self.rename,underline=0) menu.add_command(label='Clone..', command=self.clone,underline=0) menu.add_command(label='Export..', command=self.export,underline=1) ModifierButton.all_of_us.append(self) # Now we need to load the contents. oldpath = sys.path sys.path = [ ModifierButton.plugdir ] + sys.path self.mod = None try: self.mod = __import__(basefn) self.mod.show = show except Exception as e: self.name = name = self.extrname() self.config(text=name) sys.path = oldpath emsg = str(e).replace(self.basefn+'.py',self.name) emsg = re.sub(self.linefinder, lambda x: 'line ' + str(int(x.group(1)) - 2), emsg) raise Exception(emsg) else: sys.path = oldpath self.name = name = self.mod.name self.config(text=name) # Bye. def destroy(self): where = ModifierButton.all_of_us.index(self) ModifierButton.all_of_us[where:where+1] = [ ] Button.destroy(self) def name(self): return self.name # Arrange/rearrange the buttons. def cmp(self,x): if self.name < x.name: return -1 elif self.name > x.name: return 1 else: return 0 def arrange(): #ModifierButton.all_of_us.sort(key=lambda a,b: a.cmp(b)) ModifierButton.all_of_us.sort(key=cmp_to_key(lambda a,b: a.cmp(b))) for b in ModifierButton.all_of_us: b.grid_forget() r = 1 c = 0 for b in ModifierButton.all_of_us: b.grid(row=r, column=c) b.bind("", lambda e, but=b: but.dropdown()) b.bind("", lambda e, but=b: but.dropdown()) b.bind("", lambda e, but=b: but.dropdown()) b.bind("", lambda e, but=b: but.dropdown()) c = c + 1 if c == ModifierButton.ncol: c = 0 r = r + 1 arrange = staticmethod(arrange) # Button pushed. def pushed(self): global pic, shush tried = False m = self.mod if m == None: showerror("Bad Modifier", "This modifier did not load correctly.\n" + " You will need to edit it and correct it.") return try: # Image information. m.width = pic.width() m.height = pic.height() shush = False # Call first init hook. if 'init' in m.__dict__: m.init() # Call the setup hook if 'inspect' in m.__dict__: for row in range(0, pic.height()): for col in range(0, pic.width()): p = pic.idat[row][col] m.inspect(p.r, p.g, p.b, row, col) # Call second init hook. if 'summarize' in m.__dict__: m.summarize() # Fire off one per-pixel modifier. if 'each_pixel' in m.__dict__: tried = True for row in range(0, pic.height()): for col in range(0, pic.width()): m.each_pixel(pic.idat[row][col], row, col) # Run the whole image munger, if present. if 'whole' in m.__dict__: tried = True m.whole(pic) # Limit ranges. for row in range(0, pic.height()): for col in range(0, pic.width()): pic.idat[row][col].bound() except Exception as e: showerror('Modify Failed', 'Modify operation failed: ' + str(e)) else: if not tried: showerror("No Modifiers", "Plugin does not define any\n" + " functions which modify the image.") else: pic.sync() # Open right-click dropdown menu def dropdown(self): x = self.winfo_rootx() y = self.winfo_rooty() self.menu.post(x+3,y+3) # Menu methods def runedit(self): if self.editor: self.editor.tkraise() else: try: self.editor = Editor(self.fn, self) self.editor.load() except Exception as e: showerror('Edit Failed', 'Could not edit: ' + str(e)) self.editor = False else: self.config(state="disabled") # Reload the module. def reload(self, note = "Reload"): oldpath = sys.path sys.path = [ ModifierButton.plugdir ] + sys.path try: if self.mod: imp.reload(self.mod) else: self.mod = __import__(self.basefn) imp.reload(self.mod) # Sometimes the system will __import__ without throwing, # even though the file has syntax errors. I think it may # have to do with using the compiled file. The reload # seems to fix that. I think this also fixes other observed # problems with changes being ignored. except Exception as e: emsg = str(e).replace(self.basefn+'.py',self.name) emsg = re.sub(self.linefinder, lambda x: 'line ' + str(int(x.group(1)) - 2), emsg) showerror(note + " Failed", emsg) self.mod = None sys.path = oldpath return False else: sys.path = oldpath self.mod.show = show return True # finally: # sys.path = oldpath # return True # Some bug keeps it from accpeting the finally clause. # We're through editing. def doneedit(self): self.reload() self.config(state="normal") self.editor = False def delbut(self): # Get rid of the file try: os.unlink(self.fn) os.unlink(self.fn + 'c') except Exception as e: None self.destroy() ModifierButton.arrange() # Copy a modifier with a new name. May throw all kinds of stuff. def docopy(self, fr, to, name): inf = None out = None try: inf = open(fr, "r") out = open(to, "w") while True: line = inf.readline() if not line: break if line == marker: break out.write("name = '" + name + "'\n") out.write(marker) while True: line = inf.readline() if not line: break out.write(line) finally: if out: out.close() if inf: inf.close() # Rename a modifier. def rename(self): res = getname("Please enter a new button name.") if not res: return try: tmp = self.fn + '_TMP' os.rename(self.fn, tmp) self.docopy(tmp, self.fn, res) os.unlink(tmp) except Exception as e: showerror('File Copy Error', 'Unable to rename file: ' + str(e)) return self.name = res self.config(text=self.name) ModifierButton.arrange() # Copy a modifier. def clone(self): res = getname("Please enter a new button name.") if not res: return try: newname = ModifierButton.fullify(ModifierButton.nextfn()) self.docopy(self.fn, newname, res) except Exception as e: showerror('File Copy Error', 'Unable to rename file: ' + str(e)) return #ModifierButton(self.parent(), name = newname) ModifierButton(bgframe, fn = newname) ModifierButton.arrange() # Export a modifier to an external file. def export(self): fn = asksaveasfilename(defaultextension="py", filetypes=[('Python Files','*.py')], initialfile=self.name + '.py') if not fn: return try: self.docopy(self.fn, fn, self.name) except Exception as e: showerror('File Copy Error', 'Unable to save file: ' + str(e)) return # New modifier. def newmod(): res = getname("What is the new modifier's name?") if res: try: ModifierButton(bgframe, name = res) ModifierButton.arrange() except Exception as e: showerror('Create Failed', 'Unable to write file :' + str(e)) # For importing a module. def impmod(): ifn = askopenfilename(filetypes=[("Python Files", '.py')]) if ifn: try: ModifierButton(bgframe, fn = ifn, external = True) ModifierButton.arrange() except Exception as e: showerror('Import Failed', 'Unable to import file ' + ifn + ': ' + str(e)) # Action for setting the Modifiers directory. def setmodloc(): global bgframe fn = askdirectory(initialdir=plugdir) if fn: ModifierButton.loadall(bgframe,fn) # Menu bar contents fmenu = Menu(top, bg=bgcolor, activebackground=abgcolor) fmenu.add_command(label='Open...', command=getfile, underline=0, accelerator="Ctrl+O") #fmenu.add_command(label='Save...', command=savefile, underline=0, # accelerator="Ctrl+S") fmenu.add_command(label='Reload', command=relfile, underline=0, state='disabled') fmenu.add_command(label='Clear', command=clearimg, underline=0) fmenu.add_separator() fmenu.add_command(label='Exit', command=quit, underline=0) top.add_cascade(label='File', menu=fmenu, underline=0) pmenu = Menu(top, bg=bgcolor, activebackground=abgcolor) #pmenu.add_command(label='Reload', command=modrel, underline=0) pmenu.add_command(label='New...', command=newmod, underline=0) pmenu.add_command(label='Import...', command=impmod, underline=0) pmenu.add_command(label='Location...', command=setmodloc, underline=0) top.add_cascade(label='Modifiers', menu=pmenu, underline=0) # Display the image ilab = Label(bgframe, image=pic) ilab.grid(row=0, column=0, columnspan=ModifierButton.numcol()) root.bind("", lambda e: getfile()) ilab.bind("", lambda e: savefile()) ModifierButton.loadall(bgframe,plugdir) class DispLabel(Label): """ A label which shows a string of the form name=value, where the value is an integer """ def __init__(self, parent, name, val): Label.__init__(self, parent, bg=bgcolor, width=10) self.name = name self.set(val) def set(self, val): self.config(text = "%s=%d" % (self.name, val)) cpop = None class CPop(Toplevel): """Color description window. Describes the color when the user clicks. Intended for there to be zero on one instance, stored in global cpop.""" def mklab(self, name, val, rw, cl, stk): "Private. Make a value-describing label" ret = DispLabel(self, name, val) ret.grid(row=rw, column=cl, sticky=stk) return ret def __init__(self, row, col, r, g, b): "Initialize with a particular position and color." Toplevel.__init__(self) self.title("Color") self.rowlab = self.mklab("row", row, 0, 0, 'e') self.collab = self.mklab("col", col, 1, 0, 'e') self.redlab = self.mklab("red", r, 0, 1, 'w') self.greenlab = self.mklab("green", g, 1, 1, 'w') self.bluelab = self.mklab("blue", b, 2, 1, 'w') self.colorlab = Label(self, background=("#%02x%02x%02x" %(r,g,b))) self.colorlab.grid(row=2, column=0,sticky='news') Button(self, text="Ok", command=self.drop, bg=bgcolor, activebackground=abgcolor,).\ grid(row=3,column=0,columnspan=2,sticky='news') self.protocol('WM_DELETE_WINDOW', self.drop) global cpop cpop = self def set(self, row, col, r, g, b): "Set to a new position and color" self.rowlab.set(row) self.collab.set(col) self.redlab.set(r) self.greenlab.set(g) self.bluelab.set(b) self.colorlab.config(background=("#%02x%02x%02x" %(r,g,b))) def drop(self): "Bye." global cpop cpop = None self.destroy() # Describe the pixel you clicked. def colorpop(e): "Pops up a dialog giving the color at the selected location" row = e.y - int(str(ilab.cget('pady'))) col = e.x - int(str(ilab.cget('padx'))) if row < pic.height() and col < pic.width(): global cpop if cpop == None: cpop = CPop(row, col, pic.idat[row][col].r, pic.idat[row][col].g, pic.idat[row][col].b) else: cpop.set(row, col, pic.idat[row][col].r, pic.idat[row][col].g, pic.idat[row][col].b) ilab.bind("", colorpop) def bye(): ModifierButton.closeall(), root.destroy() root.protocol('WM_DELETE_WINDOW', bye) root.mainloop() os.unlink(upbmp)