blob: ea456e577e12a991a265ed0df98adcffc2940afa [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
Georg Brandlbf76ce12010-08-02 22:08:58 +00009from tkinter import *
10from tkinter import messagebox, filedialog
Barry Warsaw33699271999-04-27 19:51:55 +000011import ColorDB
Barry Warsawa69c1ba1998-09-28 23:38:44 +000012
13# Milliseconds between interrupt checks
14KEEPALIVE_TIMER = 500
Barry Warsawfefbf791998-01-31 23:39:01 +000015
Barry Warsaw552ac171998-02-17 22:25:23 +000016
17
Barry Warsawa69c1ba1998-09-28 23:38:44 +000018class PyncheWidget:
Barry Warsaw9af697b1999-04-27 18:55:48 +000019 def __init__(self, version, switchboard, master=None, extrapath=[]):
Barry Warsaw86daeb71998-10-01 16:46:16 +000020 self.__sb = switchboard
21 self.__version = version
22 self.__textwin = None
Barry Warsawbd36d6e1998-10-02 16:05:48 +000023 self.__listwin = None
Barry Warsaw9044b8e1998-10-05 21:14:46 +000024 self.__detailswin = None
Barry Warsawce0bbd21998-12-03 19:50:24 +000025 self.__helpwin = None
Barry Warsaw0604d721999-04-26 23:17:16 +000026 self.__dialogstate = {}
Barry Warsawca07ba01998-10-22 03:25:59 +000027 modal = self.__modal = not not master
28 # If a master was given, we are running as a modal dialog servant to
29 # some other application. We rearrange our UI in this case (there's
30 # no File menu and we get `Okay' and `Cancel' buttons), and we do a
31 # grab_set() to make ourselves modal
32 if modal:
33 self.__tkroot = tkroot = Toplevel(master, class_='Pynche')
34 tkroot.grab_set()
35 tkroot.withdraw()
36 else:
37 # Is there already a default root for Tk, say because we're
38 # running under Guido's IDE? :-) Two conditions say no, either the
Serhiy Storchaka3d569fd2020-12-19 12:17:08 +020039 # _default_root is None or it is unset.
40 tkroot = getattr(tkinter, '_default_root', None)
Barry Warsawca07ba01998-10-22 03:25:59 +000041 if not tkroot:
Serhiy Storchaka3d569fd2020-12-19 12:17:08 +020042 tkroot = Tk(className='Pynche')
43 self.__tkroot = tkroot
Barry Warsawca07ba01998-10-22 03:25:59 +000044 # but this isn't our top level widget, so make it invisible
45 tkroot.withdraw()
Barry Warsawa69c1ba1998-09-28 23:38:44 +000046 # create the menubar
Barry Warsaw38365031998-10-06 19:39:34 +000047 menubar = self.__menubar = Menu(tkroot)
Barry Warsawa69c1ba1998-09-28 23:38:44 +000048 #
49 # File menu
50 #
Barry Warsaw0604d721999-04-26 23:17:16 +000051 filemenu = self.__filemenu = Menu(menubar, tearoff=0)
52 filemenu.add_command(label='Load palette...',
53 command=self.__load,
54 underline=0)
Barry Warsawca07ba01998-10-22 03:25:59 +000055 if not modal:
Barry Warsawca07ba01998-10-22 03:25:59 +000056 filemenu.add_command(label='Quit',
57 command=self.__quit,
58 accelerator='Alt-Q',
59 underline=0)
Barry Warsawa69c1ba1998-09-28 23:38:44 +000060 #
Barry Warsaw86daeb71998-10-01 16:46:16 +000061 # View menu
62 #
Barry Warsaw9af697b1999-04-27 18:55:48 +000063 views = make_view_popups(self.__sb, self.__tkroot, extrapath)
Barry Warsaw38365031998-10-06 19:39:34 +000064 viewmenu = Menu(menubar, tearoff=0)
Barry Warsaw9af697b1999-04-27 18:55:48 +000065 for v in views:
66 viewmenu.add_command(label=v.menutext(),
67 command=v.popup,
68 underline=v.underline())
Barry Warsaw38365031998-10-06 19:39:34 +000069 #
Barry Warsawa69c1ba1998-09-28 23:38:44 +000070 # Help menu
71 #
Barry Warsaw38365031998-10-06 19:39:34 +000072 helpmenu = Menu(menubar, name='help', tearoff=0)
Martin v. Löwis23b44a32003-10-24 20:09:23 +000073 helpmenu.add_command(label='About Pynche...',
Barry Warsawa69c1ba1998-09-28 23:38:44 +000074 command=self.__popup_about,
75 underline=0)
Barry Warsawce0bbd21998-12-03 19:50:24 +000076 helpmenu.add_command(label='Help...',
77 command=self.__popup_usage,
78 underline=0)
Barry Warsaw38365031998-10-06 19:39:34 +000079 #
80 # Tie them all together
81 #
Barry Warsaw6e7f6ea1999-04-26 23:36:47 +000082 menubar.add_cascade(label='File',
83 menu=filemenu,
84 underline=0)
Barry Warsaw38365031998-10-06 19:39:34 +000085 menubar.add_cascade(label='View',
86 menu=viewmenu,
87 underline=0)
88 menubar.add_cascade(label='Help',
89 menu=helpmenu,
90 underline=0)
91
92 # now create the top level window
93 root = self.__root = Toplevel(tkroot, class_='Pynche', menu=menubar)
Barry Warsawca07ba01998-10-22 03:25:59 +000094 root.protocol('WM_DELETE_WINDOW',
Barry Warsawd9e52141998-10-22 18:46:28 +000095 modal and self.__bell or self.__quit)
Barry Warsaw38365031998-10-06 19:39:34 +000096 root.title('Pynche %s' % version)
97 root.iconname('Pynche')
Barry Warsawca07ba01998-10-22 03:25:59 +000098 # Only bind accelerators for the File->Quit menu item if running as a
99 # standalone app
100 if not modal:
101 root.bind('<Alt-q>', self.__quit)
102 root.bind('<Alt-Q>', self.__quit)
103 else:
104 # We're a modal dialog so we have a new row of buttons
105 bframe = Frame(root, borderwidth=1, relief=RAISED)
106 bframe.grid(row=4, column=0, columnspan=2,
107 sticky='EW',
108 ipady=5)
109 okay = Button(bframe,
110 text='Okay',
111 command=self.__okay)
112 okay.pack(side=LEFT, expand=1)
113 cancel = Button(bframe,
114 text='Cancel',
115 command=self.__cancel)
116 cancel.pack(side=LEFT, expand=1)
Barry Warsawe7f4a471998-10-06 19:50:33 +0000117
118 def __quit(self, event=None):
Barry Warsawca07ba01998-10-22 03:25:59 +0000119 self.__tkroot.quit()
120
Barry Warsawd9e52141998-10-22 18:46:28 +0000121 def __bell(self, event=None):
122 self.__tkroot.bell()
Barry Warsawca07ba01998-10-22 03:25:59 +0000123
124 def __okay(self, event=None):
125 self.__sb.withdraw_views()
126 self.__tkroot.grab_release()
127 self.__quit()
128
129 def __cancel(self, event=None):
130 self.__sb.canceled()
131 self.__okay()
Barry Warsaw552ac171998-02-17 22:25:23 +0000132
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000133 def __keepalive(self):
134 # Exercise the Python interpreter regularly so keyboard interrupts get
135 # through.
Barry Warsaw38365031998-10-06 19:39:34 +0000136 self.__tkroot.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
Barry Warsaw552ac171998-02-17 22:25:23 +0000137
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000138 def start(self):
Barry Warsawca07ba01998-10-22 03:25:59 +0000139 if not self.__modal:
140 self.__keepalive()
Barry Warsaw38365031998-10-06 19:39:34 +0000141 self.__tkroot.mainloop()
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000142
Barry Warsawca07ba01998-10-22 03:25:59 +0000143 def window(self):
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000144 return self.__root
Barry Warsawf67575d1998-03-10 00:17:01 +0000145
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000146 def __popup_about(self, event=None):
Barry Warsaw0926dea1998-10-07 03:36:58 +0000147 from Main import __version__
Georg Brandlbf76ce12010-08-02 22:08:58 +0000148 messagebox.showinfo('About Pynche ' + __version__,
Barry Warsawa9b45581998-09-29 20:03:53 +0000149 '''\
Barry Warsawad3a67c1998-10-06 18:52:59 +0000150Pynche %s
151The PYthonically Natural
152Color and Hue Editor
Barry Warsawa69c1ba1998-09-28 23:38:44 +0000153
Barry Warsaw64039911998-12-15 01:02:51 +0000154For information
155contact: Barry A. Warsaw
156email: bwarsaw@python.org''' % __version__)
Barry Warsaw86daeb71998-10-01 16:46:16 +0000157
Barry Warsawce0bbd21998-12-03 19:50:24 +0000158 def __popup_usage(self, event=None):
159 if not self.__helpwin:
160 self.__helpwin = Helpwin(self.__root, self.__quit)
161 self.__helpwin.deiconify()
162
Barry Warsaw0604d721999-04-26 23:17:16 +0000163 def __load(self, event=None):
Barry Warsaw0604d721999-04-26 23:17:16 +0000164 while 1:
Barry Warsaw33699271999-04-27 19:51:55 +0000165 idir, ifile = os.path.split(self.__sb.colordb().filename())
Georg Brandlbf76ce12010-08-02 22:08:58 +0000166 file = filedialog.askopenfilename(
Barry Warsaw33699271999-04-27 19:51:55 +0000167 filetypes=[('Text files', '*.txt'),
168 ('All files', '*'),
169 ],
170 initialdir=idir,
171 initialfile=ifile)
172 if not file:
Barry Warsaw0604d721999-04-26 23:17:16 +0000173 # cancel button
174 return
175 try:
176 colordb = ColorDB.get_colordb(file)
177 except IOError:
Georg Brandlbf76ce12010-08-02 22:08:58 +0000178 messagebox.showerror('Read error', '''\
Barry Warsaw0604d721999-04-26 23:17:16 +0000179Could not open file for reading:
180%s''' % file)
181 continue
182 if colordb is None:
Georg Brandlbf76ce12010-08-02 22:08:58 +0000183 messagebox.showerror('Unrecognized color file type', '''\
Barry Warsaw0604d721999-04-26 23:17:16 +0000184Unrecognized color file type in file:
185%s''' % file)
186 continue
187 break
188 self.__sb.set_colordb(colordb)
Barry Warsaw0604d721999-04-26 23:17:16 +0000189
Barry Warsawca07ba01998-10-22 03:25:59 +0000190 def withdraw(self):
191 self.__root.withdraw()
Barry Warsawd9e52141998-10-22 18:46:28 +0000192
193 def deiconify(self):
194 self.__root.deiconify()
Barry Warsawce0bbd21998-12-03 19:50:24 +0000195
196
197
198class Helpwin:
199 def __init__(self, master, quitfunc):
Barry Warsaw6a552262001-04-18 03:50:07 +0000200 from Main import docstring
Barry Warsawce0bbd21998-12-03 19:50:24 +0000201 self.__root = root = Toplevel(master, class_='Pynche')
202 root.protocol('WM_DELETE_WINDOW', self.__withdraw)
203 root.title('Pynche Help Window')
204 root.iconname('Pynche Help Window')
205 root.bind('<Alt-q>', quitfunc)
206 root.bind('<Alt-Q>', quitfunc)
207 root.bind('<Alt-w>', self.__withdraw)
208 root.bind('<Alt-W>', self.__withdraw)
209
210 # more elaborate help is available in the README file
211 readmefile = os.path.join(sys.path[0], 'README')
212 try:
213 fp = None
214 try:
215 fp = open(readmefile)
216 contents = fp.read()
217 # wax the last page, it contains Emacs cruft
Barry Warsaw8e4fa072001-07-10 21:44:24 +0000218 i = contents.rfind('\f')
Barry Warsawce0bbd21998-12-03 19:50:24 +0000219 if i > 0:
Barry Warsaw8e4fa072001-07-10 21:44:24 +0000220 contents = contents[:i].rstrip()
Barry Warsawce0bbd21998-12-03 19:50:24 +0000221 finally:
222 if fp:
223 fp.close()
224 except IOError:
225 sys.stderr.write("Couldn't open Pynche's README, "
226 'using docstring instead.\n')
227 contents = docstring()
228
229 self.__text = text = Text(root, relief=SUNKEN,
230 width=80, height=24)
Barry Warsawecb1a651999-03-26 16:11:40 +0000231 self.__text.focus_set()
Barry Warsawce0bbd21998-12-03 19:50:24 +0000232 text.insert(0.0, contents)
233 scrollbar = Scrollbar(root)
234 scrollbar.pack(fill=Y, side=RIGHT)
235 text.pack(fill=BOTH, expand=YES)
236 text.configure(yscrollcommand=(scrollbar, 'set'))
237 scrollbar.configure(command=(text, 'yview'))
238
239 def __withdraw(self, event=None):
240 self.__root.withdraw()
241
242 def deiconify(self):
243 self.__root.deiconify()
Barry Warsaw9af697b1999-04-27 18:55:48 +0000244
245
246
Georg Brandlbf76ce12010-08-02 22:08:58 +0000247import functools
248@functools.total_ordering
Barry Warsaw9af697b1999-04-27 18:55:48 +0000249class PopupViewer:
250 def __init__(self, module, name, switchboard, root):
251 self.__m = module
252 self.__name = name
253 self.__sb = switchboard
254 self.__root = root
255 self.__menutext = module.ADDTOVIEW
256 # find the underline character
Barry Warsaw8e4fa072001-07-10 21:44:24 +0000257 underline = module.ADDTOVIEW.find('%')
Barry Warsaw9af697b1999-04-27 18:55:48 +0000258 if underline == -1:
259 underline = 0
260 else:
Barry Warsaw8e4fa072001-07-10 21:44:24 +0000261 self.__menutext = module.ADDTOVIEW.replace('%', '', 1)
Barry Warsaw9af697b1999-04-27 18:55:48 +0000262 self.__underline = underline
263 self.__window = None
264
265 def menutext(self):
266 return self.__menutext
267
268 def underline(self):
269 return self.__underline
270
271 def popup(self, event=None):
272 if not self.__window:
273 # class and module must have the same name
274 class_ = getattr(self.__m, self.__name)
275 self.__window = class_(self.__sb, self.__root)
276 self.__sb.add_view(self.__window)
277 self.__window.deiconify()
278
Georg Brandlbf76ce12010-08-02 22:08:58 +0000279 def __eq__(self, other):
Serhiy Storchaka662db122019-08-08 08:42:54 +0300280 if isinstance(self, PopupViewer):
281 return self.__menutext == other.__menutext
282 return NotImplemented
Georg Brandlbf76ce12010-08-02 22:08:58 +0000283
284 def __lt__(self, other):
Serhiy Storchaka662db122019-08-08 08:42:54 +0300285 if isinstance(self, PopupViewer):
286 return self.__menutext < other.__menutext
287 return NotImplemented
Barry Warsaw9af697b1999-04-27 18:55:48 +0000288
289
290def make_view_popups(switchboard, root, extrapath):
291 viewers = []
292 # where we are in the file system
293 dirs = [os.path.dirname(__file__)] + extrapath
294 for dir in dirs:
295 if dir == '':
296 dir = '.'
297 for file in os.listdir(dir):
298 if file[-9:] == 'Viewer.py':
299 name = file[:-3]
Barry Warsaw17a8b5d1999-07-06 22:00:52 +0000300 try:
301 module = __import__(name)
302 except ImportError:
303 # Pynche is running from inside a package, so get the
304 # module using the explicit path.
305 pkg = __import__('pynche.'+name)
306 module = getattr(pkg, name)
Barry Warsaw9af697b1999-04-27 18:55:48 +0000307 if hasattr(module, 'ADDTOVIEW') and module.ADDTOVIEW:
308 # this is an external viewer
309 v = PopupViewer(module, name, switchboard, root)
310 viewers.append(v)
311 # sort alphabetically
312 viewers.sort()
313 return viewers