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