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
19 UNDOLABELS
= [ # Indexed by WEGetUndoInfo() value
20 None, "", "typing", "Cut", "Paste", "Clear", "Drag", "Style"]
22 # Style and size menu. Note that style order is important (tied to bit values)
24 ("Bold", "B"), ("Italic", "I"), ("Underline", "U"), ("Outline", "O"),
25 ("Shadow", ""), ("Condensed", ""), ("Extended", "")
27 SIZES
= [ 9, 10, 12, 14, 18, 24]
30 Qd
.SetRectRgn(BIGREGION
, -16000, -16000, 16000, 16000)
32 class WasteWindow(ScrolledWindow
):
33 def open(self
, path
, name
, data
):
36 r
= windowbounds(400, 400)
37 w
= Win
.NewWindow(r
, name
, 1, 0, -1, 1, 0)
39 vr
= 0, 0, r
[2]-r
[0]-15, r
[3]-r
[1]-15
44 flags
= WASTEconst
.weDoAutoScroll | WASTEconst
.weDoOutlineHilite | \
46 self
.ted
= waste
.WENew(dr
, vr
, flags
)
47 self
.ted
.WEInstallTabHooks()
48 style
, soup
= self
.getstylesoup()
49 self
.ted
.WEInsert(data
, style
, soup
)
50 self
.ted
.WESetSelection(0,0)
52 self
.ted
.WEResetModCount()
56 self
.do_activate(1, None)
58 def getstylesoup(self
):
61 oldrf
= Res
.CurResFile()
63 rf
= Res
.FSpOpenResFile(self
.path
, 1)
67 hstyle
= Res
.Get1Resource('styl', 128)
68 hstyle
.DetachResource()
72 hsoup
= Res
.Get1Resource('SOUP', 128)
73 hsoup
.DetachResource()
80 def do_idle(self
, event
):
81 (what
, message
, when
, where
, modifiers
) = event
84 if self
.ted
.WEAdjustCursor(where
, BIGREGION
):
86 Qd
.SetCursor(Qd
.qd
.arrow
)
88 def getscrollbarvalues(self
):
89 dr
= self
.ted
.WEGetDestRect()
90 vr
= self
.ted
.WEGetViewRect()
91 vx
= self
.scalebarvalue(dr
[0], dr
[2], vr
[0], vr
[2])
92 vy
= self
.scalebarvalue(dr
[1], dr
[3], vr
[1], vr
[3])
95 def scrollbar_callback(self
, which
, what
, value
):
98 height
= self
.ted
.WEGetHeight(0, 0x3fffffff)
99 cur
= self
.getscrollbarvalues()[1]
100 delta
= (cur
-value
)*height
/32767
102 topline_off
,dummy
= self
.ted
.WEGetOffset((1,1))
103 topline_num
= self
.ted
.WEOffsetToLine(topline_off
)
104 delta
= self
.ted
.WEGetHeight(topline_num
, topline_num
+1)
106 delta
= (self
.ted
.WEGetViewRect()[3]-10)
108 delta
= 10 # Random value
110 # XXXX Wrong: should be bottom line size
111 topline_off
,dummy
= self
.ted
.WEGetOffset((1,1))
112 topline_num
= self
.ted
.WEOffsetToLine(topline_off
)
113 delta
= -self
.ted
.WEGetHeight(topline_num
, topline_num
+1)
115 delta
= -(self
.ted
.WEGetViewRect()[3]-10)
118 self
.ted
.WEScroll(0, delta
)
122 vr
= self
.ted
.WEGetViewRect()
123 winwidth
= vr
[2]-vr
[0]
132 self
.ted
.WEScroll(delta
, 0)
134 l
, t
, r
, b
= self
.ted
.WEGetDestRect()
135 vl
, vt
, vr
, vb
= self
.ted
.WEGetViewRect()
140 self
.ted
.WEScroll(dx
, dy
)
142 self
.ted
.WEScroll(0, b
-vb
)
145 def do_activate(self
, onoff
, evt
):
147 ScrolledWindow
.do_activate(self
, onoff
, evt
)
149 self
.ted
.WEActivate()
150 self
.parent
.active
= self
151 self
.parent
.updatemenubar()
153 self
.ted
.WEDeactivate()
155 def do_update(self
, wid
, event
):
156 region
= wid
.GetWindowPort().visRgn
157 if Qd
.EmptyRgn(region
):
160 self
.ted
.WEUpdate(region
)
161 self
.updatescrollbars()
163 def do_postresize(self
, width
, height
, window
):
164 l
, t
, r
, b
= self
.ted
.WEGetViewRect()
165 vr
= (l
, t
, l
+width
-15, t
+height
-15)
166 self
.ted
.WESetViewRect(vr
)
167 self
.wid
.InvalWindowRect(vr
)
168 ScrolledWindow
.do_postresize(self
, width
, height
, window
)
170 def do_contentclick(self
, local
, modifiers
, evt
):
171 (what
, message
, when
, where
, modifiers
) = evt
172 self
.ted
.WEClick(local
, modifiers
, when
)
173 self
.updatescrollbars()
174 self
.parent
.updatemenubar()
176 def do_char(self
, ch
, event
):
178 (what
, message
, when
, where
, modifiers
) = event
179 self
.ted
.WEKey(ord(ch
), modifiers
)
180 self
.updatescrollbars()
181 self
.parent
.updatemenubar()
184 if self
.ted
.WEGetModCount():
185 save
= EasyDialogs
.AskYesNoCancel('Save window "%s" before closing?'%self
.name
, 1)
190 if self
.parent
.active
== self
:
191 self
.parent
.active
= None
192 self
.parent
.updatemenubar()
199 return # Will call us recursively
203 dhandle
= self
.ted
.WEGetText()
205 fp
= open(self
.path
, 'wb') # NOTE: wb, because data has CR for end-of-line
207 if data
[-1] <> '\r': fp
.write('\r')
210 # Now save style and soup
212 oldresfile
= Res
.CurResFile()
214 rf
= Res
.FSpOpenResFile(self
.path
, 3)
216 Res
.FSpCreateResFile(self
.path
, '????', 'TEXT', MACFS
.smAllScripts
)
217 rf
= Res
.FSpOpenResFile(self
.path
, 3)
218 styles
= Res
.Resource('')
219 soup
= Res
.Resource('')
220 self
.ted
.WECopyRange(0, 0x3fffffff, None, styles
, soup
)
221 styles
.AddResource('styl', 128, '')
222 soup
.AddResource('SOUP', 128, '')
224 Res
.UseResFile(oldresfile
)
226 self
.ted
.WEResetModCount()
228 def menu_save_as(self
):
229 fss
, ok
= macfs
.StandardPutFile('Save as:')
231 self
.path
= fss
.as_pathname()
232 self
.name
= os
.path
.split(self
.path
)[-1]
233 self
.wid
.SetWTitle(self
.name
)
238 if hasattr(Scrap
, 'ZeroScrap'):
241 Scrap
.ClearCurrentScrap()
243 self
.updatescrollbars()
244 self
.parent
.updatemenubar()
247 if hasattr(Scrap
, 'ZeroScrap'):
250 Scrap
.ClearCurrentScrap()
252 self
.updatescrollbars()
253 self
.parent
.updatemenubar()
255 def menu_paste(self
):
258 self
.updatescrollbars()
259 self
.parent
.updatemenubar()
261 def menu_clear(self
):
264 self
.updatescrollbars()
265 self
.parent
.updatemenubar()
269 self
.updatescrollbars()
270 self
.parent
.updatemenubar()
272 def menu_setfont(self
, font
):
273 font
= Fm
.GetFNum(font
)
274 self
.mysetstyle(WASTEconst
.weDoFont
, (font
, 0, 0, (0,0,0)))
275 self
.parent
.updatemenubar()
277 def menu_modface(self
, face
):
278 self
.mysetstyle(WASTEconst
.weDoFace|WASTEconst
.weDoToggleFace
,
279 (0, face
, 0, (0,0,0)))
281 def menu_setface(self
, face
):
282 self
.mysetstyle(WASTEconst
.weDoFace|WASTEconst
.weDoReplaceFace
,
283 (0, face
, 0, (0,0,0)))
285 def menu_setsize(self
, size
):
286 self
.mysetstyle(WASTEconst
.weDoSize
, (0, 0, size
, (0,0,0)))
288 def menu_incsize(self
, size
):
289 self
.mysetstyle(WASTEconst
.weDoAddSize
, (0, 0, size
, (0,0,0)))
291 def mysetstyle(self
, which
, how
):
293 self
.ted
.WESetStyle(which
, how
)
294 self
.parent
.updatemenubar()
296 def have_selection(self
):
297 start
, stop
= self
.ted
.WEGetSelection()
301 return self
.ted
.WECanPaste()
304 which
, redo
= self
.ted
.WEGetUndoInfo()
305 which
= UNDOLABELS
[which
]
306 if which
== None: return None
312 def getruninfo(self
):
313 all
= (WASTEconst
.weDoFont | WASTEconst
.weDoFace | WASTEconst
.weDoSize
)
314 dummy
, mode
, (font
, face
, size
, color
) = self
.ted
.WEContinuousStyle(all
)
315 if not (mode
& WASTEconst
.weDoFont
):
318 font
= Fm
.GetFontName(font
)
319 if not (mode
& WASTEconst
.weDoFace
): fact
= None
320 if not (mode
& WASTEconst
.weDoSize
): size
= None
321 return font
, face
, size
323 class Wed(Application
):
325 Application
.__init
__(self
)
329 waste
.STDObjectHandlers()
331 def makeusermenus(self
):
332 self
.filemenu
= m
= Menu(self
.menubar
, "File")
333 self
.newitem
= MenuItem(m
, "New window", "N", self
.open)
334 self
.openitem
= MenuItem(m
, "Open...", "O", self
.openfile
)
335 self
.closeitem
= MenuItem(m
, "Close", "W", self
.closewin
)
337 self
.saveitem
= MenuItem(m
, "Save", "S", self
.save
)
338 self
.saveasitem
= MenuItem(m
, "Save as...", "", self
.saveas
)
340 self
.quititem
= MenuItem(m
, "Quit", "Q", self
.quit
)
342 self
.editmenu
= m
= Menu(self
.menubar
, "Edit")
343 self
.undoitem
= MenuItem(m
, "Undo", "Z", self
.undo
)
344 self
.cutitem
= MenuItem(m
, "Cut", "X", self
.cut
)
345 self
.copyitem
= MenuItem(m
, "Copy", "C", self
.copy
)
346 self
.pasteitem
= MenuItem(m
, "Paste", "V", self
.paste
)
347 self
.clearitem
= MenuItem(m
, "Clear", "", self
.clear
)
351 # Groups of items enabled together:
352 self
.windowgroup
= [self
.closeitem
, self
.saveitem
, self
.saveasitem
,
353 self
.editmenu
, self
.fontmenu
, self
.facemenu
, self
.sizemenu
]
354 self
.focusgroup
= [self
.cutitem
, self
.copyitem
, self
.clearitem
]
355 self
.windowgroup_on
= -1
356 self
.focusgroup_on
= -1
357 self
.pastegroup_on
= -1
358 self
.undo_label
= "never"
361 def makefontmenu(self
):
362 self
.fontmenu
= Menu(self
.menubar
, "Font")
363 self
.fontnames
= getfontnames()
365 for n
in self
.fontnames
:
366 m
= MenuItem(self
.fontmenu
, n
, "", self
.selfont
)
367 self
.fontitems
.append(m
)
368 self
.facemenu
= Menu(self
.menubar
, "Style")
370 for n
, shortcut
in STYLES
:
371 m
= MenuItem(self
.facemenu
, n
, shortcut
, self
.selface
)
372 self
.faceitems
.append(m
)
373 self
.facemenu
.addseparator()
374 self
.faceitem_normal
= MenuItem(self
.facemenu
, "Normal", "N",
376 self
.sizemenu
= Menu(self
.menubar
, "Size")
379 m
= MenuItem(self
.sizemenu
, `n`
, "", self
.selsize
)
380 self
.sizeitems
.append(m
)
381 self
.sizemenu
.addseparator()
382 self
.sizeitem_bigger
= MenuItem(self
.sizemenu
, "Bigger", "+",
384 self
.sizeitem_smaller
= MenuItem(self
.sizemenu
, "Smaller", "-",
387 def selfont(self
, id, item
, *rest
):
389 font
= self
.fontnames
[item
-1]
390 self
.active
.menu_setfont(font
)
392 EasyDialogs
.Message("No active window?")
394 def selface(self
, id, item
, *rest
):
397 self
.active
.menu_modface(face
)
399 EasyDialogs
.Message("No active window?")
401 def selfacenormal(self
, *rest
):
403 self
.active
.menu_setface(0)
405 EasyDialogs
.Message("No active window?")
407 def selsize(self
, id, item
, *rest
):
410 self
.active
.menu_setsize(size
)
412 EasyDialogs
.Message("No active window?")
414 def selsizebigger(self
, *rest
):
416 self
.active
.menu_incsize(2)
418 EasyDialogs
.Message("No active window?")
420 def selsizesmaller(self
, *rest
):
422 self
.active
.menu_incsize(-2)
424 EasyDialogs
.Message("No active window?")
426 def updatemenubar(self
):
428 on
= (self
.active
<> None)
429 if on
<> self
.windowgroup_on
:
430 for m
in self
.windowgroup
:
432 self
.windowgroup_on
= on
435 # only if we have an edit menu
436 on
= self
.active
.have_selection()
437 if on
<> self
.focusgroup_on
:
438 for m
in self
.focusgroup
:
440 self
.focusgroup_on
= on
442 on
= self
.active
.can_paste()
443 if on
<> self
.pastegroup_on
:
444 self
.pasteitem
.enable(on
)
445 self
.pastegroup_on
= on
447 on
= self
.active
.can_undo()
448 if on
<> self
.undo_label
:
450 self
.undoitem
.enable(1)
451 self
.undoitem
.settext(on
)
454 self
.undoitem
.settext("Nothing to undo")
455 self
.undoitem
.enable(0)
457 if self
.updatefontmenus():
462 def updatefontmenus(self
):
463 info
= self
.active
.getruninfo()
464 if info
== self
.ffs_values
:
466 # Remove old checkmarks
467 if self
.ffs_values
== ():
468 self
.ffs_values
= (None, None, None)
469 font
, face
, size
= self
.ffs_values
471 fnum
= self
.fontnames
.index(font
)
472 self
.fontitems
[fnum
].check(0)
474 for i
in range(len(self
.faceitems
)):
476 self
.faceitems
[i
].check(0)
478 for i
in range(len(self
.sizeitems
)):
480 self
.sizeitems
[i
].check(0)
482 self
.ffs_values
= info
484 font
, face
, size
= self
.ffs_values
486 fnum
= self
.fontnames
.index(font
)
487 self
.fontitems
[fnum
].check(1)
489 for i
in range(len(self
.faceitems
)):
491 self
.faceitems
[i
].check(1)
493 for i
in range(len(self
.sizeitems
)):
495 self
.sizeitems
[i
].check(1)
496 # Set outline/normal for sizes
498 exists
= getfontsizes(font
, SIZES
)
499 for i
in range(len(self
.sizeitems
)):
501 self
.sizeitems
[i
].setstyle(0)
503 self
.sizeitems
[i
].setstyle(8)
509 def do_about(self
, id, item
, window
, event
):
510 EasyDialogs
.Message("A simple single-font text editor based on WASTE")
516 def open(self
, *args
):
519 def openfile(self
, *args
):
522 def _open(self
, askfile
):
524 fss
, ok
= macfs
.StandardGetFile('TEXT')
527 path
= fss
.as_pathname()
528 name
= os
.path
.split(path
)[-1]
530 fp
= open(path
, 'rb') # NOTE binary, we need cr as end-of-line
534 EasyDialogs
.Message("IOERROR: "+`arg`
)
538 name
= "Untitled %d"%self
.num
540 w
= WasteWindow(self
)
541 w
.open(path
, name
, data
)
542 self
.num
= self
.num
+ 1
544 def closewin(self
, *args
):
548 EasyDialogs
.Message("No active window?")
550 def save(self
, *args
):
552 self
.active
.menu_save()
554 EasyDialogs
.Message("No active window?")
556 def saveas(self
, *args
):
558 self
.active
.menu_save_as()
560 EasyDialogs
.Message("No active window?")
563 def quit(self
, *args
):
564 for w
in self
._windows
.values():
574 def undo(self
, *args
):
576 self
.active
.menu_undo()
578 EasyDialogs
.Message("No active window?")
580 def cut(self
, *args
):
582 self
.active
.menu_cut()
584 EasyDialogs
.Message("No active window?")
586 def copy(self
, *args
):
588 self
.active
.menu_copy()
590 EasyDialogs
.Message("No active window?")
592 def paste(self
, *args
):
594 self
.active
.menu_paste()
596 EasyDialogs
.Message("No active window?")
598 def clear(self
, *args
):
600 self
.active
.menu_clear()
602 EasyDialogs
.Message("No active window?")
608 def idle(self
, event
):
610 self
.active
.do_idle(event
)
612 Qd
.SetCursor(Qd
.qd
.arrow
)
617 n
= Fm
.GetFontName(i
)
618 if n
: names
.append(n
)
621 def getfontsizes(name
, sizes
):
623 num
= Fm
.GetFNum(name
)
625 if Fm
.RealFont(num
, sz
):
635 if __name__
== '__main__':