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