1 # A minimal text editor.
4 # - Functionality: find, etc.
6 from Menu
import DrawMenuBar
7 from FrameWork
import *
21 WATCH
= Qd
.GetCursor(4).data
25 UNDOLABELS
= [ # Indexed by WEGetUndoInfo() value
26 None, "", "typing", "Cut", "Paste", "Clear", "Drag", "Style"]
28 # Style and size menu. Note that style order is important (tied to bit values)
30 ("Bold", "B"), ("Italic", "I"), ("Underline", "U"), ("Outline", "O"),
31 ("Shadow", ""), ("Condensed", ""), ("Extended", "")
33 SIZES
= [ 9, 10, 12, 14, 18, 24]
35 # Sizes for HTML tag types
42 Qd
.SetRectRgn(BIGREGION
, -16000, -16000, 16000, 16000)
44 class WasteWindow(ScrolledWindow
):
45 def open(self
, path
, name
, data
):
48 r
= windowbounds(400, 400)
49 w
= Win
.NewWindow(r
, name
, 1, 0, -1, 1, 0x55555555)
51 vr
= LEFTMARGIN
, 0, r
[2]-r
[0]-15, r
[3]-r
[1]-15
56 flags
= WASTEconst
.weDoAutoScroll | WASTEconst
.weDoOutlineHilite | \
57 WASTEconst
.weDoMonoStyled | WASTEconst
.weDoUndo
58 self
.ted
= waste
.WENew(dr
, vr
, flags
)
59 self
.ted
.WEInstallTabHooks()
60 style
, soup
= self
.getstylesoup(self
.path
)
61 self
.ted
.WEInsert(data
, style
, soup
)
62 self
.ted
.WESetSelection(0,0)
64 self
.ted
.WEResetModCount()
68 self
.do_activate(1, None)
70 def getstylesoup(self
, pathname
):
73 oldrf
= Res
.CurResFile()
75 rf
= Res
.OpenResFile(self
.path
)
79 hstyle
= Res
.Get1Resource('styl', 128)
80 hstyle
.DetachResource()
84 hsoup
= Res
.Get1Resource('SOUP', 128)
85 hsoup
.DetachResource()
92 def do_idle(self
, event
):
93 (what
, message
, when
, where
, modifiers
) = event
96 if self
.ted
.WEAdjustCursor(where
, BIGREGION
):
98 Qd
.SetCursor(Qd
.qd
.arrow
)
100 def getscrollbarvalues(self
):
101 dr
= self
.ted
.WEGetDestRect()
102 vr
= self
.ted
.WEGetViewRect()
103 vx
= self
.scalebarvalue(dr
[0], dr
[2], vr
[0], vr
[2])
104 vy
= self
.scalebarvalue(dr
[1], dr
[3], vr
[1], vr
[3])
107 def scrollbar_callback(self
, which
, what
, value
):
110 # "line" size is minimum of top and bottom line size
112 topline_off
,dummy
= self
.ted
.WEGetOffset((1,1))
113 topline_num
= self
.ted
.WEOffsetToLine(topline_off
)
114 toplineheight
= self
.ted
.WEGetHeight(topline_num
, topline_num
+1)
116 botlinepos
= self
.ted
.WEGetViewRect()[3]
117 botline_off
, dummy
= self
.ted
.WEGetOffset((1, botlinepos
-1))
118 botline_num
= self
.ted
.WEOffsetToLine(botline_off
)
119 botlineheight
= self
.ted
.WEGetHeight(botline_num
, botline_num
+1)
121 if botlineheight
== 0:
122 botlineheight
= self
.ted
.WEGetHeight(botline_num
-1, botline_num
)
123 if botlineheight
< toplineheight
:
124 lineheight
= botlineheight
126 lineheight
= toplineheight
130 # Now do the command.
133 height
= self
.ted
.WEGetHeight(0, 0x3fffffff)
134 cur
= self
.getscrollbarvalues()[1]
135 delta
= (cur
-value
)*height
/32767
139 delta
= (self
.ted
.WEGetViewRect()[3]-lineheight
)
145 delta
= -(self
.ted
.WEGetViewRect()[3]-lineheight
)
148 self
.ted
.WEScroll(0, delta
)
152 vr
= self
.ted
.WEGetViewRect()
153 winwidth
= vr
[2]-vr
[0]
162 self
.ted
.WEScroll(delta
, 0)
164 l
, t
, r
, b
= self
.ted
.WEGetDestRect()
165 vl
, vt
, vr
, vb
= self
.ted
.WEGetViewRect()
170 self
.ted
.WEScroll(dx
, dy
)
172 self
.ted
.WEScroll(0, vb
-b
)
175 def do_activate(self
, onoff
, evt
):
177 ScrolledWindow
.do_activate(self
, onoff
, evt
)
179 self
.ted
.WEActivate()
180 self
.parent
.active
= self
181 self
.parent
.updatemenubar()
183 self
.ted
.WEDeactivate()
185 def do_update(self
, wid
, event
):
186 region
= wid
.GetWindowPort().visRgn
187 if Qd
.EmptyRgn(region
):
190 self
.ted
.WEUpdate(region
)
191 self
.updatescrollbars()
193 def do_postresize(self
, width
, height
, window
):
194 l
, t
, r
, b
= self
.ted
.WEGetViewRect()
195 vr
= (l
, t
, l
+width
-15, t
+height
-15)
196 self
.ted
.WESetViewRect(vr
)
198 ScrolledWindow
.do_postresize(self
, width
, height
, window
)
200 def do_contentclick(self
, local
, modifiers
, evt
):
201 (what
, message
, when
, where
, modifiers
) = evt
202 self
.ted
.WEClick(local
, modifiers
, when
)
203 self
.updatescrollbars()
204 self
.parent
.updatemenubar()
206 def do_char(self
, ch
, event
):
208 (what
, message
, when
, where
, modifiers
) = event
209 self
.ted
.WEKey(ord(ch
), modifiers
)
210 self
.updatescrollbars()
211 self
.parent
.updatemenubar()
214 if self
.ted
.WEGetModCount():
215 save
= EasyDialogs
.AskYesNoCancel('Save window "%s" before closing?'%self
.name
, 1)
220 if self
.parent
.active
== self
:
221 self
.parent
.active
= None
222 self
.parent
.updatemenubar()
229 return # Will call us recursively
233 dhandle
= self
.ted
.WEGetText()
235 fp
= open(self
.path
, 'wb') # NOTE: wb, because data has CR for end-of-line
237 if data
[-1] <> '\r': fp
.write('\r')
240 # Now save style and soup
242 oldresfile
= Res
.CurResFile()
244 rf
= Res
.OpenResFile(self
.path
)
246 Res
.CreateResFile(self
.path
)
247 rf
= Res
.OpenResFile(self
.path
)
248 styles
= Res
.Resource('')
249 soup
= Res
.Resource('')
250 self
.ted
.WECopyRange(0, 0x3fffffff, None, styles
, soup
)
251 styles
.AddResource('styl', 128, '')
252 soup
.AddResource('SOUP', 128, '')
254 Res
.UseResFile(oldresfile
)
256 self
.ted
.WEResetModCount()
258 def menu_save_as(self
):
259 fss
, ok
= macfs
.StandardPutFile('Save as:')
261 self
.path
= fss
.as_pathname()
262 self
.name
= os
.path
.split(self
.path
)[-1]
263 self
.wid
.SetWTitle(self
.name
)
266 def menu_insert(self
, fp
):
269 self
.ted
.WEInsert(data
, None, None)
270 self
.updatescrollbars()
271 self
.parent
.updatemenubar()
273 def menu_insert_html(self
, fp
):
276 f
= formatter
.AbstractFormatter(self
)
278 # Remember where we are, and don't update
280 start
, dummy
= self
.ted
.WEGetSelection()
281 self
.ted
.WEFeatureFlag(WASTEconst
.weFInhibitRecal
, 1)
287 # Restore updating, recalc, set focus
288 dummy
, end
= self
.ted
.WEGetSelection()
290 self
.ted
.WESetSelection(start
, end
)
292 self
.ted
.WEFeatureFlag(WASTEconst
.weFInhibitRecal
, 0)
293 Win
.InvalRect(self
.ted
.WEGetViewRect())
295 self
.updatescrollbars()
296 self
.parent
.updatemenubar()
303 self
.updatescrollbars()
304 self
.parent
.updatemenubar()
309 self
.updatescrollbars()
310 self
.parent
.updatemenubar()
312 def menu_paste(self
):
315 self
.updatescrollbars()
316 self
.parent
.updatemenubar()
318 def menu_clear(self
):
321 self
.updatescrollbars()
322 self
.parent
.updatemenubar()
326 self
.updatescrollbars()
327 self
.parent
.updatemenubar()
329 def menu_setfont(self
, font
):
330 font
= Fm
.GetFNum(font
)
331 self
.mysetstyle(WASTEconst
.weDoFont
, (font
, 0, 0, (0,0,0)))
332 self
.parent
.updatemenubar()
334 def menu_modface(self
, face
):
335 self
.mysetstyle(WASTEconst
.weDoFace|WASTEconst
.weDoToggleFace
,
336 (0, face
, 0, (0,0,0)))
338 def menu_setface(self
, face
):
339 self
.mysetstyle(WASTEconst
.weDoFace|WASTEconst
.weDoReplaceFace
,
340 (0, face
, 0, (0,0,0)))
342 def menu_setsize(self
, size
):
343 self
.mysetstyle(WASTEconst
.weDoSize
, (0, 0, size
, (0,0,0)))
345 def menu_incsize(self
, size
):
346 self
.mysetstyle(WASTEconst
.weDoAddSize
, (0, 0, size
, (0,0,0)))
348 def mysetstyle(self
, which
, how
):
350 self
.ted
.WESetStyle(which
, how
)
351 self
.parent
.updatemenubar()
353 def have_selection(self
):
354 start
, stop
= self
.ted
.WEGetSelection()
358 return self
.ted
.WECanPaste()
361 which
, redo
= self
.ted
.WEGetUndoInfo()
362 which
= UNDOLABELS
[which
]
363 if which
== None: return None
369 def getruninfo(self
):
370 all
= (WASTEconst
.weDoFont | WASTEconst
.weDoFace | WASTEconst
.weDoSize
)
371 dummy
, mode
, (font
, face
, size
, color
) = self
.ted
.WEContinuousStyle(all
)
372 if not (mode
& WASTEconst
.weDoFont
):
375 font
= Fm
.GetFontName(font
)
376 if not (mode
& WASTEconst
.weDoFace
): fact
= None
377 if not (mode
& WASTEconst
.weDoSize
): size
= None
378 return font
, face
, size
381 # Methods for writer class for html formatter
385 self
.html_font
= [12, 0, 0, 0]
387 self
.html_color
= (0,0,0)
388 self
.new_font(self
.html_font
)
390 def new_font(self
, font
):
393 font
= map(lambda x
:x
, font
)
394 for i
in range(len(font
)):
396 font
[i
] = self
.html_font
[i
]
397 [size
, italic
, bold
, tt
] = font
398 self
.html_font
= font
[:]
400 font
= Fm
.GetFNum('Courier')
402 font
= Fm
.GetFNum('Times')
403 if HTML_SIZE
.has_key(size
):
404 size
= HTML_SIZE
[size
]
408 if bold
: face
= face |
1
409 if italic
: face
= face |
2
410 face
= face | self
.html_style
411 self
.ted
.WESetStyle(WASTEconst
.weDoFont | WASTEconst
.weDoFace |
412 WASTEconst
.weDoSize | WASTEconst
.weDoColor
,
413 (font
, face
, size
, self
.html_color
))
415 def new_margin(self
, margin
, level
):
416 self
.ted
.WEInsert('[Margin %s %s]'%(margin
, level
), None, None)
418 def new_spacing(self
, spacing
):
419 self
.ted
.WEInsert('[spacing %s]'%spacing
, None, None)
421 def new_styles(self
, styles
):
423 self
.html_color
= (0,0,0)
424 if 'anchor' in styles
:
425 self
.html_style
= self
.html_style |
4
426 self
.html_color
= (0xffff, 0, 0)
427 self
.new_font(self
.html_font
)
429 def send_paragraph(self
, blankline
):
430 self
.ted
.WEInsert('\r'*(blankline
+1), None, None)
432 def send_line_break(self
):
433 self
.ted
.WEInsert('\r', None, None)
435 def send_hor_rule(self
, *args
, **kw
):
436 # Ignore ruler options, for now
437 dummydata
= Res
.Resource('')
438 self
.ted
.WEInsertObject('rulr', dummydata
, (0,0))
440 def send_label_data(self
, data
):
441 self
.ted
.WEInsert(data
, None, None)
443 def send_flowing_data(self
, data
):
444 self
.ted
.WEInsert(data
, None, None)
446 def send_literal_data(self
, data
):
447 data
= regsub
.gsub('\n', '\r', data
)
448 data
= string
.expandtabs(data
)
449 self
.ted
.WEInsert(data
, None, None)
451 class Wed(Application
):
453 Application
.__init
__(self
)
457 waste
.STDObjectHandlers()
458 # Handler for horizontal ruler
459 waste
.WEInstallObjectHandler('rulr', 'new ', self
.newRuler
)
460 waste
.WEInstallObjectHandler('rulr', 'draw', self
.drawRuler
)
462 def makeusermenus(self
):
463 self
.filemenu
= m
= Menu(self
.menubar
, "File")
464 self
.newitem
= MenuItem(m
, "New window", "N", self
.open)
465 self
.openitem
= MenuItem(m
, "Open...", "O", self
.openfile
)
466 self
.closeitem
= MenuItem(m
, "Close", "W", self
.closewin
)
468 self
.saveitem
= MenuItem(m
, "Save", "S", self
.save
)
469 self
.saveasitem
= MenuItem(m
, "Save as...", "", self
.saveas
)
471 self
.insertitem
= MenuItem(m
, "Insert plaintext...", "", self
.insertfile
)
472 self
.htmlitem
= MenuItem(m
, "Insert HTML...", "", self
.inserthtml
)
474 self
.quititem
= MenuItem(m
, "Quit", "Q", self
.quit
)
476 self
.editmenu
= m
= Menu(self
.menubar
, "Edit")
477 self
.undoitem
= MenuItem(m
, "Undo", "Z", self
.undo
)
478 self
.cutitem
= MenuItem(m
, "Cut", "X", self
.cut
)
479 self
.copyitem
= MenuItem(m
, "Copy", "C", self
.copy
)
480 self
.pasteitem
= MenuItem(m
, "Paste", "V", self
.paste
)
481 self
.clearitem
= MenuItem(m
, "Clear", "", self
.clear
)
485 # Groups of items enabled together:
486 self
.windowgroup
= [self
.closeitem
, self
.saveitem
, self
.saveasitem
,
487 self
.editmenu
, self
.fontmenu
, self
.facemenu
, self
.sizemenu
,
489 self
.focusgroup
= [self
.cutitem
, self
.copyitem
, self
.clearitem
]
490 self
.windowgroup_on
= -1
491 self
.focusgroup_on
= -1
492 self
.pastegroup_on
= -1
493 self
.undo_label
= "never"
496 def makefontmenu(self
):
497 self
.fontmenu
= Menu(self
.menubar
, "Font")
498 self
.fontnames
= getfontnames()
500 for n
in self
.fontnames
:
501 m
= MenuItem(self
.fontmenu
, n
, "", self
.selfont
)
502 self
.fontitems
.append(m
)
503 self
.facemenu
= Menu(self
.menubar
, "Style")
505 for n
, shortcut
in STYLES
:
506 m
= MenuItem(self
.facemenu
, n
, shortcut
, self
.selface
)
507 self
.faceitems
.append(m
)
508 self
.facemenu
.addseparator()
509 self
.faceitem_normal
= MenuItem(self
.facemenu
, "Normal", "N",
511 self
.sizemenu
= Menu(self
.menubar
, "Size")
514 m
= MenuItem(self
.sizemenu
, `n`
, "", self
.selsize
)
515 self
.sizeitems
.append(m
)
516 self
.sizemenu
.addseparator()
517 self
.sizeitem_bigger
= MenuItem(self
.sizemenu
, "Bigger", "+",
519 self
.sizeitem_smaller
= MenuItem(self
.sizemenu
, "Smaller", "-",
522 def selfont(self
, id, item
, *rest
):
524 font
= self
.fontnames
[item
-1]
525 self
.active
.menu_setfont(font
)
527 EasyDialogs
.Message("No active window?")
529 def selface(self
, id, item
, *rest
):
532 self
.active
.menu_modface(face
)
534 EasyDialogs
.Message("No active window?")
536 def selfacenormal(self
, *rest
):
538 self
.active
.menu_setface(0)
540 EasyDialogs
.Message("No active window?")
542 def selsize(self
, id, item
, *rest
):
545 self
.active
.menu_setsize(size
)
547 EasyDialogs
.Message("No active window?")
549 def selsizebigger(self
, *rest
):
551 self
.active
.menu_incsize(2)
553 EasyDialogs
.Message("No active window?")
555 def selsizesmaller(self
, *rest
):
557 self
.active
.menu_incsize(-2)
559 EasyDialogs
.Message("No active window?")
561 def updatemenubar(self
):
563 on
= (self
.active
<> None)
564 if on
<> self
.windowgroup_on
:
565 for m
in self
.windowgroup
:
567 self
.windowgroup_on
= on
570 # only if we have an edit menu
571 on
= self
.active
.have_selection()
572 if on
<> self
.focusgroup_on
:
573 for m
in self
.focusgroup
:
575 self
.focusgroup_on
= on
577 on
= self
.active
.can_paste()
578 if on
<> self
.pastegroup_on
:
579 self
.pasteitem
.enable(on
)
580 self
.pastegroup_on
= on
582 on
= self
.active
.can_undo()
583 if on
<> self
.undo_label
:
585 self
.undoitem
.enable(1)
586 self
.undoitem
.settext(on
)
589 self
.undoitem
.settext("Nothing to undo")
590 self
.undoitem
.enable(0)
592 if self
.updatefontmenus():
597 def updatefontmenus(self
):
598 info
= self
.active
.getruninfo()
599 if info
== self
.ffs_values
:
601 # Remove old checkmarks
602 if self
.ffs_values
== ():
603 self
.ffs_values
= (None, None, None)
604 font
, face
, size
= self
.ffs_values
606 fnum
= self
.fontnames
.index(font
)
607 self
.fontitems
[fnum
].check(0)
609 for i
in range(len(self
.faceitems
)):
611 self
.faceitems
[i
].check(0)
613 for i
in range(len(self
.sizeitems
)):
615 self
.sizeitems
[i
].check(0)
617 self
.ffs_values
= info
619 font
, face
, size
= self
.ffs_values
621 fnum
= self
.fontnames
.index(font
)
622 self
.fontitems
[fnum
].check(1)
624 for i
in range(len(self
.faceitems
)):
626 self
.faceitems
[i
].check(1)
628 for i
in range(len(self
.sizeitems
)):
630 self
.sizeitems
[i
].check(1)
631 # Set outline/normal for sizes
633 exists
= getfontsizes(font
, SIZES
)
634 for i
in range(len(self
.sizeitems
)):
636 self
.sizeitems
[i
].setstyle(0)
638 self
.sizeitems
[i
].setstyle(8)
644 def do_about(self
, id, item
, window
, event
):
645 EasyDialogs
.Message("A simple single-font text editor based on WASTE")
651 def open(self
, *args
):
654 def openfile(self
, *args
):
657 def _open(self
, askfile
):
659 fss
, ok
= macfs
.StandardGetFile('TEXT')
662 path
= fss
.as_pathname()
663 name
= os
.path
.split(path
)[-1]
665 fp
= open(path
, 'rb') # NOTE binary, we need cr as end-of-line
669 EasyDialogs
.Message("IOERROR: "+`arg`
)
673 name
= "Untitled %d"%self
.num
675 w
= WasteWindow(self
)
676 w
.open(path
, name
, data
)
677 self
.num
= self
.num
+ 1
679 def insertfile(self
, *args
):
681 fss
, ok
= macfs
.StandardGetFile('TEXT')
684 path
= fss
.as_pathname()
686 fp
= open(path
, 'rb') # NOTE binary, we need cr as end-of-line
688 EasyDialogs
.Message("IOERROR: "+`arg`
)
690 self
.active
.menu_insert(fp
)
692 EasyDialogs
.Message("No active window?")
694 def inserthtml(self
, *args
):
696 fss
, ok
= macfs
.StandardGetFile('TEXT')
699 path
= fss
.as_pathname()
703 EasyDialogs
.Message("IOERROR: "+`arg`
)
705 self
.active
.menu_insert_html(fp
)
707 EasyDialogs
.Message("No active window?")
710 def closewin(self
, *args
):
714 EasyDialogs
.Message("No active window?")
716 def save(self
, *args
):
718 self
.active
.menu_save()
720 EasyDialogs
.Message("No active window?")
722 def saveas(self
, *args
):
724 self
.active
.menu_save_as()
726 EasyDialogs
.Message("No active window?")
729 def quit(self
, *args
):
730 for w
in self
._windows
.values():
740 def undo(self
, *args
):
742 self
.active
.menu_undo()
744 EasyDialogs
.Message("No active window?")
746 def cut(self
, *args
):
748 self
.active
.menu_cut()
750 EasyDialogs
.Message("No active window?")
752 def copy(self
, *args
):
754 self
.active
.menu_copy()
756 EasyDialogs
.Message("No active window?")
758 def paste(self
, *args
):
760 self
.active
.menu_paste()
762 EasyDialogs
.Message("No active window?")
764 def clear(self
, *args
):
766 self
.active
.menu_clear()
768 EasyDialogs
.Message("No active window?")
774 def idle(self
, event
):
776 self
.active
.do_idle(event
)
778 def newRuler(self
, obj
):
779 """Insert a new ruler. Make it as wide as the window minus 2 pxls"""
780 ted
= obj
.WEGetObjectOwner()
781 l
, t
, r
, b
= ted
.WEGetDestRect()
784 def drawRuler(self
, (l
, t
, r
, b
), obj
):
790 class MyHTMLParser(htmllib
.HTMLParser
):
792 def anchor_bgn(self
, href
, name
, type):
795 self
.anchorlist
.append(href
)
796 self
.formatter
.push_style('anchor')
798 def anchor_end(self
):
801 self
.formatter
.pop_style()
807 n
= Fm
.GetFontName(i
)
808 if n
: names
.append(n
)
811 def getfontsizes(name
, sizes
):
813 num
= Fm
.GetFNum(name
)
815 if Fm
.RealFont(num
, sz
):
825 if __name__
== '__main__':