• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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