blob: fcfe7ce6c1f3bbbd0984b055031dd90c29b5ae73 [file] [log] [blame]
Barry Warsawfefbf791998-01-31 23:39:01 +00001"""Main Pynche (Pythonically Natural Color and Hue Editor) widget.
Barry Warsaweb61fbd1998-10-02 16:06:27 +00002
3This window provides the basic decorations, primarily including the menubar.
4It is used to bring up other windows.
Barry Warsawfefbf791998-01-31 23:39:01 +00005"""
6
Barry Warsawce0bbd21998-12-03 19:50:24 +00007import sys
8import os
Barry Warsawfefbf791998-01-31 23:39:01 +00009from Tkinter import *
Barry Warsawa9b45581998-09-29 20:03:53 +000010import tkMessageBox
Barry Warsaw33699271999-04-27 19:51:55 +000011import tkFileDialog
12import ColorDB
Barry Warsawa69c1ba1998-09-28 23:38:44 +000013
14# Milliseconds between interrupt checks
15KEEPALIVE_TIMER = 500
Barry Warsawfefbf791998-01-31 23:39:01 +000016
Barry Warsaw552ac171998-02-17 22:25:23 +000017
18
Barry Warsawa69c1ba1998-09-28 23:38:44 +000019class PyncheWidget:
Barry Warsaw9af697b1999-04-27 18:55:48 +000020 def __init__(self, version, switchboard, master=None, extrapath=[]):
Barry Warsaw86daeb71998-10-01 16:46:16 +000021 self.__sb = switchboard
22 self.__version = version
23 self.__textwin = None
Barry Warsawbd36d6e1998-10-02 16:05:48 +000024 self.__listwin = None
Barry Warsaw9044b8e1998-10-05 21:14:46 +000025 self.__detailswin = None
Barry Warsawce0bbd21998-12-03 19:50:24 +000026 self.__helpwin = None
Barry Warsaw0604d721999-04-26 23:17:16 +000027 self.__dialogstate = {}
Barry Warsawca07ba01998-10-22 03:25:59 +000028 modal = self.__modal = not not master
29 # If a master was given, we are running as a modal dialog servant to
30 # some other application. We rearrange our UI in this case (there's
31 # no File menu and we get `Okay' and `Cancel' buttons), and we do a
32 # grab_set() to make ourselves modal
33 if modal:
34 self.__tkroot = tkroot = Toplevel(master, class_='Pynche')
35 tkroot.grab_set()
36 tkroot.withdraw()
37 else:
38 # Is there already a default root for Tk, say because we're
39 # running under Guido's IDE? :-) Two conditions say no, either the
40 # import fails or _default_root is None.
41 tkroot = None
42 try:
43 from Tkinter import _default_root
44 tkroot = self.__tkroot = _default_root
45 except ImportError:
46 pass
47 if not tkroot:
48 tkroot = self.__tkroot = Tk(className='Pynche')
49 # but this isn't our top level widget, so make it invisible
50 tkroot.withdraw()
Barry Warsawa69c1ba1998-09-28 23:38:44 +000051 # create the menubar
Barry Warsaw38365031998-10-06 19:39:34 +000052 menubar = self.__menubar = Menu(tkroot)
Barry Warsawa69c1ba1998-09-28 23:38:44 +000053 #
54 # File menu
55 #
Barry Warsaw0604d721999-04-26 23:17:16 +000056 filemenu = self.__filemenu = Menu(menubar, tearoff=0)
57 filemenu.add_command(label='Load palette...',
58 command=self.__load,
59 underline=0)
Barry Warsawca07ba01998-10-22 03:25:59 +000060 if not modal:
Barry Warsawca07ba01998-10-22 03:25:59 +000061 filemenu.add_command(label='Quit',
62 command=self.__quit,
63 accelerator='Alt-Q',
64 underline=0)
Barry Warsawa69c1ba1998-09-28 23:38:44 +000065 #
Barry Warsaw86daeb71998-10-01 16:46:16 +000066 # View menu
67 #
Barry Warsaw9af697b1999-04-27 18:55:48 +000068 views = make_view_popups(self.__sb, self.__tkroot, extrapath)
Barry Warsaw38365031998-10-06 19:39:34 +000069 viewmenu = Menu(menubar, tearoff=0)
Barry Warsaw9af697b1999-04-27 18:55:48 +000070 for v in views:
71 viewmenu.add_command(label=v.menutext(),
72 command=v.popup,
73 underline=v.underline())
Barry Warsaw38365031998-10-06 19:39:34 +000074 #
Barry Warsawa69c1ba1998-09-28 23:38:44 +000075 # Help menu
76 #
Barry Warsaw38365031998-10-06 19:39:34 +000077 helpmenu = Menu(menubar, name='help', tearoff=0)
Martin v. Löwis23b44a32003-10-24 20:09:23 +000078 helpmenu.add_command(label='About Pynche...',
Barry Warsawa69c1ba1998-09-28 23:38:44 +000079 command=self.__popup_about,
80 underline=0)
Barry Warsawce0bbd21998-12-03 19:50:24 +000081 helpmenu.add_command(label='Help...',
82 command=self.__popup_usage,
83 underline=0)
Barry Warsaw38365031998-10-06 19:39:34 +000084 #
85 # Tie them all together
86 #
Barry Warsaw6e7f6ea1999-04-26 23:36:47 +000087 menubar.add_cascade(label='File',
88 menu=filemenu,
89 underline=0)
Barry Warsaw38365031998-10-06 19:39:34 +000090 menubar.add_cascade(label='View',
91 menu=viewmenu,
92 underline=0)
93 menubar.add_cascade(label='Help',
94 menu=helpmenu,
95 underline=0)
96
97 # now create the top level window
98 root = self.__root = Toplevel(tkroot, class_='Pynche', menu=menubar)
Barry Warsawca07ba01998-10-22 03:25:59 +000099 root.protocol('WM_DELETE_WINDOW',
Barry Warsawd9e52141998-10-22 18:46:28 +0000100 modal and self.__bell or self.__quit)
Barry Warsaw38365031998-10-06 19:39:34 +0000101 root.title('Pynche %s' % version)
102 root.iconname('Pynche')
Barry Warsawca07ba01998-10-22 03:25:59 +0000103 # Only bind accelerators for the File->Quit menu item if running as a
104 # standalone app
105 if not modal:
106 root.bind('<Alt-q>', self.__quit)
107 root.bind('<Alt-Q>', self.__quit)
108 else:
109 # We're a modal dialog so we have a new row of buttons
110 bframe = Frame(root, borderwidth=1, relief=RAISED)
111 bframe.grid(row=4, column=0, columnspan=2,
112 sticky='EW',
113 ipady=5)
114 okay = Button(bframe,
115 text='Okay',
116 command=self.__okay)
117 okay.pack(side=LEFT, expand=1)
118 cancel = Button(bframe,
119 text='Cancel',
120 command=self.__cancel)
121 cancel.pack(side=LEFT, expand=1)
Barry Warsawe7f4a471998-10-06 19:50:33 +0000122
123 def __quit(self, event=None):
Barry Warsawca07ba01998-10-22 03:25:59 +0000124 self.__tkroot.quit()
125
Barry Warsawd9e52141998-10-22 18:46:28 +0000126 def __bell(self, event=None):
127 self.__tkroot.bell()
Barry Warsawca07ba01998-10-22 03:25:59 +0000128
129 def __okay(self, event=None):
130 self.__sb.withdraw_views()
131 self.__tkroot.grab_release()
132 self.__quit()
133
134 def __cancel(self, event=None):
135 self.__sb.canceled()
136 self.__okay()
Barry Warsaw552ac171998-02-17 22:25:23 +0000137
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000138 def __keepalive(self):
139 # Exercise the Python interpreter regularly so keyboard interrupts get
140 # through.
Barry Warsaw38365031998-10-06 19:39:34 +0000141 self.__tkroot.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
Barry Warsaw552ac171998-02-17 22:25:23 +0000142
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000143 def start(self):
Barry Warsawca07ba01998-10-22 03:25:59 +0000144 if not self.__modal:
145 self.__keepalive()
Barry Warsaw38365031998-10-06 19:39:34 +0000146 self.__tkroot.mainloop()
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000147
Barry Warsawca07ba01998-10-22 03:25:59 +0000148 def window(self):
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000149 return self.__root
Barry Warsawf67575d1998-03-10 00:17:01 +0000150
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000151 def __popup_about(self, event=None):
Barry Warsaw0926dea1998-10-07 03:36:58 +0000152 from Main import __version__
Barry Warsawad3a67c1998-10-06 18:52:59 +0000153 tkMessageBox.showinfo('About Pynche ' + __version__,
Barry Warsawa9b45581998-09-29 20:03:53 +0000154 '''\
Barry Warsawad3a67c1998-10-06 18:52:59 +0000155Pynche %s
156The PYthonically Natural
157Color and Hue Editor
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000158
Barry Warsaw64039911998-12-15 01:02:51 +0000159For information
160contact: Barry A. Warsaw
161email: bwarsaw@python.org''' % __version__)
Barry Warsaw86daeb71998-10-01 16:46:16 +0000162
Barry Warsawce0bbd21998-12-03 19:50:24 +0000163 def __popup_usage(self, event=None):
164 if not self.__helpwin:
165 self.__helpwin = Helpwin(self.__root, self.__quit)
166 self.__helpwin.deiconify()
167
Barry Warsaw0604d721999-04-26 23:17:16 +0000168 def __load(self, event=None):
Barry Warsaw0604d721999-04-26 23:17:16 +0000169 while 1:
Barry Warsaw33699271999-04-27 19:51:55 +0000170 idir, ifile = os.path.split(self.__sb.colordb().filename())
171 file = tkFileDialog.askopenfilename(
172 filetypes=[('Text files', '*.txt'),
173 ('All files', '*'),
174 ],
175 initialdir=idir,
176 initialfile=ifile)
177 if not file:
Barry Warsaw0604d721999-04-26 23:17:16 +0000178 # cancel button
179 return
180 try:
181 colordb = ColorDB.get_colordb(file)
182 except IOError:
183 tkMessageBox.showerror('Read error', '''\
184Could not open file for reading:
185%s''' % file)
186 continue
187 if colordb is None:
188 tkMessageBox.showerror('Unrecognized color file type', '''\
189Unrecognized color file type in file:
190%s''' % file)
191 continue
192 break
193 self.__sb.set_colordb(colordb)
Barry Warsaw0604d721999-04-26 23:17:16 +0000194
Barry Warsawca07ba01998-10-22 03:25:59 +0000195 def withdraw(self):
196 self.__root.withdraw()
Barry Warsawd9e52141998-10-22 18:46:28 +0000197
198 def deiconify(self):
199 self.__root.deiconify()
Barry Warsawce0bbd21998-12-03 19:50:24 +0000200
201
202
203class Helpwin:
204 def __init__(self, master, quitfunc):
Barry Warsaw6a552262001-04-18 03:50:07 +0000205 from Main import docstring
Barry Warsawce0bbd21998-12-03 19:50:24 +0000206 self.__root = root = Toplevel(master, class_='Pynche')
207 root.protocol('WM_DELETE_WINDOW', self.__withdraw)
208 root.title('Pynche Help Window')
209 root.iconname('Pynche Help Window')
210 root.bind('<Alt-q>', quitfunc)
211 root.bind('<Alt-Q>', quitfunc)
212 root.bind('<Alt-w>', self.__withdraw)
213 root.bind('<Alt-W>', self.__withdraw)
214
215 # more elaborate help is available in the README file
216 readmefile = os.path.join(sys.path[0], 'README')
217 try:
218 fp = None
219 try:
220 fp = open(readmefile)
221 contents = fp.read()
222 # wax the last page, it contains Emacs cruft
Barry Warsaw8e4fa072001-07-10 21:44:24 +0000223 i = contents.rfind('\f')
Barry Warsawce0bbd21998-12-03 19:50:24 +0000224 if i > 0:
Barry Warsaw8e4fa072001-07-10 21:44:24 +0000225 contents = contents[:i].rstrip()
Barry Warsawce0bbd21998-12-03 19:50:24 +0000226 finally:
227 if fp:
228 fp.close()
229 except IOError:
230 sys.stderr.write("Couldn't open Pynche's README, "
231 'using docstring instead.\n')
232 contents = docstring()
233
234 self.__text = text = Text(root, relief=SUNKEN,
235 width=80, height=24)
Barry Warsawecb1a651999-03-26 16:11:40 +0000236 self.__text.focus_set()
Barry Warsawce0bbd21998-12-03 19:50:24 +0000237 text.insert(0.0, contents)
238 scrollbar = Scrollbar(root)
239 scrollbar.pack(fill=Y, side=RIGHT)
240 text.pack(fill=BOTH, expand=YES)
241 text.configure(yscrollcommand=(scrollbar, 'set'))
242 scrollbar.configure(command=(text, 'yview'))
243
244 def __withdraw(self, event=None):
245 self.__root.withdraw()
246
247 def deiconify(self):
248 self.__root.deiconify()
Barry Warsaw9af697b1999-04-27 18:55:48 +0000249
250
251
252class PopupViewer:
253 def __init__(self, module, name, switchboard, root):
254 self.__m = module
255 self.__name = name
256 self.__sb = switchboard
257 self.__root = root
258 self.__menutext = module.ADDTOVIEW
259 # find the underline character
Barry Warsaw8e4fa072001-07-10 21:44:24 +0000260 underline = module.ADDTOVIEW.find('%')
Barry Warsaw9af697b1999-04-27 18:55:48 +0000261 if underline == -1:
262 underline = 0
263 else:
Barry Warsaw8e4fa072001-07-10 21:44:24 +0000264 self.__menutext = module.ADDTOVIEW.replace('%', '', 1)
Barry Warsaw9af697b1999-04-27 18:55:48 +0000265 self.__underline = underline
266 self.__window = None
267
268 def menutext(self):
269 return self.__menutext
270
271 def underline(self):
272 return self.__underline
273
274 def popup(self, event=None):
275 if not self.__window:
276 # class and module must have the same name
277 class_ = getattr(self.__m, self.__name)
278 self.__window = class_(self.__sb, self.__root)
279 self.__sb.add_view(self.__window)
280 self.__window.deiconify()
281
282 def __cmp__(self, other):
283 return cmp(self.__menutext, other.__menutext)
284
285
286def make_view_popups(switchboard, root, extrapath):
287 viewers = []
288 # where we are in the file system
289 dirs = [os.path.dirname(__file__)] + extrapath
290 for dir in dirs:
291 if dir == '':
292 dir = '.'
293 for file in os.listdir(dir):
294 if file[-9:] == 'Viewer.py':
295 name = file[:-3]
Barry Warsaw17a8b5d1999-07-06 22:00:52 +0000296 try:
297 module = __import__(name)
298 except ImportError:
299 # Pynche is running from inside a package, so get the
300 # module using the explicit path.
301 pkg = __import__('pynche.'+name)
302 module = getattr(pkg, name)
Barry Warsaw9af697b1999-04-27 18:55:48 +0000303 if hasattr(module, 'ADDTOVIEW') and module.ADDTOVIEW:
304 # this is an external viewer
305 v = PopupViewer(module, name, switchboard, root)
306 viewers.append(v)
307 # sort alphabetically
308 viewers.sort()
309 return viewers