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 }