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