1 // Copyright (c) 2017-2018 Matthew Brennan Jones <matthew.brennan.jones@gmail.com>
2 // Boost Software License - Version 1.0
3 // A simple progress dialog for the D programming language
4 // https://github.com/workhorsy/d-progress-dialog
5 
6 /++
7 A simple progress dialog for the D programming language
8 
9 It should work without requiring any 3rd party GUI toolkits. But will work with what it can find on your OS at runtime.
10 
11 Tries to make a progress dialog with:
12 
13 * DlangUI (win32 on Windows or SDL2 on Linux)
14 
15 * Zenity (Gtk/Gnome)
16 
17 * Kdialog (KDE)
18 
19 Home page:
20 $(LINK https://github.com/workhorsy/d-progress-dialog)
21 
22 Version: 0.3.0
23 
24 License:
25 Boost Software License - Version 1.0
26 
27 Examples:
28 ----
29 import std.stdio : stdout, stderr;
30 import progress_dialog : ProgressDialog, RUN_MAIN;
31 
32 mixin RUN_MAIN;
33 
34 extern (C) int UIAppMain(string[] args) {
35 	import core.thread;
36 
37 	// Create the dialog
38 	auto dialog = new ProgressDialog("It's waitin' time!", "Waiting ...");
39 
40 	// Set the error handler
41 	dialog.onError((Throwable err) {
42 		stderr.writefln("Failed to show progress dialog: %s", err);
43 		dialog.close();
44 	});
45 
46 	// Show the progress dialog
47 	dialog.show({
48 		// Update the progress for 5 seconds
49 		int percent = 0;
50 		while (percent < 100) {
51 			dialog.setPercent(percent);
52 			percent += 20;
53 			Thread.sleep(1.seconds);
54 			stdout.writefln("percent: %s", percent);
55 			stdout.flush();
56 		}
57 
58 		// Close the dialog
59 		dialog.close();
60 	});
61 
62 	return 0;
63 }
64 
65 ----
66 +/
67 
68 module progress_dialog;
69 
70 bool is_sdl2_loadable = false;
71 bool use_log = false;
72 
73 static this() {
74 	import std.stdio : stdout;
75 
76 	// Figure out if the SDL2 libraries can be loaded
77 	version (Have_derelict_sdl2) {
78 		import derelict.util.exception : SharedLibLoadException;
79 		import derelict.sdl2.sdl : DerelictSDL2, SharedLibVersion;
80 		try {
81 			DerelictSDL2.load(SharedLibVersion(2, 0, 2));
82 			is_sdl2_loadable = true;
83 			stdout.writefln("SDL was found ...");
84 		} catch (SharedLibLoadException) {
85 			stdout.writefln("SDL was NOT found ...");
86 		}
87 	}
88 }
89 
90 /++
91 This should be called once at the start of a program. It generates the proper
92 main function for your environment (win32/posix/dmain) and boot straps the
93 main loop for the GUI. This will call your UIAppMain function when ready.
94 +/
95 mixin template RUN_MAIN() {
96 	version (unittest) { } else {
97 		// On Windows use the normal dlangui main
98 		version (Windows) {
99 			import dlangui;
100 			mixin APP_ENTRY_POINT;
101 		// On Linux use a custom main that checks if SDL is installed
102 		} else {
103 			int main(string[] args) {
104 				import progress_dialog : is_sdl2_loadable;
105 				// If SDL2 can be loaded, start the SDL2 main
106 				if (is_sdl2_loadable) {
107 					import dlangui.platforms.sdl.sdlapp : sdlmain;
108 					return sdlmain(args);
109 				// If not, use the normal main provided by the user
110 				} else {
111 					return UIAppMain(args);
112 				}
113 			}
114 		}
115 	}
116 }
117 
118 /++
119 If true will print output of external program to console.
120 Params:
121  is_logging = If true will print to output
122 +/
123 public void setUseLog(bool is_logging) {
124 	use_log = is_logging;
125 }
126 
127 /++
128 Returns if external program logging is on or off.
129 +/
130 public bool getUseLog() {
131 	return use_log;
132 }
133 
134 abstract class ProgressDialogBase {
135 	this(string title, string message) {
136 		_title = title;
137 		_message = message;
138 	}
139 
140 	void onError(void delegate(Throwable err) cb) {
141 		_on_error_cb = cb;
142 	}
143 
144 	void fireOnError(Throwable err) {
145 		auto old_cb = _on_error_cb;
146 		_on_error_cb = null;
147 
148 		if (old_cb) old_cb(err);
149 	}
150 
151 	void show(void delegate() cb);
152 	void setPercent(int percent);
153 	void close();
154 
155 	string _title;
156 	string _message;
157 	void delegate(Throwable err) _on_error_cb;
158 }
159 
160 /++
161 The ProgressDialog class
162 +/
163 class ProgressDialog {
164 	import progress_dialog_zenity : ProgressDialogZenity;
165 	import progress_dialog_kdialog : ProgressDialogKDialog;
166 	//import progress_dialog_win32 : ProgressDialogWin32;
167 	import progress_dialog_dlangui : ProgressDialogDlangUI;
168 
169 	/++
170 	Sets up the progress dialog with the desired title, and message. Does not
171 	show it until the show method is called.
172 	Params:
173 	 title = The string to show in the progress dialog title
174 	 message = The string to show in the progress dialog body
175 	Throws:
176 	 If it fails to find any programs or libraries to make a progress dialog with.
177 	+/
178 	this(string title, string message) {
179 		/*if (ProgressDialogWin32.isSupported()) {
180 			_dialog = new ProgressDialogWin32(title, message);
181 		} else */
182 
183 		if (ProgressDialogDlangUI.isSupported()) {
184 			_dialog = new ProgressDialogDlangUI(title, message);
185 		} else if (ProgressDialogZenity.isSupported()) {
186 			_dialog = new ProgressDialogZenity(title, message);
187 		} else if (ProgressDialogKDialog.isSupported()) {
188 			_dialog = new ProgressDialogKDialog(title, message);
189 		} else {
190 			throw new Exception("Failed to find a way to make a progress dialog.");
191 		}
192 	}
193 
194 	/++
195 	This method is called if there is an error when showing the progress dialog.
196 	Params:
197 	 cb = The call back to fire when there is an error.
198 	+/
199 	void onError(void delegate(Throwable err) cb) {
200 		_dialog._on_error_cb = cb;
201 	}
202 
203 	/++
204 	Shows the progress dialog. Will run the callback in a thread and
205 	block until it is closed or percent reaches 100.
206 	+/
207 	void show(void delegate() cb) {
208 		_dialog.show(cb);
209 	}
210 
211 	/++
212 	Set the percent of the progess bar. Will close on 100.
213 	Params:
214 	 percent = from 0 to 100
215 	+/
216 	void setPercent(int percent) {
217 		_dialog.setPercent(percent);
218 	}
219 
220 	/++
221 	Close the dialog.
222 	+/
223 	void close() {
224 		_dialog.close();
225 	}
226 
227 	ProgressDialogBase _dialog;
228 }