Fix issue number in comment.
[python.git] / Tools / pynche / PyncheWidget.py
blobfcfe7ce6c1f3bbbd0984b055031dd90c29b5ae73
1 """Main Pynche (Pythonically Natural Color and Hue Editor) widget.
3 This window provides the basic decorations, primarily including the menubar.
4 It is used to bring up other windows.
5 """
7 import sys
8 import os
9 from Tkinter import *
10 import tkMessageBox
11 import tkFileDialog
12 import ColorDB
14 # Milliseconds between interrupt checks
15 KEEPALIVE_TIMER = 500
19 class PyncheWidget:
20 def __init__(self, version, switchboard, master=None, extrapath=[]):
21 self.__sb = switchboard
22 self.__version = version
23 self.__textwin = None
24 self.__listwin = None
25 self.__detailswin = None
26 self.__helpwin = None
27 self.__dialogstate = {}
28 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()
51 # create the menubar
52 menubar = self.__menubar = Menu(tkroot)
54 # File menu
56 filemenu = self.__filemenu = Menu(menubar, tearoff=0)
57 filemenu.add_command(label='Load palette...',
58 command=self.__load,
59 underline=0)
60 if not modal:
61 filemenu.add_command(label='Quit',
62 command=self.__quit,
63 accelerator='Alt-Q',
64 underline=0)
66 # View menu
68 views = make_view_popups(self.__sb, self.__tkroot, extrapath)
69 viewmenu = Menu(menubar, tearoff=0)
70 for v in views:
71 viewmenu.add_command(label=v.menutext(),
72 command=v.popup,
73 underline=v.underline())
75 # Help menu
77 helpmenu = Menu(menubar, name='help', tearoff=0)
78 helpmenu.add_command(label='About Pynche...',
79 command=self.__popup_about,
80 underline=0)
81 helpmenu.add_command(label='Help...',
82 command=self.__popup_usage,
83 underline=0)
85 # Tie them all together
87 menubar.add_cascade(label='File',
88 menu=filemenu,
89 underline=0)
90 menubar.add_cascade(label='View',
91 menu=viewmenu,
92 underline=0)
93 menubar.add_cascade(label='Help',
94 menu=helpmenu,
95 underline=0)
97 # now create the top level window
98 root = self.__root = Toplevel(tkroot, class_='Pynche', menu=menubar)
99 root.protocol('WM_DELETE_WINDOW',
100 modal and self.__bell or self.__quit)
101 root.title('Pynche %s' % version)
102 root.iconname('Pynche')
103 # 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)
123 def __quit(self, event=None):
124 self.__tkroot.quit()
126 def __bell(self, event=None):
127 self.__tkroot.bell()
129 def __okay(self, event=None):
130 self.__sb.withdraw_views()
131 self.__tkroot.grab_release()
132 self.__quit()
134 def __cancel(self, event=None):
135 self.__sb.canceled()
136 self.__okay()
138 def __keepalive(self):
139 # Exercise the Python interpreter regularly so keyboard interrupts get
140 # through.
141 self.__tkroot.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
143 def start(self):
144 if not self.__modal:
145 self.__keepalive()
146 self.__tkroot.mainloop()
148 def window(self):
149 return self.__root
151 def __popup_about(self, event=None):
152 from Main import __version__
153 tkMessageBox.showinfo('About Pynche ' + __version__,
154 '''\
155 Pynche %s
156 The PYthonically Natural
157 Color and Hue Editor
159 For information
160 contact: Barry A. Warsaw
161 email: bwarsaw@python.org''' % __version__)
163 def __popup_usage(self, event=None):
164 if not self.__helpwin:
165 self.__helpwin = Helpwin(self.__root, self.__quit)
166 self.__helpwin.deiconify()
168 def __load(self, event=None):
169 while 1:
170 idir, ifile = os.path.split(self.__sb.colordb().filename())
171 file = tkFileDialog.askopenfilename(
172 filetypes=[('Text files', '*.txt'),
173 ('All files', '*'),
175 initialdir=idir,
176 initialfile=ifile)
177 if not file:
178 # cancel button
179 return
180 try:
181 colordb = ColorDB.get_colordb(file)
182 except IOError:
183 tkMessageBox.showerror('Read error', '''\
184 Could not open file for reading:
185 %s''' % file)
186 continue
187 if colordb is None:
188 tkMessageBox.showerror('Unrecognized color file type', '''\
189 Unrecognized color file type in file:
190 %s''' % file)
191 continue
192 break
193 self.__sb.set_colordb(colordb)
195 def withdraw(self):
196 self.__root.withdraw()
198 def deiconify(self):
199 self.__root.deiconify()
203 class Helpwin:
204 def __init__(self, master, quitfunc):
205 from Main import docstring
206 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)
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
223 i = contents.rfind('\f')
224 if i > 0:
225 contents = contents[:i].rstrip()
226 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()
234 self.__text = text = Text(root, relief=SUNKEN,
235 width=80, height=24)
236 self.__text.focus_set()
237 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'))
244 def __withdraw(self, event=None):
245 self.__root.withdraw()
247 def deiconify(self):
248 self.__root.deiconify()
252 class 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
260 underline = module.ADDTOVIEW.find('%')
261 if underline == -1:
262 underline = 0
263 else:
264 self.__menutext = module.ADDTOVIEW.replace('%', '', 1)
265 self.__underline = underline
266 self.__window = None
268 def menutext(self):
269 return self.__menutext
271 def underline(self):
272 return self.__underline
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()
282 def __cmp__(self, other):
283 return cmp(self.__menutext, other.__menutext)
286 def 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]
296 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)
303 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