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