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 // Fix to remove hard-coded background colors from widgets. 94 version (Windows) 95 { 96 this._tk.eval("ttk::style configure TEntry -fieldbackground {SystemWindow}"); 97 this._tk.eval("ttk::style configure TSpinbox -fieldbackground {SystemWindow}"); 98 this._tk.eval("ttk::style configure Treeview -fieldbackground {SystemWindow}"); 99 } 100 101 this._mainWindow = Window.getMainWindow(); 102 this.initInterface(); 103 } 104 105 /** 106 * Get the main window of the application. 107 * 108 * Returns: 109 * The main window. 110 */ 111 public @property Window mainWindow() 112 { 113 return this._mainWindow; 114 } 115 116 /** 117 * Set the overall theme of the user interface. 118 * 119 * Params: 120 * theme = The theme to use. 121 * 122 * See_also: 123 * $(LINK2 ./theme.html, tkd.theme) $(BR) 124 */ 125 public void setTheme(string theme) 126 { 127 this._tk.eval("ttk::style theme use %s", theme); 128 } 129 130 /** 131 * Run the application. 132 */ 133 public void run() 134 { 135 this._tk.run(); 136 } 137 138 /** 139 * This method is used to bring the application 'up to date' by entering 140 * the event loop repeatedly until all pending events (including idle 141 * callbacks) have been processed. 142 * 143 * Returns: 144 * This widget to aid method chaining. 145 */ 146 public auto update(this T)() 147 { 148 this._tk.eval("update"); 149 150 return cast(T) this; 151 } 152 153 /** 154 * Exit the application. 155 */ 156 public void exit() 157 { 158 this.mainWindow.destroy(); 159 } 160 161 /** 162 * Associates the virtual event with the binding, so that the virtual event 163 * will trigger whenever the binding occurs. Virtual events may be any 164 * string value and binding may have any of the values allowed for the 165 * binding argument of the $(LINK2 ./element/uielement.html#UiElement.bind, 166 * bind) method. If the virtual event is already defined, the new binding 167 * adds to the existing bindings for the event. 168 * 169 * Params: 170 * virtualEvent = The virtual event to create. 171 * binding = The binding that triggers this event. 172 * 173 * See_Also: 174 * $(LINK2 ./element/uielement.html, tkd.element.uielement) 175 */ 176 public void addVirtualEvent(this T)(string virtualEvent, string binding) 177 { 178 assert(!std.regex.match(virtualEvent, r"^<<.*?>>$").empty, "Virtual event must take the form of <<event>>"); 179 180 this._tk.eval("event add %s %s", virtualEvent, binding); 181 } 182 183 /** 184 * Deletes each of the bindings from those associated with the virtual 185 * event. Virtual events may be any string value and binding may have any 186 * of the values allowed for the binding argument of the $(LINK2 187 * ./uielement.html#UiElement.bind, bind) method. Any bindings not 188 * currently associated with virtual events are ignored. If no binding 189 * argument is provided, all bindings are removed for the virtual event, so 190 * that the virtual event will not trigger anymore. 191 * 192 * Params: 193 * virtualEvent = The virtual event to create. 194 * binding = The binding that triggers this event. 195 * 196 * See_Also: 197 * $(LINK2 ./element/uielement.html, tkd.element.uielement) 198 */ 199 public void deleteVirtualEvent(this T)(string virtualEvent, string binding = null) 200 { 201 assert(!std.regex.match(virtualEvent, r"^<<.*?>>$").empty, "Virtual event must take the form of <<event>>"); 202 203 this._tk.eval("event delete %s %s", virtualEvent, binding); 204 } 205 206 /** 207 * All element creation and layout should take place within this method. 208 * This method is called by the constructor to create the initial GUI. 209 */ 210 abstract protected void initInterface(); 211 }