Many changes to support a second mode of operation. Pynche can now be

run either as a standalone application (by running pynche or
pynche.pyw), or as a modal dialog inside another application.  This
can be done by importing pyColorChooser and running askcolor().  The
API for this is the same as the tkColorChooser.askcolor() API, namely:

    When `Okay' is hit, askcolor() returns ((r, g, b), "name").  When
    `Cancel' is hit, askcolor() returns (None, None).

Note the following differences:

    1. pyColorChooser.askcolor() takes an optional keyword `master'
       which if set tells Pynche to run as a modal dialog.  `master'
       is a Tkinter parent window.  Without the `master' keyword
       Pynche runs standalone.

    2. in pyColorChooser.askcolor() will return a Tk/X11 color name as
       "name" if there is an exact match, otherwise it will return a
       color spec, e.g. "#rrggbb".  tkColorChooser can't return a
       color name.

There are also some UI differences when running standalone vs. modal.
When modal, there is no "File" menu, but instead there are "Okay" and
"Cancel" buttons.

The implementation of all this is a bit of a hack, but it seems to
work moderately well.  I'm not guaranteeing the pyColorChooser.Chooser
class has the same semantics as the tkColorChooser.Chooser class.
This commit is contained in:
Barry Warsaw 1998-10-22 03:25:59 +00:00
parent 04da10c7a2
commit ca07ba00ac
10 changed files with 266 additions and 130 deletions

View File

@ -23,7 +23,7 @@ class ChipWidget:
_HEIGHT = 80
def __init__(self,
parent = None,
master = None,
width = _WIDTH,
height = _HEIGHT,
text = 'Color',
@ -31,16 +31,16 @@ class ChipWidget:
presscmd = None,
releasecmd = None):
# create the text label
self.__label = Label(parent, text=text)
self.__label = Label(master, text=text)
self.__label.grid(row=0, column=0)
# create the color chip, implemented as a frame
self.__chip = Frame(parent, relief=RAISED, borderwidth=2,
self.__chip = Frame(master, relief=RAISED, borderwidth=2,
width=width,
height=height,
background=initialcolor)
self.__chip.grid(row=1, column=0)
# create the color name, ctor argument must be a string
self.__name = Label(parent, text=initialcolor)
self.__name = Label(master, text=initialcolor)
self.__name.grid(row=2, column=0)
#
# set bindings
@ -65,10 +65,10 @@ class ChipWidget:
class ChipViewer:
def __init__(self, switchboard, parent=None):
def __init__(self, switchboard, master=None):
self.__sb = switchboard
self.__frame = Frame(parent) #, relief=GROOVE, borderwidth=2)
self.__frame.grid(row=3, column=0)
self.__frame = Frame(master, relief=RAISED, borderwidth=1)
self.__frame.grid(row=3, column=0, ipadx=5)
# create the chip that will display the currently selected color
# exactly
self.__sframe = Frame(self.__frame)
@ -81,11 +81,6 @@ class ChipViewer:
self.__nearest = ChipWidget(self.__nframe, text='Nearest',
presscmd = self.__buttonpress,
releasecmd = self.__buttonrelease)
self.__div = Frame(self.__frame,
width=2,
borderwidth=2,
relief=RAISED)
self.__div.grid(row=0, column=2, sticky='NS', padx=5)
def update_yourself(self, red, green, blue):
# TBD: should exactname default to X11 color name if their is an exact
@ -110,6 +105,3 @@ class ChipViewer:
colorname = self.__nearest.get_color()
red, green, blue = self.__sb.colordb().find_byname(colorname)
self.__sb.update_views(red, green, blue)
def save_options(self, optiondb):
pass

View File

@ -61,19 +61,19 @@ GRAV = 'Squash'
class DetailsViewer:
def __init__(self, switchboard, parent=None):
def __init__(self, switchboard, master=None):
self.__sb = switchboard
optiondb = switchboard.optiondb()
self.__red, self.__green, self.__blue = switchboard.current_rgb()
# GUI
root = self.__root = Toplevel(parent, class_='Pynche')
root.protocol('WM_DELETE_WINDOW', self.__withdraw)
root = self.__root = Toplevel(master, class_='Pynche')
root.protocol('WM_DELETE_WINDOW', self.withdraw)
root.title('Pynche Details Window')
root.iconname('Pynche Details Window')
root.bind('<Alt-q>', self.__quit)
root.bind('<Alt-Q>', self.__quit)
root.bind('<Alt-w>', self.__withdraw)
root.bind('<Alt-W>', self.__withdraw)
root.bind('<Alt-w>', self.withdraw)
root.bind('<Alt-W>', self.withdraw)
# accelerators
root.bind('<KeyPress-Left>', self.__minus1)
root.bind('<KeyPress-Right>', self.__plus1)
@ -158,7 +158,7 @@ class DetailsViewer:
def __quit(self, event=None):
self.__root.quit()
def __withdraw(self, event=None):
def withdraw(self, event=None):
self.__root.withdraw()
def deiconify(self, event=None):

View File

@ -19,20 +19,20 @@ from Tkinter import *
import ColorDB
class ListViewer:
def __init__(self, switchboard, parent=None):
def __init__(self, switchboard, master=None):
self.__sb = switchboard
optiondb = switchboard.optiondb()
self.__lastbox = None
self.__dontcenter = 0
# GUI
root = self.__root = Toplevel(parent, class_='Pynche')
root.protocol('WM_DELETE_WINDOW', self.__withdraw)
root = self.__root = Toplevel(master, class_='Pynche')
root.protocol('WM_DELETE_WINDOW', self.withdraw)
root.title('Pynche Color List')
root.iconname('Pynche Color List')
root.bind('<Alt-q>', self.__quit)
root.bind('<Alt-Q>', self.__quit)
root.bind('<Alt-w>', self.__withdraw)
root.bind('<Alt-W>', self.__withdraw)
root.bind('<Alt-w>', self.withdraw)
root.bind('<Alt-W>', self.withdraw)
#
# create the canvas which holds everything, and its scrollbar
#
@ -125,7 +125,7 @@ class ListViewer:
def __quit(self, event=None):
self.__root.quit()
def __withdraw(self, event=None):
def withdraw(self, event=None):
self.__root.withdraw()
def deiconify(self, event=None):

View File

@ -103,6 +103,56 @@ def initial_color(s, colordb):
return r, g, b
def build(master=None, initialcolor=None, initfile=None, ignore=None):
# create the windows and go
for f in RGB_TXT:
try:
colordb = ColorDB.get_colordb(f)
if colordb:
break
except IOError:
pass
else:
usage(1, 'No color database file found, see the -d option.')
# create all output widgets
s = Switchboard(colordb, not ignore and initfile)
# create the application window decorations
app = PyncheWidget(__version__, s, master=master)
w = app.window()
s.add_view(StripViewer(s, w))
s.add_view(ChipViewer(s, w))
s.add_view(TypeinViewer(s, w))
# get the initial color as components and set the color on all views. if
# there was no initial color given on the command line, use the one that's
# stored in the option database
if initialcolor is None:
optiondb = s.optiondb()
red = optiondb.get('RED')
green = optiondb.get('GREEN')
blue = optiondb.get('BLUE')
# but if there wasn't any stored in the database, use grey50
if red is None or blue is None or green is None:
red, green, blue = initial_color('grey50', colordb)
else:
red, green, blue = initial_color(initialcolor, colordb)
s.update_views(red, green, blue)
return app, s
def run(app, s):
try:
app.start()
except KeyboardInterrupt:
pass
# save the option database
s.save_views()
def main():
try:
@ -132,51 +182,7 @@ def main():
elif opt in ('-i', '--initfile'):
initfile = arg
# create the windows and go
for f in RGB_TXT:
try:
colordb = ColorDB.get_colordb(f)
if colordb:
break
except IOError:
pass
else:
usage(1, 'No color database file found, see the -d option.')
# create all output widgets
s = Switchboard(colordb, not ignore and initfile)
# create the application window decorations
app = PyncheWidget(__version__, s)
parent = app.parent()
s.add_view(StripViewer(s, parent))
s.add_view(ChipViewer(s, parent))
s.add_view(TypeinViewer(s, parent))
# get the initial color as components and set the color on all views. if
# there was no initial color given on the command line, use the one that's
# stored in the option database
if initialcolor is None:
optiondb = s.optiondb()
red = optiondb.get('RED')
green = optiondb.get('GREEN')
blue = optiondb.get('BLUE')
# but if there wasn't any stored in the database, use grey50
if red is None or blue is None or green is None:
red, green, blue = initial_color('grey50', colordb)
else:
red, green, blue = initial_color(initialcolor, colordb)
s.update_views(red, green, blue)
try:
app.start()
except KeyboardInterrupt:
pass
# save the option database
s.save_views(initfile)
run()
if __name__ == '__main__':

View File

@ -13,36 +13,46 @@ KEEPALIVE_TIMER = 500
class PyncheWidget:
def __init__(self, version, switchboard):
def __init__(self, version, switchboard, master=None):
self.__sb = switchboard
self.__version = version
self.__textwin = None
self.__listwin = None
self.__detailswin = None
#
# Is there already a default root for Tk, say because we're running
# under Guido's IDE? :-) Two conditions say no, either the import
# fails or _default_root is None.
tkroot = None
try:
from Tkinter import _default_root
tkroot = self.__tkroot = _default_root
except ImportError:
pass
if not tkroot:
tkroot = self.__tkroot = Tk(className='Pynche')
# but this isn't our top level widget, so make it invisible
tkroot.withdraw()
modal = self.__modal = not not master
# If a master was given, we are running as a modal dialog servant to
# some other application. We rearrange our UI in this case (there's
# no File menu and we get `Okay' and `Cancel' buttons), and we do a
# grab_set() to make ourselves modal
if modal:
self.__tkroot = tkroot = Toplevel(master, class_='Pynche')
tkroot.grab_set()
tkroot.withdraw()
else:
# Is there already a default root for Tk, say because we're
# running under Guido's IDE? :-) Two conditions say no, either the
# import fails or _default_root is None.
tkroot = None
try:
from Tkinter import _default_root
tkroot = self.__tkroot = _default_root
except ImportError:
pass
if not tkroot:
tkroot = self.__tkroot = Tk(className='Pynche')
# but this isn't our top level widget, so make it invisible
tkroot.withdraw()
# create the menubar
menubar = self.__menubar = Menu(tkroot)
#
# File menu
#
filemenu = self.__filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label='Quit',
command=self.__quit,
accelerator='Alt-Q',
underline=0)
if not modal:
filemenu = self.__filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label='Quit',
command=self.__quit,
accelerator='Alt-Q',
underline=0)
#
# View menu
#
@ -66,9 +76,10 @@ class PyncheWidget:
#
# Tie them all together
#
menubar.add_cascade(label='File',
menu=filemenu,
underline=0)
if not modal:
menubar.add_cascade(label='File',
menu=filemenu,
underline=0)
menubar.add_cascade(label='View',
menu=viewmenu,
underline=0)
@ -78,15 +89,44 @@ class PyncheWidget:
# now create the top level window
root = self.__root = Toplevel(tkroot, class_='Pynche', menu=menubar)
root.protocol('WM_DELETE_WINDOW', self.__quit)
root.protocol('WM_DELETE_WINDOW',
modal and self.__beep or self.__quit)
root.title('Pynche %s' % version)
root.iconname('Pynche')
root.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
root.bind('<Alt-q>', self.__quit)
root.bind('<Alt-Q>', self.__quit)
# Only bind accelerators for the File->Quit menu item if running as a
# standalone app
if not modal:
root.bind('<Alt-q>', self.__quit)
root.bind('<Alt-Q>', self.__quit)
else:
# We're a modal dialog so we have a new row of buttons
bframe = Frame(root, borderwidth=1, relief=RAISED)
bframe.grid(row=4, column=0, columnspan=2,
sticky='EW',
ipady=5)
okay = Button(bframe,
text='Okay',
command=self.__okay)
okay.pack(side=LEFT, expand=1)
cancel = Button(bframe,
text='Cancel',
command=self.__cancel)
cancel.pack(side=LEFT, expand=1)
def __quit(self, event=None):
self.__root.quit()
self.__tkroot.quit()
def __beep(self, event=None):
self.__tkroot.beep()
def __okay(self, event=None):
self.__sb.withdraw_views()
self.__tkroot.grab_release()
self.__quit()
def __cancel(self, event=None):
self.__sb.canceled()
self.__okay()
def __keepalive(self):
# Exercise the Python interpreter regularly so keyboard interrupts get
@ -94,10 +134,11 @@ class PyncheWidget:
self.__tkroot.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
def start(self):
self.__keepalive()
if not self.__modal:
self.__keepalive()
self.__tkroot.mainloop()
def parent(self):
def window(self):
return self.__root
def __popup_about(self, event=None):
@ -135,3 +176,6 @@ email : bwarsaw@python.org''' % __version__)
self.__detailswin = DetailsViewer(self.__sb, self.__root)
self.__sb.add_view(self.__detailswin)
self.__detailswin.deiconify()
def withdraw(self):
self.__root.withdraw()

View File

@ -147,7 +147,7 @@ class StripWidget:
_NUMCHIPS = 40
def __init__(self, switchboard,
parent = None,
master = None,
chipwidth = _CHIPWIDTH,
chipheight = _CHIPHEIGHT,
numchips = _NUMCHIPS,
@ -171,13 +171,12 @@ class StripWidget:
canvasheight = chipheight + 43 # TBD: Kludge
# create the canvas and pack it
canvas = self.__canvas = Canvas(
parent,
width=canvaswidth,
height=canvasheight,
## borderwidth=2,
## relief=GROOVE
)
canvas = self.__canvas = Canvas(master,
width=canvaswidth,
height=canvasheight,
## borderwidth=2,
## relief=GROOVE
)
canvas.pack()
canvas.bind('<ButtonPress-1>', self.__select_chip)
@ -296,11 +295,11 @@ class StripWidget:
class StripViewer:
def __init__(self, switchboard, parent=None):
def __init__(self, switchboard, master=None):
self.__sb = switchboard
optiondb = switchboard.optiondb()
# create a frame inside the parent
self.__frame = Frame(parent) #, relief=GROOVE, borderwidth=2)
# create a frame inside the master
self.__frame = Frame(master, relief=RAISED, borderwidth=1)
self.__frame.grid(row=1, column=0, columnspan=2, sticky='EW')
uwd = self.__uwdvar = BooleanVar()
uwd.set(optiondb.get('UPWHILEDRAG', 0))
@ -338,12 +337,6 @@ class StripViewer:
command=self.__togglehex)
self.__hex.grid(row=1, column=0, sticky=W)
self.__div = Frame(self.__frame,
height=2,
borderwidth=2,
relief=RAISED)
self.__div.pack(expand=1, fill=X)
def update_yourself(self, red, green, blue):
self.__reds.update_yourself(red, green, blue)
self.__greens.update_yourself(red, green, blue)

View File

@ -17,12 +17,14 @@ import marshal
class Switchboard:
def __init__(self, colordb, initfile):
self.__initfile = initfile
self.__colordb = colordb
self.__optiondb = {}
self.__views = []
self.__red = 0
self.__green = 0
self.__blue = 0
self.__canceled = 0
# read the initialization file
fp = None
if initfile:
@ -61,17 +63,18 @@ class Switchboard:
def optiondb(self):
return self.__optiondb
def save_views(self, file):
def save_views(self):
# save the current color
self.__optiondb['RED'] = self.__red
self.__optiondb['GREEN'] = self.__green
self.__optiondb['BLUE'] = self.__blue
for v in self.__views:
v.save_options(self.__optiondb)
if hasattr(v, 'save_options'):
v.save_options(self.__optiondb)
fp = None
try:
try:
fp = open(file, 'w')
fp = open(self.__initfile, 'w')
except IOError:
print 'Cannot write options to file:', file
else:
@ -79,3 +82,14 @@ class Switchboard:
finally:
if fp:
fp.close()
def withdraw_views(self):
for v in self.__views:
if hasattr(v, 'withdraw'):
v.withdraw()
def canceled(self):
self.__canceled = 1
def canceled_p(self):
return self.__canceled

View File

@ -19,17 +19,17 @@ from Tkinter import *
import ColorDB
class TextViewer:
def __init__(self, switchboard, parent=None):
def __init__(self, switchboard, master=None):
self.__sb = switchboard
optiondb = switchboard.optiondb()
root = self.__root = Toplevel(parent, class_='Pynche')
root.protocol('WM_DELETE_WINDOW', self.__withdraw)
root = self.__root = Toplevel(master, class_='Pynche')
root.protocol('WM_DELETE_WINDOW', self.withdraw)
root.title('Pynche Text Window')
root.iconname('Pynche Text Window')
root.bind('<Alt-q>', self.__quit)
root.bind('<Alt-Q>', self.__quit)
root.bind('<Alt-w>', self.__withdraw)
root.bind('<Alt-W>', self.__withdraw)
root.bind('<Alt-w>', self.withdraw)
root.bind('<Alt-W>', self.withdraw)
#
# create the text widget
#
@ -114,7 +114,7 @@ and choosing a color.'''))
def __quit(self, event=None):
self.__root.quit()
def __withdraw(self, event=None):
def withdraw(self, event=None):
self.__root.withdraw()
def deiconify(self, event=None):

