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