1 /**
2  * Element module.
3  *
4  * License:
5  *     MIT. See LICENSE for full details.
6  */
7 module tkd.element.element;
8 
9 /**
10  * Imports.
11  */
12 import core.memory;
13 import std.array;
14 import std.conv;
15 import std.digest.crc;
16 import std.random;
17 import std.string;
18 import tcltk.tk;
19 import tkd.interpreter;
20 
21 /**
22  * The ui element base class.
23  */
24 abstract class Element
25 {
26 	/*
27 	 * The Tk interpreter.
28 	 */
29 	protected Tk _tk;
30 
31 	/*
32 	 * The parent of this element if nested within another.
33 	 */
34 	protected Element _parent;
35 
36 	/*
37 	 * An optional identifier that overrides the generated id.
38 	 */
39 	protected string _manualIdentifier;
40 
41 	/*
42 	 * Internal element identifier.
43 	 */
44 	protected string _elementId;
45 
46 	/*
47 	 * The unique hash of this element.
48 	 */
49 	protected string _hash;
50 
51 	/**
52 	 * Construct the element.
53 	 *
54 	 * Params:
55 	 *     parent = An optional parent of this element.
56 	 */
57 	public this(Element parent)
58 	{
59 		this._tk        = Tk.getInstance();
60 		this._parent    = parent;
61 		this._elementId = "element";
62 		this._hash      = this.generateHash();
63 	}
64 
65 	/**
66 	 * Construct the element.
67 	 */
68 	public this()
69 	{
70 		this(null);
71 	}
72 
73 	/**
74 	 * The unique id of this element.
75 	 *
76 	 * Returns:
77 	 *     The string id.
78 	 */
79 	public @property string id() nothrow
80 	{
81 		if (this._manualIdentifier !is null)
82 		{
83 			return this._manualIdentifier;
84 		}
85 
86 		string parentId;
87 
88 		if (this._parent !is null && this._parent.id != ".")
89 		{
90 			parentId = this._parent.id;
91 		}
92 
93 		return parentId ~ "." ~ this._elementId ~ "-" ~ this._hash;
94 	}
95 
96 	/**
97 	 * The parent element if any.
98 	 *
99 	 * Returns:
100 	 *     The parent element or null.
101 	 */
102 	public @property Element parent()
103 	{
104 		return this._parent;
105 	}
106 
107 	/*
108 	 * Override the unique id of this element.
109 	 *
110 	 * Params:
111 	 *     identifier = The overriden identifier.
112 	 */
113 	protected void overrideGeneratedId(string identifier) nothrow
114 	{
115 		this._manualIdentifier = identifier;
116 	}
117 
118 	/*
119 	 * Generate the unique hash for this element.
120 	 *
121 	 * Returns:
122 	 *     The string hash.
123 	 */
124 	protected string generateHash()
125 	{
126 		string text = Random(unpredictableSeed).front.to!(string);
127 		return hexDigest!(CRC32)(text).array.to!(string);
128 	}
129 
130 	/*
131 	 * Generate the unique hash for this element.
132 	 *
133 	 * Params:
134 	 *     text = The format of the text to generate a hash of.
135 	 *     args = The arguments that the format defines (if any).
136 	 *
137 	 * Returns:
138 	 *     The string hash.
139 	 */
140 	protected string generateHash(A...)(string text, A args)
141 	{
142 		static if (A.length)
143 		{
144 			text = format(text, args);
145 		}
146 		return hexDigest!(CRC32)(text).array.to!(string);
147 	}
148 
149 	/*
150 	 * Get the internal name for a command.
151 	 *
152 	 * Params:
153 	 *     uniqueData = An extra seed for the internal command name hash.
154 	 *
155 	 * Returns:
156 	 *     The internal name of the command.
157 	 */
158 	protected string getCommandName(string uniqueData = null)
159 	{
160 		return format("command-%s", this.generateHash("command%s%s", uniqueData, this.id));
161 	}
162 
163 	/*
164 	 * Create a command.
165 	 *
166 	 * Params:
167 	 *     callback = The callback to register as a command.
168 	 *     uniqueData = An extra seed for the internal command name hash.
169 	 *
170 	 * Returns:
171 	 *     The internal command name.
172 	 *
173 	 * See_Also:
174 	 *     $(LINK2 ./element.html#CommandCallback, tkd.element.element.CommandCallback)
175 	 */
176 	protected string createCommand(CommandCallback callback, string uniqueData = null)
177 	{
178 		assert(callback !is null, "A command callback can not be null.");
179 
180 		Tcl_CmdProc commandCallbackHandler = function(ClientData data, Tcl_Interp* tclInterpreter, int argc, const(char)** argv)
181 		{
182 			CommandArgs args = *cast(CommandArgs*)data;
183 
184 			try
185 			{
186 				T getCommandParameter(T)(const(char)** argv, int index)
187 				{
188 					string raw = argv[index].to!(string);
189 
190 					if (raw != "??")
191 					{
192 						return raw.to!(T);
193 					}
194 
195 					return T.init;
196 				}
197 
198 				if (argc == 9)
199 				{
200 					args.event.button  = getCommandParameter!(int)(argv, 1);
201 					args.event.keyCode = getCommandParameter!(int)(argv, 2);
202 					args.event.x       = getCommandParameter!(int)(argv, 3);
203 					args.event.y       = getCommandParameter!(int)(argv, 4);
204 					args.event.wheel   = getCommandParameter!(int)(argv, 5);
205 					args.event.key     = getCommandParameter!(string)(argv, 6);
206 					args.event.screenX = getCommandParameter!(int)(argv, 7);
207 					args.event.screenY = getCommandParameter!(int)(argv, 8);
208 				}
209 				else if (argc == 2)
210 				{
211 					args.dialog.font = getCommandParameter!(string)(argv, 1);
212 				}
213 
214 				args.callback(args);
215 			}
216 			catch (Throwable ex)
217 			{
218 				string error = "Error occurred in command callback. ";
219 				error ~= ex.msg ~ "\n";
220 				error ~= "Element: " ~ args.element.id ~ "\n";
221 
222 				Tcl_SetResult(tclInterpreter, error.toStringz, TCL_STATIC);
223 				return TCL_ERROR;
224 			}
225 
226 			return TCL_OK;
227 		};
228 
229 		Tcl_CmdDeleteProc deleteCallbackHandler = function(ClientData data)
230 		{
231 			GC.removeRoot(data);
232 			GC.clrAttr(data, GC.BlkAttr.NO_MOVE);
233 		};
234 
235 		CommandArgs* args = cast(CommandArgs*)GC.malloc(CommandArgs.sizeof, GC.BlkAttr.NO_MOVE);
236 		GC.addRoot(args);
237 
238 		(*args)            = CommandArgs.init;
239 		(*args).element    = this;
240 		(*args).uniqueData = uniqueData;
241 		(*args).callback   = callback;
242 
243 		string command = this.getCommandName(uniqueData);
244 		this._tk.createCommand(command, commandCallbackHandler, args, deleteCallbackHandler);
245 
246 		return command;
247 	}
248 }
249 
250 /**
251  * Alias representing a command callback.
252  */
253 alias void delegate(CommandArgs args) CommandCallback;
254 
255 /**
256  * The CommandArgs struct passed to the CommandCallback on invocation.
257  */
258 struct CommandArgs
259 {
260 	/**
261 	 * The element that issued the command.
262 	 */
263 	Element element;
264 
265 	/**
266 	 * Any unique extra data.
267 	 */
268 	string uniqueData;
269 
270 	/**
271 	 * The callback which was invoked as the command.
272 	 */
273 	CommandCallback callback;
274 
275 	/**
276 	 * Data populated from a event binding created using the bind method.
277 	 */
278 	static struct Event
279 	{
280 		/**
281 		 * The number of any button that was pressed.
282 		 */
283 		int button;
284 
285 		/**
286 		 * The key code of any key pressed.
287 		 */
288 		int keyCode;
289 
290 		/**
291 		 * The horizontal position of the mouse relative to the widget.
292 		 */
293 		int x;
294 
295 		/**
296 		 * The vertical position of the mouse relative to the widget.
297 		 */
298 		int y;
299 
300 		/**
301 		 * Mouse wheel delta.
302 		 */
303 		int wheel;
304 
305 		/**
306 		 * Key symbol of any key pressed.
307 		 */
308 		string key;
309 
310 		/**
311 		 * The horizontal position of the mouse relative to the screen.
312 		 */
313 		int screenX;
314 
315 		/**
316 		 * The vertical position of the mouse relative to the screen.
317 		 */
318 		int screenY;
319 	}
320 
321 	Event event;
322 
323 	/**
324 	 * Data populated from dialog interaction.
325 	 */
326 	static struct Dialog
327 	{
328 		/**
329 		 * Font information populated from dialog interaction.
330 		 */
331 		string font;
332 	}
333 
334 	Dialog dialog;
335 }