Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (96 page)

PyView: An Image and Notes Slideshow

A
picture may be worth a thousand words, but it takes
considerably fewer to display one with Python. The next program, PyView,
implements a simple photo slideshow program in portable
Python/tkinter code. It doesn’t have any image-processing abilities such
as PyPhoto’s resizing, but it does provide different tools, such as image
note files, and it can be run without the optional PIL extension.

Running PyView

PyView pulls together
many of the topics we studied in
Chapter 9
: it uses
after
events to sequence a slideshow, displays
image objects in an automatically sized canvas, and so on. Its main
window displays a photo on a canvas; users can either open and view a
photo directly or start a slideshow mode that picks and displays a
random photo from a directory at regular intervals specified with a
scale widget.

By default, PyView slideshows show images in the book’s image file
directory (though the Open button allows you to load images in arbitrary
directories). To view other sets of photos, either pass a directory name
in as a first command-line argument or change the default directory name
in the script itself. I can’t show you a slideshow in action here, but I
can show you the main window in general.
Figure 11-12
shows the main PyView window’s default
display on Windows 7, created by running the
slideShowPlus.py
script we’ll see in
Example 11-6
ahead.

Figure 11-12. PyView without notes

Though it’s not obvious as rendered in this book, the black-on-red
label at the top gives the pathname of the photo file displayed. For a
good time, move the slider at the bottom all the way over to “0” to
specify no delay between photo changes, and then click Start to begin a
very fast slideshow. If your computer is at least as fast as mine,
photos flip by much too fast to be useful for anything but subliminal
advertising. Slideshow photos are loaded on startup to retain references
to them (remember, you must hold on to image objects). But the speed
with which large GIFs can be thrown up in a window in Python is
impressive, if not downright exhilarating.

The GUI’s Start button changes to a Stop button during a slideshow
(its text attribute is reset with the widget
config
method).
Figure 11-13
shows the scene after
pressing Stop at an opportune moment.

Figure 11-13. PyView after stopping a slideshow

In addition, each photo can have an associated “notes” text file
that is automatically opened along with the image. You can use this
feature to record basic information about the photo. Press the Note
button to open an additional set of widgets that let you view and change
the note file associated with the currently displayed photo. This
additional set of widgets should look familiar—the PyEdit text editor
from earlier in this chapter is attached to PyView in a variety of
selectable modes to serve as a display and editing widget for photo
notes.
Figure 11-14
shows PyView with the
attached PyEdit note-editing component opened (I resized the window a
bit interactively for presentation here).

Figure 11-14. PyView with notes

Embedding PyEdit in PyView

This makes for a
very big window, usually best viewed maximized (taking
up the entire screen). The main thing to notice, though, is the
lower-right corner of this display, above the scale—it’s simply an
attached PyEdit object, running the very same code listed in the
earlier section. Because PyEdit is implemented as a GUI class, it can
be reused like this in any GUI that needs a text-editing
interface.

When embedded this way, PyEdit is a nested frame attached to a
slideshow frame. Its menus are based on a frame (it doesn’t own the
window at large), text content is stored and fetched directly by the
enclosing program, and some standalone options are
omitted
(e.g., the File pull down menu
and Quit button are gone). On the other hand, you get all of the rest
of PyEdit’s functionality, including cut and paste, search and
replace, grep to search external files, colors and fonts, undo and
redo, and so on. Even the Clone option works here to open a new edit
window, albeit making a frame-based menu without a Quit or File pull
down, and which doesn’t test for changes on exit—a usage mode that
could be tightened up with a new PyEdit top-level component class if
desired.

For variety, if you pass a third command-line argument to PyView
after the image directory name, it uses it as an index into a list of
PyEdit top-level mode classes. An argument of 0 uses the main window
mode, which places the note editor below the image and a window menu
at top (its
Frame
is packed into
the window’s remaining space, not the slide show frame); 1 pops up the
note editor as a separate, independent
Toplevel
window (disabled when notes are
turned off); 2 and 3 run PyEdit as an embedded component nested in the
slide show frame, with
Frame
menus
(2 includes all menu options which may or may not be appropriate in
this role, and 3 is the default limited options mode).

