Home | History | Annotate | Download | only in pynche
      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