1 module main;
2 
3 /**
4  * Imports.
5  */
6 import std.algorithm;
7 import std.array;
8 import std.datetime;
9 import std.file;
10 import tkd.tkdapplication;
11 
12 /**
13  * Sample application.
14  */
15 class Application : TkdApplication
16 {
17 	/**
18 	 * Widgets we need access to on the class level.
19 	 */
20 	private Entry _entry;
21 	private ProgressBar _progressBar;
22 	private Canvas _canvas;
23 	private Entry _fontEntry;
24 	private Entry _colorEntry;
25 	private Entry _directoryEntry;
26 	private Entry _openFileEntry;
27 	private Entry _saveFileEntry;
28 	private Entry _messageEntry;
29 
30 	/**
31 	 * Open the font dialog.
32 	 *
33 	 * Params:
34 	 *     args = The callback args.
35 	 */
36 	private void openFontDialog(CommandArgs args)
37 	{
38 		auto dialog = new FontDialog("Choose a font")
39 			.setCommand(delegate(CommandArgs args){
40 				this._fontEntry.setValue(args.dialog.font);
41 			})
42 			.show();
43 	}
44 
45 	/**
46 	 * Open the color dialog.
47 	 *
48 	 * Params:
49 	 *     args = The callback args.
50 	 */
51 	private void openColorDialog(CommandArgs args)
52 	{
53 		auto dialog = new ColorDialog("Choose a color")
54 			.setInitialColor(Color.beige)
55 			.show();
56 		this._colorEntry.setValue(dialog.getResult());
57 	}
58 
59 	/**
60 	 * Open the directory dialog.
61 	 *
62 	 * Params:
63 	 *     args = The callback args.
64 	 */
65 	private void openDirectoryDialog(CommandArgs args)
66 	{
67 		auto dialog = new DirectoryDialog("Choose a directory")
68 			.setDirectoryMustExist(true)
69 			.show();
70 		this._directoryEntry.setValue(dialog.getResult());
71 	}
72 
73 	/**
74 	 * Open the open file dialog.
75 	 *
76 	 * Params:
77 	 *     args = The callback args.
78 	 */
79 	private void openOpenFileDialog(CommandArgs args)
80 	{
81 		auto dialog = new OpenFileDialog()
82 			.setMultiSelection(true)
83 			.setDefaultExtension(".dmo")
84 			.addFileType("{{All files} {*}}")
85 			.addFileType("{{D language files} {.d .di}}")
86 			.addFileType("{{HTML files} {.htm .html}}")
87 			.addFileType("{{Text files} {.txt}}")
88 			.setInitialDirectory("~")
89 			.setInitialFile("file-to-open.dmo")
90 			.show();
91 		this._openFileEntry.setValue(dialog.getResults().join(" "));
92 	}
93 
94 	/**
95 	 * Open the save file dialog.
96 	 *
97 	 * Params:
98 	 *     args = The callback args.
99 	 */
100 	private void openSaveFileDialog(CommandArgs args)
101 	{
102 		auto dialog = new SaveFileDialog()
103 			.setConfirmOverwrite(true)
104 			.setDefaultExtension(".dmo")
105 			.addFileType("{{All files} {*}}")
106 			.addFileType("{{D language files} {.d .di}}")
107 			.addFileType("{{HTML files} {.htm .html}}")
108 			.addFileType("{{Text files} {.txt}}")
109 			.setInitialDirectory("~")
110 			.setInitialFile("file-to-save.dmo")
111 			.show();
112 		this._saveFileEntry.setValue(dialog.getResult());
113 	}
114 
115 	/**
116 	 * Open the message dialog.
117 	 *
118 	 * Params:
119 	 *     args = The callback args.
120 	 */
121 	private void openMessageDialog(CommandArgs args)
122 	{
123 		auto dialog = new MessageDialog()
124 			.setMessage("Lorem ipsum dolor sit amet.")
125 			.setDetailMessage("Nunc at aliquam arcu. Sed eget tellus ligula.\nSed egestas est et tempus cursus.")
126 			.setType(MessageDialogType.okcancel)
127 			.show();
128 		this._messageEntry.setValue(dialog.getResult());
129 	}
130 
131 	/**
132 	 * Mark a point in the canvas to drag from.
133 	 *
134 	 * Params:
135 	 *     args = The callback args.
136 	 */
137 	private void mark(CommandArgs args)
138 	{
139 		this._canvas.setScanMark(args.event.x, args.event.y);
140 	}
141 
142 	/**
143 	 * Drag the canvas to reposition the contents.
144 	 *
145 	 * Params:
146 	 *     args = The callback args.
147 	 */
148 	private void drag(CommandArgs args)
149 	{
150 		this._canvas.scanDragTo(args.event.x, args.event.y);
151 	}
152 
153 	/**
154 	 * Show the about box.
155 	 */
156 	private void showAbout(CommandArgs args)
157 	{
158 		auto dialog = new MessageDialog("About")
159 			.setMessage("Tkd Showcase")
160 			.setDetailMessage("An showcase Tkd application demonstrating menus, widgets and dialogs.\n\nThe possiblities are endless.")
161 			.show();
162 	}
163 
164 	/**
165 	 * Create the menu.
166 	 */
167 	private void createMenu()
168 	{
169 		auto menuBar = new MenuBar(this.mainWindow);
170 
171 		auto checkButtonSubMenu = new Menu()
172 			.addCheckButtonEntry("Option 1", delegate(CommandArgs args){})
173 			.addCheckButtonEntry("Option 2", delegate(CommandArgs args){})
174 			.addCheckButtonEntry("Option 3", delegate(CommandArgs args){});
175 
176 		auto radioButtonSubMenu = new Menu()
177 			.addRadioButtonEntry("Option 1", delegate(CommandArgs args){})
178 			.addRadioButtonEntry("Option 2", delegate(CommandArgs args){})
179 			.addRadioButtonEntry("Option 3", delegate(CommandArgs args){});
180 
181 		auto fileMenu = new Menu(menuBar, "File", 0)
182 			.addMenuEntry("Checkbutton submenu", checkButtonSubMenu)
183 			.addMenuEntry("Radiobutton submenu", radioButtonSubMenu)
184 			.addSeparator()
185 			.addEntry(new EmbeddedPng!("cancel.png"), "Quit", &this.exitApplication, ImagePosition.left, "Ctrl-Q");
186 
187 		auto helpMenu = new Menu(menuBar, "Help", 0)
188 			.addEntry(new EmbeddedPng!("help.png"), "About...", &this.showAbout, ImagePosition.left, "F1");
189 	}
190 
191 	/**
192 	 * Create the widget pane for the note book.
193 	 *
194 	 * Returns:
195 	 *     The widget pane.
196 	 */
197 	private Frame createWidgetPane()
198 	{
199 		auto widgetPane = new Frame();
200 
201 		auto entryLabelFrame = new LabelFrame(widgetPane, "Text Entry")
202 			.pack(10, 0, GeometrySide.top, GeometryFill.both, AnchorPosition.center, true);
203 			auto entry1 = new Text(entryLabelFrame)
204 				.setWidth(0)
205 				.setHeight(3)
206 				.appendText("import std.stdio;\n\nvoid main(string[] args)\n{\n\twriteln(\"Hello World!\");\n}")
207 				.pack(5, 0, GeometrySide.bottom, GeometryFill.both, AnchorPosition.northWest, true);
208 			this._entry = new Entry(entryLabelFrame)
209 				.pack(5, 0, GeometrySide.left, GeometryFill.x, AnchorPosition.northWest, true);
210 			auto entry3 = new SpinBox(entryLabelFrame)
211 				.setData(["$foo", "[bar]", "\"baz\"", "{qux}"])
212 				.setWrap(true)
213 				.setWidth(5)
214 				.pack(5, 0, GeometrySide.left);
215 			auto entry4 = new ComboBox(entryLabelFrame)
216 				.setData(["Option 1", "Option 2", "Option 3"])
217 				.setValue("Option 1")
218 				.pack(5, 0, GeometrySide.left, GeometryFill.x, AnchorPosition.northWest, true);
219 
220 		auto rangeLabelFrame = new LabelFrame(widgetPane, "Progress & Scale")
221 			.pack(10, 0, GeometrySide.bottom, GeometryFill.both, AnchorPosition.center);
222 			this._progressBar = new ProgressBar(rangeLabelFrame)
223 				.setMaxValue(10)
224 				.setValue(4)
225 				.pack(5, 0, GeometrySide.top, GeometryFill.x, AnchorPosition.center, true);
226 			auto scale = new Scale(rangeLabelFrame)
227 				.setFromValue(10)
228 				.setToValue(0)
229 				.setCommand(delegate(CommandArgs args){
230 					auto scale = cast(Scale)args.element;
231 					this._progressBar.setValue(scale.getValue());
232 				})
233 				.setValue(4)
234 				.pack(5, 0, GeometrySide.top, GeometryFill.x, AnchorPosition.center, true);
235 
236 		auto buttonLabelFrame = new LabelFrame(widgetPane, "Buttons")
237 			.pack(10, 0, GeometrySide.left, GeometryFill.both, AnchorPosition.center, true);
238 			auto button1 = new Button(buttonLabelFrame, "Text button")
239 				.pack(5);
240 			auto button2 = new Button(buttonLabelFrame, new EmbeddedPng!("thumbnail.png"), "Image button", ImagePosition.left)
241 				.pack(5);
242 			auto buttonMenu = new Menu()
243 				.addEntry("Option 1", delegate(CommandArgs args){})
244 				.addEntry("Option 2", delegate(CommandArgs args){})
245 				.addEntry("Option 3", delegate(CommandArgs args){});
246 			auto button3 = new MenuButton(buttonLabelFrame, "Menu button", buttonMenu)
247 				.pack(5);
248 
249 		auto checkBoxLabelFrame = new LabelFrame(widgetPane, "Check buttons")
250 			.pack(10, 0, GeometrySide.left, GeometryFill.both, AnchorPosition.center, true);
251 			auto checkButton1 = new CheckButton(checkBoxLabelFrame, "Option 1")
252 				.check()
253 				.pack(5);
254 			auto checkButton2 = new CheckButton(checkBoxLabelFrame, "Option 2")
255 				.pack(5);
256 			auto checkButton3 = new CheckButton(checkBoxLabelFrame, "Option 3")
257 				.pack(5);
258 
259 		auto radioLabelFrame = new LabelFrame(widgetPane, "Radio buttons")
260 			.pack(10, 0, GeometrySide.left, GeometryFill.both, AnchorPosition.center, true);
261 			auto radioButton1 = new RadioButton(radioLabelFrame, "Option 1")
262 				.setSelectedValue("1")
263 				.select()
264 				.pack(5);
265 			auto radioButton2 = new RadioButton(radioLabelFrame, "Option 2")
266 				.setSelectedValue("2")
267 				.pack(5);
268 			auto radioButton3 = new RadioButton(radioLabelFrame, "Option 3")
269 				.setSelectedValue("3")
270 				.pack(5);
271 
272 		return widgetPane;
273 	}
274 
275 	/**
276 	 * Create the paned pane for the note book.
277 	 *
278 	 * Returns:
279 	 *     The paned pane.
280 	 */
281 	private Frame createPanedPane()
282 	{
283 		auto panedPane = new Frame();
284 
285 			auto panedWindow = new PanedWindow(panedPane);
286 
287 				auto rows = new TreeViewRow(["Computer"], true, ["computer"]);
288 				rows.children ~= new TreeViewRow(["Documents"], true, ["folder"]);
289 					rows.children[0].children ~= new TreeViewRow(["Important notes.txt"], true, ["file"]);
290 					rows.children[0].children ~= new TreeViewRow(["The D Programming Language.pdf"], true, ["pdf"]);
291 				rows.children ~= new TreeViewRow(["Pictures"], true, ["folder"]);
292 					rows.children[1].children ~= new TreeViewRow(["Gary and Tessa.jpg"], true, ["image"]);
293 				rows.children ~= new TreeViewRow(["Videos"], true, ["folder"]);
294 					rows.children[2].children ~= new TreeViewRow(["Carlito's Way (1993).mpg"], true, ["video"]);
295 					rows.children[2].children ~= new TreeViewRow(["Aliens (1986).mpg"], true, ["video"]);
296 
297 				auto tree1 = new TreeView(panedWindow)
298 					.setHeading("Directory listing")
299 					.setTag("computer", new EmbeddedPng!("computer.png"))
300 					.setTag("folder", new EmbeddedPng!("folder.png"))
301 					.setTag("file", new EmbeddedPng!("page.png"))
302 					.setTag("pdf", new EmbeddedPng!("page_white_acrobat.png"))
303 					.setTag("video", new EmbeddedPng!("film.png"))
304 					.setTag("image", new EmbeddedPng!("images.png"))
305 					.addRow(rows);
306 
307 				auto tree2 = new TreeView(panedWindow)
308 					.setHeading("Film")
309 					.setWidth(250)
310 					.addColumn(new TreeViewColumn("Year").setWidth(20))
311 					.addColumn(new TreeViewColumn("IMDB ranking").setWidth(50))
312 					.addRow(new TreeViewRow(["The Shawshank Redemption", "1994", "1"]))
313 					.addRow(new TreeViewRow(["The Godfather", "1972", "2"]))
314 					.addRow(new TreeViewRow(["The Godfather: Part II", "1974", "3"]))
315 					.addRow(new TreeViewRow(["The Dark Knight", "2008", "4"]))
316 					.addRow(new TreeViewRow(["Pulp Fiction", "1994", "5"]))
317 					.addRow(new TreeViewRow(["The Good, the Bad and the Ugly", "1966", "6"]))
318 					.addRow(new TreeViewRow(["Schindler's List", "1993", "7"]))
319 					.addRow(new TreeViewRow(["Angry Men", "1957", "8"]))
320 					.addRow(new TreeViewRow(["The Lord of the Rings: The Return of the King", "2003", "9"]))
321 					.addRow(new TreeViewRow(["Fight Club", "1999", "10"]));
322 
323 		panedWindow
324 			.addPane(tree1).setPaneWeight(0, 1)
325 			.addPane(tree2).setPaneWeight(1, 1)
326 			.pack(10, 0, GeometrySide.top, GeometryFill.both, AnchorPosition.center, true);
327 
328 		return panedPane;
329 	}
330 
331 	/**
332 	 * Create the canvas pane for the note book.
333 	 *
334 	 * Returns:
335 	 *     The canvas pane.
336 	 */
337 	private Frame createCanvasPane()
338 	{
339 		auto canvasPane = new Frame();
340 
341 			auto container = new Frame(canvasPane)
342 				.pack(10, 0, GeometrySide.top, GeometryFill.both, AnchorPosition.center, true);
343 
344 			container.configureGeometryColumn(0, 1);
345 			container.configureGeometryRow(0, 1);
346 
347 				this._canvas = new Canvas(container, Color.bisque)
348 					.setCursor(Cursor.hand1)
349 					.setScrollRegion(-300, -250, 610, 500)
350 					.addItem(new CanvasRectangle([10, 10, 200, 100]).addTag("tagged"))
351 					.addItem(new CanvasArc([10, 110, 110, 210], CanvasArcStyle.pie, Color.paleGreen))
352 					.addItem(new CanvasImage([210, 10], new EmbeddedPng!("thumbnail.png")))
353 					.addItem(new CanvasImage([260, 10], new EmbeddedGif!("thumbnail.gif")))
354 					.addItem(new CanvasLine([120, 110, 200, 110, 200, 160]).setArrowPosition(CanvasLineArrow.last))
355 					.addItem(new CanvasOval([10, 170, 200, 245], Color.rosyBrown3))
356 					.addItem(new CanvasPolygon([220, 80, 320, 80, 300, 120, 240, 120], Color.mediumPurple))
357 					.addItem(new CanvasText([30, 192], "Tkd Canvas", Color.white).setFont("{Times New Roman} 22 bold"))
358 					.addItem(new CanvasWidget([220, 140], new Button("Embedded\nWidget", new EmbeddedPng!("error.png"))).setWidth(100).setHeight(100))
359 					.addTagConfig(new CanvasTagConfig("tagged").setFillColor(Color.salmon))
360 					.setXView(0.25)
361 					.setYView(0.24)
362 					.bind("<ButtonPress-1>", &this.mark)
363 					.bind("<B1-Motion>", &this.drag);
364 
365 				auto xscroll = new XScrollBar(container)
366 					.attachWidget(this._canvas)
367 					.grid(0, 1, 0, 0, 1, 1, "esw");
368 
369 				auto yscroll = new YScrollBar(container)
370 					.attachWidget(this._canvas)
371 					.grid(1, 0, 0, 0, 1, 1, "nes");
372 
373 				this._canvas
374 					.attachXScrollBar(xscroll)
375 					.attachYScrollBar(yscroll)
376 					.grid(0, 0, 0, 0, 1, 1, "nesw");
377 
378 		return canvasPane;
379 	}
380 
381 	/**
382 	 * Create the dialog pane for the note book.
383 	 *
384 	 * Returns:
385 	 *     The dialog pane.
386 	 */
387 	private Frame createDialogPane()
388 	{
389 		auto dialogPane = new Frame();
390 
391 			auto modalLabelFrame = new LabelFrame(dialogPane, "Modal")
392 				.configureGeometryColumn(1, 1)
393 				.pack(10, 0, GeometrySide.top, GeometryFill.both, AnchorPosition.center, true);
394 
395 				auto colorButton = new Button(modalLabelFrame, new EmbeddedPng!("color_swatch.png"), "Color", ImagePosition.left)
396 					.setCommand(&this.openColorDialog)
397 					.grid(0, 0, 10);
398 
399 				this._colorEntry = new Entry(modalLabelFrame)
400 					.grid(1, 0, 10, 0, 1, 1, "ew");
401 
402 				auto directoryButton = new Button(modalLabelFrame, new EmbeddedPng!("chart_organisation.png"), "Directory", ImagePosition.left)
403 					.setCommand(&this.openDirectoryDialog)
404 					.grid(0, 1, 10);
405 
406 				this._directoryEntry = new Entry(modalLabelFrame)
407 					.setWidth(40)
408 					.grid(1, 1, 10, 0, 1, 1, "ew");
409 
410 				auto fileOpenButton = new Button(modalLabelFrame, new EmbeddedPng!("folder_page.png"), "Open File", ImagePosition.left)
411 					.setCommand(&this.openOpenFileDialog)
412 					.grid(0, 2, 10);
413 
414 				this._openFileEntry = new Entry(modalLabelFrame)
415 					.setWidth(40)
416 					.grid(1, 2, 10, 0, 1, 1, "ew");
417 
418 				auto fileSaveButton = new Button(modalLabelFrame, new EmbeddedPng!("disk.png"), "Save File", ImagePosition.left)
419 					.setCommand(&this.openSaveFileDialog)
420 					.grid(0, 3, 10);
421 
422 				this._saveFileEntry = new Entry(modalLabelFrame)
423 					.setWidth(40)
424 					.grid(1, 3, 10, 0, 1, 1, "ew");
425 
426 				auto messageButton = new Button(modalLabelFrame, new EmbeddedPng!("comment.png"), "Message", ImagePosition.left)
427 					.setCommand(&this.openMessageDialog)
428 					.grid(0, 4, 10);
429 
430 				this._messageEntry = new Entry(modalLabelFrame)
431 					.setWidth(40)
432 					.grid(1, 4, 10, 0, 1, 1, "ew");
433 
434 			auto nonModalLabelFrame = new LabelFrame(dialogPane, "Non Modal")
435 				.configureGeometryColumn(1, 1)
436 				.pack(10, 0, GeometrySide.top, GeometryFill.both, AnchorPosition.center, true);
437 
438 				auto fontButton = new Button(nonModalLabelFrame, new EmbeddedPng!("style.png"), "Font", ImagePosition.left)
439 					.setCommand(&this.openFontDialog)
440 					.grid(0, 0, 10);
441 
442 				this._fontEntry = new Entry(nonModalLabelFrame)
443 					.setWidth(40)
444 					.grid(1, 0, 10, 0, 1, 1, "ew");
445 
446 		return dialogPane;
447 	}
448 
449 	/**
450 	 * Set up the key bindings for the application.
451 	 */
452 	private void setUpKeyBindings()
453 	{
454 		this.mainWindow.bind("<F1>", &this.showAbout);
455 		this.mainWindow.bind("<Control-q>", &this.exitApplication);
456 	}
457 
458 	/**
459 	 * Initialise the user interface.
460 	 */
461 	override protected void initInterface()
462 	{
463 		this.mainWindow.setTitle("Tkd Showcase");
464 		this.mainWindow.setMinSize(550, 560);
465 		this.mainWindow.setDefaultIcon([new EmbeddedPng!("tkicon.png")]);
466 
467 		this.mainWindow.setProtocolCommand(WindowProtocol.deleteWindow, delegate(CommandArgs args){
468 			this.showAbout(args);
469 			this.exitApplication(args);
470 		});
471 
472 		this.mainWindow.setIdleCommand(delegate(CommandArgs args){
473 			this._entry.setValue(Clock.currTime().toLocalTime().toSimpleString().findSplitBefore(".")[0]);
474 			this.mainWindow.setIdleCommand(args.callback, 1000);
475 		});
476 
477 		this.createMenu();
478 
479 		auto noteBook   = new NoteBook();
480 		auto widgetPane = this.createWidgetPane();
481 		auto panedPane  = this.createPanedPane();
482 		auto canvasPane = this.createCanvasPane();
483 		auto dialogPane = this.createDialogPane();
484 
485 		noteBook
486 			.addTab("Widgets", widgetPane).setTabImage(0, new EmbeddedPng!("layout_content.png"), ImagePosition.left)
487 			.addTab("Panes", panedPane).setTabImage(1, new EmbeddedPng!("application_tile_horizontal.png"), ImagePosition.left)
488 			.addTab("Canvas", canvasPane).setTabImage(2, new EmbeddedPng!("shape_ungroup.png"), ImagePosition.left)
489 			.addTab("Dialogs", dialogPane).setTabImage(3, new EmbeddedPng!("application_double.png"), ImagePosition.left)
490 			.pack(10, 0, GeometrySide.top, GeometryFill.both, AnchorPosition.center, true);
491 
492 		auto exitButton = new Button("Exit")
493 			.setCommand(&this.exitApplication)
494 			.pack(5);
495 
496 		auto sizeGrip = new SizeGrip(this.mainWindow)
497 			.pack(0, 0, GeometrySide.bottom, GeometryFill.none, AnchorPosition.southEast);
498 
499 		this.setUpKeyBindings();
500 	}
501 
502 	/**
503 	 * Exit the application.
504 	 *
505 	 * Params:
506 	 *     args = The command arguments.
507 	 */
508 	private void exitApplication(CommandArgs args)
509 	{
510 		this.exit();
511 	}
512 
513 }
514 
515 /**
516  * Main entry point.
517  *
518  * Params:
519  *     args = An array of command line arguments passed to this program.
520  */
521 void main(string[] args)
522 {
523 	auto app = new Application();
524 	app.run();
525 }