Figure 11-15
captures option 0,
PyEdit’s main window mode; there are really two independent frames on
the window here—a slideshow on top and a text editor on bottom. The
disadvantage of this over nested component or pop-up window modes is
that PyEdit really does assume control of the program’s window
(including its title and window manager close button), and packing the
note editor at the bottom means it might not appear for tall images.
Run this on your own to sample the other PyEdit flavors, with a
command line of this form:

C:\...\PP4E\Gui\SlideShow>
slideShowPlus.py ../gifs 0

Figure 11-15. PyView other PyEdit notes

The note file viewer appears only if you press the Note button,
and it is erased if you press it again—PyView uses the widget
pack
and
pack_forget
methods introduced near the end
of
Chapter 9
to show and hide the
note viewer frame. The window automatically expands to accommodate the
note viewer when it is packed and displayed. Note that it’s important
that the note editor be repacked with
expand=YES
and
fill=BOTH
when it’s unhidden, or else it
won’t expand in some modes; PyEdit’s frame packs itself this way in
GuiMaker
when first made, but
pack_forget
appears to,
well…forget.

It is also possible to open the note file in a PyEdit pop-up
window, but PyView embeds the editor by default to retain a direct
visual association and avoid issues if the pop up is destroyed
independently. As is, this program must wrap the PyEdit classes with
its
WrapEditor
in order to catch
independent destroys of the PyEdit frame when it is run in either
pop-up window or full-option component modes—the note editor can no
longer be accessed or repacked once it’s destroyed. This isn’t an
issue in main window mode (Quit ends the program) or the default
minimal component mode (the editor has no Quit). Watch for PyEdit to
show up embedded as a component like this within another GUI when we
meet PyMailGUI in
Chapter 14
.

A caveat here: out of the box, PyView supports as many photo
formats as tkinter’s
PhotoImage
object does; that’s why it looks for GIF files by default. You can
improve this by installing the PIL extension to view JPEGs (and many
others). But because PIL is an optional extension today, it’s not
incorporated into this PyView release. See the end of
Chapter 8
for more on PIL and image
formats.

PyView Source Code

Because the PyView
program was implemented in stages, you need to study the
union of two files and classes to understand how it truly works. One
file implements a class that provides core slideshow functionality; the
other implements a class that extends the original class, to add
additional features on top of the core behavior. Let’s start with the
extension class:
Example 11-6
adds a set of
features to an imported slideshow base class—note editing, a delay scale
and file label, and so on. This is the file that is actually run to
start PyView.

Example 11-6. PP4E\Gui\SlideShow\slideShowPlus.py

