1"""Main Pynche (Pythonically Natural Color and Hue Editor) widget. 2 3This window provides the basic decorations, primarily including the menubar. 4It is used to bring up other windows. 5""" 6 7import sys 8import os 9from Tkinter import * 10import tkMessageBox 11import tkFileDialog 12import ColorDB 13 14# Milliseconds between interrupt checks 15KEEPALIVE_TIMER = 500 16 17 18 19class 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 '''\ 155Pynche %s 156The PYthonically Natural 157Color and Hue Editor 158 159For information 160contact: Barry A. Warsaw 161email: [email protected]''' % __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', '''\ 184Could not open file for reading: 185%s''' % file) 186 continue 187 if colordb is None: 188 tkMessageBox.showerror('Unrecognized color file type', '''\ 189Unrecognized 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 203class 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 252class 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 286def 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 310