1"======================================================================
2|
3|   Smalltalk Tk-based GUI building blocks (text widget).
4|
5|
6 ======================================================================"
7
8"======================================================================
9|
10| Copyright 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
11| Written by Paolo Bonzini and Robert Collins.
12|
13| This file is part of the GNU Smalltalk class library.
14|
15| The GNU Smalltalk class library is free software; you can redistribute it
16| and/or modify it under the terms of the GNU Lesser General Public License
17| as published by the Free Software Foundation; either version 2.1, or (at
18| your option) any later version.
19|
20| The GNU Smalltalk class library is distributed in the hope that it will be
21| useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
22| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
23| General Public License for more details.
24|
25| You should have received a copy of the GNU Lesser General Public License
26| along with the GNU Smalltalk class library; see the file COPYING.LESSER.
27| If not, write to the Free Software Foundation, 59 Temple Place - Suite
28| 330, Boston, MA 02110-1301, USA.
29|
30 ======================================================================"
31
32
33
34BViewport subclass: BText [
35    | callback tagInfo images gtkbuffer |
36
37    <comment: '
38I represent a text viewer with pretty good formatting options.'>
39    <category: 'Graphics-Windows'>
40
41    BText class >> emacsLike [
42	"Answer whether we are using Emacs or Motif key bindings."
43
44	<category: 'accessing'>
45	'FIXME: emacsLike should die?' printNl.
46	^false
47	"self tclEval: 'return $tk_strictMotif'.
48	 ^self tclResult = '0'"
49    ]
50
51    BText class >> emacsLike: aBoolean [
52	"Set whether we are using Emacs or Motif key bindings."
53
54	<category: 'accessing'>
55	'FIXME: emacsLike should die?' printNl
56	"self tclEval:
57	 'set tk_strictMotif ', (aBoolean ifTrue: [ '0' ] ifFalse: [ '1' ])."
58    ]
59
60    BText class >> newReadOnly: parent [
61	"Answer a new read-only text widget (read-only is achieved simply
62	 by setting its state to be disabled)"
63
64	<category: 'instance creation'>
65	| ctl |
66	ctl := self new: parent.
67	ctl tclEval: ctl connected , ' configure -state disabled'.
68	^ctl
69    ]
70
71    backgroundColor [
72	"Answer the value of the backgroundColor option for the widget.
73
74	 Specifies the normal background color to use when displaying the widget."
75
76	<category: 'accessing'>
77	self properties at: #background ifPresent: [:value | ^value].
78	self
79	    tclEval: '%1 cget -background'
80	    with: self connected
81	    with: self container.
82	^self properties at: #background put: self tclResult
83    ]
84
85    backgroundColor: value [
86	"Set the value of the backgroundColor option for the widget.
87
88	 Specifies the normal background color to use when displaying the widget."
89
90	<category: 'accessing'>
91	self
92	    tclEval: '%1 configure -background %3'
93	    with: self connected
94	    with: self container
95	    with: value asTkString.
96	self properties at: #background put: value
97    ]
98
99    callback [
100	"Answer a DirectedMessage that is sent when the receiver is modified,
101	 or nil if none has been set up."
102
103	<category: 'accessing'>
104	^callback
105    ]
106
107    callback: aReceiver message: aSymbol [
108	"Set up so that aReceiver is sent the aSymbol message (the name of
109	 a zero- or one-argument selector) when the receiver is modified.
110	 If the method accepts an argument, the receiver is passed."
111
112	<category: 'accessing'>
113	| arguments selector numArgs |
114	selector := aSymbol asSymbol.
115	numArgs := selector numArgs.
116	arguments := #().
117	numArgs = 1 ifTrue: [arguments := Array with: self].
118	callback := DirectedMessage
119		    selector: selector
120		    arguments: arguments
121		    receiver: aReceiver
122    ]
123
124    contents [
125	"Return the contents of the widget"
126
127	<category: 'accessing'>
128	| bounds |
129	bounds := self gtkbuffer getBounds.
130	^(bounds at: 1) getVisibleText: (bounds at: 2)
131    ]
132
133    contents: aString [
134	"Set the contents of the widget"
135
136	<category: 'accessing'>
137	self gtkbuffer setText: aString
138    ]
139
140    font [
141	"Answer the value of the font option for the widget.
142
143	 Specifies the font to use when drawing text inside the widget. The font
144	 can be given as either an X font name or a Blox font description string.
145
146	 X font names are given as many fields, each led by a minus, and each of
147	 which can be replaced by an * to indicate a default value is ok:
148	 foundry, family, weight, slant, setwidth, addstyle, pixel size, point size
149	 (the same as pixel size for historical reasons), horizontal resolution,
150	 vertical resolution, spacing, width, charset and character encoding.
151
152	 Blox font description strings have three fields, which must be separated by
153	 a space and of which only the first is mandatory: the font family, the font
154	 size in points (or in pixels if a negative value is supplied), and a number
155	 of styles separated by a space (valid styles are normal, bold, italic,
156	 underline and overstrike). Examples of valid fonts are ``Helvetica 10 Bold'',
157	 ``Times -14'', ``Futura Bold Underline''.  You must enclose the font family
158	 in braces if it is made of two or more words."
159
160	<category: 'accessing'>
161	self properties at: #font ifPresent: [:value | ^value].
162	self
163	    tclEval: '%1 cget -font'
164	    with: self connected
165	    with: self container.
166	^self properties at: #font put: self tclResult
167    ]
168
169    font: value [
170	"Set the value of the font option for the widget.
171
172	 Specifies the font to use when drawing text inside the widget. The font
173	 can be given as either an X font name or a Blox font description string.
174
175	 X font names are given as many fields, each led by a minus, and each of
176	 which can be replaced by an * to indicate a default value is ok:
177	 foundry, family, weight, slant, setwidth, addstyle, pixel size, point size
178	 (the same as pixel size for historical reasons), horizontal resolution,
179	 vertical resolution, spacing, width, charset and character encoding.
180
181	 Blox font description strings have three fields, which must be separated by
182	 a space and of which only the first is mandatory: the font family, the font
183	 size in points (or in pixels if a negative value is supplied), and a number
184	 of styles separated by a space (valid styles are normal, bold, italic,
185	 underline and overstrike). Examples of valid fonts are ``Helvetica 10 Bold'',
186	 ``Times -14'', ``Futura Bold Underline''.  You must enclose the font family
187	 in braces if it is made of two or more words."
188
189	"Change default font throughout the widget"
190
191	<category: 'accessing'>
192	self connected modifyFont: (GTK.PangoFontDescription fromString: value).
193	self properties at: #font put: value
194    ]
195
196    foregroundColor [
197	"Answer the value of the foregroundColor option for the widget.
198
199	 Specifies the normal foreground color to use when displaying the widget."
200
201	<category: 'accessing'>
202	self properties at: #foreground ifPresent: [:value | ^value].
203	self
204	    tclEval: '%1 cget -foreground'
205	    with: self connected
206	    with: self container.
207	^self properties at: #foreground put: self tclResult
208    ]
209
210    foregroundColor: value [
211	"Set the value of the foregroundColor option for the widget.
212
213	 Specifies the normal foreground color to use when displaying the widget."
214
215	<category: 'accessing'>
216	self
217	    tclEval: '%1 configure -foreground %3'
218	    with: self connected
219	    with: self container
220	    with: value asTkString.
221	self properties at: #foreground put: value
222    ]
223
224    getSelection [
225	"Answer an empty string if the widget has no selection, else answer
226	 the currently selected text"
227
228	<category: 'accessing'>
229	| bounds |
230	bounds := self gtkbuffer getSelectionBounds.
231	^(bounds at: 1) getVisibleText: (bounds at: 2)
232    ]
233
234    selectBackground [
235	"Answer the value of the selectBackground option for the widget.
236
237	 Specifies the background color to use when displaying selected parts
238	 of the widget."
239
240	<category: 'accessing'>
241	self properties at: #selectbackground ifPresent: [:value | ^value].
242	self
243	    tclEval: '%1 cget -selectbackground'
244	    with: self connected
245	    with: self container.
246	^self properties at: #selectbackground put: self tclResult
247    ]
248
249    selectBackground: value [
250	"Set the value of the selectBackground option for the widget.
251
252	 Specifies the background color to use when displaying selected parts
253	 of the widget."
254
255	<category: 'accessing'>
256	self
257	    tclEval: '%1 configure -selectbackground %3'
258	    with: self connected
259	    with: self container
260	    with: value asTkString.
261	self properties at: #selectbackground put: value
262    ]
263
264    selectForeground [
265	"Answer the value of the selectForeground option for the widget.
266
267	 Specifies the foreground color to use when displaying selected parts
268	 of the widget."
269
270	<category: 'accessing'>
271	self properties at: #selectforeground ifPresent: [:value | ^value].
272	self
273	    tclEval: '%1 cget -selectforeground'
274	    with: self connected
275	    with: self container.
276	^self properties at: #selectforeground put: self tclResult
277    ]
278
279    selectForeground: value [
280	"Set the value of the selectForeground option for the widget.
281
282	 Specifies the foreground color to use when displaying selected parts
283	 of the widget."
284
285	<category: 'accessing'>
286	self
287	    tclEval: '%1 configure -selectforeground %3'
288	    with: self connected
289	    with: self container
290	    with: value asTkString.
291	self properties at: #selectforeground put: value
292    ]
293
294    wrap [
295	"Answer the value of the wrap option for the widget.
296
297	 Specifies how to handle lines in the text that are too long to be displayed
298	 in a single line of the text's window. The value must be #none or #char or
299	 #word. A wrap mode of none means that each line of text appears as exactly
300	 one line on the screen; extra characters that do not fit on the screen are
301	 not displayed. In the other modes each line of text will be broken up into
302	 several screen lines if necessary to keep all the characters visible. In
303	 char mode a screen line break may occur after any character; in word mode a
304	 line break will only be made at word boundaries."
305
306	<category: 'accessing'>
307	self properties at: #wrap ifPresent: [:value | ^value].
308	self
309	    tclEval: '%1 cget -wrap'
310	    with: self connected
311	    with: self container.
312	^self properties at: #wrap put: self tclResult asSymbol
313    ]
314
315    wrap: value [
316	"Set the value of the wrap option for the widget.
317
318	 Specifies how to handle lines in the text that are too long to be displayed
319	 in a single line of the text's window. The value must be #none or #char or
320	 #word. A wrap mode of none means that each line of text appears as exactly
321	 one line on the screen; extra characters that do not fit on the screen are
322	 not displayed. In the other modes each line of text will be broken up into
323	 several screen lines if necessary to keep all the characters visible. In
324	 char mode a screen line break may occur after any character; in word mode a
325	 line break will only be made at word boundaries."
326
327	<category: 'accessing'>
328	self
329	    tclEval: '%1 configure -wrap %3'
330	    with: self connected
331	    with: self container
332	    with: value asTkString.
333	self properties at: #wrap put: value
334    ]
335
336    insertAtEnd: aString attribute: attr [
337	"Clear the selection and append aString at the end of the
338	 widget.  Use the given attributes to format the text."
339
340	<category: 'attributes'>
341	| start tmpMark end |
342	attr isNil ifTrue: [^self insertAtEnd: aString].
343	end := self gtkbuffer getEndIter.
344	tmpMark := self gtkbuffer
345		    createMark: 'temporary'
346		    where: end
347		    leftGravity: true.
348	self gtkbuffer beginUserAction.
349	self gtkbuffer insert: end text: aString.
350	start := self gtkbuffer getIterAtMark: tmpMark.
351	end := self gtkbuffer getEndIter.
352	self gtkbuffer placeCursor: end.
353	self
354	    setAttributes: attr
355	    start: start
356	    end: end.
357	self gtkbuffer endUserAction
358    ]
359
360    insertText: aString attribute: attr [
361	"Insert aString in the widget at the current insertion point,
362	 replacing the currently selected text (if any).  Use the
363	 given attributes to format the text."
364
365	<category: 'attributes'>
366	| bounds start end tmpMark |
367	attr isNil ifTrue: [^self insertText: aString].
368
369	"We need a temporary mark to save the beginning of the selection."
370	bounds := self gtkbuffer getSelectionBounds.
371	tmpMark := self gtkbuffer
372		    createMark: 'temporary'
373		    where: (bounds at: 1)
374		    leftGravity: true.
375	(self gtkbuffer)
376	    beginUserAction;
377	    deleteSelection: false defaultEditable: true;
378	    insertAtCursor: aString.
379	start := self gtkbuffer getIterAtMark: tmpMark.
380	end := self gtkbuffer getIterAtMark: self gtkbuffer getInsert.
381	self
382	    setAttributes: attr
383	    start: start
384	    end: end.
385	self gtkbuffer endUserAction
386    ]
387
388    removeAttributes [
389	"Remove any kind of formatting from the text in the widget"
390
391	<category: 'attributes'>
392	tagInfo isNil ifTrue: [^self].
393	self removeAttributesInside:
394		{self gtkbuffer getStartIter.
395		self gtkbuffer getEndIter}
396    ]
397
398    removeAttributesFrom: aPoint to: endPoint [
399	"Remove any kind of formatting from the text in the widget
400	 between the given endpoints.  The two endpoints are Point
401	 objects in which both coordinates are 1-based: the first
402	 line is line 1, and the first character in the first line
403	 is character 1."
404
405	<category: 'attributes'>
406	tagInfo isNil ifTrue: [^self].
407	self removeAttributesInside: (self from: aPoint to: endPoint)
408    ]
409
410    setAttributes: attr from: aPoint to: endPoint [
411	"Add the formatting given by attr to the text in the widget
412	 between the given endpoints.  The two endpoints are Point
413	 objects in which both coordinates are 1-based: the first
414	 line is line 1, and the first character in the first line
415	 is character 1."
416
417	<category: 'attributes'>
418	| range tag tags tagtable |
419	attr isNil ifTrue: [^self].
420	range := self from: aPoint to: endPoint.
421	self
422	    setAttributes: attr
423	    start: (range at: 1)
424	    end: (range at: 2)
425    ]
426
427    child: child height: value [
428	"Set the height of the given child to be `value' pixels."
429
430	<category: 'geometry management'>
431	| width height |
432	height := self at: #heightGeom put: value asInteger.
433	width := self at: #widthGeom ifAbsentPut: [self widthAbsolute]
434	"self
435	 tclEval: 'wm geometry %1 =%2x%3'
436	 with: child container
437	 with: width printString
438	 with: height printString"
439    ]
440
441    child: child heightOffset: value [
442	"Adjust the height of the given child to be given by `value'
443	 more pixels."
444
445	<category: 'geometry management'>
446	self child: child height: (self heightChild: child) + value
447    ]
448
449    child: child width: value [
450	"Set the width of the given child to be `value' pixels."
451
452	<category: 'geometry management'>
453	| width height |
454	width := self at: #widthGeom put: value asInteger.
455	height := self at: #heightGeom ifAbsentPut: [child heightAbsolute]
456	"self
457	 tclEval: 'wm geometry %1 =%2x%3'
458	 with: child container
459	 with: width printString
460	 with: height printString"
461    ]
462
463    child: child widthOffset: value [
464	"Adjust the width of the given child to be given by `value'
465	 more pixels."
466
467	<category: 'geometry management'>
468	self child: child width: (self widthChild: child) + value
469    ]
470
471    child: child x: value [
472	"Never fail and do nothing, the children stay where
473	 the text ended at the time each child was added in
474	 the widget"
475
476	<category: 'geometry management'>
477
478    ]
479
480    child: child xOffset: value [
481	<category: 'geometry management'>
482	self shouldNotImplement
483    ]
484
485    child: child y: value [
486	"Never fail and do nothing, the children stay where
487	 the text ended at the time each child was added in
488	 the widget"
489
490	<category: 'geometry management'>
491
492    ]
493
494    child: child yOffset: value [
495	<category: 'geometry management'>
496	self shouldNotImplement
497    ]
498
499    heightChild: child [
500	"Answer the given child's height in pixels."
501
502	<category: 'geometry management'>
503	^child at: #heightGeom ifAbsentPut: [child heightAbsolute]
504    ]
505
506    widthChild: child [
507	"Answer the given child's width in pixels."
508
509	<category: 'geometry management'>
510	^child at: #widthGeom ifAbsentPut: [child widthAbsolute]
511    ]
512
513    xChild: child [
514	"Answer the given child's top-left border's x coordinate.
515	 We always answer 0 since the children actually move when
516	 the text widget scrolls"
517
518	<category: 'geometry management'>
519	^0
520    ]
521
522    yChild: child [
523	"Answer the given child's top-left border's y coordinate.
524	 We always answer 0 since the children actually move when
525	 the text widget scrolls"
526
527	<category: 'geometry management'>
528	^0
529    ]
530
531    insertImage: anObject [
532	"Insert an image where the insertion point currently lies in the widget.
533	 anObject can be a String containing image data (either Base-64 encoded
534	 GIF data, XPM data, or PPM data), or the result or registering an image
535	 with #registerImage:"
536
537	<category: 'images'>
538	| key |
539	key := self registerImage: anObject.
540	self
541	    tclEval: '%1 image create insert -align baseline -image %2'
542	    with: self connected
543	    with: key value.
544	^key
545    ]
546
547    insertImage: anObject at: position [
548	"Insert an image at the given position in the widget.  The
549	 position is a Point object in which both coordinates are 1-based:
550	 the first line is line 1, and the first character in the first
551	 line is character 1.
552
553	 anObject can be a String containing image data (either Base-64 encoded
554	 GIF data, XPM data, or PPM data), or the result or registering an image
555	 with #registerImage:"
556
557	<category: 'images'>
558	| key |
559	key := self registerImage: anObject.
560	self
561	    tclEval: '%1 image create %2.%3 -align baseline -image %4'
562	    with: self connected
563	    with: position y printString
564	    with: (position x - 1) printString
565	    with: key value.
566	^key
567    ]
568
569    insertImageAtEnd: anObject [
570	"Insert an image at the end of the widgets text.
571	 anObject can be a String containing image data (either Base-64 encoded
572	 GIF data, XPM data, or PPM data), or the result or registering an image
573	 with #registerImage:"
574
575	<category: 'images'>
576	| key |
577	key := self registerImage: anObject.
578	self
579	    tclEval: '%1 image create end -align baseline -image %2'
580	    with: self connected
581	    with: key value.
582	^key
583    ]
584
585    registerImage: anObject [
586	"Register an image (whose data is in anObject, a String including
587	 Base-64 encoded GIF data, XPM data, or PPM data) to be used
588	 in the widget.  If the same image must be used a lot of times,
589	 it is better to register it once and then pass the result of
590	 #registerImage: to the image insertion methods.
591
592	 Registered image are private within each BText widget.  Registering
593	 an image with a widget and using it with another could give
594	 unpredictable results."
595
596	<category: 'images'>
597	| imageName |
598	anObject class == ValueHolder ifTrue: [^anObject].
599	self tclEval: 'image create photo -data ' , anObject asTkImageString.
600	images isNil ifTrue: [images := OrderedCollection new].
601	imageName := images add: self tclResult.
602	^ValueHolder value: imageName
603    ]
604
605    insertAtEnd: aString [
606	"Clear the selection and append aString at the end of the
607	 widget."
608
609	<category: 'inserting text'>
610	(self gtkbuffer)
611	    insert: self gtkbuffer getEndIter text: aString;
612	    placeCursor: self gtkbuffer getEndIter
613    ]
614
615    insertText: aString [
616	"Insert aString in the widget at the current insertion point,
617	 replacing the currently selected text (if any)."
618
619	<category: 'inserting text'>
620	(self gtkbuffer)
621	    beginUserAction;
622	    deleteSelection: false defaultEditable: true;
623	    insertAtCursor: aString;
624	    endUserAction
625    ]
626
627    insertSelectedText: aString [
628	"Insert aString in the widget at the current insertion point,
629	 leaving the currently selected text (if any) in place, and
630	 selecting the text."
631
632	<category: 'inserting text'>
633	| bounds selBound tmpMark |
634	selBound := self gtkbuffer getSelectionBound.
635	bounds := self gtkbuffer getSelectionBounds.
636
637	"We need a temporary mark to keep the beginning of the selection
638	 where it is."
639	tmpMark := self gtkbuffer
640		    createMark: 'temporary'
641		    where: (bounds at: 1)
642		    leftGravity: true.
643	(self gtkbuffer)
644	    beginUserAction;
645	    placeCursor: (bounds at: 2);
646	    insertAtCursor: aString;
647	    moveMark: selBound where: (self gtkbuffer getIterAtMark: tmpMark);
648	    endUserAction;
649	    deleteMark: tmpMark
650    ]
651
652    insertText: aString at: position [
653	"Insert aString in the widget at the given position,
654	 replacing the currently selected text (if any).  The
655	 position is a Point object in which both coordinates are 1-based:
656	 the first line is line 1, and the first character in the first
657	 line is character 1."
658
659	<category: 'inserting text'>
660	self
661	    tclEval: '%1 delete sel.first sel.last
662	%1 insert %2.%3 %4
663	%1 see insert'
664	    with: self connected
665	    with: position y printString
666	    with: (position x - 1) printString
667	    with: aString asTkString
668    ]
669
670    insertTextSelection: aString [
671	"Insert aString in the widget after the current selection,
672	 leaving the currently selected text (if any) intact."
673
674	<category: 'inserting text'>
675	| bounds selBound tmpMark |
676	selBound := self gtkbuffer getSelectionBound.
677	bounds := self gtkbuffer getSelectionBounds.
678
679	"We need a temporary mark to put the beginning of the selection
680	 where the selection used to end."
681	tmpMark := self gtkbuffer
682		    createMark: 'temporary'
683		    where: (bounds at: 2)
684		    leftGravity: true.
685	(self gtkbuffer)
686	    beginUserAction;
687	    placeCursor: (bounds at: 2);
688	    insertAtCursor: aString;
689	    moveMark: selBound where: (self gtkbuffer getIterAtMark: tmpMark);
690	    endUserAction;
691	    deleteMark: tmpMark
692    ]
693
694    invokeCallback [
695	"Generate a synthetic callback."
696
697	<category: 'inserting text'>
698	self callback isNil ifFalse: [self callback send]
699    ]
700
701    nextPut: aCharacter [
702	"Clear the selection and append aCharacter at the end of the
703	 widget."
704
705	<category: 'inserting text'>
706	self insertAtEnd: (String with: aCharacter)
707    ]
708
709    nextPutAll: aString [
710	"Clear the selection and append aString at the end of the
711	 widget."
712
713	<category: 'inserting text'>
714	self insertAtEnd: aString
715    ]
716
717    nl [
718	"Clear the selection and append a linefeed character at the
719	 end of the widget."
720
721	<category: 'inserting text'>
722	self insertAtEnd: Character nl asString
723    ]
724
725    refuseTabs [
726	"Arrange so that Tab characters, instead of being inserted
727	 in the widget, traverse the widgets in the parent window."
728
729	<category: 'inserting text'>
730	self
731	    tclEval: '
732	bind %1 <Tab> {
733	    focus [tk_focusNext %W]
734	    break
735	}
736	bind %1 <Shift-Tab> {
737	    focus [tk_focusPrev %W]
738	    break
739	}'
740	    with: self connected
741    ]
742
743    replaceSelection: aString [
744	"Insert aString in the widget at the current insertion point,
745	 replacing the currently selected text (if any), and leaving
746	 the text selected."
747
748	<category: 'inserting text'>
749	| bounds |
750	bounds := self gtkbuffer getSelectionBounds.
751	self gtkbuffer delete: (bounds at: 1) end: (bounds at: 2).
752	self gtkbuffer insertAtCursor: aString
753    ]
754
755    searchString: aString [
756	"Search aString in the widget.  If it is not found,
757	 answer zero, else answer the 1-based line number
758	 and move the insertion point to the place where
759	 the string was found."
760
761	<category: 'inserting text'>
762	| result |
763	self
764	    tclEval: self connected , ' search ' , aString asTkString , ' 1.0 end'.
765	result := self tclResult.
766	result isEmpty ifTrue: [^0].
767	self
768	    tclEval: '
769	%1 mark set insert %2
770	%1 see insert'
771	    with: self connected
772	    with: result.
773
774	"Sending asInteger removes the column"
775	^result asInteger
776    ]
777
778    space [
779	"Clear the selection and append a space at the end of the
780	 widget."
781
782	<category: 'inserting text'>
783	self insertAtEnd: ' '
784    ]
785
786    charsInLine: number [
787	"Answer how many characters are there in the number-th line"
788
789	<category: 'position & lines'>
790	| iter |
791	iter := self gtkbuffer getIterAtLine: number.
792	iter forwardToLineEnd.
793	^1 + iter getLineOffset
794    ]
795
796    currentColumn [
797	"Answer the 1-based column number where the insertion point
798	 currently lies."
799
800	<category: 'position & lines'>
801	| mark iter |
802	mark := self gtkbuffer getInsert.
803	iter := self gtkbuffer getIterAtMark: mark.
804	^1 + iter getLineOffset
805    ]
806
807    currentLine [
808	"Answer the 1-based line number where the insertion point
809	 currently lies."
810
811	<category: 'position & lines'>
812	| mark iter |
813	mark := self gtkbuffer getInsert.
814	iter := self gtkbuffer getIterAtMark: mark.
815	^1 + iter getLine
816    ]
817
818    currentPosition [
819	"Answer a Point representing where the insertion point
820	 currently lies.  Both coordinates in the answer are 1-based:
821	 the first line is line 1, and the first character in the first
822	 line is character 1."
823
824	<category: 'position & lines'>
825	| mark iter |
826	mark := self gtkbuffer getInsert.
827	iter := self gtkbuffer getIterAtMark: mark.
828	^(1 + iter getLine) @ (1 + iter getLineOffset)
829    ]
830
831    currentPosition: aPoint [
832	"Move the insertion point to the position given by aPoint.
833	 Both coordinates in aPoint are interpreted as 1-based:
834	 the first line is line 1, and the first character in the first
835	 line is character 1."
836
837	<category: 'position & lines'>
838	| iter |
839	iter := self gtkbuffer getIterAtLineOffset: aPoint y - 1
840		    charOffset: aPoint x - 1.
841	self gtkbuffer placeCursor: iter
842    ]
843
844    gotoLine: line end: aBoolean [
845	"If aBoolean is true, move the insertion point to the last
846	 character of the line-th line (1 being the first line
847	 in the widget); if aBoolean is false, move it to the start
848	 of the line-th line."
849
850	<category: 'position & lines'>
851	| iter |
852	iter := self gtkbuffer getIterAtLine: line - 1.
853	aBoolean ifTrue: [iter forwardToLineEnd].
854	self gtkbuffer placeCursor: iter
855    ]
856
857    indexAt: point [
858	"Answer the position of the character that covers the
859	 pixel whose coordinates within the text's window are
860	 given by the supplied Point object."
861
862	<category: 'position & lines'>
863	self
864	    tclEval: self connected , ' index @%1,%2'
865	    with: point x printString
866	    with: point y printString.
867	^self parseResult
868    ]
869
870    lineAt: number [
871	"Answer the number-th line of text in the widget"
872
873	<category: 'position & lines'>
874	| start end |
875	start := self gtkbuffer getIterAtLine: number - 1.
876	end := self gtkbuffer getIterAtLine: number - 1.
877	end forwardToLineEnd.
878	^start getVisibleText: end
879    ]
880
881    numberOfLines [
882	"Answer the number of lines in the widget"
883
884	<category: 'position & lines'>
885	^self gtkbuffer getLineCount
886    ]
887
888    selectFrom: first to: last [
889	"Select the text between the given endpoints.  The two endpoints
890	 are Point objects in which both coordinates are 1-based: the
891	 first line is line 1, and the first character in the first line
892	 is character 1."
893
894	<category: 'position & lines'>
895	| bounds |
896	bounds := self from: first to: last.
897	self gtkbuffer selectRange: (bounds at: 1) bound: (bounds at: 2)
898    ]
899
900    setToEnd [
901	"Move the insertion point to the end of the widget"
902
903	<category: 'position & lines'>
904	self tclEval: '
905	%1 mark set insert end-1c
906	%1 see end'
907	    with: self connected
908    ]
909
910    addChild: child [
911	<category: 'private'>
912	self
913	    tclEval: '%1 window create end -window %2'
914	    with: self connected
915	    with: child container
916    ]
917
918    setAttributes: attr start: startTextIter end: endTextIter [
919	<category: 'private'>
920	| tags |
921	tagInfo isNil ifTrue: [tagInfo := BTextTags new: self].
922	tags := attr tags: tagInfo.
923	tags do:
924		[:each |
925		self gtkbuffer
926		    applyTag: each
927		    start: startTextIter
928		    end: endTextIter]
929    ]
930
931    gtkbuffer [
932	"answer the gtk text buffer"
933
934	<category: 'private'>
935	gtkbuffer isNil ifTrue: [self createWidget].
936	^gtkbuffer
937    ]
938
939    onChanged: userData data: unused [
940	<category: 'private'>
941	self invokeCallback
942    ]
943
944    create [
945	"initialise a Text widget"
946
947	<category: 'private'>
948	self connected: GTK.GtkTextView new.
949	gtkbuffer := self connected getBuffer.
950	self gtkbuffer
951	    connectSignal: 'changed'
952	    to: self
953	    selector: #onChanged:data:
954	    userData: nil
955    ]
956
957    defineTag: name as: options [
958	<category: 'private'>
959	options class = String
960	    ifTrue:
961		[options printNl.
962		0 unconverted defineTag call].
963	"FIXME/TODO: use g_object_set_property and recreate createTag"
964	self gtkbuffer createTag: name varargs: options
965    ]
966
967    destroyed [
968	<category: 'private'>
969	super destroyed.
970	images isNil ifTrue: [^self].
971	images do: [:name | self tclEval: 'image delete ' , name].
972	images := nil
973    ]
974
975    from: aPoint to: endPoint [
976	<category: 'private'>
977	| start end |
978	start := self gtkbuffer getIterAtLineOffset: aPoint y - 1
979		    charOffset: aPoint x - 1.
980	end := self gtkbuffer getIterAtLineOffset: endPoint y - 1
981		    charOffset: endPoint x - 1.
982	^
983	{start.
984	end}
985    ]
986
987    removeAttributesInside: range [
988	<category: 'private'>
989	| start end |
990	start := range at: 1.
991	end := range at: 2.
992	self gtkbuffer removeAllTags: start end: end
993    ]
994
995    tag: name bind: event to: aSymbol of: anObject parameters: params [
996	<category: 'private'>
997	self
998	    bind: event
999	    to: aSymbol
1000	    of: anObject
1001	    parameters: params
1002	    prefix: '%1 tag bind %2' %
1003			{self connected.
1004			name}
1005    ]
1006]
1007
1008
1009
1010BEventTarget subclass: BTextBindings [
1011    | list tagName |
1012
1013    <comment: 'This object is used to assign event handlers to particular sections of
1014text in a BText widget.  To use it, you simply have to add event handlers
1015to it, and then create a BTextAttributes object that refers to it.'>
1016    <category: 'Graphics-Windows'>
1017
1018    BTextBindings class >> new [
1019	"Create a new instance of the receiver."
1020
1021	<category: 'instance creation'>
1022	^self basicNew initialize
1023    ]
1024
1025    defineTagFor: aBText [
1026	<category: 'private - BTextTags protocol'>
1027	list do: [:each | each sendTo: aBText]
1028    ]
1029
1030    tagName [
1031	<category: 'private - BTextTags protocol'>
1032	^tagName
1033    ]
1034
1035    initialize [
1036	<category: 'private'>
1037	tagName := 'ev' , (Time millisecondClockValue printString: 36).
1038	list := OrderedCollection new
1039    ]
1040
1041    primBind: event to: aSymbol of: anObject parameters: params [
1042	<category: 'private'>
1043	| args |
1044	(args := Array new: 5)
1045	    at: 1 put: tagName;
1046	    at: 2 put: event;
1047	    at: 3 put: aSymbol;
1048	    at: 4 put: anObject;
1049	    at: 5 put: params.
1050	list add: (Message selector: #tag:bind:to:of:parameters: arguments: args)
1051    ]
1052]
1053
1054
1055
1056Object subclass: BTextAttributes [
1057    | bgColor fgColor font styles events |
1058
1059    <category: 'Graphics-Windows'>
1060    <comment: '
1061I help you creating wonderful, colorful BTexts.'>
1062
1063    BTextAttributes class >> backgroundColor: color [
1064	"Create a new BTextAttributes object resulting in text
1065	 with the given background color."
1066
1067	<category: 'instance-creation shortcuts'>
1068	^self new backgroundColor: color
1069    ]
1070
1071    BTextAttributes class >> black [
1072	"Create a new BTextAttributes object resulting in black text."
1073
1074	<category: 'instance-creation shortcuts'>
1075	^self new foregroundColor: 'black'
1076    ]
1077
1078    BTextAttributes class >> blue [
1079	"Create a new BTextAttributes object resulting in blue text."
1080
1081	<category: 'instance-creation shortcuts'>
1082	^self new foregroundColor: 'blue'
1083    ]
1084
1085    BTextAttributes class >> center [
1086	"Create a new BTextAttributes object resulting in centered
1087	 paragraphs."
1088
1089	<category: 'instance-creation shortcuts'>
1090	^self new center
1091    ]
1092
1093    BTextAttributes class >> cyan [
1094	"Create a new BTextAttributes object resulting in cyan text."
1095
1096	<category: 'instance-creation shortcuts'>
1097	^self new foregroundColor: 'cyan'
1098    ]
1099
1100    BTextAttributes class >> darkCyan [
1101	"Create a new BTextAttributes object resulting in dark cyan text."
1102
1103	<category: 'instance-creation shortcuts'>
1104	^self new foregroundColor: 'PureDarkCyan'
1105    ]
1106
1107    BTextAttributes class >> darkGreen [
1108	"Create a new BTextAttributes object resulting in dark green text."
1109
1110	<category: 'instance-creation shortcuts'>
1111	^self new foregroundColor: 'PureDarkGreen'
1112    ]
1113
1114    BTextAttributes class >> darkMagenta [
1115	"Create a new BTextAttributes object resulting in dark purple text."
1116
1117	<category: 'instance-creation shortcuts'>
1118	^self new foregroundColor: 'PureDarkMagenta'
1119    ]
1120
1121    BTextAttributes class >> events: aBTextBindings [
1122	"Create a new BTextAttributes object for text that responds to
1123	 events according to the callbacks established in aBTextBindings."
1124
1125	<category: 'instance-creation shortcuts'>
1126	^self new events: aBTextBindings
1127    ]
1128
1129    BTextAttributes class >> font: font [
1130	"Create a new BTextAttributes object resulting in text with the given font.
1131	 The font can be given as either an X font name or a Blox font description
1132	 string.
1133
1134	 X font names are given as many fields, each led by a minus, and each of
1135	 which can be replaced by an * to indicate a default value is ok:
1136	 foundry, family, weight, slant, setwidth, addstyle, pixel size, point size
1137	 (the same as pixel size for historical reasons), horizontal resolution,
1138	 vertical resolution, spacing, width, charset and character encoding.
1139
1140	 Blox font description strings have three fields, which must be separated by
1141	 a space and of which only the first is mandatory: the font family, the font
1142	 size in points (or in pixels if a negative value is supplied), and a number
1143	 of styles separated by a space (valid styles are normal, bold, italic,
1144	 underline and overstrike). Examples of valid fonts are ``Helvetica 10 Bold'',
1145	 ``Times -14'', ``Futura Bold Underline''.  You must enclose the font family
1146	 in braces if it is made of two or more words."
1147
1148	<category: 'instance-creation shortcuts'>
1149	^self new font: font
1150    ]
1151
1152    BTextAttributes class >> foregroundColor: color [
1153	"Create a new BTextAttributes object resulting in text
1154	 with the given foreground color."
1155
1156	<category: 'instance-creation shortcuts'>
1157	^self new foregroundColor: color
1158    ]
1159
1160    BTextAttributes class >> green [
1161	"Create a new BTextAttributes object resulting in green text."
1162
1163	<category: 'instance-creation shortcuts'>
1164	^self new foregroundColor: 'green'
1165    ]
1166
1167    BTextAttributes class >> magenta [
1168	"Create a new BTextAttributes object resulting in magenta text."
1169
1170	<category: 'instance-creation shortcuts'>
1171	^self new foregroundColor: 'magenta'
1172    ]
1173
1174    BTextAttributes class >> red [
1175	"Create a new BTextAttributes object resulting in red text."
1176
1177	<category: 'instance-creation shortcuts'>
1178	^self new foregroundColor: 'red'
1179    ]
1180
1181    BTextAttributes class >> strikeout [
1182	"Create a new BTextAttributes object resulting in struck-out text."
1183
1184	<category: 'instance-creation shortcuts'>
1185	^self new strikeout
1186    ]
1187
1188    BTextAttributes class >> underline [
1189	"Create a new BTextAttributes object resulting in underlined text."
1190
1191	<category: 'instance-creation shortcuts'>
1192	^self new underline
1193    ]
1194
1195    BTextAttributes class >> yellow [
1196	"Create a new BTextAttributes object resulting in yellow text."
1197
1198	<category: 'instance-creation shortcuts'>
1199	^self new foregroundColor: 'yellow'
1200    ]
1201
1202    BTextAttributes class >> white [
1203	"Create a new BTextAttributes object resulting in white text."
1204
1205	<category: 'instance-creation shortcuts'>
1206	^self new foregroundColor: 'white'
1207    ]
1208
1209    black [
1210	"Set the receiver so that applying it results in black text."
1211
1212	<category: 'colors'>
1213	self foregroundColor: 'black'
1214    ]
1215
1216    blue [
1217	"Set the receiver so that applying it results in blue text."
1218
1219	<category: 'colors'>
1220	self foregroundColor: 'blue'
1221    ]
1222
1223    cyan [
1224	"Set the receiver so that applying it results in cyan text."
1225
1226	<category: 'colors'>
1227	self foregroundColor: 'cyan'
1228    ]
1229
1230    darkCyan [
1231	"Set the receiver so that applying it results in dark cyan text."
1232
1233	<category: 'colors'>
1234	self foregroundColor: 'PureDarkCyan'
1235    ]
1236
1237    darkGreen [
1238	"Set the receiver so that applying it results in dark green text."
1239
1240	<category: 'colors'>
1241	self foregroundColor: 'PureDarkGreen'
1242    ]
1243
1244    darkMagenta [
1245	"Set the receiver so that applying it results in dark magenta text."
1246
1247	<category: 'colors'>
1248	self foregroundColor: 'PureDarkMagenta'
1249    ]
1250
1251    green [
1252	"Set the receiver so that applying it results in green text."
1253
1254	<category: 'colors'>
1255	self foregroundColor: 'green'
1256    ]
1257
1258    magenta [
1259	"Set the receiver so that applying it results in magenta text."
1260
1261	<category: 'colors'>
1262	self foregroundColor: 'magenta'
1263    ]
1264
1265    red [
1266	"Set the receiver so that applying it results in red text."
1267
1268	<category: 'colors'>
1269	self foregroundColor: 'red'
1270    ]
1271
1272    white [
1273	"Set the receiver so that applying it results in white text."
1274
1275	<category: 'colors'>
1276	self foregroundColor: 'white'
1277    ]
1278
1279    yellow [
1280	"Set the receiver so that applying it results in black text."
1281
1282	<category: 'colors'>
1283	self foregroundColor: 'yellow'
1284    ]
1285
1286    hasStyle: aSymbol [
1287	<category: 'private'>
1288	^styles notNil and: [styles includes: aSymbol]
1289    ]
1290
1291    style: aSymbol [
1292	<category: 'private'>
1293	styles isNil ifTrue: [styles := Set new].
1294	styles add: aSymbol
1295    ]
1296
1297    tags: aBTextTags [
1298	<category: 'private'>
1299	| s tagTable |
1300	tagTable := aBTextTags tagTable.
1301	s := OrderedCollection new.
1302	fgColor isNil
1303	    ifFalse: [s add: (tagTable lookup: (aBTextTags fgColor: fgColor))].
1304	bgColor isNil
1305	    ifFalse: [s add: (tagTable lookup: (aBTextTags bgColor: bgColor))].
1306	font isNil ifFalse: [s add: (tagTable lookup: (aBTextTags font: font))].
1307	events isNil
1308	    ifFalse: [s add: (tagTable lookup: (aBTextTags events: events))].
1309	styles isNil
1310	    ifFalse: [styles do: [:each | s add: (tagTable lookup: each)]].
1311	^s
1312    ]
1313
1314    backgroundColor [
1315	"Answer the value of the backgroundColor option for the text.
1316
1317	 Specifies the background color to use when displaying text with
1318	 these attributes.  nil indicates that the default value is not
1319	 overridden."
1320
1321	<category: 'setting attributes'>
1322	^bgColor
1323    ]
1324
1325    backgroundColor: color [
1326	"Set the value of the backgroundColor option for the text.
1327
1328	 Specifies the background color to use when displaying text with
1329	 these attributes.  nil indicates that the default value is not
1330	 overridden."
1331
1332	<category: 'setting attributes'>
1333	bgColor := color
1334    ]
1335
1336    center [
1337	"Center the text to which these attributes are applied"
1338
1339	<category: 'setting attributes'>
1340	self style: #STYLEcenter
1341    ]
1342
1343    events [
1344	"Answer the event bindings which apply to text subject to these
1345	 attributes"
1346
1347	<category: 'setting attributes'>
1348	^events
1349    ]
1350
1351    events: aBTextBindings [
1352	"Set the event bindings which apply to text subject to these
1353	 attributes"
1354
1355	<category: 'setting attributes'>
1356	events := aBTextBindings
1357    ]
1358
1359    font [
1360	"Answer the value of the font option for the text.
1361	 The font can be given as either an X font name or a Blox font description
1362	 string, or nil if you want the widget's default font to apply.
1363
1364	 X font names are given as many fields, each led by a minus, and each of
1365	 which can be replaced by an * to indicate a default value is ok:
1366	 foundry, family, weight, slant, setwidth, addstyle, pixel size, point size
1367	 (the same as pixel size for historical reasons), horizontal resolution,
1368	 vertical resolution, spacing, width, charset and character encoding.
1369
1370	 Blox font description strings have three fields, which must be separated by
1371	 a space and of which only the first is mandatory: the font family, the font
1372	 size in points (or in pixels if a negative value is supplied), and a number
1373	 of styles separated by a space (valid styles are normal, bold, italic,
1374	 underline and overstrike). Examples of valid fonts are ``Helvetica 10 Bold'',
1375	 ``Times -14'', ``Futura Bold Underline''.  You must enclose the font family
1376	 in braces if it is made of two or more words."
1377
1378	<category: 'setting attributes'>
1379	^font
1380    ]
1381
1382    font: fontName [
1383	"Set the value of the font option for the text.
1384	 The font can be given as either an X font name or a Blox font description
1385	 string, or nil if you want the widget's default font to apply.
1386
1387	 X font names are given as many fields, each led by a minus, and each of
1388	 which can be replaced by an * to indicate a default value is ok:
1389	 foundry, family, weight, slant, setwidth, addstyle, pixel size, point size
1390	 (the same as pixel size for historical reasons), horizontal resolution,
1391	 vertical resolution, spacing, width, charset and character encoding.
1392
1393	 Blox font description strings have three fields, which must be separated by
1394	 a space and of which only the first is mandatory: the font family, the font
1395	 size in points (or in pixels if a negative value is supplied), and a number
1396	 of styles separated by a space (valid styles are normal, bold, italic,
1397	 underline and overstrike). Examples of valid fonts are ``Helvetica 10 Bold'',
1398	 ``Times -14'', ``Futura Bold Underline''.  You must enclose the font family
1399	 in braces if it is made of two or more words."
1400
1401	<category: 'setting attributes'>
1402	font := fontName
1403    ]
1404
1405    foregroundColor [
1406	"Answer the value of the foregroundColor option for the text.
1407
1408	 Specifies the foreground color to use when displaying text with
1409	 these attributes.  nil indicates that the default value is not
1410	 overridden."
1411
1412	<category: 'setting attributes'>
1413	^fgColor
1414    ]
1415
1416    foregroundColor: color [
1417	"Set the value of the foregroundColor option for the text.
1418
1419	 Specifies the foreground color to use when displaying text with
1420	 these attributes.  nil indicates that the default value is not
1421	 overridden."
1422
1423	<category: 'setting attributes'>
1424	fgColor := color
1425    ]
1426
1427    isCentered [
1428	"Answer whether the text to which these attributes are applied
1429	 is centered"
1430
1431	<category: 'setting attributes'>
1432	^self hasStyle: #STYLEcenter
1433    ]
1434
1435    isStruckout [
1436	"Answer whether the text to which these attributes are applied
1437	 is struckout"
1438
1439	<category: 'setting attributes'>
1440	^self hasStyle: #STYLEstrikeout
1441    ]
1442
1443    isUnderlined [
1444	"Answer whether the text to which these attributes are applied
1445	 is underlined"
1446
1447	<category: 'setting attributes'>
1448	^self hasStyle: #STYLEunderline
1449    ]
1450
1451    strikeout [
1452	"Strike out the text to which these attributes are applied"
1453
1454	<category: 'setting attributes'>
1455	self style: #STYLEstrikeout
1456    ]
1457
1458    underline [
1459	"Underline the text to which these attributes are applied"
1460
1461	<category: 'setting attributes'>
1462	self style: #STYLEunderline
1463    ]
1464]
1465
1466
1467
1468Object subclass: BTextTags [
1469    | client tags |
1470
1471    <category: 'Graphics-Windows'>
1472    <comment: 'I am a private class. I sit between a BText and BTextAttributes, helping
1473the latter in telling the former which attributes to use.'>
1474
1475    BTextTags class >> new [
1476	<category: 'private - instance creation'>
1477	self shouldNotImplement
1478    ]
1479
1480    BTextTags class >> new: client [
1481	<category: 'private - instance creation'>
1482	^super new initialize: client
1483    ]
1484
1485    bgColor: color [
1486	<category: 'private - BTextAttributes protocol'>
1487	^'b_' , (self color: color)
1488    ]
1489
1490    events: aBTextBindings [
1491	<category: 'private - BTextAttributes protocol'>
1492	| tagName |
1493	tagName := aBTextBindings tagName.
1494	(tags includes: tagName)
1495	    ifFalse:
1496		[tags add: tagName.
1497		aBTextBindings defineTagFor: client].
1498	^tagName
1499    ]
1500
1501    fgColor: color [
1502	<category: 'private - BTextAttributes protocol'>
1503	^'f_' , (self color: color)
1504    ]
1505
1506    font: font [
1507	<category: 'private - BTextAttributes protocol'>
1508	| tagName |
1509	tagName := WriteStream on: (String new: 20).
1510	font substrings do:
1511		[:each |
1512		tagName
1513		    nextPutAll: each;
1514		    nextPut: $_].
1515	tagName := tagName contents.
1516	(tags includes: tagName)
1517	    ifFalse:
1518		[tags add: tagName.
1519		'FIXME fonts.. ' display.
1520		font printNl.
1521		client defineTag: tagName
1522		    as:
1523			{'font'.
1524			font.
1525			nil}].
1526	^tagName
1527    ]
1528
1529    color: color [
1530	<category: 'private'>
1531	| tagName |
1532	tagName := (color at: 1) = $#
1533		    ifTrue:
1534			[(color copy)
1535			    at: 1 put: $_;
1536			    yourself]
1537		    ifFalse: [color asLowercase].
1538	(tags includes: tagName)
1539	    ifFalse:
1540		[tags add: tagName.
1541		client defineTag: 'f_' , tagName
1542		    as:
1543			{'foreground'.
1544			color.
1545			nil}.
1546		client defineTag: 'b_' , tagName
1547		    as:
1548			{'background'.
1549			color.
1550			nil}].
1551	^tagName
1552    ]
1553
1554    initialize: clientBText [
1555	"initialise for use with clientBText"
1556
1557	<category: 'private'>
1558	client := clientBText.
1559	tags := Set new.
1560	client defineTag: 'STYLEstrikeout'
1561	    as:
1562		{'strikethrough'.
1563		true.
1564		nil}.
1565	client defineTag: 'STYLEunderline'
1566	    as:
1567		{'underline'.
1568		GTK.Pango pangoUnderlineSingle.
1569		nil}.
1570	client defineTag: 'STYLEcenter'
1571	    as:
1572		{'justification'.
1573		GTK.Gtk gtkJustifyCenter.
1574		nil}
1575    ]
1576
1577    tagTable [
1578	<category: 'private'>
1579	^client gtkbuffer getTagTable
1580    ]
1581]
1582
1583
1584
1585"-------------------------- BText class -----------------------------"
1586
1587
1588
1589"-------------------------- BTextBindings class -----------------------------"
1590
1591
1592
1593"-------------------------- BTextAttributes class -----------------------------"
1594
1595
1596
1597"-------------------------- BTextTags class -----------------------------"
1598
1599