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 }