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