The standard text widgets only allow one text style in the entire widget.
This is fine for things like code editors, but WASTE has built-in support
for styled text, so it would be nice to have a text widget which can use
those features. We'll subclass the W.TextEditor
class to
provide these services.
As before, we have to import some modules to help us, and define a couple of constants which are used in the menu handlers.
import W import Wapplication import WASTEconst import waste import FrameWork import Res import Fm import Menu # Style and size menu. Note that style order is important (tied to bit values) STYLES = [ ("Bold", "B"), ("Italic", "I"), ("Underline", "U"), ("Outline", "O"), ("Shadow", ""), ("Condensed", ""), ("Extended", "") ] SIZES = [ 9, 10, 12, 14, 18, 24]
Now comes the main definition. We use W.TextEditor
as our
base, but we now allow the passing of style and object information along
with the text, via the styles and soup parameters.
To cope with this change, we redefine the set()
and
get()
methods, and make sure that open()
and
close()
use the new information. For convenience, we define
settext()
and gettext()
methods, so that we can
get our hands on the text without having to worry about the style
information. They key changes are highlighted in red.
class StyledEditor(W.TextEditor): def __init__(self, possize, text = "", styles = None, soup = None, callback=None, wrap=1, inset=(4, 4), fontsettings=None, tabsettings=(32, 0), readonly=0): W.TextEditor.__init__(self, possize, text = "", callback=None, wrap=1, inset=(4, 4), fontsettings=None, tabsettings=(32, 0), readonly=0)
self.tempstyles = styles self.tempsoup = soup
def open(self): if not hasattr(self._parent, "_barx"): self._parent._barx = None if not hasattr(self._parent, "_bary"): self._parent._bary = None self._calcbounds() self.SetPort() viewrect, destrect = self._calctextbounds() flags = self._getflags() self.ted = waste.WENew(destrect, viewrect, flags) self.ted.WEInstallTabHooks() self.setfontsettings(self.fontsettings) self.settabsettings(self.tabsettings)
self.ted.WEInsert(self.temptext, self.tempstyles, self.tempsoup)
self.ted.WECalText() self.ted.WEResetModCount() if self.selection: self.setselection(self.selection[0], self.selection[1]) self.selection = None else: self.selview() self.temptext = None self.tempstyles = None self.tempsoup = None self.updatescrollbars() self.bind("pageup", self.scrollpageup) self.bind("pagedown", self.scrollpagedown) self.bind("top", self.scrolltop) self.bind("bottom", self.scrollbottom) self.selchanged = 0 def close(self):
self.tempstyles = None self.tempsoup = None
W.TextEditor.close(self) def set(self, text, style=None, soup=None): if not self.ted: self.temptext = text self.tempstyles = style self.tempsoup = soup else:
texthandle = Res.Resource(text) self.ted.WESetSelection(0, self.tedWEGetTextLength()) self.ted.WEDelete() self.ted.WEInsert(text, style, soup) self.ted.WESetSelection(0,0) self.ted.WECalText()
self.SetPort() viewrect, destrect = self._calctextbounds() self.ted.WESetViewRect(viewrect) self.ted.WESetDestRect(destrect) rgn = Qd.NewRgn() Qd.RectRgn(rgn, viewrect) Qd.EraseRect(viewrect) self.draw(rgn) self.updatescrollbars() def get(self): if not self.ted: return self.temptext, self.tempstyles, self.tempsoup else:
texthandle = Res.Resource('') styles = Res.Resource('') soup = Res.Resource('') self.ted.WECopyRange(0, self.tedWEGetTextLength(), texthandle, styles, soup)
return text.data, styles, soup def settext(self, text): return W.TextEditor.set(self, text) def gettext(self): return W.TextEditor.get(self)
We need a general routine which allows us to set the style of the selection
in response to menu items or other commands. setstyle()
does
this job. Which is flags from WASTEconst
, which
tell WASTE which aspects of the text style of the selection we want to
change. Fontsettings is a
standard tuple which tells WASTE the font, face, size and color.
def setstyle(self, which, fontsettings): self.ted.WESelView() self.ted.WESetStyle(which, fontsettings)
We also need to override the _getflags()
method to let WASTE
know we we want a widget with styled text.
def _getflags(self): flags = WASTEconst.weDoAutoScroll | WASTEconst.weDoOutlineHilite if self.readonly: flags = flags | WASTEconst.weDoReadOnly else: flags = flags | WASTEconst.weDoUndo return flags
Finally we need to set hooks for Font, Style and Size menus, if they are
available. The domenu_
methods will change the appropriate
aspect of the selected text in the widget; the can_
methods
handle putting checkmarks in the menu for those styles which span the whole
selection. All that is needed to make these work is to create a Font menu
(it should contain a list of all 'FOND'
resources which don't
start with '.'
or '%'
- or some appropriate
subset); a Style menu whose items are listed in the STYLES
variable (the order here is important); and a Size menu (the sizes listed
in SIZES
are a good selection, but any menu containing numbers
is OK). All the menu items should have callbacks of the form
'setfont'
, 'setface'
or 'setsize'
as
appropriate.
def domenu_setfont(self, id, item, window, event): text = Menu.GetMenuHandle(id).GetMenuItemText(item) font = W.GetFNum(text) self.setstyle(WASTEconst.weDoFont, (font, 0, 0, (0, 0, 0))) def can_setfont(self, item): any, mode, (font, face, size, color) = self.ted.WEContinuousStyle(WASTEconst.weDoFont) if any and Fm.GetFontName(font) == item.menu.items[item.item-1][0]: item.check(1) else: item.check(0) return 1 def domenu_setface(self, id, item, window, event): face = (1 << (item-1)) self.setstyle(WASTEconst.weDoFace | WASTEconst.weDoToggleFace, (0, face, 0, (0, 0, 0))) def can_setface(self, item): any, mode, (font, face, size, color) = self.ted.WEContinuousStyle(WASTEconst.weDoFace) if any and item.menu.items[item.item-1][0] in getfaces(face): item.check(1) else: item.check(0) return 1 def domenu_setsize(self, id, item, window, event): size = Menu.GetMenuHandle(id).GetMenuItemText(item) self.setstyle(WASTEconst.weDoSize, (0, 0, int(size), (0, 0, 0))) def can_setsize(self, item): any, mode, (font, face, size, color) = self.ted.WEContinuousStyle(WASTEconst.weDoSize) if any and item.menu.items[item.item-1][0] == str(size): item.check(1) else: item.check(0) return 1
Finally, we define some utility routines which help is set up the various menus, if the don't already exist. Unfortunately, we have to do this by hand, since otherwise we are likely to get multiple copies of the menus. These would likely be created by any application which used this widget.
def MakeFontMenu(menubar, where = 0): app = W.getapplication() fontmenu = Wapplication.Menu(menubar, "Font", where) for font in getfontnames(): item = FrameWork.MenuItem(fontmenu, font, "", "setfont") app._menustocheck.append(item) return fontmenu def MakeFaceMenu(menubar, where = 0): app = W.getapplication() facemenu = Wapplication.Menu(menubar, "Style", where) for face, key in STYLES: item = FrameWork.MenuItem(facemenu, face, key, "setface") app._menustocheck.append(item) return facemenu def MakeSizeMenu(menubar, where=0): app = W.getapplication() sizemenu = Wapplication.Menu(menubar, "Size", where) for size in SIZES: item = FrameWork.MenuItem(sizemenu, str(size), "", "setsize") app._menustocheck.append(item) return sizemenu def MakeNewMenus(): mbar = W.getapplication().menubar MakeFontMenu(mbar) MakeFaceMenu(mbar) MakeSizeMenu(mbar) def getfontnames(): fontnames = [] for i in range(1, Res.CountResources('FOND') + 1): r = Res.GetIndResource('FOND', i) name = r.GetResInfo()[2] if name[0] not in [".", "%"]: fontnames.append(name) fontnames.sort() return fontnames def getfaces(face): faces = [] for i in range(len(STYLES)): if face & (1 << i): faces.append(STYLES[i][0]) return faces
You can view the code put together into a complete module. Run it as main in the IDE to see it in action. To get the most out of it, create Font, Style and Size menus for the IDE by modularizing it, and typing:
>>> import stylededitor >>> stylededitor.MakeNewMenus()in the console window.
Ideally we'd add in more hooks here for other useful font-based menu items, like "Larger", "Smaller" and "Other" menu items for the Size menu, and maybe a "Plain" option for the Style menu. We could also add hooks for a Color menu and justifying the text. With a bit more work, it might be possible to jack this widget up to create a simple editor roughly equivalent to SimpleText in power, or perhaps a simple HTML viewer.
domenu_
and commands for "Larger" and "Smaller" menu
items in the Size menu.domenu_setface
and can_setface
to
allow a "Plain" menu item in the Style menu.STYLES
variable must appear first and in that order (if
you did exercise 2, you may have noticed this). Redesign it to be more
robust.