1 /**
2  * Widget module.
3  *
4  * License:
5  *     MIT. See LICENSE for full details.
6  */
7 module tkd.widget.text;
8 
9 /**
10  * Imports.
11  */
12 import std.conv;
13 import tkd.element.uielement;
14 import tkd.image.image;
15 import tkd.widget.common.border;
16 import tkd.widget.common.height;
17 import tkd.widget.common.relief;
18 import tkd.widget.common.width;
19 import tkd.widget.common.xscrollcommand;
20 import tkd.widget.common.xview;
21 import tkd.widget.common.yscrollcommand;
22 import tkd.widget.common.yview;
23 import tkd.widget.textwrapmode;
24 import tkd.widget.widget;
25 
26 /**
27  * A text widget displays one or more lines of text and allows that text to be 
28  * edited. Text widgets support embedded widgets or embedded images.
29  *
30  * Example:
31  * ---
32  * auto text = new Text()
33  * 	.appendText("Text")
34  * 	.pack();
35  * ---
36  *
37  * Common_Commands:
38  *     These are injected common commands that can also be used with this widget.
39  *     $(P
40  *         $(LINK2 ./common/border.html, Border) $(BR)
41  *         $(LINK2 ./common/height.html, Height) $(BR)
42  *         $(LINK2 ./common/relief.html, Relief) $(BR)
43  *         $(LINK2 ./common/width.html, Width) $(BR)
44  *         $(LINK2 ./common/xscrollcommand.html, XScrollCommand) $(BR)
45  *         $(LINK2 ./common/xview.html, XView) $(BR)
46  *         $(LINK2 ./common/yscrollcommand.html, YScrollCommand) $(BR)
47  *         $(LINK2 ./common/yview.html, YView) $(BR)
48  *     )
49  *
50  * Additional_Events:
51  *     Additional events that can also be bound to using the $(LINK2 ../element/uielement.html#UiElement.bind, bind) method.
52  *     $(P
53  *         <<Clear>>,
54  *         <<Copy>>,
55  *         <<Cut>>,
56  *         <<Modified>>,
57  *         <<Paste>>,
58  *         <<PasteSelection>>,
59  *         <<PrevWindow>>,
60  *         <<Redo>>,
61  *         <<Selection>>,
62  *         <<Undo>>,
63  *         <Alt-Key>,
64  *         <B1-Enter>,
65  *         <B1-Leave>,
66  *         <B1-Motion>,
67  *         <B2-Motion>,
68  *         <Button-1>,
69  *         <Button-2>,
70  *         <Button-4>,
71  *         <Button-5>,
72  *         <ButtonRelease-1>,
73  *         <Control-Button-1>,
74  *         <Control-Key-Down>,
75  *         <Control-Key-End>,
76  *         <Control-Key-Home>,
77  *         <Control-Key-Left>,
78  *         <Control-Key-Next>,
79  *         <Control-Key-Prior>,
80  *         <Control-Key-Right>,
81  *         <Control-Key-Tab>,
82  *         <Control-Key-Up>,
83  *         <Control-Key-a>,
84  *         <Control-Key-b>,
85  *         <Control-Key-backslash>,
86  *         <Control-Key-d>,
87  *         <Control-Key-e>,
88  *         <Control-Key-f>,
89  *         <Control-Key-h>,
90  *         <Control-Key-i>,
91  *         <Control-Key-k>,
92  *         <Control-Key-n>,
93  *         <Control-Key-o>,
94  *         <Control-Key-p>,
95  *         <Control-Key-slash>,
96  *         <Control-Key-space>,
97  *         <Control-Key-t>,
98  *         <Control-Key>,
99  *         <Control-Shift-Key-Down>,
100  *         <Control-Shift-Key-End>,
101  *         <Control-Shift-Key-Home>,
102  *         <Control-Shift-Key-Left>,
103  *         <Control-Shift-Key-Right>,
104  *         <Control-Shift-Key-Tab>,
105  *         <Control-Shift-Key-Up>,
106  *         <Control-Shift-Key-space>,
107  *         <Double-Button-1>,
108  *         <Double-Shift-Button-1>,
109  *         <Key-BackSpace>,
110  *         <Key-Delete>,
111  *         <Key-Down>,
112  *         <Key-End>,
113  *         <Key-Escape>,
114  *         <Key-F10>,
115  *         <Key-Home>,
116  *         <Key-Insert>,
117  *         <Key-KP_Enter>,
118  *         <Key-Left>,
119  *         <Key-Next>,
120  *         <Key-Prior>,
121  *         <Key-Return>,
122  *         <Key-Right>,
123  *         <Key-Select>,
124  *         <Key-Tab>,
125  *         <Key-Up>,
126  *         <Key>,
127  *         <Meta-Key-BackSpace>,
128  *         <Meta-Key-Delete>,
129  *         <Meta-Key-b>,
130  *         <Meta-Key-d>,
131  *         <Meta-Key-f>,
132  *         <Meta-Key-greater>,
133  *         <Meta-Key-less>,
134  *         <Meta-Key>,
135  *         <MouseWheel>,
136  *         <Shift-Button-1>,
137  *         <Shift-Key-Down>,
138  *         <Shift-Key-End>,
139  *         <Shift-Key-Home>,
140  *         <Shift-Key-Left>,
141  *         <Shift-Key-Next>,
142  *         <Shift-Key-Prior>,
143  *         <Shift-Key-Right>,
144  *         <Shift-Key-Select>,
145  *         <Shift-Key-Tab>,
146  *         <Shift-Key-Up>,
147  *         <Triple-Button-1>,
148  *         <Triple-Shift-Button-1>,
149  *     )
150  *
151  * See_Also:
152  *     $(LINK2 ./widget.html, tkd.widget.widget)
153  */
154 class Text : Widget, IXScrollable!(Text), IYScrollable!(Text)
155 {
156 	/**
157 	 * Construct the widget.
158 	 *
159 	 * Params:
160 	 *     parent = The parent of this widget.
161 	 *
162 	 * See_Also:
163 	 *     $(LINK2 ../element/uielement.html, tkd.element.UiElement) $(BR)
164 	 */
165 	public this(UiElement parent = null)
166 	{
167 		super(parent);
168 		this._elementId = "text";
169 
170 		this._tk.eval("text %s -highlightthickness 0", this.id);
171 
172 		this.setUndoSupport(true);
173 		this.setUndoLevels(25);
174 		this.setWrapMode(TextWrapMode.word);
175 	}
176 
177 	/**
178 	 * Set the amount of padding in the text widget.
179 	 *
180 	 * Params:
181 	 *     padding = The amount of padding in the text widget.
182 	 *
183 	 * Returns:
184 	 *     This widget to aid method chaining.
185 	 */
186 	public auto setPadding(this T)(int padding)
187 	{
188 		this._tk.eval("%s configure -padx %s -pady %s", this.id, padding, padding);
189 
190 		return cast(T) this;
191 	}
192 
193 	/**
194 	 * Set if the widget is readonly or not.
195 	 *
196 	 * Params:
197 	 *     readOnly = Flag to toggle readonly state.
198 	 *
199 	 * Returns:
200 	 *     This widget to aid method chaining.
201 	 */
202 	public auto setReadOnly(this T)(bool readOnly = true)
203 	{
204 		if(readOnly)
205 		{
206 			this._tk.eval("%s configure -state disabled", this.id);
207 		}
208 		else
209 		{
210 			this._tk.eval("%s configure -state normal", this.id);
211 		}
212 
213 		return cast(T) this;
214 	}
215 
216 	/**
217 	 * Enable or disable undo support.
218 	 *
219 	 * Params:
220 	 *     enable = True to enable undo support, false to disable it.
221 	 *
222 	 * Returns:
223 	 *     This widget to aid method chaining.
224 	 */
225 	public auto setUndoSupport(this T)(bool enable)
226 	{
227 		this._tk.eval("%s configure -undo %s", this.id, enable);
228 
229 		return cast(T) this;
230 	}
231 
232 	/**
233 	 * Set the number of undo levels the widget will support.
234 	 *
235 	 * Params:
236 	 *     undoLevels = The number of undo levels the widget will support.
237 	 *
238 	 * Returns:
239 	 *     This widget to aid method chaining.
240 	 */
241 	public auto setUndoLevels(this T)(int undoLevels)
242 	{
243 		this._tk.eval("%s configure -maxundo %s", this.id, undoLevels);
244 
245 		return cast(T) this;
246 	}
247 
248 	/**
249 	 * Set the wrap mode of the text.
250 	 *
251 	 * Params:
252 	 *     mode = The mode to wrap the text with.
253 	 *
254 	 * Returns:
255 	 *     This widget to aid method chaining.
256 	 *
257 	 * See_Also:
258 	 *     $(LINK2 ./textwrapmode.html, tkd.widget.textwrapmode)
259 	 */
260 	public auto setWrapMode(this T)(string mode)
261 	{
262 		this._tk.eval("%s configure -wrap %s", this.id, mode);
263 
264 		return cast(T) this;
265 	}
266 
267 	/**
268 	 * Appends text to the widget.
269 	 *
270 	 * Params:
271 	 *     text = The text to append.
272 	 *
273 	 * Returns:
274 	 *     This widget to aid method chaining.
275 	 */
276 	public auto appendText(this T)(string text)
277 	{
278 		// String concatenation is used to build the script here instead of 
279 		// using format specifiers to enable supporting input which includes 
280 		// Tcl/Tk reserved characters and elements that could be construed as 
281 		// format specifiers.
282 		string script = std.conv.text(this.id, ` insert end "`, this._tk.escape(text), `"`);
283 		this._tk.eval(script);
284 
285 		return cast(T) this;
286 	}
287 
288 	/**
289 	 * Inserts text into the widget at a specified line and character index.
290 	 *
291 	 * Params:
292 	 *     line = The line at which to insert the text. Indexes start at 1.
293 	 *     character = The character at which to insert the text. Indexes start at 0.
294 	 *     text = The text to insert.
295 	 *
296 	 * Returns:
297 	 *     This widget to aid method chaining.
298 	 */
299 	public auto insertText(this T)(int line, int character, string text)
300 	{
301 		// String concatenation is used to build the script here instead of 
302 		// using format specifiers to enable supporting input which includes 
303 		// Tcl/Tk reserved characters and elements that could be construed as 
304 		// format specifiers.
305 		string script = std.conv.text(this.id, ` insert `, line, `.`, character, ` "`, this._tk.escape(text), `"`);
306 		this._tk.eval(script);
307 
308 		return cast(T) this;
309 	}
310 
311 	/**
312 	 * Get the text from the widget.
313 	 *
314 	 * Returns:
315 	 *     The text from the widget.
316 	 */
317 	public string getText(this T)()
318 	{
319 		this._tk.eval("%s get 0.0 end", this.id);
320 
321 		return this._tk.getResult!(string);
322 	}
323 
324 	/**
325 	 * Delete text from the widget.
326 	 *
327 	 * Params:
328 	 *     fromLine = The line from which to start deleting. Indexes start at 1.
329 	 *     fromChar = The character from which to start deleting. Indexes start at 0.
330 	 *     toLine = The line up to (but not including) which to delete. Indexes start at 1.
331 	 *     toChar = The character up to (but not including) which to delete. Indexes start at 0.
332 	 *
333 	 * Returns:
334 	 *     This widget to aid method chaining.
335 	 */
336 	public auto deleteText(this T)(int fromLine, int fromChar, int toLine, int toChar)
337 	{
338 		this._tk.eval("%s delete %s.%s %s.%s", this.id, fromLine, fromChar, toLine, toChar);
339 
340 		return cast(T) this;
341 	}
342 
343 	/**
344 	 * Delete all content from the widget.
345 	 *
346 	 * Returns:
347 	 *     This widget to aid method chaining.
348 	 */
349 	public auto clear(this T)()
350 	{
351 		this._tk.eval("%s delete 0.0 end", this.id);
352 
353 		return cast(T) this;
354 	}
355 
356 	/**
357 	 * Embed a widget into the text.
358 	 *
359 	 * Params:
360 	 *     line = The line at which to insert the text. Indexes start at 1.
361 	 *     character = The character at which to insert the text. Indexes start at 0.
362 	 *     widget = The widget to embed.
363 	 *     padding = The amount of padding around the widget.
364 	 *
365 	 * Returns:
366 	 *     This widget to aid method chaining.
367 	 */
368 	public auto embedWidget(this T)(int line, int character, Widget widget, int padding = 0)
369 	{
370 		this._tk.eval("%s window create %s.%s -window %s -align center -padx %s -pady %s", this.id, line, character, widget.id, padding, padding);
371 
372 		return cast(T) this;
373 	}
374 
375 	/**
376 	 * Embed an image into the text.
377 	 *
378 	 * Params:
379 	 *     line = The line at which to insert the text. Indexes start at 1.
380 	 *     character = The character at which to insert the text. Indexes start at 0.
381 	 *     image = The image to embed.
382 	 *     padding = The amount of padding around the widget.
383 	 *
384 	 * Returns:
385 	 *     This widget to aid method chaining.
386 	 */
387 	public auto embedImage(this T)(int line, int character, Image image, int padding = 0)
388 	{
389 		this._tk.eval("%s image create %s.%s -image %s -align center -padx %s -pady %s", this.id, line, character, image.id, padding, padding);
390 
391 		return cast(T) this;
392 	}
393 
394 	/**
395 	 * Undo the last edit to the widget. This only applied to the widget if 
396 	 * undo is enabled.
397 	 *
398 	 * Returns:
399 	 *     This widget to aid method chaining.
400 	 */
401 	public auto undo(this T)()
402 	{
403 		this._tk.eval("%s edit undo", this.id);
404 
405 		return cast(T) this;
406 	}
407 
408 	/**
409 	 * Redo the last edit to the widget. This only applied to the widget if 
410 	 * undo is enabled.
411 	 *
412 	 * Returns:
413 	 *     This widget to aid method chaining.
414 	 */
415 	public auto redo(this T)()
416 	{
417 		this._tk.eval("%s edit redo", this.id);
418 
419 		return cast(T) this;
420 	}
421 
422 	/**
423 	 * Clear all undo's. This only applied to the widget if 
424 	 * undo is enabled.
425 	 *
426 	 * Returns:
427 	 *     This widget to aid method chaining.
428 	 */
429 	public auto resetUndo(this T)()
430 	{
431 		this._tk.eval("%s edit reset", this.id);
432 
433 		return cast(T) this;
434 	}
435 
436 	/**
437 	 * See a particular text index. The text widget automatically scrolls to 
438 	 * see the passed indexes.
439 	 *
440 	 * Params:
441 	 *     line = The line to see. Indexes start at 1.
442 	 *     character = The character to see. Indexes start at 0.
443 	 *
444 	 * Returns:
445 	 *     This widget to aid method chaining.
446 	 */
447 	public auto seeText(this T)(int line, int character = 0)
448 	{
449 		this._tk.eval("%s see %s.%s", this.id, line, character);
450 
451 		return cast(T) this;
452 	}
453 
454 	/**
455 	 * Cut the selected text to the clipboard.
456 	 *
457 	 * Returns:
458 	 *     This widget to aid method chaining.
459 	 */
460 	public auto cutText(this T)()
461 	{
462 		this._tk.eval("tk_textCut %s", this.id);
463 
464 		return cast(T) this;
465 	}
466 
467 	/**
468 	 * Copy the selected text to the clipboard.
469 	 *
470 	 * Returns:
471 	 *     This widget to aid method chaining.
472 	 */
473 	public auto copyText(this T)()
474 	{
475 		this._tk.eval("tk_textCopy %s", this.id);
476 
477 		return cast(T) this;
478 	}
479 
480 	/**
481 	 * Paste the selected text from the clipboard at the cursor position.
482 	 *
483 	 * Returns:
484 	 *     This widget to aid method chaining.
485 	 */
486 	public auto pasteText(this T)()
487 	{
488 		this._tk.eval("tk_textPaste %s", this.id);
489 
490 		return cast(T) this;
491 	}
492 
493 	/**
494 	 * Mixin common commands.
495 	 */
496 	mixin Border;
497 	mixin Height;
498 	mixin Relief;
499 	mixin Width;
500 	mixin XScrollCommand!(Text);
501 	mixin XView;
502 	mixin YScrollCommand!(Text);
503 	mixin YView;
504 }