1 /** 2 * Window module. 3 * 4 * License: 5 * MIT. See LICENSE for full details. 6 */ 7 module tkd.window.window; 8 9 /** 10 * Private imports. 11 */ 12 import std.conv; 13 import std.string; 14 import tkd.element.element : CommandCallback; 15 import tkd.element.uielement; 16 import tkd.image.image; 17 import tkd.interpreter; 18 19 /** 20 * The Window class creates a new toplevel window. 21 * 22 * A window is similar to a frame except that it is created as a top-level 23 * widget. The primary purpose of a toplevel is to serve as a container for 24 * dialog boxes and other collections of widgets. 25 * 26 * Example: 27 * --- 28 * auto window = new Window("New window") 29 * .setGeometry(640, 480, 10, 10) 30 * .setDefaultIcon(new Png!("icon.png")) 31 * .setMaxSize(1024, 768) 32 * .setProtocolCommand(WindowProtocol.deleteWindow, delegate(CommandArgs args){ ... }); 33 * --- 34 * Additional_Events: 35 * Additional events that can also be bound to using the $(LINK2 ../element/uielement.html#UiElement.bind, bind) method. 36 * $(P 37 * <<PrevWindow>>, 38 * <Alt-Key>, 39 * <Key-Tab>, 40 * <Key-F10>, 41 * ) 42 * 43 * See_Also: 44 * $(LINK2 ../element/uielement.html, tkd.element.uielement) 45 */ 46 class Window : UiElement 47 { 48 /** 49 * The main window of the application. 50 */ 51 static private Window _mainWindow; 52 53 /* 54 * Constructor. 55 * 56 * This constructor is mainly for internal use. When creating a window 57 * using this constructor what you are really doing is creating a new 58 * reference to the main application window. 59 */ 60 private this() 61 { 62 super(); 63 this.overrideGeneratedId("."); 64 } 65 66 /** 67 * Constructor. 68 * 69 * It's important to understand that the window will be drawn immediately 70 * by default. This means that the window will display before any other 71 * actions take place, including drawing or managing other widgets. This is 72 * so other methods (such as platformId) that rely on the window being 73 * drawn don't fail if immediately used afterwards. 74 * 75 * This behaviour can be overridden by passing false as the waitForWindow 76 * argument which is useful if you want the entire UI belonging to the new 77 * window to be drawn before actaully showing it. 78 * 79 * The parent window is responsible for the life of this window. If the 80 * parent window is destroyed, this one will be too. 81 * 82 * Params: 83 * parent = The window to act as a parent. 84 * title = The title of the window. 85 * waitForWindow = Whether to wait for the window to be drawn before continuing. 86 */ 87 public this(Window parent, string title, bool waitForWindow = true) 88 { 89 super(parent); 90 this._elementId = "window"; 91 this._tk.eval("toplevel %s", this.id); 92 93 if (waitForWindow) 94 { 95 // This tip was gathered from the following post, if it proves not 96 // to be cross-platform there are other tips in this post that 97 // could be used. 98 // http://stackoverflow.com/questions/8929031/grabbing-a-new-window-in-tcl-tk 99 this._tk.eval("tkwait visibility %s", this.id); 100 } 101 102 this.setTitle(title); 103 } 104 105 /** 106 * Constructor. 107 * 108 * It's important to understand that the window will be drawn immediately 109 * by default. This means that the window will display before any other 110 * actions take place, including drawing or managing other widgets. This is 111 * so other methods (such as platformId) that rely on the window being 112 * drawn don't fail if immediately used afterwards. 113 * 114 * This behaviour can be overridden by passing false as the waitForWindow 115 * argument which is useful if you want the entire UI belonging to the new 116 * window to be drawn before actaully showing it. 117 * 118 * If no parent is specified the new window with be a child of the main 119 * window. 120 * 121 * Params: 122 * title = The title of the window. 123 * waitForWindow = Whether to wait for the window to be drawn before continuing. 124 */ 125 public this(string title, bool waitForWindow = true) 126 { 127 this(new Window(), title, waitForWindow); 128 } 129 130 /** 131 * Set the title of the window. 132 * 133 * Params: 134 * title = The title of the window. 135 * 136 * Returns: 137 * This window to aid method chaining. 138 */ 139 public auto setTitle(this T)(string title) 140 { 141 this._tk.eval("wm title %s {%s}", this.id, title); 142 143 return cast(T) this; 144 } 145 146 /** 147 * Set the opacity of the window. 148 * 149 * Params: 150 * opacity = A number between 0.0 and 1.0 specifying the transparency. 151 * 152 * Returns: 153 * This window to aid method chaining. 154 */ 155 public auto setOpacity(this T)(double opacity) 156 { 157 assert(opacity >= 0 && opacity <= 1, "Opacity must be between 0.0 and 1.0."); 158 159 this._tk.eval("wm attributes %s -alpha %s", this.id, opacity); 160 161 return cast(T) this; 162 } 163 164 /** 165 * Handle setting the window to fullscreen. 166 * 167 * Params: 168 * fullscreen = A boolean specifying if the window should be fullscreen or not. 169 * 170 * Returns: 171 * This window to aid method chaining. 172 */ 173 public auto setFullscreen(this T)(bool fullscreen) 174 { 175 this._tk.eval("wm attributes %s -fullscreen %s", this.id, fullscreen); 176 177 return cast(T) this; 178 } 179 180 /** 181 * Handle setting the window to be the top-most. This makes the window not 182 * able to be lowered behind any others. 183 * 184 * Params: 185 * topmost = A boolean specifying if the window should be top-most or not. 186 * 187 * Returns: 188 * This window to aid method chaining. 189 */ 190 public auto setTopmost(this T)(bool topmost) 191 { 192 this._tk.eval("wm attributes %s -topmost %s", this.id, topmost); 193 194 return cast(T) this; 195 } 196 197 version (Windows) 198 { 199 /** 200 * Handle disabling the window. Windows only. 201 * 202 * Params: 203 * disabled = A boolean specifying if the window should be disabled or not. 204 * 205 * Returns: 206 * This window to aid method chaining. 207 */ 208 public auto setDisabled(this T)(bool disabled) 209 { 210 this._tk.eval("wm attributes %s -disabled %s", this.id, disabled); 211 212 return cast(T) this; 213 } 214 215 /** 216 * Handle changing the window to a tool window. Windows only. 217 * 218 * Params: 219 * toolWindow = A boolean specifying if the window should be a tool window or not. 220 * 221 * Returns: 222 * This window to aid method chaining. 223 */ 224 public auto setToolWindow(this T)(bool toolWindow) 225 { 226 this._tk.eval("wm attributes %s -toolwindow %s", this.id, toolWindow); 227 228 return cast(T) this; 229 } 230 } 231 232 version (OSX) 233 { 234 /** 235 * Set the modified state of the window. Mac OSX only. 236 * 237 * Params: 238 * modified = A boolean specifying if the window should show it's been modified or not. 239 * 240 * Returns: 241 * This window to aid method chaining. 242 */ 243 public auto setModified(this T)(bool modified) 244 { 245 this._tk.eval("wm attributes %s -modified %s", this.id, modified); 246 247 return cast(T) this; 248 } 249 250 /** 251 * Set the notify state of the window. On Mac OS it usually bounces the 252 * dock icon. Mac OSX only. 253 * 254 * Params: 255 * modified = A boolean specifying if the window should show a notification or not. 256 * 257 * Returns: 258 * This window to aid method chaining. 259 */ 260 public auto setNotify(this T)(bool modified) 261 { 262 this._tk.eval("wm attributes %s -notify %s", this.id, modified); 263 264 return cast(T) this; 265 } 266 } 267 268 /** 269 * Restore the window's state to before it was minimised or withdrawn. 270 * 271 * Returns: 272 * This window to aid method chaining. 273 */ 274 public auto deiconify(this T)() 275 { 276 this._tk.eval("wm deiconify %s", this.id); 277 278 return cast(T) this; 279 } 280 281 /** 282 * Minimise the window. 283 * 284 * Returns: 285 * This window to aid method chaining. 286 */ 287 public auto iconify(this T)() 288 { 289 this._tk.eval("wm iconify %s", this.id); 290 291 return cast(T) this; 292 } 293 294 /** 295 * Set the size and postition of the window. 296 * 297 * Params: 298 * width = The width of the window. 299 * height = The height of the window. 300 * xPos = The horizontal position of the window. 301 * yPos = The vertical position of the window. 302 * 303 * Returns: 304 * This window to aid method chaining. 305 */ 306 public auto setGeometry(this T)(int width, int height, int xPos, int yPos) 307 { 308 this._tk.eval("wm geometry %s %sx%s+%s+%s", this.id, width, height, xPos, yPos); 309 310 return cast(T) this; 311 } 312 313 /** 314 * Set the default icon for this window. This is applied to all future 315 * child windows as well. 316 * 317 * The data in the images is taken as a snapshot at the time of invocation. 318 * If the images are later changed, this is not reflected to the titlebar 319 * icons. Multiple images are accepted to allow different images sizes 320 * (e.g., 16x16 and 32x32) to be provided. The window manager may scale 321 * provided icons to an appropriate size. 322 * 323 * Params: 324 * images = A variadic list of images. 325 * 326 * Returns: 327 * This window to aid method chaining. 328 */ 329 public auto setDefaultIcon(this T)(Image[] images ...) 330 { 331 string defaultImages; 332 333 foreach (image; images) 334 { 335 defaultImages ~= format("%s ", image.id); 336 } 337 338 this._tk.eval("wm iconphoto %s -default %s", this.id, defaultImages); 339 340 return cast(T) this; 341 } 342 343 /** 344 * Set the icon for this window, this overrides the default icon. 345 * 346 * Params: 347 * images = A variadic list of images. 348 * 349 * Returns: 350 * This window to aid method chaining. 351 */ 352 public auto setIcon(this T)(Image[] images ...) 353 { 354 string defaultImages; 355 356 foreach (image; images) 357 { 358 defaultImages ~= format("%s ", image.id); 359 } 360 361 this._tk.eval("wm iconphoto %s %s", this.id, defaultImages); 362 363 return cast(T) this; 364 } 365 366 /** 367 * Set the maximum size of the window. 368 * 369 * Params: 370 * width = The maximum width of the window. 371 * height = The maximum height of the window. 372 * 373 * Returns: 374 * This window to aid method chaining. 375 */ 376 public auto setMaxSize(this T)(int width, int height) 377 { 378 this._tk.eval("wm maxsize %s %s %s", this.id, width, height); 379 380 return cast(T) this; 381 } 382 383 /** 384 * Set the minimum size of the window. 385 * 386 * Params: 387 * width = The minimum width of the window. 388 * height = The minimum height of the window. 389 * 390 * Returns: 391 * This window to aid method chaining. 392 */ 393 public auto setMinSize(this T)(int width, int height) 394 { 395 this._tk.eval("wm minsize %s %s %s", this.id, width, height); 396 397 return cast(T) this; 398 } 399 400 /** 401 * This command is used to manage window manager protocols such as 402 * WM_DELETE_WINDOW. Protocol is the name of an atom corresponding to a 403 * window manager protocol, such as WM_DELETE_WINDOW or WM_SAVE_YOURSELF or 404 * WM_TAKE_FOCUS. 405 * 406 * Params: 407 * protocol = The protocol to respond to. 408 * callback = The callback to invoke when the protocol is encountered. 409 * 410 * Returns: 411 * This window to aid method chaining. 412 * 413 * Callback_Arguments: 414 * These are the fields within the callback's $(LINK2 415 * ../element/element.html#CommandArgs, CommandArgs) parameter which 416 * are populated by this method when the callback is executed. 417 * $(P 418 * $(PARAM_TABLE 419 * $(PARAM_ROW CommandArgs.element, The window that executed the callback.) 420 * $(PARAM_ROW CommandArgs.uniqueData, The protocol that was responded to.) 421 * $(PARAM_ROW CommandArgs.callback, The callback which was executed.) 422 * ) 423 * ) 424 * 425 * See_Also: 426 * $(LINK2 ../element/element.html#CommandCallback, tkd.element.element.CommandCallback) $(BR) 427 * $(LINK2 ../tkdapplication.html#WindowProtocol, tkd.tkdapplication.WindowProtocol) $(BR) 428 */ 429 public auto setProtocolCommand(this T)(string protocol, CommandCallback callback) 430 { 431 string command = this.createCommand(callback, protocol); 432 this._tk.eval("wm protocol %s %s %s", this.id, protocol, command); 433 434 return cast(T) this; 435 } 436 437 /** 438 * Remove a previously set protocol command. 439 * 440 * Params: 441 * protocol = The protocol which will have the command removed. 442 * 443 * Returns: 444 * This window to aid method chaining. 445 * 446 * See_Also: 447 * $(LINK2 ../tkdapplication.html#WindowProtocol, tkd.tkdapplication.WindowProtocol) 448 */ 449 public auto removeProtocolCommand(this T)(string protocol) 450 { 451 this._tk.deleteCommand(this.getCommandName(protocol)); 452 this._tk.eval("wm protocol %s %s {}", this.id, protocol); 453 454 return cast(T) this; 455 } 456 457 /** 458 * Set if the width and height can be resized. 459 * 460 * Params: 461 * resizeWidth = True to allow width resizing, false to disable. 462 * resizeHeight = True to allow height resizing, false to disable. 463 * 464 * Returns: 465 * This window to aid method chaining. 466 */ 467 public auto setResizable(this T)(bool resizeWidth, bool resizeHeight) 468 { 469 this._tk.eval("wm resizable %s %s %s", this.id, resizeWidth, resizeHeight); 470 471 return cast(T) this; 472 } 473 474 /** 475 * Determine if this window is above another. 476 * 477 * Params: 478 * other = The other window to check this one is above. 479 * 480 * Returns: 481 * true if this window is above other, false if not. 482 */ 483 public bool isAbove(Window other) 484 { 485 this._tk.eval("wm stackorder %s isabove %s", this.id, other.id); 486 487 return this._tk.getResult!(int) == 1; 488 } 489 490 /** 491 * Determine if this window is below another. 492 * 493 * Params: 494 * other = The other window to check this one is below. 495 * 496 * Returns: 497 * true if this window is below other, false if not. 498 */ 499 public bool isBelow(Window other) 500 { 501 this._tk.eval("wm stackorder %s isbelow %s", this.id, other.id); 502 503 return this._tk.getResult!(int) == 1; 504 } 505 506 /** 507 * Withdraw a window from being displayed/mapped by the window manager. 508 * 509 * Returns: 510 * This window to aid method chaining. 511 */ 512 public auto withdraw(this T)() 513 { 514 this._tk.eval("wm withdraw %s", this.id); 515 516 return cast(T) this; 517 } 518 519 /** 520 * Wait until this window has been destroyed. 521 * 522 * Returns: 523 * This window to aid method chaining. 524 */ 525 public auto wait(this T)() 526 { 527 this._tk.eval("tkwait window %s", this.id); 528 529 return cast(T) this; 530 } 531 532 /** 533 * Set a callback to be executed after a delay and after processing all 534 * other events. The callback is executed only once and discarded. This is 535 * useful for refreshing the GUI at regular intervals when monitoring 536 * something or to schedule a future action. 537 * 538 * Params: 539 * callback = The delegate function to be executed on idle. 540 * msDelay = The delay in millisecond before executing the given callback. 541 * 542 * Returns: 543 * This window to aid method chaining. 544 * 545 * Example: 546 * ---- 547 * InputStream stream = ...; // A data provider. 548 * 549 * this.mainWindow.setIdleCommand(delegate(CommandArgs args){ 550 * 551 * // Use data. 552 * if (!stream.empty && stream.finished) 553 * { 554 * performStep(stream.next); 555 * } 556 * 557 * // Re-arm this callback and wait for more data. 558 * this.mainWindow.setIdleCommand(args.callback, 10_000); 559 * 560 * }, 10_000); 561 * 562 * ---- 563 * Caveats: 564 * The callback executed by this method is not asynchronous and could 565 * halt the GUI from processing events if it takes a long time to 566 * finish. 567 */ 568 public auto setIdleCommand(this T)(CommandCallback callback, int msDelay = 1) 569 { 570 assert(msDelay > 0, "The delay in milliseconds should be greater than zero."); 571 572 string command = this.createCommand(callback, "idle"); 573 this._tk.eval("after idle [list after %s %s]", msDelay.to!(string), command); 574 575 return cast(T) this; 576 } 577 578 /** 579 * Get the main window of the application. This is just a convenience 580 * method and shouldn't really be used. Instead use the $(LINK2 581 * ../tkdapplication.html#TkdApplication.mainWindow, mainWindow) property 582 * of the $(LINK2 ../tkdapplication.html, TkdApplication) class. 583 * 584 * Returns: 585 * A reference to the main window of the application. 586 * 587 * See_Also: 588 * $(LINK2 ../tkdapplication.html, tkd.tkdapplication) 589 */ 590 static public Window getMainWindow() 591 { 592 if (this._mainWindow is null) 593 { 594 this._mainWindow = new Window(); 595 } 596 597 return this._mainWindow; 598 } 599 } 600 601 /** 602 * Window manager protocols. 603 * 604 * Caveats: 605 * This list is incomplete. 606 */ 607 enum WindowProtocol : string 608 { 609 deleteWindow = "WM_DELETE_WINDOW", /// Issued when the window is to be deleted. 610 saveYourself = "WM_SAVE_YOURSELF", /// Issued when the window is required to save itself. 611 takeFocus = "WM_TAKE_FOCUS", /// Issued then the window is focused. 612 }