| 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
|
|---|