"""
#############################################################################
PyView 1.2: an image slide show with associated text notes.
SlideShow subclass which adds note files with an attached PyEdit object,
a scale for setting the slideshow delay interval, and a label that gives
the name of the image file currently being displayed;
Version 1.2 is a Python 3.x port, but also improves repacking note for
expansion when it's unhidden, catches note destroys in a subclass to avoid
exceptions when popup window or full component editor has been closed,
and runs update() before inserting text into newly packed note so it is
positioned correctly at line 1 (see the book's coverage of PyEdit updates).
#############################################################################
"""
import os
from tkinter import *
from PP4E.Gui.TextEditor.textEditor import *
from slideShow import SlideShow
#from slideShow_threads import SlideShow
Size = (300, 550) # 1.2: start shorter here, (h, w)
class SlideShowPlus(SlideShow):
def __init__(self, parent, picdir, editclass, msecs=2000, size=Size):
self.msecs = msecs
self.editclass = editclass
SlideShow.__init__(self, parent, picdir, msecs, size)
def makeWidgets(self):
self.name = Label(self, text='None', bg='red', relief=RIDGE)
self.name.pack(fill=X)
SlideShow.makeWidgets(self)
Button(self, text='Note', command=self.onNote).pack(fill=X)
Button(self, text='Help', command=self.onHelp).pack(fill=X)
s = Scale(label='Speed: msec delay', command=self.onScale,
from_=0, to=3000, resolution=50, showvalue=YES,
length=400, tickinterval=250, orient='horizontal')
s.pack(side=BOTTOM, fill=X)
s.set(self.msecs)
# 1.2: need to know if editor destroyed, in popup or full component modes
self.editorGone = False
class WrapEditor(self.editclass): # extend PyEdit class to catch Quit
def onQuit(editor): # editor is PyEdit instance arg subject
self.editorGone = True # self is slide show in enclosing scope
self.editorUp = False
self.editclass.onQuit(editor) # avoid recursion
# attach editor frame to window or slideshow frame
if issubclass(WrapEditor, TextEditorMain): # make editor now
self.editor = WrapEditor(self.master) # need root for menu
else:
self.editor = WrapEditor(self) # embedded or pop-up
self.editor.pack_forget() # hide editor initially
self.editorUp = self.image = None
def onStart(self):
SlideShow.onStart(self)
self.config(cursor='watch')
def onStop(self):
SlideShow.onStop(self)
self.config(cursor='hand2')
def onOpen(self):
SlideShow.onOpen(self)
if self.image:
self.name.config(text=os.path.split(self.image[0])[1])
self.config(cursor='crosshair')
self.switchNote()
def quit(self):
self.saveNote()
SlideShow.quit(self)
def drawNext(self):
SlideShow.drawNext(self)
if self.image:
self.name.config(text=os.path.split(self.image[0])[1])
self.loadNote()
def onScale(self, value):
self.msecs = int(value)
def onNote(self):
if self.editorGone: # 1.2: has been destroyed
return # don't rebuild: assume unwanted
if self.editorUp:
#self.saveNote() # if editor already open
self.editor.pack_forget() # save text?, hide editor
self.editorUp = False
else:
# 1.2: repack for expansion again, else won't expand now
# 1.2: update between pack and insert, else @ line 2 initially
self.editor.pack(side=TOP, expand=YES, fill=BOTH)
self.editorUp = True # else unhide/pack editor
self.update() # see Pyedit: same as loadFirst issue
self.loadNote() # and load image note text
def switchNote(self):
if self.editorUp:
self.saveNote() # save current image's note
self.loadNote() # load note for new image
def saveNote(self):
if self.editorUp:
currfile = self.editor.getFileName() # or self.editor.onSave()
currtext = self.editor.getAllText() # but text may be empty
if currfile and currtext:
try:
open(currfile, 'w').write(currtext)
except:
pass # failure may be normal if run off a cd
def loadNote(self):
if self.image and self.editorUp:
root, ext = os.path.splitext(self.image[0])
notefile = root + '.note'
self.editor.setFileName(notefile)
try:
self.editor.setAllText(open(notefile).read())
except:
self.editor.clearAllText() # might not have a note
def onHelp(self):
showinfo('About PyView',
'PyView version 1.2\nMay, 2010\n(1.1 July, 1999)\n'
'An image slide show\nProgramming Python 4E')
if __name__ == '__main__':
import sys
picdir = '../gifs'
if len(sys.argv) >= 2:
picdir = sys.argv[1]
editstyle = TextEditorComponentMinimal
if len(sys.argv) == 3:
try:
editstyle = [TextEditorMain,
TextEditorMainPopup,
TextEditorComponent,
TextEditorComponentMinimal][int(sys.argv[2])]
except: pass
root = Tk()
root.title('PyView 1.2 - plus text notes')
Label(root, text="Slide show subclass").pack()
SlideShowPlus(parent=root, picdir=picdir, editclass=editstyle)
root.mainloop()

The core functionality extended by
SlideShowPlus
lives in
Example 11-7
. This was the
initial slideshow implementation; it opens images, displays photos, and
cycles through a slideshow. You can run it by itself, but you won’t get
advanced features such as notes and sliders added by the
SlideShowPlus
subclass.

Example 11-7. PP4E\Gui\SlideShow\slideShow.py

