|
|
"""Main Pynche (Pythonically Natural Color and Hue Editor) widget.
|
|
|
|
|
|
This window provides the basic decorations, primarily including the menubar.
|
|
|
It is used to bring up other windows.
|
|
|
"""
|
|
|
|
|
|
import sys
|
|
|
import os
|
|
|
from tkinter import *
|
|
|
from tkinter import messagebox, filedialog
|
|
|
import ColorDB
|
|
|
|
|
|
# Milliseconds between interrupt checks
|
|
|
KEEPALIVE_TIMER = 500
|
|
|
|
|
|
|
|
|
|
|
|
class PyncheWidget:
|
|
|
def __init__(self, version, switchboard, master=None, extrapath=[]):
|
|
|
self.__sb = switchboard
|
|
|
self.__version = version
|
|
|
self.__textwin = None
|
|
|
self.__listwin = None
|
|
|
self.__detailswin = None
|
|
|
self.__helpwin = None
|
|
|
self.__dialogstate = {}
|
|
|
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='Load palette...',
|
|
|
command=self.__load,
|
|
|
underline=0)
|
|
|
if not modal:
|
|
|
filemenu.add_command(label='Quit',
|
|
|
command=self.__quit,
|
|
|
accelerator='Alt-Q',
|
|
|
underline=0)
|
|
|
#
|
|
|
# View menu
|
|
|
#
|
|
|
views = make_view_popups(self.__sb, self.__tkroot, extrapath)
|
|
|
viewmenu = Menu(menubar, tearoff=0)
|
|
|
for v in views:
|
|
|
viewmenu.add_command(label=v.menutext(),
|
|
|
command=v.popup,
|
|
|
underline=v.underline())
|
|
|
#
|
|
|
# Help menu
|
|
|
#
|
|
|
helpmenu = Menu(menubar, name='help', tearoff=0)
|
|
|
helpmenu.add_command(label='About Pynche...',
|
|
|
command=self.__popup_about,
|
|
|
underline=0)
|
|
|
helpmenu.add_command(label='Help...',
|
|
|
command=self.__popup_usage,
|
|
|
underline=0)
|
|
|
#
|
|
|
# Tie them all together
|
|
|
#
|
|
|
menubar.add_cascade(label='File',
|
|
|
menu=filemenu,
|
|
|
underline=0)
|
|
|
menubar.add_cascade(label='View',
|
|
|
menu=viewmenu,
|
|
|
underline=0)
|
|
|
menubar.add_cascade(label='Help',
|
|
|
menu=helpmenu,
|
|
|
underline=0)
|
|
|
|
|
|
# now create the top level window
|
|
|
root = self.__root = Toplevel(tkroot, class_='Pynche', menu=menubar)
|
|
|
root.protocol('WM_DELETE_WINDOW',
|
|
|
modal and self.__bell or self.__quit)
|
|
|
root.title('Pynche %s' % version)
|
|
|
root.iconname('Pynche')
|
|
|
# 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.__tkroot.quit()
|
|
|
|
|
|
def __bell(self, event=None):
|
|
|
self.__tkroot.bell()
|
|
|
|
|
|
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
|
|
|
# through.
|
|
|
self.__tkroot.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
|
|
|
|
|
|
def start(self):
|
|
|
if not self.__modal:
|
|
|
self.__keepalive()
|
|
|
self.__tkroot.mainloop()
|
|
|
|
|
|
def window(self):
|
|
|
return self.__root
|
|
|
|
|
|
def __popup_about(self, event=None):
|
|
|
from Main import __version__
|
|
|
messagebox.showinfo('About Pynche ' + __version__,
|
|
|
'''\
|
|
|
Pynche %s
|
|
|
The PYthonically Natural
|
|
|
Color and Hue Editor
|
|
|
|
|
|
For information
|
|
|
contact: Barry A. Warsaw
|
|
|
email: bwarsaw@python.org''' % __version__)
|
|
|
|
|
|
def __popup_usage(self, event=None):
|
|
|
if not self.__helpwin:
|
|
|
self.__helpwin = Helpwin(self.__root, self.__quit)
|
|
|
self.__helpwin.deiconify()
|
|
|
|
|
|
def __load(self, event=None):
|
|
|
while 1:
|
|
|
idir, ifile = os.path.split(self.__sb.colordb().filename())
|
|
|
file = filedialog.askopenfilename(
|
|
|
filetypes=[('Text files', '*.txt'),
|
|
|
('All files', '*'),
|
|
|
],
|
|
|
initialdir=idir,
|
|
|
initialfile=ifile)
|
|
|
if not file:
|
|
|
# cancel button
|
|
|
return
|
|
|
try:
|
|
|
colordb = ColorDB.get_colordb(file)
|
|
|
except IOError:
|
|
|
messagebox.showerror('Read error', '''\
|
|
|
Could not open file for reading:
|
|
|
%s''' % file)
|
|
|
continue
|
|
|
if colordb is None:
|
|
|
messagebox.showerror('Unrecognized color file type', '''\
|
|
|
Unrecognized color file type in file:
|
|
|
%s''' % file)
|
|
|
continue
|
|
|
break
|
|
|
self.__sb.set_colordb(colordb)
|
|
|
|
|
|
def withdraw(self):
|
|
|
self.__root.withdraw()
|
|
|
|
|
|
def deiconify(self):
|
|
|
self.__root.deiconify()
|
|
|
|
|
|
|
|
|
|
|
|
class Helpwin:
|
|
|
def __init__(self, master, quitfunc):
|
|
|
from Main import docstring
|
|
|
self.__root = root = Toplevel(master, class_='Pynche')
|
|
|
root.protocol('WM_DELETE_WINDOW', self.__withdraw)
|
|
|
root.title('Pynche Help Window')
|
|
|
root.iconname('Pynche Help Window')
|
|
|
root.bind('<Alt-q>', quitfunc)
|
|
|
root.bind('<Alt-Q>', quitfunc)
|
|
|
root.bind('<Alt-w>', self.__withdraw)
|
|
|
root.bind('<Alt-W>', self.__withdraw)
|
|
|
|
|
|
# more elaborate help is available in the README file
|
|
|
readmefile = os.path.join(sys.path[0], 'README')
|
|
|
try:
|
|
|
fp = None
|
|
|
try:
|
|
|
fp = open(readmefile)
|
|
|
contents = fp.read()
|
|
|
# wax the last page, it contains Emacs cruft
|
|
|
i = contents.rfind('\f')
|
|
|
if i > 0:
|
|
|
contents = contents[:i].rstrip()
|
|
|
finally:
|
|
|
if fp:
|
|
|
fp.close()
|
|
|
except IOError:
|
|
|
sys.stderr.write("Couldn't open Pynche's README, "
|
|
|
'using docstring instead.\n')
|
|
|
contents = docstring()
|
|
|
|
|
|
self.__text = text = Text(root, relief=SUNKEN,
|
|
|
width=80, height=24)
|
|
|
self.__text.focus_set()
|
|
|
text.insert(0.0, contents)
|
|
|
scrollbar = Scrollbar(root)
|
|
|
scrollbar.pack(fill=Y, side=RIGHT)
|
|
|
text.pack(fill=BOTH, expand=YES)
|
|
|
text.configure(yscrollcommand=(scrollbar, 'set'))
|
|
|
scrollbar.configure(command=(text, 'yview'))
|
|
|
|
|
|
def __withdraw(self, event=None):
|
|
|
self.__root.withdraw()
|
|
|
|
|
|
def deiconify(self):
|
|
|
self.__root.deiconify()
|
|
|
|
|
|
|
|
|
|
|
|
import functools
|
|
|
@functools.total_ordering
|
|
|
class PopupViewer:
|
|
|
def __init__(self, module, name, switchboard, root):
|
|
|
self.__m = module
|
|
|
self.__name = name
|
|
|
self.__sb = switchboard
|
|
|
self.__root = root
|
|
|
self.__menutext = module.ADDTOVIEW
|
|
|
# find the underline character
|
|
|
underline = module.ADDTOVIEW.find('%')
|
|
|
if underline == -1:
|
|
|
underline = 0
|
|
|
else:
|
|
|
self.__menutext = module.ADDTOVIEW.replace('%', '', 1)
|
|
|
self.__underline = underline
|
|
|
self.__window = None
|
|
|
|
|
|
def menutext(self):
|
|
|
return self.__menutext
|
|
|
|
|
|
def underline(self):
|
|
|
return self.__underline
|
|
|
|
|
|
def popup(self, event=None):
|
|
|
if not self.__window:
|
|
|
# class and module must have the same name
|
|
|
class_ = getattr(self.__m, self.__name)
|
|
|
self.__window = class_(self.__sb, self.__root)
|
|
|
self.__sb.add_view(self.__window)
|
|
|
self.__window.deiconify()
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
return self.__menutext == other.__menutext
|
|
|
|
|
|
def __lt__(self, other):
|
|
|
return self.__menutext < other.__menutext
|
|
|
|
|
|
|
|
|
def make_view_popups(switchboard, root, extrapath):
|
|
|
viewers = []
|
|
|
# where we are in the file system
|
|
|
dirs = [os.path.dirname(__file__)] + extrapath
|
|
|
for dir in dirs:
|
|
|
if dir == '':
|
|
|
dir = '.'
|
|
|
for file in os.listdir(dir):
|
|
|
if file[-9:] == 'Viewer.py':
|
|
|
name = file[:-3]
|
|
|
try:
|
|
|
module = __import__(name)
|
|
|
except ImportError:
|
|
|
# Pynche is running from inside a package, so get the
|
|
|
# module using the explicit path.
|
|
|
pkg = __import__('pynche.'+name)
|
|
|
module = getattr(pkg, name)
|
|
|
if hasattr(module, 'ADDTOVIEW') and module.ADDTOVIEW:
|
|
|
# this is an external viewer
|
|
|
v = PopupViewer(module, name, switchboard, root)
|
|
|
viewers.append(v)
|
|
|
# sort alphabetically
|
|
|
viewers.sort()
|
|
|
return viewers
|