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