1 # A minimal text editor.
4 # - Functionality: find, etc.
6 from Carbon
.Menu
import DrawMenuBar
7 from FrameWork
import *
10 from Carbon
import Res
14 from Carbon
import Scrap
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, 0)
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
.FSpOpenResFile(self
.path
, 1)
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
.GetQDGlobalsArrow())
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
)
197 self
.wid
.InvalWindowRect(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
.FSpOpenResFile(self
.path
, 3)
246 Res
.FSpCreateResFile(self
.path
, '????', 'TEXT', macfs
.smAllScripts
)
247 rf
= Res
.FSpOpenResFile(self
.path
, 3)
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 path
= EasyDialogs
.AskFileForSave(message
='Save as:')
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 self
.wid
.InvalWindowRect(self
.ted
.WEGetViewRect())
295 self
.updatescrollbars()
296 self
.parent
.updatemenubar()
300 if hasattr(Scrap
, 'ZeroScrap'):
303 Scrap
.ClearCurrentScrap()
305 self
.updatescrollbars()
306 self
.parent
.updatemenubar()
309 if hasattr(Scrap
, 'ZeroScrap'):
312 Scrap
.ClearCurrentScrap()
314 self
.updatescrollbars()
315 self
.parent
.updatemenubar()
317 def menu_paste(self
):
320 self
.updatescrollbars()
321 self
.parent
.updatemenubar()
323 def menu_clear(self
):
326 self
.updatescrollbars()
327 self
.parent
.updatemenubar()
331 self
.updatescrollbars()
332 self
.parent
.updatemenubar()
334 def menu_setfont(self
, font
):
335 font
= Fm
.GetFNum(font
)
336 self
.mysetstyle(WASTEconst
.weDoFont
, (font
, 0, 0, (0,0,0)))
337 self
.parent
.updatemenubar()
339 def menu_modface(self
, face
):
340 self
.mysetstyle(WASTEconst
.weDoFace|WASTEconst
.weDoToggleFace
,
341 (0, face
, 0, (0,0,0)))
343 def menu_setface(self
, face
):
344 self
.mysetstyle(WASTEconst
.weDoFace|WASTEconst
.weDoReplaceFace
,
345 (0, face
, 0, (0,0,0)))
347 def menu_setsize(self
, size
):
348 self
.mysetstyle(WASTEconst
.weDoSize
, (0, 0, size
, (0,0,0)))
350 def menu_incsize(self
, size
):
351 self
.mysetstyle(WASTEconst
.weDoAddSize
, (0, 0, size
, (0,0,0)))
353 def mysetstyle(self
, which
, how
):
355 self
.ted
.WESetStyle(which
, how
)
356 self
.parent
.updatemenubar()
358 def have_selection(self
):
359 start
, stop
= self
.ted
.WEGetSelection()
363 return self
.ted
.WECanPaste()
366 which
, redo
= self
.ted
.WEGetUndoInfo()
367 which
= UNDOLABELS
[which
]
368 if which
== None: return None
374 def getruninfo(self
):
375 all
= (WASTEconst
.weDoFont | WASTEconst
.weDoFace | WASTEconst
.weDoSize
)
376 dummy
, mode
, (font
, face
, size
, color
) = self
.ted
.WEContinuousStyle(all
)
377 if not (mode
& WASTEconst
.weDoFont
):
380 font
= Fm
.GetFontName(font
)
381 if not (mode
& WASTEconst
.weDoFace
): fact
= None
382 if not (mode
& WASTEconst
.weDoSize
): size
= None
383 return font
, face
, size
386 # Methods for writer class for html formatter
390 self
.html_font
= [12, 0, 0, 0]
392 self
.html_color
= (0,0,0)
393 self
.new_font(self
.html_font
)
395 def new_font(self
, font
):
398 font
= map(lambda x
:x
, font
)
399 for i
in range(len(font
)):
401 font
[i
] = self
.html_font
[i
]
402 [size
, italic
, bold
, tt
] = font
403 self
.html_font
= font
[:]
405 font
= Fm
.GetFNum('Courier')
407 font
= Fm
.GetFNum('Times')
408 if HTML_SIZE
.has_key(size
):
409 size
= HTML_SIZE
[size
]
413 if bold
: face
= face |
1
414 if italic
: face
= face |
2
415 face
= face | self
.html_style
416 self
.ted
.WESetStyle(WASTEconst
.weDoFont | WASTEconst
.weDoFace |
417 WASTEconst
.weDoSize | WASTEconst
.weDoColor
,
418 (font
, face
, size
, self
.html_color
))
420 def new_margin(self
, margin
, level
):
421 self
.ted
.WEInsert('[Margin %s %s]'%(margin
, level
), None, None)
423 def new_spacing(self
, spacing
):
424 self
.ted
.WEInsert('[spacing %s]'%spacing
, None, None)
426 def new_styles(self
, styles
):
428 self
.html_color
= (0,0,0)
429 if 'anchor' in styles
:
430 self
.html_style
= self
.html_style |
4
431 self
.html_color
= (0xffff, 0, 0)
432 self
.new_font(self
.html_font
)
434 def send_paragraph(self
, blankline
):
435 self
.ted
.WEInsert('\r'*(blankline
+1), None, None)
437 def send_line_break(self
):
438 self
.ted
.WEInsert('\r', None, None)
440 def send_hor_rule(self
, *args
, **kw
):
441 # Ignore ruler options, for now
442 dummydata
= Res
.Resource('')
443 self
.ted
.WEInsertObject('rulr', dummydata
, (0,0))
445 def send_label_data(self
, data
):
446 self
.ted
.WEInsert(data
, None, None)
448 def send_flowing_data(self
, data
):
449 self
.ted
.WEInsert(data
, None, None)
451 def send_literal_data(self
, data
):
452 data
= string
.replace(data
, '\n', '\r')
453 data
= string
.expandtabs(data
)
454 self
.ted
.WEInsert(data
, None, None)
456 class Wed(Application
):
458 Application
.__init
__(self
)
462 waste
.STDObjectHandlers()
463 # Handler for horizontal ruler
464 waste
.WEInstallObjectHandler('rulr', 'new ', self
.newRuler
)
465 waste
.WEInstallObjectHandler('rulr', 'draw', self
.drawRuler
)
467 def makeusermenus(self
):
468 self
.filemenu
= m
= Menu(self
.menubar
, "File")
469 self
.newitem
= MenuItem(m
, "New window", "N", self
.open)
470 self
.openitem
= MenuItem(m
, "Open...", "O", self
.openfile
)
471 self
.closeitem
= MenuItem(m
, "Close", "W", self
.closewin
)
473 self
.saveitem
= MenuItem(m
, "Save", "S", self
.save
)
474 self
.saveasitem
= MenuItem(m
, "Save as...", "", self
.saveas
)
476 self
.insertitem
= MenuItem(m
, "Insert plaintext...", "", self
.insertfile
)
477 self
.htmlitem
= MenuItem(m
, "Insert HTML...", "", self
.inserthtml
)
479 self
.quititem
= MenuItem(m
, "Quit", "Q", self
.quit
)
481 self
.editmenu
= m
= Menu(self
.menubar
, "Edit")
482 self
.undoitem
= MenuItem(m
, "Undo", "Z", self
.undo
)
483 self
.cutitem
= MenuItem(m
, "Cut", "X", self
.cut
)
484 self
.copyitem
= MenuItem(m
, "Copy", "C", self
.copy
)
485 self
.pasteitem
= MenuItem(m
, "Paste", "V", self
.paste
)
486 self
.clearitem
= MenuItem(m
, "Clear", "", self
.clear
)
490 # Groups of items enabled together:
491 self
.windowgroup
= [self
.closeitem
, self
.saveitem
, self
.saveasitem
,
492 self
.editmenu
, self
.fontmenu
, self
.facemenu
, self
.sizemenu
,
494 self
.focusgroup
= [self
.cutitem
, self
.copyitem
, self
.clearitem
]
495 self
.windowgroup_on
= -1
496 self
.focusgroup_on
= -1
497 self
.pastegroup_on
= -1
498 self
.undo_label
= "never"
501 def makefontmenu(self
):
502 self
.fontmenu
= Menu(self
.menubar
, "Font")
503 self
.fontnames
= getfontnames()
505 for n
in self
.fontnames
:
506 m
= MenuItem(self
.fontmenu
, n
, "", self
.selfont
)
507 self
.fontitems
.append(m
)
508 self
.facemenu
= Menu(self
.menubar
, "Style")
510 for n
, shortcut
in STYLES
:
511 m
= MenuItem(self
.facemenu
, n
, shortcut
, self
.selface
)
512 self
.faceitems
.append(m
)
513 self
.facemenu
.addseparator()
514 self
.faceitem_normal
= MenuItem(self
.facemenu
, "Normal", "N",
516 self
.sizemenu
= Menu(self
.menubar
, "Size")
519 m
= MenuItem(self
.sizemenu
, `n`
, "", self
.selsize
)
520 self
.sizeitems
.append(m
)
521 self
.sizemenu
.addseparator()
522 self
.sizeitem_bigger
= MenuItem(self
.sizemenu
, "Bigger", "+",
524 self
.sizeitem_smaller
= MenuItem(self
.sizemenu
, "Smaller", "-",
527 def selfont(self
, id, item
, *rest
):
529 font
= self
.fontnames
[item
-1]
530 self
.active
.menu_setfont(font
)
532 EasyDialogs
.Message("No active window?")
534 def selface(self
, id, item
, *rest
):
537 self
.active
.menu_modface(face
)
539 EasyDialogs
.Message("No active window?")
541 def selfacenormal(self
, *rest
):
543 self
.active
.menu_setface(0)
545 EasyDialogs
.Message("No active window?")
547 def selsize(self
, id, item
, *rest
):
550 self
.active
.menu_setsize(size
)
552 EasyDialogs
.Message("No active window?")
554 def selsizebigger(self
, *rest
):
556 self
.active
.menu_incsize(2)
558 EasyDialogs
.Message("No active window?")
560 def selsizesmaller(self
, *rest
):
562 self
.active
.menu_incsize(-2)
564 EasyDialogs
.Message("No active window?")
566 def updatemenubar(self
):
568 on
= (self
.active
<> None)
569 if on
<> self
.windowgroup_on
:
570 for m
in self
.windowgroup
:
572 self
.windowgroup_on
= on
575 # only if we have an edit menu
576 on
= self
.active
.have_selection()
577 if on
<> self
.focusgroup_on
:
578 for m
in self
.focusgroup
:
580 self
.focusgroup_on
= on
582 on
= self
.active
.can_paste()
583 if on
<> self
.pastegroup_on
:
584 self
.pasteitem
.enable(on
)
585 self
.pastegroup_on
= on
587 on
= self
.active
.can_undo()
588 if on
<> self
.undo_label
:
590 self
.undoitem
.enable(1)
591 self
.undoitem
.settext(on
)
594 self
.undoitem
.settext("Nothing to undo")
595 self
.undoitem
.enable(0)
597 if self
.updatefontmenus():
602 def updatefontmenus(self
):
603 info
= self
.active
.getruninfo()
604 if info
== self
.ffs_values
:
606 # Remove old checkmarks
607 if self
.ffs_values
== ():
608 self
.ffs_values
= (None, None, None)
609 font
, face
, size
= self
.ffs_values
611 fnum
= self
.fontnames
.index(font
)
612 self
.fontitems
[fnum
].check(0)
614 for i
in range(len(self
.faceitems
)):
616 self
.faceitems
[i
].check(0)
618 for i
in range(len(self
.sizeitems
)):
620 self
.sizeitems
[i
].check(0)
622 self
.ffs_values
= info
624 font
, face
, size
= self
.ffs_values
626 fnum
= self
.fontnames
.index(font
)
627 self
.fontitems
[fnum
].check(1)
629 for i
in range(len(self
.faceitems
)):
631 self
.faceitems
[i
].check(1)
633 for i
in range(len(self
.sizeitems
)):
635 self
.sizeitems
[i
].check(1)
636 # Set outline/normal for sizes
638 exists
= getfontsizes(font
, SIZES
)
639 for i
in range(len(self
.sizeitems
)):
641 self
.sizeitems
[i
].setstyle(0)
643 self
.sizeitems
[i
].setstyle(8)
649 def do_about(self
, id, item
, window
, event
):
650 EasyDialogs
.Message("A simple single-font text editor based on WASTE")
656 def open(self
, *args
):
659 def openfile(self
, *args
):
662 def _open(self
, askfile
):
664 path
= EasyDialogs
.AskFileForOpen(typeList
=('TEXT',))
667 name
= os
.path
.split(path
)[-1]
669 fp
= open(path
, 'rb') # NOTE binary, we need cr as end-of-line
673 EasyDialogs
.Message("IOERROR: "+`arg`
)
677 name
= "Untitled %d"%self
.num
679 w
= WasteWindow(self
)
680 w
.open(path
, name
, data
)
681 self
.num
= self
.num
+ 1
683 def insertfile(self
, *args
):
685 path
= EasyDialogs
.AskFileForOpen(typeList
=('TEXT',))
689 fp
= open(path
, 'rb') # NOTE binary, we need cr as end-of-line
691 EasyDialogs
.Message("IOERROR: "+`arg`
)
693 self
.active
.menu_insert(fp
)
695 EasyDialogs
.Message("No active window?")
697 def inserthtml(self
, *args
):
699 path
= EasyDialogs
.AskFileForOpen(typeList
=('TEXT',))
705 EasyDialogs
.Message("IOERROR: "+`arg`
)
707 self
.active
.menu_insert_html(fp
)
709 EasyDialogs
.Message("No active window?")
712 def closewin(self
, *args
):
716 EasyDialogs
.Message("No active window?")
718 def save(self
, *args
):
720 self
.active
.menu_save()
722 EasyDialogs
.Message("No active window?")
724 def saveas(self
, *args
):
726 self
.active
.menu_save_as()
728 EasyDialogs
.Message("No active window?")
731 def quit(self
, *args
):
732 for w
in self
._windows
.values():
742 def undo(self
, *args
):
744 self
.active
.menu_undo()
746 EasyDialogs
.Message("No active window?")
748 def cut(self
, *args
):
750 self
.active
.menu_cut()
752 EasyDialogs
.Message("No active window?")
754 def copy(self
, *args
):
756 self
.active
.menu_copy()
758 EasyDialogs
.Message("No active window?")
760 def paste(self
, *args
):
762 self
.active
.menu_paste()
764 EasyDialogs
.Message("No active window?")
766 def clear(self
, *args
):
768 self
.active
.menu_clear()
770 EasyDialogs
.Message("No active window?")
776 def idle(self
, event
):
778 self
.active
.do_idle(event
)
780 Qd
.SetCursor(Qd
.GetQDGlobalsArrow())
782 def newRuler(self
, obj
):
783 """Insert a new ruler. Make it as wide as the window minus 2 pxls"""
784 ted
= obj
.WEGetObjectOwner()
785 l
, t
, r
, b
= ted
.WEGetDestRect()
788 def drawRuler(self
, (l
, t
, r
, b
), obj
):
794 class MyHTMLParser(htmllib
.HTMLParser
):
796 def anchor_bgn(self
, href
, name
, type):
799 self
.anchorlist
.append(href
)
800 self
.formatter
.push_style('anchor')
802 def anchor_end(self
):
805 self
.formatter
.pop_style()
811 n
= Fm
.GetFontName(i
)
812 if n
: names
.append(n
)
815 def getfontsizes(name
, sizes
):
817 num
= Fm
.GetFNum(name
)
819 if Fm
.RealFont(num
, sz
):
829 if __name__
== '__main__':