1 /**
2  * Widget module.
3  *
4  * License:
5  *     MIT. See LICENSE for full details.
6  */
7 module tkd.widget.notebook;
8 
9 /**
10  * Imports.
11  */
12 import std.array;
13 import std.conv;
14 import tkd.element.uielement;
15 import tkd.image.image;
16 import tkd.image.imageposition;
17 import tkd.widget.common.height;
18 import tkd.widget.common.padding;
19 import tkd.widget.common.width;
20 import tkd.widget.widget;
21 
22 /**
23  * A notebook widget manages a collection of panes and displays a single one at 
24  * a time. Each pane is associated with a tab, which the user may select to 
25  * change the currently-displayed pane.
26  *
27  * Example:
28  * ---
29  * // The notebook must be created first.
30  * // See the constructor notes in the documentation.
31  * auto noteBook = new NoteBook();
32  *
33  * // The pane's widgets are contained within the frame.
34  * auto pane = new Frame(noteBook);
35  *
36  * noteBook.addTab("Text", pane)
37  * 	.pack();
38  * ---
39  *
40  * Common_Commands:
41  *     These are injected common commands that can also be used with this widget.
42  *     $(P
43  *         $(LINK2 ./common/height.html, Height) $(BR)
44  *         $(LINK2 ./common/padding.html, Padding) $(BR)
45  *         $(LINK2 ./common/width.html, Width) $(BR)
46  *     )
47  *
48  * Additional_Events:
49  *     Additional events that can also be bound to using the $(LINK2 ../element/uielement.html#UiElement.bind, bind) method.
50  *     $(P
51  *         <<NotebookTabChanged>>,
52  *         <<PrevWindow>>,
53  *         <Alt-Key>,
54  *         <Button-1>,
55  *         <Control-Key-ISO_Left_Tab>,
56  *         <Control-Key-Tab>,
57  *         <Control-Shift-Key-Tab>,
58  *         <Destroy>,
59  *         <Key-F10>,
60  *         <Key-Left>,
61  *         <Key-Right>,
62  *         <Key-Tab>,
63  *     )
64  *
65  * See_Also:
66  *     $(LINK2 ./widget.html, tkd.widget.widget)
67  */
68 class NoteBook : Widget
69 {
70 	/**
71 	 * Construct the widget.
72 	 *
73 	 * Params:
74 	 *     parent = The parent of this widget.
75 	 *
76 	 * Bugs:
77 	 *     Because this widget contains and handles other widget's geometry, it 
78 	 *     must be created before the child panes and not chained with methods 
79 	 *     that add new tabs. If it is chained, tabs will not be handled 
80 	 *     correctly and might not show at all. This seems to be a limitation 
81 	 *     with Tcl/Tk.
82 	 *
83 	 * See_Also:
84 	 *     $(LINK2 ../element/uielement.html, tkd.element.UiElement) $(BR)
85 	 */
86 	public this(UiElement parent = null)
87 	{
88 		super(parent);
89 		this._elementId = "notebook";
90 
91 		this._tk.eval("ttk::notebook %s", this.id);
92 	}
93 
94 	/**
95 	 * Add a tab to the notebook. When adding a tab to the notebook the tab 
96 	 * gains an id that is equal to the passed widget's id and can be used 
97 	 * later to refer to the new tab.
98 	 *
99 	 * Params:
100 	 *     text = The text of the tab.
101 	 *     widget = The widget to add as the tab pane.
102 	 *
103 	 * Returns:
104 	 *     This widget to aid method chaining.
105 	 */
106 	public auto addTab(this T)(string text, Widget widget)
107 	{
108 		this.insertTab("end", text, widget);
109 
110 		return cast(T) this;
111 	}
112 
113 	/**
114 	 * Insert a tab into the notebook at a specified zero based index. When 
115 	 * adding a tab to the notebook the tab gains an id that is equal to the 
116 	 * passed widget's id and can be used later to refer to the new tab. If the 
117 	 * id of the widget passed is already used as a tab id then that existing 
118 	 * one will be moved to the new position.
119 	 *
120 	 * Params:
121 	 *     tabIdentifier = The zero based index or string id of the tab.
122 	 *     text = The text of the tab.
123 	 *     widget = The widget to add as the tab pane.
124 	 *
125 	 * Returns:
126 	 *     This widget to aid method chaining.
127 	 */
128 	public auto insertTab(this T, I)(I tabIdentifier, string text, Widget widget) if (is(I == int) || is(I == string))
129 	{
130 		// String concatenation is used to build the script here instead of 
131 		// using format specifiers to enable supporting input which includes 
132 		// Tcl/Tk reserved characters and elements that could be construed as 
133 		// format specifiers.
134 		string script = std.conv.text(this.id, ` insert `, tabIdentifier, ` `, widget.id, ` -text "`, this._tk.escape(text), `"`);
135 		this._tk.eval(script);
136 
137 		return cast(T) this;
138 	}
139 
140 	/**
141 	 * Select a tab in the notebook.
142 	 *
143 	 * Params:
144 	 *     tabIdentifier = The zero based index or string id of the tab.
145 	 *
146 	 * Returns:
147 	 *     This widget to aid method chaining.
148 	 */
149 	public auto selectTab(this T, I)(I tabIdentifier) if (is(I == int) || is(I == string))
150 	{
151 		this._tk.eval("%s select %s", this.id, tabIdentifier);
152 
153 		return cast(T) this;
154 	}
155 
156 	/**
157 	 * Remove a tab from the notbook.
158 	 *
159 	 * Params:
160 	 *     tabIdentifier = The zero based index or string id of the tab.
161 	 *
162 	 * Returns:
163 	 *     This widget to aid method chaining.
164 	 */
165 	public auto removeTab(this T, I)(I tabIdentifier) if (is(I == int) || is(I == string))
166 	{
167 		this._tk.eval("%s forget %s", this.id, tabIdentifier);
168 
169 		return cast(T) this;
170 	}
171 
172 	/**
173 	 * Hide a tab from the notbook.
174 	 *
175 	 * Params:
176 	 *     tabIdentifier = The zero based index or string id of the tab.
177 	 *
178 	 * Returns:
179 	 *     This widget to aid method chaining.
180 	 */
181 	public auto hideTab(this T, I)(I tabIdentifier) if (is(I == int) || is(I == string))
182 	{
183 		this._tk.eval("%s hide %s", this.id, tabIdentifier);
184 
185 		return cast(T) this;
186 	}
187 
188 	/**
189 	 * Set a tab's state.
190 	 *
191 	 * Params:
192 	 *     tabIdentifier = The zero based index or string id of the tab.
193 	 *     state = A widget state.
194 	 *
195 	 * Returns:
196 	 *     This widget to aid method chaining.
197 	 *
198 	 * See_Also:
199 	 *     $(LINK2 ./state.html, tkd.widget.state) for states.
200 	 */
201 	public auto setTabState(this T, I)(I tabIdentifier, string state) if (is(I == int) || is(I == string))
202 	{
203 		this._tk.eval("%s tab %s -state %s", this.id, tabIdentifier, state);
204 
205 		return cast(T) this;
206 	}
207 
208 	/**
209 	 * Set a tab pane's sticky state. Specifies how the slave widget is 
210 	 * positioned within the pane area. Sticky state is a string containing 
211 	 * zero or more of the characters n, s, e, or w. Each letter refers to a 
212 	 * side (north, south, east, or west) that the slave window will "stick" 
213 	 * to, as per the grid geometry manager.
214 	 *
215 	 * Params:
216 	 *     tabIdentifier = The zero based index or string id of the tab.
217 	 *     stickyState = A widget state.
218 	 *
219 	 * Returns:
220 	 *     This widget to aid method chaining.
221 	 */
222 	public auto setPaneStickyState(this T, I)(I tabIdentifier, string stickyState) if (is(I == int) || is(I == string))
223 	{
224 		this._tk.eval("%s tab %s -sticky %s", this.id, tabIdentifier, stickyState);
225 
226 		return cast(T) this;
227 	}
228 
229 	/**
230 	 * Set a tab pane's padding.
231 	 *
232 	 * Params:
233 	 *     tabIdentifier = The zero based index or string id of the tab.
234 	 *     padding = The desired widget padding.
235 	 *
236 	 * Returns:
237 	 *     This widget to aid method chaining.
238 	 */
239 	public auto setPanePadding(this T, I)(I tabIdentifier, int padding) if (is(I == int) || is(I == string))
240 	{
241 		this._tk.eval("%s tab %s -padding %s", this.id, tabIdentifier, padding);
242 
243 		return cast(T) this;
244 	}
245 
246 	/**
247 	 * Set a tab's text.
248 	 *
249 	 * Params:
250 	 *     tabIdentifier = The zero based index or string id of the tab.
251 	 *     text = The tab text.
252 	 *
253 	 * Returns:
254 	 *     This widget to aid method chaining.
255 	 */
256 	public auto setTabText(this T, I)(I tabIdentifier, string text) if (is(I == int) || is(I == string))
257 	{
258 		// String concatenation is used to build the script here instead of 
259 		// using format specifiers to enable supporting input which includes 
260 		// Tcl/Tk reserved characters and elements that could be construed as 
261 		// format specifiers.
262 		string script = std.conv.text(this.id, ` tab `, tabIdentifier, ` -text "`, this._tk.escape(text), `"`);
263 		this._tk.eval(script);
264 
265 		return cast(T) this;
266 	}
267 
268 	/**
269 	 * Set a tab's image.
270 	 *
271 	 * Params:
272 	 *     tabIdentifier = The zero based index or string id of the tab.
273 	 *     image = The image to set on the widget.
274 	 *     imagePosition = The position of the image relative to the text.
275 	 *
276 	 * Returns:
277 	 *     This widget to aid method chaining.
278 	 *
279 	 * See_Also:
280 	 *     $(LINK2 ../image/image.html, tkd.image.image) $(BR)
281 	 *     $(LINK2 ../image/png.html, tkd.image.png) $(BR)
282 	 *     $(LINK2 ../image/gif.html, tkd.image.gif) $(BR)
283 	 *     $(LINK2 ../image/imageposition.html, tkd.image.imageposition) $(BR)
284 	 */
285 	public auto setTabImage(this T, I)(I tabIdentifier, Image image, string imagePosition = ImagePosition.image) if (is(I == int) || is(I == string))
286 	{
287 		this._tk.eval("%s tab %s -image %s", this.id, tabIdentifier, image.id);
288 		this.setTabImagePosition(tabIdentifier, imagePosition);
289 
290 		return cast(T) this;
291 	}
292 
293 	/**
294 	 * Change the position of the tab image in relation to the text.
295 	 *
296 	 * Params:
297 	 *     tabIdentifier = The zero based index or string id of the tab.
298 	 *     imagePosition = The position of the image relative to the text.
299 	 *
300 	 * Returns:
301 	 *     This widget to aid method chaining.
302 	 *
303 	 * See_Also:
304 	 *     $(LINK2 ../image/imageposition.html, tkd.image.imageposition)
305 	 */
306 	public auto setTabImagePosition(this T, I)(I tabIdentifier, string imagePosition) if (is(I == int) || is(I == string))
307 	{
308 		this._tk.eval("%s tab %s -compound %s", this.id, tabIdentifier, imagePosition);
309 
310 		return cast(T) this;
311 	}
312 
313 	/**
314 	 * Underline a character in the tab text. The underlined character is used 
315 	 * for mnemonic activation if keyboard traversal is enabled.
316 	 *
317 	 * Params:
318 	 *     tabIdentifier = The zero based index or string id of the tab.
319 	 *     index = The index of the character to underline.
320 	 *
321 	 * Returns:
322 	 *     This widget to aid method chaining.
323 	 *
324 	 * See_Also:
325 	 *     $(LINK2 ../image/imageposition.html, tkd.image.imageposition) $(BR)
326 	 *     $(LINK2 ./notebook.html#NoteBook.enableKeyboardTraversal, enableKeyboardTraversal) $(BR)
327 	 */
328 	public auto underlineTabChar(this T, I)(I tabIdentifier, int index) if (is(I == int) || is(I == string))
329 	{
330 		this._tk.eval("%s tab %s -underline %s", this.id, tabIdentifier, index);
331 
332 		return cast(T) this;
333 	}
334 
335 	/**
336 	 * Call to enable keyboard traversal of the tabs.
337 	 *
338 	 * This will extend the bindings for the toplevel window containing the notebook as follows:
339 	 * $(UL
340 	 *     $(LI Control-Tab selects the tab following the currently selected one.)
341 	 *     $(LI Control-Shift-Tab selects the tab preceding the currently selected one.)
342 	 *     $(LI Alt-c, where c is the mnemonic (underlined) character of any tab, will select that tab.)
343 	 * )
344 	 * Multiple notebooks in a single window may be enabled for traversal, 
345 	 * including nested notebooks. However, notebook traversal only works 
346 	 * properly if all widget panes are direct children of the notebook.
347 	 *
348 	 * Returns:
349 	 *     This widget to aid method chaining.
350 	 *
351 	 * See_Also:
352 	 *     $(LINK2 ./notebook.html#NoteBook.underlineTabChar, underlineTabChar) $(BR)
353 	 */
354 	public auto enableKeyboardTraversal(this T)()
355 	{
356 		this._tk.eval("ttk::notebook::enableTraversal %s", this.id);
357 
358 		return cast(T) this;
359 	}
360 
361 	/**
362 	 * Get an array of all the current tab id's.
363 	 *
364 	 * Returns:
365 	 *     An array containing all the tab id's.
366 	 */
367 	public string[] getTabIds()
368 	{
369 		this._tk.eval("%s tabs", this.id);
370 		return this._tk.getResult!(string).split();
371 	}
372 
373 	/**
374 	 * Get the tab index from its id. The id is the widget id that was added as 
375 	 * the tab.
376 	 *
377 	 * Params:
378 	 *     tabId = The tab id of the tab.
379 	 */
380 	public int getTabIndexById(string tabId)
381 	{
382 		this._tk.eval("%s index %s", this.id, tabId);
383 		return this._tk.getResult!(int);
384 	}
385 
386 	/**
387 	 * Get the number of tabs in the notebook.
388 	 *
389 	 * Returns:
390 	 *     The number of tabs.
391 	 */
392 	public int getNumberOfTabs()
393 	{
394 		return this.getTabIndexById("end");
395 	}
396 
397 	/**
398 	 * Mixin common commands.
399 	 */
400 	mixin Height;
401 	mixin Padding;
402 	mixin Width;
403 }