|
1 """Main Pynche (Pythonically Natural Color and Hue Editor) widget. |
|
2 |
|
3 This window provides the basic decorations, primarily including the menubar. |
|
4 It is used to bring up other windows. |
|
5 """ |
|
6 |
|
7 import sys |
|
8 import os |
|
9 from Tkinter import * |
|
10 import tkMessageBox |
|
11 import tkFileDialog |
|
12 import ColorDB |
|
13 |
|
14 # Milliseconds between interrupt checks |
|
15 KEEPALIVE_TIMER = 500 |
|
16 |
|
17 |
|
18 |
|
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) |
|
53 # |
|
54 # File menu |
|
55 # |
|
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) |
|
65 # |
|
66 # View menu |
|
67 # |
|
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()) |
|
74 # |
|
75 # Help menu |
|
76 # |
|
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) |
|
84 # |
|
85 # Tie them all together |
|
86 # |
|
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) |
|
96 |
|
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) |
|
122 |
|
123 def __quit(self, event=None): |
|
124 self.__tkroot.quit() |
|
125 |
|
126 def __bell(self, event=None): |
|
127 self.__tkroot.bell() |
|
128 |
|
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() |
|
137 |
|
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) |
|
142 |
|
143 def start(self): |
|
144 if not self.__modal: |
|
145 self.__keepalive() |
|
146 self.__tkroot.mainloop() |
|
147 |
|
148 def window(self): |
|
149 return self.__root |
|
150 |
|
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 |
|
158 |
|
159 For information |
|
160 contact: Barry A. Warsaw |
|
161 email: bwarsaw@python.org''' % __version__) |
|
162 |
|
163 def __popup_usage(self, event=None): |
|
164 if not self.__helpwin: |
|
165 self.__helpwin = Helpwin(self.__root, self.__quit) |
|
166 self.__helpwin.deiconify() |
|
167 |
|
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', '*'), |
|
174 ], |
|
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) |
|
194 |
|
195 def withdraw(self): |
|
196 self.__root.withdraw() |
|
197 |
|
198 def deiconify(self): |
|
199 self.__root.deiconify() |
|
200 |
|
201 |
|
202 |
|
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) |
|
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 |
|
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() |
|
233 |
|
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')) |
|
243 |
|
244 def __withdraw(self, event=None): |
|
245 self.__root.withdraw() |
|
246 |
|
247 def deiconify(self): |
|
248 self.__root.deiconify() |
|
249 |
|
250 |
|
251 |
|
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 |
|
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 |
|
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 |