blob: a7a0666ee9addd243b26ddba3610fb8fe87f0f90 [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
9import string
Barry Warsawfefbf791998-01-31 23:39:01 +000010from Tkinter import *
Barry Warsawa9b45581998-09-29 20:03:53 +000011import tkMessageBox
Barry Warsaw33699271999-04-27 19:51:55 +000012import tkFileDialog
13import ColorDB
Barry Warsawa69c1ba1998-09-28 23:38:44 +000014
15# Milliseconds between interrupt checks
16KEEPALIVE_TIMER = 500
Barry Warsawfefbf791998-01-31 23:39:01 +000017
Barry Warsaw552ac171998-02-17 22:25:23 +000018
19
Barry Warsawa69c1ba1998-09-28 23:38:44 +000020class PyncheWidget:
Barry Warsaw9af697b1999-04-27 18:55:48 +000021 def __init__(self, version, switchboard, master=None, extrapath=[]):
Barry Warsaw86daeb71998-10-01 16:46:16 +000022 self.__sb = switchboard
23 self.__version = version
24 self.__textwin = None
Barry Warsawbd36d6e1998-10-02 16:05:48 +000025 self.__listwin = None
Barry Warsaw9044b8e1998-10-05 21:14:46 +000026 self.__detailswin = None
Barry Warsawce0bbd21998-12-03 19:50:24 +000027 self.__helpwin = None
Barry Warsaw0604d721999-04-26 23:17:16 +000028 self.__dialogstate = {}
Barry Warsawca07ba01998-10-22 03:25:59 +000029 modal = self.__modal = not not master
30 # If a master was given, we are running as a modal dialog servant to
31 # some other application. We rearrange our UI in this case (there's
32 # no File menu and we get `Okay' and `Cancel' buttons), and we do a
33 # grab_set() to make ourselves modal
34 if modal:
35 self.__tkroot = tkroot = Toplevel(master, class_='Pynche')
36 tkroot.grab_set()
37 tkroot.withdraw()
38 else:
39 # Is there already a default root for Tk, say because we're
40 # running under Guido's IDE? :-) Two conditions say no, either the
41 # import fails or _default_root is None.
42 tkroot = None
43 try:
44 from Tkinter import _default_root
45 tkroot = self.__tkroot = _default_root
46 except ImportError:
47 pass
48 if not tkroot:
49 tkroot = self.__tkroot = Tk(className='Pynche')
50 # but this isn't our top level widget, so make it invisible
51 tkroot.withdraw()
Barry Warsawa69c1ba1998-09-28 23:38:44 +000052 # create the menubar
Barry Warsaw38365031998-10-06 19:39:34 +000053 menubar = self.__menubar = Menu(tkroot)
Barry Warsawa69c1ba1998-09-28 23:38:44 +000054 #
55 # File menu
56 #
Barry Warsaw0604d721999-04-26 23:17:16 +000057 filemenu = self.__filemenu = Menu(menubar, tearoff=0)
58 filemenu.add_command(label='Load palette...',
59 command=self.__load,
60 underline=0)
Barry Warsawca07ba01998-10-22 03:25:59 +000061 if not modal:
Barry Warsawca07ba01998-10-22 03:25:59 +000062 filemenu.add_command(label='Quit',
63 command=self.__quit,
64 accelerator='Alt-Q',
65 underline=0)
Barry Warsawa69c1ba1998-09-28 23:38:44 +000066 #
Barry Warsaw86daeb71998-10-01 16:46:16 +000067 # View menu
68 #
Barry Warsaw9af697b1999-04-27 18:55:48 +000069 views = make_view_popups(self.__sb, self.__tkroot, extrapath)
Barry Warsaw38365031998-10-06 19:39:34 +000070 viewmenu = Menu(menubar, tearoff=0)
Barry Warsaw9af697b1999-04-27 18:55:48 +000071 for v in views:
72 viewmenu.add_command(label=v.menutext(),
73 command=v.popup,
74 underline=v.underline())
Barry Warsaw38365031998-10-06 19:39:34 +000075 #
Barry Warsawa69c1ba1998-09-28 23:38:44 +000076 # Help menu
77 #
Barry Warsaw38365031998-10-06 19:39:34 +000078 helpmenu = Menu(menubar, name='help', tearoff=0)
Barry Warsaw8a09e1c1998-10-20 20:45:46 +000079 helpmenu.add_command(label='About Pynche...',
Barry Warsawa69c1ba1998-09-28 23:38:44 +000080 command=self.__popup_about,
81 underline=0)
Barry Warsawce0bbd21998-12-03 19:50:24 +000082 helpmenu.add_command(label='Help...',
83 command=self.__popup_usage,
84 underline=0)
Barry Warsaw38365031998-10-06 19:39:34 +000085 #
86 # Tie them all together
87 #
Barry Warsaw6e7f6ea1999-04-26 23:36:47 +000088 menubar.add_cascade(label='File',
89 menu=filemenu,
90 underline=0)
Barry Warsaw38365031998-10-06 19:39:34 +000091 menubar.add_cascade(label='View',
92 menu=viewmenu,
93 underline=0)
94 menubar.add_cascade(label='Help',
95 menu=helpmenu,
96 underline=0)
97
98 # now create the top level window
99 root = self.__root = Toplevel(tkroot, class_='Pynche', menu=menubar)
Barry Warsawca07ba01998-10-22 03:25:59 +0000100 root.protocol('WM_DELETE_WINDOW',
Barry Warsawd9e52141998-10-22 18:46:28 +0000101 modal and self.__bell or self.__quit)
Barry Warsaw38365031998-10-06 19:39:34 +0000102 root.title('Pynche %s' % version)
103 root.iconname('Pynche')
Barry Warsawca07ba01998-10-22 03:25:59 +0000104 # Only bind accelerators for the File->Quit menu item if running as a
105 # standalone app
106 if not modal:
107 root.bind('<Alt-q>', self.__quit)
108 root.bind('<Alt-Q>', self.__quit)
109 else:
110 # We're a modal dialog so we have a new row of buttons
111 bframe = Frame(root, borderwidth=1, relief=RAISED)
112 bframe.grid(row=4, column=0, columnspan=2,
113 sticky='EW',
114 ipady=5)
115 okay = Button(bframe,
116 text='Okay',
117 command=self.__okay)
118 okay.pack(side=LEFT, expand=1)
119 cancel = Button(bframe,
120 text='Cancel',
121 command=self.__cancel)
122 cancel.pack(side=LEFT, expand=1)
Barry Warsawe7f4a471998-10-06 19:50:33 +0000123
124 def __quit(self, event=None):
Barry Warsawca07ba01998-10-22 03:25:59 +0000125 self.__tkroot.quit()
126
Barry Warsawd9e52141998-10-22 18:46:28 +0000127 def __bell(self, event=None):
128 self.__tkroot.bell()
Barry Warsawca07ba01998-10-22 03:25:59 +0000129
130 def __okay(self, event=None):
131 self.__sb.withdraw_views()
132 self.__tkroot.grab_release()
133 self.__quit()
134
135 def __cancel(self, event=None):
136 self.__sb.canceled()
137 self.__okay()
Barry Warsaw552ac171998-02-17 22:25:23 +0000138
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000139 def __keepalive(self):
140 # Exercise the Python interpreter regularly so keyboard interrupts get
141 # through.
Barry Warsaw38365031998-10-06 19:39:34 +0000142 self.__tkroot.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
Barry Warsaw552ac171998-02-17 22:25:23 +0000143
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000144 def start(self):
Barry Warsawca07ba01998-10-22 03:25:59 +0000145 if not self.__modal:
146 self.__keepalive()
Barry Warsaw38365031998-10-06 19:39:34 +0000147 self.__tkroot.mainloop()
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000148
Barry Warsawca07ba01998-10-22 03:25:59 +0000149 def window(self):
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000150 return self.__root
Barry Warsawf67575d1998-03-10 00:17:01 +0000151
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000152 def __popup_about(self, event=None):
Barry Warsaw0926dea1998-10-07 03:36:58 +0000153 from Main import __version__
Barry Warsawad3a67c1998-10-06 18:52:59 +0000154 tkMessageBox.showinfo('About Pynche ' + __version__,
Barry Warsawa9b45581998-09-29 20:03:53 +0000155 '''\
Barry Warsawad3a67c1998-10-06 18:52:59 +0000156Pynche %s
157The PYthonically Natural
158Color and Hue Editor
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000159
Barry Warsaw64039911998-12-15 01:02:51 +0000160For information
161contact: Barry A. Warsaw
162email: bwarsaw@python.org''' % __version__)
Barry Warsaw86daeb71998-10-01 16:46:16 +0000163
Barry Warsawce0bbd21998-12-03 19:50:24 +0000164 def __popup_usage(self, event=None):
165 if not self.__helpwin:
166 self.__helpwin = Helpwin(self.__root, self.__quit)
167 self.__helpwin.deiconify()
168
Barry Warsaw0604d721999-04-26 23:17:16 +0000169 def __load(self, event=None):
Barry Warsaw0604d721999-04-26 23:17:16 +0000170 while 1:
Barry Warsaw33699271999-04-27 19:51:55 +0000171 idir, ifile = os.path.split(self.__sb.colordb().filename())
172 file = tkFileDialog.askopenfilename(
173 filetypes=[('Text files', '*.txt'),
174 ('All files', '*'),
175 ],
176 initialdir=idir,
177 initialfile=ifile)
178 if not file:
Barry Warsaw0604d721999-04-26 23:17:16 +0000179 # cancel button
180 return
181 try:
182 colordb = ColorDB.get_colordb(file)
183 except IOError:
184 tkMessageBox.showerror('Read error', '''\
185Could not open file for reading:
186%s''' % file)
187 continue
188 if colordb is None:
189 tkMessageBox.showerror('Unrecognized color file type', '''\
190Unrecognized color file type in file:
191%s''' % file)
192 continue
193 break
194 self.__sb.set_colordb(colordb)
Barry Warsaw0604d721999-04-26 23:17:16 +0000195
Barry Warsawca07ba01998-10-22 03:25:59 +0000196 def withdraw(self):
197 self.__root.withdraw()
Barry Warsawd9e52141998-10-22 18:46:28 +0000198
199 def deiconify(self):
200 self.__root.deiconify()
Barry Warsawce0bbd21998-12-03 19:50:24 +0000201
202
203
204class Helpwin:
205 def __init__(self, master, quitfunc):
Barry Warsaw6a552262001-04-18 03:50:07 +0000206 from Main import docstring
Barry Warsawce0bbd21998-12-03 19:50:24 +0000207 self.__root = root = Toplevel(master, class_='Pynche')
208 root.protocol('WM_DELETE_WINDOW', self.__withdraw)
209 root.title('Pynche Help Window')
210 root.iconname('Pynche Help Window')
211 root.bind('<Alt-q>', quitfunc)
212 root.bind('<Alt-Q>', quitfunc)
213 root.bind('<Alt-w>', self.__withdraw)
214 root.bind('<Alt-W>', self.__withdraw)
215
216 # more elaborate help is available in the README file
217 readmefile = os.path.join(sys.path[0], 'README')
218 try:
219 fp = None
220 try:
221 fp = open(readmefile)
222 contents = fp.read()
223 # wax the last page, it contains Emacs cruft
224 i = string.rfind(contents, '\f')
225 if i > 0:
226 contents = string.rstrip(contents[:i])
227 finally:
228 if fp:
229 fp.close()
230 except IOError:
231 sys.stderr.write("Couldn't open Pynche's README, "
232 'using docstring instead.\n')
233 contents = docstring()
234
235 self.__text = text = Text(root, relief=SUNKEN,
236 width=80, height=24)
Barry Warsawecb1a651999-03-26 16:11:40 +0000237 self.__text.focus_set()
Barry Warsawce0bbd21998-12-03 19:50:24 +0000238 text.insert(0.0, contents)
239 scrollbar = Scrollbar(root)
240 scrollbar.pack(fill=Y, side=RIGHT)
241 text.pack(fill=BOTH, expand=YES)
242 text.configure(yscrollcommand=(scrollbar, 'set'))
243 scrollbar.configure(command=(text, 'yview'))
244
245 def __withdraw(self, event=None):
246 self.__root.withdraw()
247
248 def deiconify(self):
249 self.__root.deiconify()
Barry Warsaw9af697b1999-04-27 18:55:48 +0000250
251
252
253class PopupViewer:
254 def __init__(self, module, name, switchboard, root):
255 self.__m = module
256 self.__name = name
257 self.__sb = switchboard
258 self.__root = root
259 self.__menutext = module.ADDTOVIEW
260 # find the underline character
261 underline = string.find(module.ADDTOVIEW, '%')
262 if underline == -1:
263 underline = 0
264 else:
265 self.__menutext = string.replace(module.ADDTOVIEW, '%', '', 1)
266 self.__underline = underline
267 self.__window = None
268
269 def menutext(self):
270 return self.__menutext
271
272 def underline(self):
273 return self.__underline
274
275 def popup(self, event=None):
276 if not self.__window:
277 # class and module must have the same name
278 class_ = getattr(self.__m, self.__name)
279 self.__window = class_(self.__sb, self.__root)
280 self.__sb.add_view(self.__window)
281 self.__window.deiconify()
282
283 def __cmp__(self, other):
284 return cmp(self.__menutext, other.__menutext)
285
286
287def make_view_popups(switchboard, root, extrapath):
288 viewers = []
289 # where we are in the file system
290 dirs = [os.path.dirname(__file__)] + extrapath
291 for dir in dirs:
292 if dir == '':
293 dir = '.'
294 for file in os.listdir(dir):
295 if file[-9:] == 'Viewer.py':
296 name = file[:-3]
Barry Warsaw17a8b5d1999-07-06 22:00:52 +0000297 try:
298 module = __import__(name)
299 except ImportError:
300 # Pynche is running from inside a package, so get the
301 # module using the explicit path.
302 pkg = __import__('pynche.'+name)
303 module = getattr(pkg, name)
Barry Warsaw9af697b1999-04-27 18:55:48 +0000304 if hasattr(module, 'ADDTOVIEW') and module.ADDTOVIEW:
305 # this is an external viewer
306 v = PopupViewer(module, name, switchboard, root)
307 viewers.append(v)
308 # sort alphabetically
309 viewers.sort()
310 return viewers