"""
######################################################################
SlideShow: a simple photo image slideshow in Python/tkinter;
the base feature set coded here can be extended in subclasses;
######################################################################
"""
from tkinter import *
from glob import glob
from tkinter.messagebox import askyesno
from tkinter.filedialog import askopenfilename
import random
Size = (450, 450) # canvas height, width at startup and slideshow start
imageTypes = [('Gif files', '.gif'), # for file open dialog
('Ppm files', '.ppm'), # plus jpg with a Tk patch,
('Pgm files', '.pgm'), # plus bitmaps with BitmapImage
('All files', '*')]
class SlideShow(Frame):
def __init__(self, parent=None, picdir='.', msecs=3000, size=Size, **args):
Frame.__init__(self, parent, **args)
self.size = size
self.makeWidgets()
self.pack(expand=YES, fill=BOTH)
self.opens = picdir
files = []
for label, ext in imageTypes[:-1]:
files = files + glob('%s/*%s' % (picdir, ext))
self.images = [(x, PhotoImage(file=x)) for x in files]
self.msecs = msecs
self.beep = True
self.drawn = None
def makeWidgets(self):
height, width = self.size
self.canvas = Canvas(self, bg='white', height=height, width=width)
self.canvas.pack(side=LEFT, fill=BOTH, expand=YES)
self.onoff = Button(self, text='Start', command=self.onStart)
self.onoff.pack(fill=X)
Button(self, text='Open', command=self.onOpen).pack(fill=X)
Button(self, text='Beep', command=self.onBeep).pack(fill=X)
Button(self, text='Quit', command=self.onQuit).pack(fill=X)
def onStart(self):
self.loop = True
self.onoff.config(text='Stop', command=self.onStop)
self.canvas.config(height=self.size[0], width=self.size[1])
self.onTimer()
def onStop(self):
self.loop = False
self.onoff.config(text='Start', command=self.onStart)
def onOpen(self):
self.onStop()
name = askopenfilename(initialdir=self.opens, filetypes=imageTypes)
if name:
if self.drawn: self.canvas.delete(self.drawn)
img = PhotoImage(file=name)
self.canvas.config(height=img.height(), width=img.width())
self.drawn = self.canvas.create_image(2, 2, image=img, anchor=NW)
self.image = name, img
def onQuit(self):
self.onStop()
self.update()
if askyesno('PyView', 'Really quit now?'):
self.quit()
def onBeep(self):
self.beep = not self.beep # toggle, or use ^ 1
def onTimer(self):
if self.loop:
self.drawNext()
self.after(self.msecs, self.onTimer)
def drawNext(self):
if self.drawn: self.canvas.delete(self.drawn)
name, img = random.choice(self.images)
self.drawn = self.canvas.create_image(2, 2, image=img, anchor=NW)
self.image = name, img
if self.beep: self.bell()
self.canvas.update()
if __name__ == '__main__':
import sys
if len(sys.argv) == 2:
picdir = sys.argv[1]
else:
picdir = '../gifs'
root = Tk()
root.title('PyView 1.2')
root.iconname('PyView')
Label(root, text="Python Slide Show Viewer").pack()
SlideShow(root, picdir=picdir, bd=3, relief=SUNKEN)
root.mainloop()

To give you a better idea of what this core base class implements,
Figure 11-16
shows what it looks
like if run by itself—actually, two copies run by themselves by a script
called
slideShow_frames
, which is in
this book’s examples distribution, and whose main code looks like
this:

root = Tk()
Label(root, text="Two embedded slide shows: Frames").pack()
SlideShow(parent=root, picdir=picdir, bd=3, relief=SUNKEN).pack(side=LEFT)
SlideShow(parent=root, picdir=picdir, bd=3, relief=SUNKEN).pack(side=RIGHT)
root.mainloop()

Figure 11-16. Two attached SlideShow objects

The simple
slideShow_frames
scripts attach two instances of
SlideShow
to a single window—a feat possible
only because state information is recorded in class instance variables,
not in globals. The
slideShow_toplevels
script (also in the book’s
examples distribution) attaches two
SlideShow
s to two top-level pop-up windows
instead. In both cases, the slideshows run independently but are based
on
after
events fired from the same
single event loop in a single
process.

Other books

Lost Boy by Tim Green
Operation Massacre by Rodolfo Walsh, translation by Daniella Gitlin, foreword by Michael Greenberg, afterwood by Ricardo Piglia
Stranger in Town by Cheryl Bradshaw
Escorting Jessica by Pulkinen, Carrie
Medusa Frequency by Russell Hoban
The Accused (Modern Plays) by Jeffrey Archer


readsbookonline.com Copyright 2016 - 2024