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