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 * 10from tkinter import messagebox, filedialog 11import ColorDB 12 13# Milliseconds between interrupt checks 14KEEPALIVE_TIMER = 500 15 16 17 18class PyncheWidget: 19 def __init__(self, version, switchboard, master=None, extrapath=[]): 20 self.__sb = switchboard 21 self.__version = version 22 self.__textwin = None 23 self.__listwin = None 24 self.__detailswin = None 25 self.__helpwin = None 26 self.__dialogstate = {} 27 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 39 # import fails or _default_root is None. 40 tkroot = None 41 try: 42 from Tkinter import _default_root 43 tkroot = self.__tkroot = _default_root 44 except ImportError: 45 pass 46 if not tkroot: 47 tkroot = self.__tkroot = Tk(className='Pynche') 48 # but this isn't our top level widget, so make it invisible 49 tkroot.withdraw() 50 # create the menubar 51 menubar = self.__menubar = Menu(tkroot) 52 # 53 # File menu 54 # 55 filemenu = self.__filemenu = Menu(menubar, tearoff=0) 56 filemenu.add_command(label='Load palette...', 57 command=self.__load, 58 underline=0) 59 if not modal: 60 filemenu.add_command(label='Quit', 61 command=self.__quit, 62 accelerator='Alt-Q', 63 underline=0) 64 # 65 # View menu 66 # 67 views = make_view_popups(self.__sb, self.__tkroot, extrapath) 68 viewmenu = Menu(menubar, tearoff=0) 69 for v in views: 70 viewmenu.add_command(label=v.menutext(), 71 command=v.popup, 72 underline=v.underline()) 73 # 74 # Help menu 75 # 76 helpmenu = Menu(menubar, name='help', tearoff=0) 77 helpmenu.add_command(label='About Pynche...', 78 command=self.__popup_about, 79 underline=0) 80 helpmenu.add_command(label='Help...', 81 command=self.__popup_usage, 82 underline=0) 83 # 84 # Tie them all together 85 # 86 menubar.add_cascade(label='File', 87 menu=filemenu, 88 underline=0) 89 menubar.add_cascade(label='View', 90 menu=viewmenu, 91 underline=0) 92 menubar.add_cascade(label='Help', 93 menu=helpmenu, 94 underline=0) 95 96 # now create the top level window 97 root = self.__root = Toplevel(tkroot, class_='Pynche', menu=menubar) 98 root.protocol('WM_DELETE_WINDOW', 99 modal and self.__bell or self.__quit) 100 root.title('Pynche %s' % version) 101 root.iconname('Pynche') 102 # Only bind accelerators for the File->Quit menu item if running as a 103 # standalone app 104 if not modal: 105 root.bind('<Alt-q>', self.__quit) 106 root.bind('<Alt-Q>', self.__quit) 107 else: 108 # We're a modal dialog so we have a new row of buttons 109 bframe = Frame(root, borderwidth=1, relief=RAISED) 110 bframe.grid(row=4, column=0, columnspan=2, 111 sticky='EW', 112 ipady=5) 113 okay = Button(bframe, 114 text='Okay', 115 command=self.__okay) 116 okay.pack(side=LEFT, expand=1) 117 cancel = Button(bframe, 118 text='Cancel', 119 command=self.__cancel) 120 cancel.pack(side=LEFT, expand=1) 121 122 def __quit(self, event=None): 123 self.__tkroot.quit() 124 125 def __bell(self, event=None): 126 self.__tkroot.bell() 127 128 def __okay(self, event=None): 129 self.__sb.withdraw_views() 130 self.__tkroot.grab_release() 131 self.__quit() 132 133 def __cancel(self, event=None): 134 self.__sb.canceled() 135 self.__okay() 136 137 def __keepalive(self): 138 # Exercise the Python interpreter regularly so keyboard interrupts get 139 # through. 140 self.__tkroot.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive) 141 142 def start(self): 143 if not self.__modal: 144 self.__keepalive() 145 self.__tkroot.mainloop() 146 147 def window(self): 148 return self.__root 149 150 def __popup_about(self, event=None): 151 from Main import __version__ 152 messagebox.showinfo('About Pynche ' + __version__, 153 '''\ 154Pynche %s 155The PYthonically Natural 156Color and Hue Editor 157 158For information 159contact: Barry A. Warsaw 160email: bwarsaw@python.org''' % __version__) 161 162 def __popup_usage(self, event=None): 163 if not self.__helpwin: 164 self.__helpwin = Helpwin(self.__root, self.__quit) 165 self.__helpwin.deiconify() 166 167 def __load(self, event=None): 168 while 1: 169 idir, ifile = os.path.split(self.__sb.colordb().filename()) 170 file = filedialog.askopenfilename( 171 filetypes=[('Text files', '*.txt'), 172 ('All files', '*'), 173 ], 174 initialdir=idir, 175 initialfile=ifile) 176 if not file: 177 # cancel button 178 return 179 try: 180 colordb = ColorDB.get_colordb(file) 181 except IOError: 182 messagebox.showerror('Read error', '''\ 183Could not open file for reading: 184%s''' % file) 185 continue 186 if colordb is None: 187 messagebox.showerror('Unrecognized color file type', '''\ 188Unrecognized color file type in file: 189%s''' % file) 190 continue 191 break 192 self.__sb.set_colordb(colordb) 193 194 def withdraw(self): 195 self.__root.withdraw() 196 197 def deiconify(self): 198 self.__root.deiconify() 199 200 201 202class Helpwin: 203 def __init__(self, master, quitfunc): 204 from Main import docstring 205 self.__root = root = Toplevel(master, class_='Pynche') 206 root.protocol('WM_DELETE_WINDOW', self.__withdraw) 207 root.title('Pynche Help Window') 208 root.iconname('Pynche Help Window') 209 root.bind('<Alt-q>', quitfunc) 210 root.bind('<Alt-Q>', quitfunc) 211 root.bind('<Alt-w>', self.__withdraw) 212 root.bind('<Alt-W>', self.__withdraw) 213 214 # more elaborate help is available in the README file 215 readmefile = os.path.join(sys.path[0], 'README') 216 try: 217 fp = None 218 try: 219 fp = open(readmefile) 220 contents = fp.read() 221 # wax the last page, it contains Emacs cruft 222 i = contents.rfind('\f') 223 if i > 0: 224 contents = contents[:i].rstrip() 225 finally: 226 if fp: 227 fp.close() 228 except IOError: 229 sys.stderr.write("Couldn't open Pynche's README, " 230 'using docstring instead.\n') 231 contents = docstring() 232 233 self.__text = text = Text(root, relief=SUNKEN, 234 width=80, height=24) 235 self.__text.focus_set() 236 text.insert(0.0, contents) 237 scrollbar = Scrollbar(root) 238 scrollbar.pack(fill=Y, side=RIGHT) 239 text.pack(fill=BOTH, expand=YES) 240 text.configure(yscrollcommand=(scrollbar, 'set')) 241 scrollbar.configure(command=(text, 'yview')) 242 243 def __withdraw(self, event=None): 244 self.__root.withdraw() 245 246 def deiconify(self): 247 self.__root.deiconify() 248 249 250 251import functools 252@functools.total_ordering 253class PopupViewer: 254 def __init__(self, module, name, switchboard, root): 255 self.__m = module 256 self.__name = name 257 self.__sb = switchboard 258 self.__root = root 259 self.__menutext = module.ADDTOVIEW 260 # find the underline character 261 underline = module.ADDTOVIEW.find('%') 262 if underline == -1: 263 underline = 0 264 else: 265 self.__menutext = module.ADDTOVIEW.replace('%', '', 1) 266 self.__underline = underline 267 self.__window = None 268 269 def menutext(self): 270 return self.__menutext 271 272 def underline(self): 273 return self.__underline 274 275 def popup(self, event=None): 276 if not self.__window: 277 # class and module must have the same name 278 class_ = getattr(self.__m, self.__name) 279 self.__window = class_(self.__sb, self.__root) 280 self.__sb.add_view(self.__window) 281 self.__window.deiconify() 282 283 def __eq__(self, other): 284 return self.__menutext == other.__menutext 285 286 def __lt__(self, other): 287 return self.__menutext < other.__menutext 288 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] 300 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) 307 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 314