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 |
|
---|
20 | class PyncheWidget:
|
---|
21 | def __init__(self, version, switchboard, master=None, extrapath=[]):
|
---|
22 | self.__sb = switchboard
|
---|
23 | self.__version = version
|
---|
24 | self.__textwin = None
|
---|
25 | self.__listwin = None
|
---|
26 | self.__detailswin = None
|
---|
27 | self.__helpwin = None
|
---|
28 | self.__dialogstate = {}
|
---|
29 | 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()
|
---|
52 | # create the menubar
|
---|
53 | menubar = self.__menubar = Menu(tkroot)
|
---|
54 | #
|
---|
55 | # File menu
|
---|
56 | #
|
---|
57 | filemenu = self.__filemenu = Menu(menubar, tearoff=0)
|
---|
58 | filemenu.add_command(label='Load palette...',
|
---|
59 | command=self.__load,
|
---|
60 | underline=0)
|
---|
61 | if not modal:
|
---|
62 | filemenu.add_command(label='Quit',
|
---|
63 | command=self.__quit,
|
---|
64 | accelerator='Alt-Q',
|
---|
65 | underline=0)
|
---|
66 | #
|
---|
67 | # View menu
|
---|
68 | #
|
---|
69 | views = make_view_popups(self.__sb, self.__tkroot, extrapath)
|
---|
70 | viewmenu = Menu(menubar, tearoff=0)
|
---|
71 | for v in views:
|
---|
72 | viewmenu.add_command(label=v.menutext(),
|
---|
73 | command=v.popup,
|
---|
74 | underline=v.underline())
|
---|
75 | #
|
---|
76 | # Help menu
|
---|
77 | #
|
---|
78 | helpmenu = Menu(menubar, name='help', tearoff=0)
|
---|
79 | helpmenu.add_command(label='About Pynche...',
|
---|
80 | command=self.__popup_about,
|
---|
81 | underline=0)
|
---|
82 | helpmenu.add_command(label='Help...',
|
---|
83 | command=self.__popup_usage,
|
---|
84 | underline=0)
|
---|
85 | #
|
---|
86 | # Tie them all together
|
---|
87 | #
|
---|
88 | menubar.add_cascade(label='File',
|
---|
89 | menu=filemenu,
|
---|
90 | underline=0)
|
---|
91 | 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)
|
---|
100 | root.protocol('WM_DELETE_WINDOW',
|
---|
101 | modal and self.__bell or self.__quit)
|
---|
102 | root.title('Pynche %s' % version)
|
---|
103 | root.iconname('Pynche')
|
---|
104 | # 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)
|
---|
123 |
|
---|
124 | def __quit(self, event=None):
|
---|
125 | self.__tkroot.quit()
|
---|
126 |
|
---|
127 | def __bell(self, event=None):
|
---|
128 | self.__tkroot.bell()
|
---|
129 |
|
---|
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()
|
---|
138 |
|
---|
139 | def __keepalive(self):
|
---|
140 | # Exercise the Python interpreter regularly so keyboard interrupts get
|
---|
141 | # through.
|
---|
142 | self.__tkroot.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
|
---|
143 |
|
---|
144 | def start(self):
|
---|
145 | if not self.__modal:
|
---|
146 | self.__keepalive()
|
---|
147 | self.__tkroot.mainloop()
|
---|
148 |
|
---|
149 | def window(self):
|
---|
150 | return self.__root
|
---|
151 |
|
---|
152 | def __popup_about(self, event=None):
|
---|
153 | from Main import __version__
|
---|
154 | tkMessageBox.showinfo('About Pynche ' + __version__,
|
---|
155 | '''\
|
---|
156 | Pynche %s
|
---|
157 | The PYthonically Natural
|
---|
158 | Color and Hue Editor
|
---|
159 |
|
---|
160 | For information
|
---|
161 | contact: Barry A. Warsaw
|
---|
162 | email: bwarsaw@python.org''' % __version__)
|
---|
163 |
|
---|
164 | def __popup_usage(self, event=None):
|
---|
165 | if not self.__helpwin:
|
---|
166 | self.__helpwin = Helpwin(self.__root, self.__quit)
|
---|
167 | self.__helpwin.deiconify()
|
---|
168 |
|
---|
169 | def __load(self, event=None):
|
---|
170 | while 1:
|
---|
171 | 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:
|
---|
179 | # cancel button
|
---|
180 | return
|
---|
181 | try:
|
---|
182 | colordb = ColorDB.get_colordb(file)
|
---|
183 | except IOError:
|
---|
184 | tkMessageBox.showerror('Read error', '''\
|
---|
185 | Could not open file for reading:
|
---|
186 | %s''' % file)
|
---|
187 | continue
|
---|
188 | if colordb is None:
|
---|
189 | tkMessageBox.showerror('Unrecognized color file type', '''\
|
---|
190 | Unrecognized color file type in file:
|
---|
191 | %s''' % file)
|
---|
192 | continue
|
---|
193 | break
|
---|
194 | self.__sb.set_colordb(colordb)
|
---|
195 |
|
---|
196 | def withdraw(self):
|
---|
197 | self.__root.withdraw()
|
---|
198 |
|
---|
199 | def deiconify(self):
|
---|
200 | self.__root.deiconify()
|
---|
201 |
|
---|
202 |
|
---|
203 | |
---|
204 |
|
---|
205 | class Helpwin:
|
---|
206 | def __init__(self, master, quitfunc):
|
---|
207 | from Main import docstring
|
---|
208 | self.__root = root = Toplevel(master, class_='Pynche')
|
---|
209 | root.protocol('WM_DELETE_WINDOW', self.__withdraw)
|
---|
210 | root.title('Pynche Help Window')
|
---|
211 | root.iconname('Pynche Help Window')
|
---|
212 | root.bind('<Alt-q>', quitfunc)
|
---|
213 | root.bind('<Alt-Q>', quitfunc)
|
---|
214 | root.bind('<Alt-w>', self.__withdraw)
|
---|
215 | root.bind('<Alt-W>', self.__withdraw)
|
---|
216 |
|
---|
217 | # more elaborate help is available in the README file
|
---|
218 | readmefile = os.path.join(sys.path[0], 'README')
|
---|
219 | try:
|
---|
220 | fp = None
|
---|
221 | try:
|
---|
222 | fp = open(readmefile)
|
---|
223 | contents = fp.read()
|
---|
224 | # wax the last page, it contains Emacs cruft
|
---|
225 | i = contents.rfind('\f')
|
---|
226 | if i > 0:
|
---|
227 | contents = contents[:i].rstrip()
|
---|
228 | finally:
|
---|
229 | if fp:
|
---|
230 | fp.close()
|
---|
231 | except IOError:
|
---|
232 | sys.stderr.write("Couldn't open Pynche's README, "
|
---|
233 | 'using docstring instead.\n')
|
---|
234 | contents = docstring()
|
---|
235 |
|
---|
236 | self.__text = text = Text(root, relief=SUNKEN,
|
---|
237 | width=80, height=24)
|
---|
238 | self.__text.focus_set()
|
---|
239 | text.insert(0.0, contents)
|
---|
240 | scrollbar = Scrollbar(root)
|
---|
241 | scrollbar.pack(fill=Y, side=RIGHT)
|
---|
242 | text.pack(fill=BOTH, expand=YES)
|
---|
243 | text.configure(yscrollcommand=(scrollbar, 'set'))
|
---|
244 | scrollbar.configure(command=(text, 'yview'))
|
---|
245 |
|
---|
246 | def __withdraw(self, event=None):
|
---|
247 | self.__root.withdraw()
|
---|
248 |
|
---|
249 | def deiconify(self):
|
---|
250 | self.__root.deiconify()
|
---|
251 |
|
---|
252 |
|
---|
253 | |
---|
254 |
|
---|
255 | class PopupViewer:
|
---|
256 | def __init__(self, module, name, switchboard, root):
|
---|
257 | self.__m = module
|
---|
258 | self.__name = name
|
---|
259 | self.__sb = switchboard
|
---|
260 | self.__root = root
|
---|
261 | self.__menutext = module.ADDTOVIEW
|
---|
262 | # find the underline character
|
---|
263 | underline = module.ADDTOVIEW.find('%')
|
---|
264 | if underline == -1:
|
---|
265 | underline = 0
|
---|
266 | else:
|
---|
267 | self.__menutext = module.ADDTOVIEW.replace('%', '', 1)
|
---|
268 | self.__underline = underline
|
---|
269 | self.__window = None
|
---|
270 |
|
---|
271 | def menutext(self):
|
---|
272 | return self.__menutext
|
---|
273 |
|
---|
274 | def underline(self):
|
---|
275 | return self.__underline
|
---|
276 |
|
---|
277 | def popup(self, event=None):
|
---|
278 | if not self.__window:
|
---|
279 | # class and module must have the same name
|
---|
280 | class_ = getattr(self.__m, self.__name)
|
---|
281 | self.__window = class_(self.__sb, self.__root)
|
---|
282 | self.__sb.add_view(self.__window)
|
---|
283 | self.__window.deiconify()
|
---|
284 |
|
---|
285 | def __cmp__(self, other):
|
---|
286 | return cmp(self.__menutext, other.__menutext)
|
---|
287 |
|
---|
288 |
|
---|
289 | def make_view_popups(switchboard, root, extrapath):
|
---|
290 | viewers = []
|
---|
291 | # where we are in the file system
|
---|
292 | dirs = [os.path.dirname(__file__)] + extrapath
|
---|
293 | for dir in dirs:
|
---|
294 | if dir == '':
|
---|
295 | dir = '.'
|
---|
296 | for file in os.listdir(dir):
|
---|
297 | if file[-9:] == 'Viewer.py':
|
---|
298 | name = file[:-3]
|
---|
299 | try:
|
---|
300 | module = __import__(name)
|
---|
301 | except ImportError:
|
---|
302 | # Pynche is running from inside a package, so get the
|
---|
303 | # module using the explicit path.
|
---|
304 | pkg = __import__('pynche.'+name)
|
---|
305 | module = getattr(pkg, name)
|
---|
306 | if hasattr(module, 'ADDTOVIEW') and module.ADDTOVIEW:
|
---|
307 | # this is an external viewer
|
---|
308 | v = PopupViewer(module, name, switchboard, root)
|
---|
309 | viewers.append(v)
|
---|
310 | # sort alphabetically
|
---|
311 | viewers.sort()
|
---|
312 | return viewers
|
---|