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