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
18 UNDOLABELS
= [ # Indexed by WEGetUndoInfo() value
19 None, "", "typing", "Cut", "Paste", "Clear", "Drag", "Style"]
21 # Style and size menu. Note that style order is important (tied to bit values)
23 ("Bold", "B"), ("Italic", "I"), ("Underline", "U"), ("Outline", "O"),
24 ("Shadow", ""), ("Condensed", ""), ("Extended", "")
26 SIZES
= [ 9, 10, 12, 14, 18, 24]
29 Qd
.SetRectRgn(BIGREGION
, -16000, -16000, 16000, 16000)
31 class WasteWindow(ScrolledWindow
):
32 def open(self
, path
, name
, data
):
35 r
= windowbounds(400, 400)
36 w
= Win
.NewWindow(r
, name
, 1, 0, -1, 1, 0)
38 vr
= 0, 0, r
[2]-r
[0]-15, r
[3]-r
[1]-15
43 flags
= WASTEconst
.weDoAutoScroll | WASTEconst
.weDoOutlineHilite | \
45 self
.ted
= waste
.WENew(dr
, vr
, flags
)
46 self
.ted
.WEInstallTabHooks()
47 style
, soup
= self
.getstylesoup()
48 self
.ted
.WEInsert(data
, style
, soup
)
49 self
.ted
.WESetSelection(0,0)
51 self
.ted
.WEResetModCount()
55 self
.do_activate(1, None)
57 def getstylesoup(self
):
60 oldrf
= Res
.CurResFile()
62 rf
= Res
.FSpOpenResFile(self
.path
, 1)
66 hstyle
= Res
.Get1Resource('styl', 128)
67 hstyle
.DetachResource()
71 hsoup
= Res
.Get1Resource('SOUP', 128)
72 hsoup
.DetachResource()
79 def do_idle(self
, event
):
80 (what
, message
, when
, where
, modifiers
) = event
83 if self
.ted
.WEAdjustCursor(where
, BIGREGION
):
85 Qd
.SetCursor(Qd
.GetQDGlobalsArrow())
87 def getscrollbarvalues(self
):
88 dr
= self
.ted
.WEGetDestRect()
89 vr
= self
.ted
.WEGetViewRect()
90 vx
= self
.scalebarvalue(dr
[0], dr
[2], vr
[0], vr
[2])
91 vy
= self
.scalebarvalue(dr
[1], dr
[3], vr
[1], vr
[3])
94 def scrollbar_callback(self
, which
, what
, value
):
97 height
= self
.ted
.WEGetHeight(0, 0x3fffffff)
98 cur
= self
.getscrollbarvalues()[1]
99 delta
= (cur
-value
)*height
/32767
101 topline_off
,dummy
= self
.ted
.WEGetOffset((1,1))
102 topline_num
= self
.ted
.WEOffsetToLine(topline_off
)
103 delta
= self
.ted
.WEGetHeight(topline_num
, topline_num
+1)
105 delta
= (self
.ted
.WEGetViewRect()[3]-10)
107 delta
= 10 # Random value
109 # XXXX Wrong: should be bottom line size
110 topline_off
,dummy
= self
.ted
.WEGetOffset((1,1))
111 topline_num
= self
.ted
.WEOffsetToLine(topline_off
)
112 delta
= -self
.ted
.WEGetHeight(topline_num
, topline_num
+1)
114 delta
= -(self
.ted
.WEGetViewRect()[3]-10)
117 self
.ted
.WEScroll(0, delta
)
121 vr
= self
.ted
.WEGetViewRect()
122 winwidth
= vr
[2]-vr
[0]
131 self
.ted
.WEScroll(delta
, 0)
133 l
, t
, r
, b
= self
.ted
.WEGetDestRect()
134 vl
, vt
, vr
, vb
= self
.ted
.WEGetViewRect()
139 self
.ted
.WEScroll(dx
, dy
)
141 self
.ted
.WEScroll(0, b
-vb
)
144 def do_activate(self
, onoff
, evt
):
146 ScrolledWindow
.do_activate(self
, onoff
, evt
)
148 self
.ted
.WEActivate()
149 self
.parent
.active
= self
150 self
.parent
.updatemenubar()
152 self
.ted
.WEDeactivate()
154 def do_update(self
, wid
, event
):
155 region
= wid
.GetWindowPort().visRgn
156 if Qd
.EmptyRgn(region
):
159 self
.ted
.WEUpdate(region
)
160 self
.updatescrollbars()
162 def do_postresize(self
, width
, height
, window
):
163 l
, t
, r
, b
= self
.ted
.WEGetViewRect()
164 vr
= (l
, t
, l
+width
-15, t
+height
-15)
165 self
.ted
.WESetViewRect(vr
)
166 self
.wid
.InvalWindowRect(vr
)
167 ScrolledWindow
.do_postresize(self
, width
, height
, window
)
169 def do_contentclick(self
, local
, modifiers
, evt
):
170 (what
, message
, when
, where
, modifiers
) = evt
171 self
.ted
.WEClick(local
, modifiers
, when
)
172 self
.updatescrollbars()
173 self
.parent
.updatemenubar()
175 def do_char(self
, ch
, event
):
177 (what
, message
, when
, where
, modifiers
) = event
178 self
.ted
.WEKey(ord(ch
), modifiers
)
179 self
.updatescrollbars()
180 self
.parent
.updatemenubar()
183 if self
.ted
.WEGetModCount():
184 save
= EasyDialogs
.AskYesNoCancel('Save window "%s" before closing?'%self
.name
, 1)
189 if self
.parent
.active
== self
:
190 self
.parent
.active
= None
191 self
.parent
.updatemenubar()
198 return # Will call us recursively
202 dhandle
= self
.ted
.WEGetText()
204 fp
= open(self
.path
, 'wb') # NOTE: wb, because data has CR for end-of-line
206 if data
[-1] <> '\r': fp
.write('\r')
209 # Now save style and soup
211 oldresfile
= Res
.CurResFile()
213 rf
= Res
.FSpOpenResFile(self
.path
, 3)
215 Res
.FSpCreateResFile(self
.path
, '????', 'TEXT', macfs
.smAllScripts
)
216 rf
= Res
.FSpOpenResFile(self
.path
, 3)
217 styles
= Res
.Resource('')
218 soup
= Res
.Resource('')
219 self
.ted
.WECopyRange(0, 0x3fffffff, None, styles
, soup
)
220 styles
.AddResource('styl', 128, '')
221 soup
.AddResource('SOUP', 128, '')
223 Res
.UseResFile(oldresfile
)
225 self
.ted
.WEResetModCount()
227 def menu_save_as(self
):
228 path
= EasyDialogs
.AskFileForSave(message
='Save as:')
231 self
.name
= os
.path
.split(self
.path
)[-1]
232 self
.wid
.SetWTitle(self
.name
)
237 if hasattr(Scrap
, 'ZeroScrap'):
240 Scrap
.ClearCurrentScrap()
242 self
.updatescrollbars()
243 self
.parent
.updatemenubar()
246 if hasattr(Scrap
, 'ZeroScrap'):
249 Scrap
.ClearCurrentScrap()
251 self
.updatescrollbars()
252 self
.parent
.updatemenubar()
254 def menu_paste(self
):
257 self
.updatescrollbars()
258 self
.parent
.updatemenubar()
260 def menu_clear(self
):
263 self
.updatescrollbars()
264 self
.parent
.updatemenubar()
268 self
.updatescrollbars()
269 self
.parent
.updatemenubar()
271 def menu_setfont(self
, font
):
272 font
= Fm
.GetFNum(font
)
273 self
.mysetstyle(WASTEconst
.weDoFont
, (font
, 0, 0, (0,0,0)))
274 self
.parent
.updatemenubar()
276 def menu_modface(self
, face
):
277 self
.mysetstyle(WASTEconst
.weDoFace|WASTEconst
.weDoToggleFace
,
278 (0, face
, 0, (0,0,0)))
280 def menu_setface(self
, face
):
281 self
.mysetstyle(WASTEconst
.weDoFace|WASTEconst
.weDoReplaceFace
,
282 (0, face
, 0, (0,0,0)))
284 def menu_setsize(self
, size
):
285 self
.mysetstyle(WASTEconst
.weDoSize
, (0, 0, size
, (0,0,0)))
287 def menu_incsize(self
, size
):
288 self
.mysetstyle(WASTEconst
.weDoAddSize
, (0, 0, size
, (0,0,0)))
290 def mysetstyle(self
, which
, how
):
292 self
.ted
.WESetStyle(which
, how
)
293 self
.parent
.updatemenubar()
295 def have_selection(self
):
296 start
, stop
= self
.ted
.WEGetSelection()
300 return self
.ted
.WECanPaste()
303 which
, redo
= self
.ted
.WEGetUndoInfo()
304 which
= UNDOLABELS
[which
]
305 if which
== None: return None
311 def getruninfo(self
):
312 all
= (WASTEconst
.weDoFont | WASTEconst
.weDoFace | WASTEconst
.weDoSize
)
313 dummy
, mode
, (font
, face
, size
, color
) = self
.ted
.WEContinuousStyle(all
)
314 if not (mode
& WASTEconst
.weDoFont
):
317 font
= Fm
.GetFontName(font
)
318 if not (mode
& WASTEconst
.weDoFace
): fact
= None
319 if not (mode
& WASTEconst
.weDoSize
): size
= None
320 return font
, face
, size
322 class Wed(Application
):
324 Application
.__init
__(self
)
328 waste
.STDObjectHandlers()
330 def makeusermenus(self
):
331 self
.filemenu
= m
= Menu(self
.menubar
, "File")
332 self
.newitem
= MenuItem(m
, "New window", "N", self
.open)
333 self
.openitem
= MenuItem(m
, "Open...", "O", self
.openfile
)
334 self
.closeitem
= MenuItem(m
, "Close", "W", self
.closewin
)
336 self
.saveitem
= MenuItem(m
, "Save", "S", self
.save
)
337 self
.saveasitem
= MenuItem(m
, "Save as...", "", self
.saveas
)
339 self
.quititem
= MenuItem(m
, "Quit", "Q", self
.quit
)
341 self
.editmenu
= m
= Menu(self
.menubar
, "Edit")
342 self
.undoitem
= MenuItem(m
, "Undo", "Z", self
.undo
)
343 self
.cutitem
= MenuItem(m
, "Cut", "X", self
.cut
)
344 self
.copyitem
= MenuItem(m
, "Copy", "C", self
.copy
)
345 self
.pasteitem
= MenuItem(m
, "Paste", "V", self
.paste
)
346 self
.clearitem
= MenuItem(m
, "Clear", "", self
.clear
)
350 # Groups of items enabled together:
351 self
.windowgroup
= [self
.closeitem
, self
.saveitem
, self
.saveasitem
,
352 self
.editmenu
, self
.fontmenu
, self
.facemenu
, self
.sizemenu
]
353 self
.focusgroup
= [self
.cutitem
, self
.copyitem
, self
.clearitem
]
354 self
.windowgroup_on
= -1
355 self
.focusgroup_on
= -1
356 self
.pastegroup_on
= -1
357 self
.undo_label
= "never"
360 def makefontmenu(self
):
361 self
.fontmenu
= Menu(self
.menubar
, "Font")
362 self
.fontnames
= getfontnames()
364 for n
in self
.fontnames
:
365 m
= MenuItem(self
.fontmenu
, n
, "", self
.selfont
)
366 self
.fontitems
.append(m
)
367 self
.facemenu
= Menu(self
.menubar
, "Style")
369 for n
, shortcut
in STYLES
:
370 m
= MenuItem(self
.facemenu
, n
, shortcut
, self
.selface
)
371 self
.faceitems
.append(m
)
372 self
.facemenu
.addseparator()
373 self
.faceitem_normal
= MenuItem(self
.facemenu
, "Normal", "N",
375 self
.sizemenu
= Menu(self
.menubar
, "Size")
378 m
= MenuItem(self
.sizemenu
, `n`
, "", self
.selsize
)
379 self
.sizeitems
.append(m
)
380 self
.sizemenu
.addseparator()
381 self
.sizeitem_bigger
= MenuItem(self
.sizemenu
, "Bigger", "+",
383 self
.sizeitem_smaller
= MenuItem(self
.sizemenu
, "Smaller", "-",
386 def selfont(self
, id, item
, *rest
):
388 font
= self
.fontnames
[item
-1]
389 self
.active
.menu_setfont(font
)
391 EasyDialogs
.Message("No active window?")
393 def selface(self
, id, item
, *rest
):
396 self
.active
.menu_modface(face
)
398 EasyDialogs
.Message("No active window?")
400 def selfacenormal(self
, *rest
):
402 self
.active
.menu_setface(0)
404 EasyDialogs
.Message("No active window?")
406 def selsize(self
, id, item
, *rest
):
409 self
.active
.menu_setsize(size
)
411 EasyDialogs
.Message("No active window?")
413 def selsizebigger(self
, *rest
):
415 self
.active
.menu_incsize(2)
417 EasyDialogs
.Message("No active window?")
419 def selsizesmaller(self
, *rest
):
421 self
.active
.menu_incsize(-2)
423 EasyDialogs
.Message("No active window?")
425 def updatemenubar(self
):
427 on
= (self
.active
<> None)
428 if on
<> self
.windowgroup_on
:
429 for m
in self
.windowgroup
:
431 self
.windowgroup_on
= on
434 # only if we have an edit menu
435 on
= self
.active
.have_selection()
436 if on
<> self
.focusgroup_on
:
437 for m
in self
.focusgroup
:
439 self
.focusgroup_on
= on
441 on
= self
.active
.can_paste()
442 if on
<> self
.pastegroup_on
:
443 self
.pasteitem
.enable(on
)
444 self
.pastegroup_on
= on
446 on
= self
.active
.can_undo()
447 if on
<> self
.undo_label
:
449 self
.undoitem
.enable(1)
450 self
.undoitem
.settext(on
)
453 self
.undoitem
.settext("Nothing to undo")
454 self
.undoitem
.enable(0)
456 if self
.updatefontmenus():
461 def updatefontmenus(self
):
462 info
= self
.active
.getruninfo()
463 if info
== self
.ffs_values
:
465 # Remove old checkmarks
466 if self
.ffs_values
== ():
467 self
.ffs_values
= (None, None, None)
468 font
, face
, size
= self
.ffs_values
470 fnum
= self
.fontnames
.index(font
)
471 self
.fontitems
[fnum
].check(0)
473 for i
in range(len(self
.faceitems
)):
475 self
.faceitems
[i
].check(0)
477 for i
in range(len(self
.sizeitems
)):
479 self
.sizeitems
[i
].check(0)
481 self
.ffs_values
= info
483 font
, face
, size
= self
.ffs_values
485 fnum
= self
.fontnames
.index(font
)
486 self
.fontitems
[fnum
].check(1)
488 for i
in range(len(self
.faceitems
)):
490 self
.faceitems
[i
].check(1)
492 for i
in range(len(self
.sizeitems
)):
494 self
.sizeitems
[i
].check(1)
495 # Set outline/normal for sizes
497 exists
= getfontsizes(font
, SIZES
)
498 for i
in range(len(self
.sizeitems
)):
500 self
.sizeitems
[i
].setstyle(0)
502 self
.sizeitems
[i
].setstyle(8)
508 def do_about(self
, id, item
, window
, event
):
509 EasyDialogs
.Message("A simple single-font text editor based on WASTE")
515 def open(self
, *args
):
518 def openfile(self
, *args
):
521 def _open(self
, askfile
):
523 path
= EasyDialogs
.AskFileForOpen(typeList
=('TEXT',))
526 name
= os
.path
.split(path
)[-1]
528 fp
= open(path
, 'rb') # NOTE binary, we need cr as end-of-line
532 EasyDialogs
.Message("IOERROR: "+`arg`
)
536 name
= "Untitled %d"%self
.num
538 w
= WasteWindow(self
)
539 w
.open(path
, name
, data
)
540 self
.num
= self
.num
+ 1
542 def closewin(self
, *args
):
546 EasyDialogs
.Message("No active window?")
548 def save(self
, *args
):
550 self
.active
.menu_save()
552 EasyDialogs
.Message("No active window?")
554 def saveas(self
, *args
):
556 self
.active
.menu_save_as()
558 EasyDialogs
.Message("No active window?")
561 def quit(self
, *args
):
562 for w
in self
._windows
.values():
572 def undo(self
, *args
):
574 self
.active
.menu_undo()
576 EasyDialogs
.Message("No active window?")
578 def cut(self
, *args
):
580 self
.active
.menu_cut()
582 EasyDialogs
.Message("No active window?")
584 def copy(self
, *args
):
586 self
.active
.menu_copy()
588 EasyDialogs
.Message("No active window?")
590 def paste(self
, *args
):
592 self
.active
.menu_paste()
594 EasyDialogs
.Message("No active window?")
596 def clear(self
, *args
):
598 self
.active
.menu_clear()
600 EasyDialogs
.Message("No active window?")
606 def idle(self
, event
):
608 self
.active
.do_idle(event
)
610 Qd
.SetCursor(Qd
.GetQDGlobalsArrow())
615 n
= Fm
.GetFontName(i
)
616 if n
: names
.append(n
)
619 def getfontsizes(name
, sizes
):
621 num
= Fm
.GetFNum(name
)
623 if Fm
.RealFont(num
, sz
):
633 if __name__
== '__main__':