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 		return hexDigest!(CRC32)(format(text, args)).array.to!(string);
143 	}
144 
145 	/*
146 	 * Get the internal name for a command.
147 	 *
148 	 * Params:
149 	 *     uniqueData = An extra seed for the internal command name hash.
150 	 *
151 	 * Returns:
152 	 *     The internal name of the command.
153 	 */
154 	protected string getCommandName(string uniqueData = null)
155 	{
156 		return format("command-%s", this.generateHash("command%s%s", uniqueData, this.id));
157 	}
158 
159 	/*
160 	 * Create a command.
161 	 *
162 	 * Params:
163 	 *     callback = The callback to register as a command.
164 	 *     uniqueData = An extra seed for the internal command name hash.
165 	 *
166 	 * Returns:
167 	 *     The internal command name.
168 	 *
169 	 * See_Also:
170 	 *     $(LINK2 ./element.html#CommandCallback, tkd.element.element.CommandCallback)
171 	 */
172 	protected string createCommand(CommandCallback callback, string uniqueData = null)
173 	{
174 		Tcl_CmdProc commandCallbackHandler = function(ClientData data, Tcl_Interp* tclInterpreter, int argc, const(char)** argv)
175 		{
176 			CommandArgs args = *cast(CommandArgs*)data;
177 
178 			try
179 			{
180 				T getCommandParameter(T)(const(char)** argv, int index)
181 				{
182 					string raw = argv[index].to!(string);
183 
184 					if (raw != "??")
185 					{
186 						return raw.to!(T);
187 					}
188 
189 					return T.init;
190 				}
191 
192 				if (argc == 9)
193 				{
194 					args.event.button  = getCommandParameter!(int)(argv, 1);
195 					args.event.keyCode = getCommandParameter!(int)(argv, 2);
196 					args.event.x       = getCommandParameter!(int)(argv, 3);
197 					args.event.y       = getCommandParameter!(int)(argv, 4);
198 					args.event.wheel   = getCommandParameter!(int)(argv, 5);
199 					args.event.key     = getCommandParameter!(string)(argv, 6);
200 					args.event.screenX = getCommandParameter!(int)(argv, 7);
201 					args.event.screenY = getCommandParameter!(int)(argv, 8);
202 				}
203 				else if (argc == 2)
204 				{
205 					args.dialog.font = getCommandParameter!(string)(argv, 1);
206 				}
207 
208 				args.callback(args);
209 			}
210 			catch (Throwable ex)
211 			{
212 				string error = "Error occurred in command callback. ";
213 				error ~= ex.msg ~ "\n";
214 				error ~= "Element: " ~ args.element.id ~ "\n";
215 
216 				Tcl_SetResult(tclInterpreter, error.toStringz, TCL_STATIC);
217 				return TCL_ERROR;
218 			}
219 
220 			return TCL_OK;
221 		};
222 
223 		Tcl_CmdDeleteProc deleteCallbackHandler = function(ClientData data)
224 		{
225 			GC.removeRoot(data);
226 			GC.clrAttr(data, GC.BlkAttr.NO_MOVE);
227 		};
228 
229 		CommandArgs* args = cast(CommandArgs*)GC.malloc(CommandArgs.sizeof, GC.BlkAttr.NO_MOVE);
230 		GC.addRoot(args);
231 
232 		(*args)            = CommandArgs.init;
233 		(*args).element    = this;
234 		(*args).uniqueData = uniqueData;
235 		(*args).callback   = callback;
236 
237 		string command = this.getCommandName(uniqueData);
238 		this._tk.createCommand(command, commandCallbackHandler, args, deleteCallbackHandler);
239 
240 		return command;
241 	}
242 }
243 
244 /**
245  * Alias representing a command callback.
246  */
247 alias void delegate(CommandArgs args) CommandCallback;
248 
249 /**
250  * The CommandArgs struct passed to the CommandCallback on invocation.
251  */
252 struct CommandArgs
253 {
254 	/**
255 	 * The element that issued the command.
256 	 */
257 	Element element;
258 
259 	/**
260 	 * Any unique extra data.
261 	 */
262 	string uniqueData;
263 
264 	/**
265 	 * The callback which was invoked as the command.
266 	 */
267 	CommandCallback callback;
268 
269 	/**
270 	 * Data populated from a event binding created using the bind method.
271 	 */
272 	static struct Event
273 	{
274 		/**
275 		 * The number of any button that was pressed.
276 		 */
277 		int button;
278 
279 		/**
280 		 * The key code of any key pressed.
281 		 */
282 		int keyCode;
283 
284 		/**
285 		 * The horizontal position of the mouse relative to the widget.
286 		 */
287 		int x;
288 
289 		/**
290 		 * The vertical position of the mouse relative to the widget.
291 		 */
292 		int y;
293 
294 		/**
295 		 * Mouse wheel delta.
296 		 */
297 		int wheel;
298 
299 		/**
300 		 * Key symbol of any key pressed.
301 		 */
302 		string key;
303 
304 		/**
305 		 * The horizontal position of the mouse relative to the screen.
306 		 */
307 		int screenX;
308 
309 		/**
310 		 * The vertical position of the mouse relative to the screen.
311 		 */
312 		int screenY;
313 	}
314 
315 	Event event;
316 
317 	/**
318 	 * Data populated from dialog interaction.
319 	 */
320 	static struct Dialog
321 	{
322 		/**
323 		 * Font information populated from dialog interaction.
324 		 */
325 		string font;
326 	}
327 
328 	Dialog dialog;
329 }