1 # A minimal text editor.
4 # - Functionality: find, etc.
6 from Menu
import DrawMenuBar
7 from FrameWork
import *
22 WATCH
= Qd
.GetCursor(4).data
26 UNDOLABELS
= [ # Indexed by WEGetUndoInfo() value
27 None, "", "typing", "Cut", "Paste", "Clear", "Drag", "Style"]
29 # Style and size menu. Note that style order is important (tied to bit values)
31 ("Bold", "B"), ("Italic", "I"), ("Underline", "U"), ("Outline", "O"),
32 ("Shadow", ""), ("Condensed", ""), ("Extended", "")
34 SIZES
= [ 9, 10, 12, 14, 18, 24]
36 # Sizes for HTML tag types
43 Qd
.SetRectRgn(BIGREGION
, -16000, -16000, 16000, 16000)
45 class WasteWindow(ScrolledWindow
):
46 def open(self
, path
, name
, data
):
49 r
= windowbounds(400, 400)
50 w
= Win
.NewWindow(r
, name
, 1, 0, -1, 1, 0x55555555)
52 vr
= LEFTMARGIN
, 0, r
[2]-r
[0]-15, r
[3]-r
[1]-15
57 flags
= WASTEconst
.weDoAutoScroll | WASTEconst
.weDoOutlineHilite | \
58 WASTEconst
.weDoMonoStyled | WASTEconst
.weDoUndo
59 self
.ted
= waste
.WENew(dr
, vr
, flags
)
60 self
.ted
.WEInstallTabHooks()
61 style
, soup
= self
.getstylesoup(self
.path
)
62 self
.ted
.WEInsert(data
, style
, soup
)
63 self
.ted
.WESetSelection(0,0)
65 self
.ted
.WEResetModCount()
69 self
.do_activate(1, None)
71 def getstylesoup(self
, pathname
):
74 oldrf
= Res
.CurResFile()
76 rf
= Res
.FSpOpenResFile(self
.path
, 1)
80 hstyle
= Res
.Get1Resource('styl', 128)
81 hstyle
.DetachResource()
85 hsoup
= Res
.Get1Resource('SOUP', 128)
86 hsoup
.DetachResource()
93 def do_idle(self
, event
):
94 (what
, message
, when
, where
, modifiers
) = event
97 if self
.ted
.WEAdjustCursor(where
, BIGREGION
):
99 Qd
.SetCursor(Qd
.qd
.arrow
)
101 def getscrollbarvalues(self
):
102 dr
= self
.ted
.WEGetDestRect()
103 vr
= self
.ted
.WEGetViewRect()
104 vx
= self
.scalebarvalue(dr
[0], dr
[2], vr
[0], vr
[2])
105 vy
= self
.scalebarvalue(dr
[1], dr
[3], vr
[1], vr
[3])
108 def scrollbar_callback(self
, which
, what
, value
):
111 # "line" size is minimum of top and bottom line size
113 topline_off
,dummy
= self
.ted
.WEGetOffset((1,1))
114 topline_num
= self
.ted
.WEOffsetToLine(topline_off
)
115 toplineheight
= self
.ted
.WEGetHeight(topline_num
, topline_num
+1)
117 botlinepos
= self
.ted
.WEGetViewRect()[3]
118 botline_off
, dummy
= self
.ted
.WEGetOffset((1, botlinepos
-1))
119 botline_num
= self
.ted
.WEOffsetToLine(botline_off
)
120 botlineheight
= self
.ted
.WEGetHeight(botline_num
, botline_num
+1)
122 if botlineheight
== 0:
123 botlineheight
= self
.ted
.WEGetHeight(botline_num
-1, botline_num
)
124 if botlineheight
< toplineheight
:
125 lineheight
= botlineheight
127 lineheight
= toplineheight
131 # Now do the command.
134 height
= self
.ted
.WEGetHeight(0, 0x3fffffff)
135 cur
= self
.getscrollbarvalues()[1]
136 delta
= (cur
-value
)*height
/32767
140 delta
= (self
.ted
.WEGetViewRect()[3]-lineheight
)
146 delta
= -(self
.ted
.WEGetViewRect()[3]-lineheight
)
149 self
.ted
.WEScroll(0, delta
)
153 vr
= self
.ted
.WEGetViewRect()
154 winwidth
= vr
[2]-vr
[0]
163 self
.ted
.WEScroll(delta
, 0)
165 l
, t
, r
, b
= self
.ted
.WEGetDestRect()
166 vl
, vt
, vr
, vb
= self
.ted
.WEGetViewRect()
171 self
.ted
.WEScroll(dx
, dy
)
173 self
.ted
.WEScroll(0, vb
-b
)
176 def do_activate(self
, onoff
, evt
):
178 ScrolledWindow
.do_activate(self
, onoff
, evt
)
180 self
.ted
.WEActivate()
181 self
.parent
.active
= self
182 self
.parent
.updatemenubar()
184 self
.ted
.WEDeactivate()
186 def do_update(self
, wid
, event
):
187 region
= wid
.GetWindowPort().visRgn
188 if Qd
.EmptyRgn(region
):
191 self
.ted
.WEUpdate(region
)
192 self
.updatescrollbars()
194 def do_postresize(self
, width
, height
, window
):
195 l
, t
, r
, b
= self
.ted
.WEGetViewRect()
196 vr
= (l
, t
, l
+width
-15, t
+height
-15)
197 self
.ted
.WESetViewRect(vr
)
198 self
.wid
.InvalWindowRect(vr
)
199 ScrolledWindow
.do_postresize(self
, width
, height
, window
)
201 def do_contentclick(self
, local
, modifiers
, evt
):
202 (what
, message
, when
, where
, modifiers
) = evt
203 self
.ted
.WEClick(local
, modifiers
, when
)
204 self
.updatescrollbars()
205 self
.parent
.updatemenubar()
207 def do_char(self
, ch
, event
):
209 (what
, message
, when
, where
, modifiers
) = event
210 self
.ted
.WEKey(ord(ch
), modifiers
)
211 self
.updatescrollbars()
212 self
.parent
.updatemenubar()
215 if self
.ted
.WEGetModCount():
216 save
= EasyDialogs
.AskYesNoCancel('Save window "%s" before closing?'%self
.name
, 1)
221 if self
.parent
.active
== self
:
222 self
.parent
.active
= None
223 self
.parent
.updatemenubar()
230 return # Will call us recursively
234 dhandle
= self
.ted
.WEGetText()
236 fp
= open(self
.path
, 'wb') # NOTE: wb, because data has CR for end-of-line
238 if data
[-1] <> '\r': fp
.write('\r')
241 # Now save style and soup
243 oldresfile
= Res
.CurResFile()
245 rf
= Res
.FSpOpenResFile(self
.path
, 3)
247 Res
.FSpCreateResFile(self
.path
, '????', 'TEXT', MACFS
.smAllScripts
)
248 rf
= Res
.FSpOpenResFile(self
.path
, 3)
249 styles
= Res
.Resource('')
250 soup
= Res
.Resource('')
251 self
.ted
.WECopyRange(0, 0x3fffffff, None, styles
, soup
)
252 styles
.AddResource('styl', 128, '')
253 soup
.AddResource('SOUP', 128, '')
255 Res
.UseResFile(oldresfile
)
257 self
.ted
.WEResetModCount()
259 def menu_save_as(self
):
260 fss
, ok
= macfs
.StandardPutFile('Save as:')
262 self
.path
= fss
.as_pathname()
263 self
.name
= os
.path
.split(self
.path
)[-1]
264 self
.wid
.SetWTitle(self
.name
)
267 def menu_insert(self
, fp
):
270 self
.ted
.WEInsert(data
, None, None)
271 self
.updatescrollbars()
272 self
.parent
.updatemenubar()
274 def menu_insert_html(self
, fp
):
277 f
= formatter
.AbstractFormatter(self
)
279 # Remember where we are, and don't update
281 start
, dummy
= self
.ted
.WEGetSelection()
282 self
.ted
.WEFeatureFlag(WASTEconst
.weFInhibitRecal
, 1)
288 # Restore updating, recalc, set focus
289 dummy
, end
= self
.ted
.WEGetSelection()
291 self
.ted
.WESetSelection(start
, end
)
293 self
.ted
.WEFeatureFlag(WASTEconst
.weFInhibitRecal
, 0)
294 self
.wid
.InvalWindowRect(self
.ted
.WEGetViewRect())
296 self
.updatescrollbars()
297 self
.parent
.updatemenubar()
304 self
.updatescrollbars()
305 self
.parent
.updatemenubar()
310 self
.updatescrollbars()
311 self
.parent
.updatemenubar()
313 def menu_paste(self
):
316 self
.updatescrollbars()
317 self
.parent
.updatemenubar()
319 def menu_clear(self
):
322 self
.updatescrollbars()
323 self
.parent
.updatemenubar()
327 self
.updatescrollbars()
328 self
.parent
.updatemenubar()
330 def menu_setfont(self
, font
):
331 font
= Fm
.GetFNum(font
)
332 self
.mysetstyle(WASTEconst
.weDoFont
, (font
, 0, 0, (0,0,0)))
333 self
.parent
.updatemenubar()
335 def menu_modface(self
, face
):
336 self
.mysetstyle(WASTEconst
.weDoFace|WASTEconst
.weDoToggleFace
,
337 (0, face
, 0, (0,0,0)))
339 def menu_setface(self
, face
):
340 self
.mysetstyle(WASTEconst
.weDoFace|WASTEconst
.weDoReplaceFace
,
341 (0, face
, 0, (0,0,0)))
343 def menu_setsize(self
, size
):
344 self
.mysetstyle(WASTEconst
.weDoSize
, (0, 0, size
, (0,0,0)))
346 def menu_incsize(self
, size
):
347 self
.mysetstyle(WASTEconst
.weDoAddSize
, (0, 0, size
, (0,0,0)))
349 def mysetstyle(self
, which
, how
):
351 self
.ted
.WESetStyle(which
, how
)
352 self
.parent
.updatemenubar()
354 def have_selection(self
):
355 start
, stop
= self
.ted
.WEGetSelection()
359 return self
.ted
.WECanPaste()
362 which
, redo
= self
.ted
.WEGetUndoInfo()
363 which
= UNDOLABELS
[which
]
364 if which
== None: return None
370 def getruninfo(self
):
371 all
= (WASTEconst
.weDoFont | WASTEconst
.weDoFace | WASTEconst
.weDoSize
)
372 dummy
, mode
, (font
, face
, size
, color
) = self
.ted
.WEContinuousStyle(all
)
373 if not (mode
& WASTEconst
.weDoFont
):
376 font
= Fm
.GetFontName(font
)
377 if not (mode
& WASTEconst
.weDoFace
): fact
= None
378 if not (mode
& WASTEconst
.weDoSize
): size
= None
379 return font
, face
, size
382 # Methods for writer class for html formatter
386 self
.html_font
= [12, 0, 0, 0]
388 self
.html_color
= (0,0,0)
389 self
.new_font(self
.html_font
)
391 def new_font(self
, font
):
394 font
= map(lambda x
:x
, font
)
395 for i
in range(len(font
)):
397 font
[i
] = self
.html_font
[i
]
398 [size
, italic
, bold
, tt
] = font
399 self
.html_font
= font
[:]
401 font
= Fm
.GetFNum('Courier')
403 font
= Fm
.GetFNum('Times')
404 if HTML_SIZE
.has_key(size
):
405 size
= HTML_SIZE
[size
]
409 if bold
: face
= face |
1
410 if italic
: face
= face |
2
411 face
= face | self
.html_style
412 self
.ted
.WESetStyle(WASTEconst
.weDoFont | WASTEconst
.weDoFace |
413 WASTEconst
.weDoSize | WASTEconst
.weDoColor
,
414 (font
, face
, size
, self
.html_color
))
416 def new_margin(self
, margin
, level
):
417 self
.ted
.WEInsert('[Margin %s %s]'%(margin
, level
), None, None)
419 def new_spacing(self
, spacing
):
420 self
.ted
.WEInsert('[spacing %s]'%spacing
, None, None)
422 def new_styles(self
, styles
):
424 self
.html_color
= (0,0,0)
425 if 'anchor' in styles
:
426 self
.html_style
= self
.html_style |
4
427 self
.html_color
= (0xffff, 0, 0)
428 self
.new_font(self
.html_font
)
430 def send_paragraph(self
, blankline
):
431 self
.ted
.WEInsert('\r'*(blankline
+1), None, None)
433 def send_line_break(self
):
434 self
.ted
.WEInsert('\r', None, None)
436 def send_hor_rule(self
, *args
, **kw
):
437 # Ignore ruler options, for now
438 dummydata
= Res
.Resource('')
439 self
.ted
.WEInsertObject('rulr', dummydata
, (0,0))
441 def send_label_data(self
, data
):
442 self
.ted
.WEInsert(data
, None, None)
444 def send_flowing_data(self
, data
):
445 self
.ted
.WEInsert(data
, None, None)
447 def send_literal_data(self
, data
):
448 data
= regsub
.gsub('\n', '\r', data
)
449 data
= string
.expandtabs(data
)
450 self
.ted
.WEInsert(data
, None, None)
452 class Wed(Application
):
454 Application
.__init
__(self
)
458 waste
.STDObjectHandlers()
459 # Handler for horizontal ruler
460 waste
.WEInstallObjectHandler('rulr', 'new ', self
.newRuler
)
461 waste
.WEInstallObjectHandler('rulr', 'draw', self
.drawRuler
)
463 def makeusermenus(self
):
464 self
.filemenu
= m
= Menu(self
.menubar
, "File")
465 self
.newitem
= MenuItem(m
, "New window", "N", self
.open)
466 self
.openitem
= MenuItem(m
, "Open...", "O", self
.openfile
)
467 self
.closeitem
= MenuItem(m
, "Close", "W", self
.closewin
)
469 self
.saveitem
= MenuItem(m
, "Save", "S", self
.save
)
470 self
.saveasitem
= MenuItem(m
, "Save as...", "", self
.saveas
)
472 self
.insertitem
= MenuItem(m
, "Insert plaintext...", "", self
.insertfile
)
473 self
.htmlitem
= MenuItem(m
, "Insert HTML...", "", self
.inserthtml
)
475 self
.quititem
= MenuItem(m
, "Quit", "Q", self
.quit
)
477 self
.editmenu
= m
= Menu(self
.menubar
, "Edit")
478 self
.undoitem
= MenuItem(m
, "Undo", "Z", self
.undo
)
479 self
.cutitem
= MenuItem(m
, "Cut", "X", self
.cut
)
480 self
.copyitem
= MenuItem(m
, "Copy", "C", self
.copy
)
481 self
.pasteitem
= MenuItem(m
, "Paste", "V", self
.paste
)
482 self
.clearitem
= MenuItem(m
, "Clear", "", self
.clear
)
486 # Groups of items enabled together:
487 self
.windowgroup
= [self
.closeitem
, self
.saveitem
, self
.saveasitem
,
488 self
.editmenu
, self
.fontmenu
, self
.facemenu
, self
.sizemenu
,
490 self
.focusgroup
= [self
.cutitem
, self
.copyitem
, self
.clearitem
]
491 self
.windowgroup_on
= -1
492 self
.focusgroup_on
= -1
493 self
.pastegroup_on
= -1
494 self
.undo_label
= "never"
497 def makefontmenu(self
):
498 self
.fontmenu
= Menu(self
.menubar
, "Font")
499 self
.fontnames
= getfontnames()
501 for n
in self
.fontnames
:
502 m
= MenuItem(self
.fontmenu
, n
, "", self
.selfont
)
503 self
.fontitems
.append(m
)
504 self
.facemenu
= Menu(self
.menubar
, "Style")
506 for n
, shortcut
in STYLES
:
507 m
= MenuItem(self
.facemenu
, n
, shortcut
, self
.selface
)
508 self
.faceitems
.append(m
)
509 self
.facemenu
.addseparator()
510 self
.faceitem_normal
= MenuItem(self
.facemenu
, "Normal", "N",
512 self
.sizemenu
= Menu(self
.menubar
, "Size")
515 m
= MenuItem(self
.sizemenu
, `n`
, "", self
.selsize
)
516 self
.sizeitems
.append(m
)
517 self
.sizemenu
.addseparator()
518 self
.sizeitem_bigger
= MenuItem(self
.sizemenu
, "Bigger", "+",
520 self
.sizeitem_smaller
= MenuItem(self
.sizemenu
, "Smaller", "-",
523 def selfont(self
, id, item
, *rest
):
525 font
= self
.fontnames
[item
-1]
526 self
.active
.menu_setfont(font
)
528 EasyDialogs
.Message("No active window?")
530 def selface(self
, id, item
, *rest
):
533 self
.active
.menu_modface(face
)
535 EasyDialogs
.Message("No active window?")
537 def selfacenormal(self
, *rest
):
539 self
.active
.menu_setface(0)
541 EasyDialogs
.Message("No active window?")
543 def selsize(self
, id, item
, *rest
):
546 self
.active
.menu_setsize(size
)
548 EasyDialogs
.Message("No active window?")
550 def selsizebigger(self
, *rest
):
552 self
.active
.menu_incsize(2)
554 EasyDialogs
.Message("No active window?")
556 def selsizesmaller(self
, *rest
):
558 self
.active
.menu_incsize(-2)
560 EasyDialogs
.Message("No active window?")
562 def updatemenubar(self
):
564 on
= (self
.active
<> None)
565 if on
<> self
.windowgroup_on
:
566 for m
in self
.windowgroup
:
568 self
.windowgroup_on
= on
571 # only if we have an edit menu
572 on
= self
.active
.have_selection()
573 if on
<> self
.focusgroup_on
:
574 for m
in self
.focusgroup
:
576 self
.focusgroup_on
= on
578 on
= self
.active
.can_paste()
579 if on
<> self
.pastegroup_on
:
580 self
.pasteitem
.enable(on
)
581 self
.pastegroup_on
= on
583 on
= self
.active
.can_undo()
584 if on
<> self
.undo_label
:
586 self
.undoitem
.enable(1)
587 self
.undoitem
.settext(on
)
590 self
.undoitem
.settext("Nothing to undo")
591 self
.undoitem
.enable(0)
593 if self
.updatefontmenus():
598 def updatefontmenus(self
):
599 info
= self
.active
.getruninfo()
600 if info
== self
.ffs_values
:
602 # Remove old checkmarks
603 if self
.ffs_values
== ():
604 self
.ffs_values
= (None, None, None)
605 font
, face
, size
= self
.ffs_values
607 fnum
= self
.fontnames
.index(font
)
608 self
.fontitems
[fnum
].check(0)
610 for i
in range(len(self
.faceitems
)):
612 self
.faceitems
[i
].check(0)
614 for i
in range(len(self
.sizeitems
)):
616 self
.sizeitems
[i
].check(0)
618 self
.ffs_values
= info
620 font
, face
, size
= self
.ffs_values
622 fnum
= self
.fontnames
.index(font
)
623 self
.fontitems
[fnum
].check(1)
625 for i
in range(len(self
.faceitems
)):
627 self
.faceitems
[i
].check(1)
629 for i
in range(len(self
.sizeitems
)):
631 self
.sizeitems
[i
].check(1)
632 # Set outline/normal for sizes
634 exists
= getfontsizes(font
, SIZES
)
635 for i
in range(len(self
.sizeitems
)):
637 self
.sizeitems
[i
].setstyle(0)
639 self
.sizeitems
[i
].setstyle(8)
645 def do_about(self
, id, item
, window
, event
):
646 EasyDialogs
.Message("A simple single-font text editor based on WASTE")
652 def open(self
, *args
):
655 def openfile(self
, *args
):
658 def _open(self
, askfile
):
660 fss
, ok
= macfs
.StandardGetFile('TEXT')
663 path
= fss
.as_pathname()
664 name
= os
.path
.split(path
)[-1]
666 fp
= open(path
, 'rb') # NOTE binary, we need cr as end-of-line
670 EasyDialogs
.Message("IOERROR: "+`arg`
)
674 name
= "Untitled %d"%self
.num
676 w
= WasteWindow(self
)
677 w
.open(path
, name
, data
)
678 self
.num
= self
.num
+ 1
680 def insertfile(self
, *args
):
682 fss
, ok
= macfs
.StandardGetFile('TEXT')
685 path
= fss
.as_pathname()
687 fp
= open(path
, 'rb') # NOTE binary, we need cr as end-of-line
689 EasyDialogs
.Message("IOERROR: "+`arg`
)
691 self
.active
.menu_insert(fp
)
693 EasyDialogs
.Message("No active window?")
695 def inserthtml(self
, *args
):
697 fss
, ok
= macfs
.StandardGetFile('TEXT')
700 path
= fss
.as_pathname()
704 EasyDialogs
.Message("IOERROR: "+`arg`
)
706 self
.active
.menu_insert_html(fp
)
708 EasyDialogs
.Message("No active window?")
711 def closewin(self
, *args
):
715 EasyDialogs
.Message("No active window?")
717 def save(self
, *args
):
719 self
.active
.menu_save()
721 EasyDialogs
.Message("No active window?")
723 def saveas(self
, *args
):
725 self
.active
.menu_save_as()
727 EasyDialogs
.Message("No active window?")
730 def quit(self
, *args
):
731 for w
in self
._windows
.values():
741 def undo(self
, *args
):
743 self
.active
.menu_undo()
745 EasyDialogs
.Message("No active window?")
747 def cut(self
, *args
):
749 self
.active
.menu_cut()
751 EasyDialogs
.Message("No active window?")
753 def copy(self
, *args
):
755 self
.active
.menu_copy()
757 EasyDialogs
.Message("No active window?")
759 def paste(self
, *args
):
761 self
.active
.menu_paste()
763 EasyDialogs
.Message("No active window?")
765 def clear(self
, *args
):
767 self
.active
.menu_clear()
769 EasyDialogs
.Message("No active window?")
775 def idle(self
, event
):
777 self
.active
.do_idle(event
)
779 def newRuler(self
, obj
):
780 """Insert a new ruler. Make it as wide as the window minus 2 pxls"""
781 ted
= obj
.WEGetObjectOwner()
782 l
, t
, r
, b
= ted
.WEGetDestRect()
785 def drawRuler(self
, (l
, t
, r
, b
), obj
):
791 class MyHTMLParser(htmllib
.HTMLParser
):
793 def anchor_bgn(self
, href
, name
, type):
796 self
.anchorlist
.append(href
)
797 self
.formatter
.push_style('anchor')
799 def anchor_end(self
):
802 self
.formatter
.pop_style()
808 n
= Fm
.GetFontName(i
)
809 if n
: names
.append(n
)
812 def getfontsizes(name
, sizes
):
814 num
= Fm
.GetFNum(name
)
816 if Fm
.RealFont(num
, sz
):
826 if __name__
== '__main__':