View File

@ -17,7 +17,7 @@ import string
import re
class TypeinViewer:
def __init__(self, switchboard, parent=None):
def __init__(self, switchboard, master=None):
# non-gui ivars
self.__sb = switchboard
optiondb = switchboard.optiondb()
@ -26,7 +26,7 @@ class TypeinViewer:
self.__uwtyping = BooleanVar()
self.__uwtyping.set(optiondb.get('UPWHILETYPE', 0))
# create the gui
self.__frame = Frame(parent) #, relief=GROOVE, borderwidth=2)
self.__frame = Frame(master, relief=RAISED, borderwidth=1)
self.__frame.grid(row=3, column=1, sticky='NS')
# Red
self.__xl = Label(self.__frame, text='Red:')

View File

@ -0,0 +1,87 @@
"""Color chooser implementing (almost) the tkColorColor interface
"""
import os
from PyncheWidget import PyncheWidget
import Main
import ColorDB
class Chooser:
"""Ask for a color"""
def __init__(self,
master = None,
initialcolor = None,
databasefile = None,
initfile = None,
ignore = None):
self.__master = master
self.__initialcolor = initialcolor
self.__databasefile = databasefile
self.__initfile = initfile or os.path.expanduser('~/.pynche')
self.__ignore = ignore
self.__pw = None
def show(self):
if not self.__pw:
self.__pw, self.__sb = \
Main.build(master = self.__master,
initialcolor = self.__initialcolor,
initfile = self.__initfile,
ignore = self.__ignore)
Main.run(self.__pw, self.__sb)
rgbtuple = self.__sb.current_rgb()
self.__pw.withdraw()
# check to see if the cancel button was pushed
if self.__sb.canceled_p():
return None, None
colordb = self.__sb.colordb()
# try to return the color name from the database if there is an exact
# match, otherwise use the "#rrggbb" spec. TBD: Forget about color
# aliases for now, maybe later we should return these too.
try:
name = colordb.find_byrgb(rgbtuple)[0]
except ColorDB.BadColor:
name = ColorDB.triplet_to_rrggbb(rgbtuple)
return rgbtuple, name
# convenience stuff
def askcolor(color = None, **options):
"""Ask for a color"""
return apply(Chooser, (), options).show()
# test stuff
if __name__ == '__main__':
class Tester:
def __init__(self):
from Tkinter import *
self.__root = tk = Tk()
b = Button(tk, text='Choose Color...', command=self.__choose)
b.pack()
self.__l = Label(tk)
self.__l.pack()
q = Button(tk, text='Quit', command=self.__quit)
q.pack()
def __choose(self, event=None):
rgb, name = askcolor(master=self.__root)
if rgb is None:
text = 'You hit CANCEL!'
else:
r, g, b = rgb
text = 'You picked %s (%3d/%3d/%3d)' % (name, r, g, b)
self.__l.configure(text=text)
def __quit(self, event=None):
self.__root.quit()
def run(self):
self.__root.mainloop()
t = Tester()
t.run()
# simpler
## print 'color:', askcolor()
## print 'color:', askcolor()