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 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 (at] 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 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 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 313