1 /**
2  * TkApplication module.
3  *
4  * License:
5  *     MIT. See LICENSE for full details.
6  */
7 module tkd.tkdapplication;
8 
9 /**
10  * Private imports.
11  */
12 import std.file : chdir, thisExePath;
13 import std.path : dirName;
14 import std.regex : match;
15 import std..string;
16 import tkd.interpreter;
17 
18 /**
19  * Public imports.
20  */
21 public import tkd.element;
22 public import tkd.image;
23 public import tkd.widget;
24 public import tkd.window;
25 
26 /**
27  * Base class of all Tkd gui applications.
28  *
29  * Example:
30  * ---
31  * import tkd.tkdapplication;
32  *
33  * class Application : TkdApplication
34  * {
35  * 	private void exitCommand(CommandArgs args)
36  * 	{
37  * 		this.exit();
38  * 	}
39  * 
40  * 	override protected void initInterface()
41  * 	{
42  * 		auto frame = new Frame(2, ReliefStyle.groove)
43  * 			.pack(10);
44  * 
45  * 		auto label = new Label(frame, "Hello World!")
46  * 			.pack(10);
47  * 
48  * 		auto exitButton = new Button(frame, "Exit")
49  * 			.setCommand(&this.exitCommand)
50  * 			.pack(10);
51  * 	}
52  * }
53  * 
54  * void main(string[] args)
55  * {
56  * 	auto app = new Application();
57  * 	app.run();
58  * }
59  * ---
60  */
61 abstract class TkdApplication
62 {
63 	/**
64 	 * The Tk interpreter.
65 	 */
66 	private Tk _tk;
67 
68 	/**
69 	 * The main window of the application.
70 	 */
71 	private Window _mainWindow;
72 
73 	/**
74 	 * constructor.
75 	 *
76 	 * Throws:
77 	 *     Exception if Tcl/Tk interpreter cannot be initialised.
78 	 */
79 	public this()
80 	{
81 		// On Windows the Tcl/Tk library folder might be included in the 
82 		// executable's directory to make the entire application self-contained 
83 		// (using a local library and Tcl/Tk DLL's). So always set the current 
84 		// working directory to that of the executable's. Tcl/Tk should then 
85 		// always find it if it's there.
86 		version (Windows)
87 		{
88 			thisExePath().dirName().chdir();
89 		}
90 
91 		this._tk = Tk.getInstance();
92 
93 		// Use UTF-8 encoding throught the entire application.
94 		this._tk.eval("encoding system utf-8");
95 
96 		// Fix to remove hard-coded background colors from widgets.
97 		version (Windows)
98 		{
99 			this._tk.eval("ttk::style configure TEntry -fieldbackground {SystemWindow}");
100 			this._tk.eval("ttk::style configure TSpinbox -fieldbackground {SystemWindow}");
101 			this._tk.eval("ttk::style configure Treeview -fieldbackground {SystemWindow}");
102 		}
103 
104 		this._mainWindow = Window.getMainWindow();
105 		this.initInterface();
106 	}
107 
108 	/**
109 	 * Get the main window of the application.
110 	 *
111 	 * Returns:
112 	 *     The main window.
113 	 */
114 	public @property Window mainWindow()
115 	{
116 		return this._mainWindow;
117 	}
118 
119 	/**
120 	 * Set the overall theme of the user interface.
121 	 *
122 	 * Params:
123 	 *     theme = The theme to use.
124 	 *
125 	 * See_also:
126 	 *     $(LINK2 ./theme.html, tkd.theme) $(BR)
127 	 */
128 	public void setTheme(string theme)
129 	{
130 		this._tk.eval("ttk::style theme use %s", theme);
131 	}
132 
133 	/**
134 	 * Run the application.
135 	 */
136 	public void run()
137 	{
138 		this._tk.run();
139 	}
140 
141 	/**
142 	 * This method is used to bring the application 'up to date' by entering 
143 	 * the event loop repeatedly until all pending events (including idle 
144 	 * callbacks) have been processed.
145 	 *
146 	 * Returns:
147 	 *     This widget to aid method chaining.
148 	 */
149 	public auto update(this T)()
150 	{
151 		this._tk.eval("update");
152 
153 		return cast(T) this;
154 	}
155 
156 	/**
157 	 * Exit the application.
158 	 */
159 	public void exit()
160 	{
161 		this.mainWindow.destroy();
162 	}
163 
164 	/**
165 	 * Associates the virtual event with the binding, so that the virtual event 
166 	 * will trigger whenever the binding occurs. Virtual events may be any 
167 	 * string value and binding may have any of the values allowed for the 
168 	 * binding argument of the $(LINK2 ./element/uielement.html#UiElement.bind, 
169 	 * bind) method. If the virtual event is already defined, the new binding 
170 	 * adds to the existing bindings for the event.
171 	 *
172 	 * Params:
173 	 *     virtualEvent = The virtual event to create.
174 	 *     binding = The binding that triggers this event.
175 	 *
176 	 * See_Also:
177 	 *     $(LINK2 ./element/uielement.html, tkd.element.uielement)
178 	 */
179 	public void addVirtualEvent(this T)(string virtualEvent, string binding)
180 	{
181 		assert(!std.regex.match(virtualEvent, r"^<<.*?>>$").empty, "Virtual event must take the form of <<event>>");
182 
183 		this._tk.eval("event add %s %s", virtualEvent, binding);
184 	}
185 
186 	/**
187 	 * Deletes each of the bindings from those associated with the virtual 
188 	 * event. Virtual events may be any string value and binding may have any 
189 	 * of the values allowed for the binding argument of the $(LINK2 
190 	 * ./uielement.html#UiElement.bind, bind) method. Any bindings not 
191 	 * currently associated with virtual events are ignored. If no binding 
192 	 * argument is provided, all bindings are removed for the virtual event, so 
193 	 * that the virtual event will not trigger anymore.
194 	 *
195 	 * Params:
196 	 *     virtualEvent = The virtual event to create.
197 	 *     binding = The binding that triggers this event.
198 	 *
199 	 * See_Also:
200 	 *     $(LINK2 ./element/uielement.html, tkd.element.uielement)
201 	 */
202 	public void deleteVirtualEvent(this T)(string virtualEvent, string binding = null)
203 	{
204 		assert(!std.regex.match(virtualEvent, r"^<<.*?>>$").empty, "Virtual event must take the form of <<event>>");
205 
206 		this._tk.eval("event delete %s %s", virtualEvent, binding);
207 	}
208 
209 	/**
210 	 * All element creation and layout should take place within this method.
211 	 * This method is called by the constructor to create the initial GUI.
212 	 */
213 	abstract protected void initInterface();
214 }