jsugar

README.md:

- Added a new limitation, regarding the omitting of some components. - Reserved the right to be lazy. =3 Window.java - Added a TODO list, and a DONE list, so I have some perspective on the progress for myself. - Added a couple of extra assertions to ensure the program's correctness. - Updated the createRadioButtons() method, which now groups the radio buttons toghether, so only one can be selected at a time.

Author
Vngngdn
Date
Aug. 10, 2016, 4:57 p.m.
Hash
333bf5782441615fdb5a95187aa4705ff2e4f1dc
Parent
44274335ba7708f829b41feed4164af7077170dd
Modified files
README.md
Window.java

README.md

13 additions and 3 deletions.

View changes Hide changes
1
1
======
2
2
3
3
JSugar is a tiny, stupid framework, in an attempt to hide away the tons of
4
4
useless cruft that you get from working with Java's Swing.
5
5
6
6
Features
7
7
--------
8
8
9
9
Purely speaking, JSugar does not add anything new compared to when you're using
10
10
Swing. It does however, offer some considerable advantages over using Swing
11
11
directly:
12
12
13
13
* Easy creation of windows, that offer a series of convenient methods like
14
14
  createButton(), allowing for easy creation of small GUI programs.
15
15
* Built-in support for action triggering; Just say which method should be
16
16
  triggered where, and you're done.
17
17
* Easy learning curve, compared to manually handling Swing. Create a new Window,
18
18
  slap some components on it, add the methods it needs to call on trigger, and
19
19
  done.
20
20
* Relies mainly on primitive types, like integer arrays, and classes that are
21
21
  available in every recent OpenJDK version, like Strings.
22
22
* Completely [free software](https://www.gnu.org/philosophy/free-sw.html).
23
23
* Very lightweight. It's just a bunch of source files that you can directly link
24
24
  to. Putting it in a seperate jar will cause more harm than good.
25
25
* Only RuntimeExceptions will be thrown, avoiding *exception infection* that
26
26
  you'll get from using self-defined exceptions in Java.
27
27
* Documentation available through JavaDoc. I do my very best to provide clear
28
28
  documentation, so you know how to use this without having to figure it out
29
29
  yourself.
30
30
* Components you add are returned to the caller, so if you do need some more
31
31
  advanced stuff, you can add it yourself.
32
32
33
33
Limitations
+
34
version, but some might not (Don't expect JSugar to become a thread-safe Swing
+
35
framework, Swing will stay Swing). I reserve the right to be lazy.
+
36
+
37
Limitations
34
38
-----------
35
39
36
40
The convenience causes some limitations, but they're fairly minor, and if you're
37
41
using JSugar, it's very unlikely you'd be bothered by them anyway, but here they
38
42
are:
39
43
40
44
* You can't add your own panels to the window, but you'll most likely just want
41
45
  to add some components to the window itself.
42
46
* The panels default to double buffering (which you should do anyway) and the
43
47
  flow layout.
44
48
* The window is automatically updated whenever a new component is added. When
45
49
  using Swing 'natively', you could postpone updating, but why did you add a
46
50
  component then in the first place?
47
51
* *Trigger methods* can only have 1 parameter, or none at all. That 1 parameter
48
52
  must be of type java.awt.event.ActionEvent. This should be enough for >80% of
49
53
  use cases, and if you really need more flexibility, you can add your own
50
54
  action handlers manually.
51
55
* Pressing the X in the title bar of the window closes it. >95% of use cases do
52
56
  this anyway.
53
57
* Some silly stuff like adding icons to buttons is not possible, you'll have to
54
-
  do that yourself. Yet for most use cases, you might just do the sane thing and
55
-
  add text.
56
-
* Certain components don't offer the ability to attach a trigger action to them.
+
58
  possible. You'll have to do that yourself. Yet for most use cases,you might
+
59
  just do the sane thing and add text.
+
60
* Certain components don't offer the ability to attach a trigger action to them.
57
61
  We're talking about components like labels. But then again, these kind of
58
62
  components shouldn't get much triggers anyway.
59
63
+
64
  information/interaction for the user. An example for this, is the
+
65
  JProgressBar. Although in some rare cases it's a very useful thing, but it's
+
66
  mainly eye candy, and you may just show the data in a JLabel anyway.
+
67
  However, if I feel like doing so, and the rest of the library is stable, I may
+
68
  add such components.
+
69

Window.java

61 additions and 1 deletion.

View changes Hide changes
1
1
 * Window.java - Module to create a new window with JSugar.
2
2
 * Copyright © 2016 Maarten "Vngngdn" Vangeneugden
3
3
 * 
4
4
 * This program is free software: you can redistribute it and/or modify
5
5
 * it under the terms of the GNU General Public License as published by
6
6
 * the Free Software Foundation, either version 3 of the License, or
7
7
 * (at your option) any later version.
8
8
 * 
9
9
 * This program is distributed in the hope that it will be useful,
10
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
12
 * GNU General Public License for more details.
13
13
 * 
14
14
 * You should have received a copy of the GNU General Public License
15
15
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
16
 */
17
17
18
18
/**
+
19
 * TODO list:
+
20
 * - JList
+
21
 * - JSlider
+
22
 * - JTable (And a JScrollBar to accompany it)
+
23
 * - JComboBox
+
24
 * - JFileChooser (?)
+
25
 * - JSpinner (Number scoller input widget)
+
26
 * DONE list:
+
27
 * - JLabel
+
28
 * - JText
+
29
 * - JButton
+
30
 * - JDialogBoxes (you know, everything dialog related)
+
31
 * - JCheckbox
+
32
 * - JRadioButton (properly grouping them has been taken care of as well)
+
33
 */
+
34
+
35
/**
19
36
 * Window class for the program.
20
37
 *
21
38
 * Window contains the necessary data and methods to present the user with what
22
39
 * he's familiar with as being a "window". To make it functional, the developer
23
40
 * can make use of a series of methods to add components to said window, remove
24
41
 * components, and so on.
25
42
 * Currently, Window also contains methods to show dialogs. This will be cleaned
26
43
 * in the near future.
27
44
 * @author Maarten Vangeneugden
28
45
 */
29
46
import javax.swing.*; // FIXME: Maybe namespacing it to "javax.swing;" is a better idea.
30
47
import java.util.NoSuchElementException;
31
48
import java.lang.reflect.Method;
32
49
33
50
class Window {
34
51
	private JPanel panel; // The panel that contains all the components.
35
52
	private JFrame frame; // The "window" being presented to the user.
36
53
37
54
	/**
38
55
	 * Constructor of Window.
39
56
	 * By creating a new Window instance, this constructor will automatically
40
57
	 * start the initialization of the GUI. After doing so, the caller can
41
58
	 * start adding components to the window as pleased.
42
59
	 * @param title The title to be shown in the window's title bar.
43
60
	 */
44
61
	public Window() {
45
62
		this.panel = new JPanel();
46
63
		// TODO: The current title is "Hello world!" but that will become caller
47
64
		// defined soon.
48
65
		JFrame frame = new JFrame("Hello world!");
49
66
		// Makes it so that if the user clicks the X in the titlebar, the window
50
67
		// closes:
51
68
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
52
69
		//frame.getContentPane().add(lblHelloWorld); // So you use a get() in order to set() data? #JavaWTF
53
70
		frame.setContentPane(this.panel); // Connecting the component panel to the window.
54
71
		// Makes the window fit to the necessary width and height, so it can show all "subcomponents".
55
72
		frame.pack(); 	
56
73
		frame.setVisible(true); // Makes the window visible to the user.
57
74
		this.frame = frame;
58
75
	}
59
76
60
77
	/**
61
78
	 * Resizes the window to fit all components.
62
79
	 * By calling this method, the window will evaluate the currently visible
63
80
	 * components, and resize itself so that all components become properly
64
81
	 * visible.
65
82
	 */
66
83
	private void updateWindow() {
67
84
		this.frame.pack();
68
85
	}
69
86
70
87
	/**
71
88
	 * A series of tests for method and class handling.
72
89
	 * When a caller presents certain methods with data concerning reflection,
73
90
	 * the Java classes you need to use for that are quite opaque, and don't
74
91
	 * offer much safety in any way.
75
92
	 * The solution therefore, is run some validation checks, but these take up
76
93
	 * a decent amount of space in terms of LoC.
77
94
	 * This method takes care of all that. Call this function whenever data
78
95
	 * needs to be validated.
79
96
	 * @param methodName The name of the method, as it is declared in object.
80
97
	 * @param object The class instance in where this method will be called.
81
98
	 * @return The method that could be derived from the supplied data, or null
82
99
	 * if that wasn't possible.
83
100
	 * @throws NullPointerException if either methodName or object are null
84
101
	 * pointers.
85
102
	 * @throws IllegalArgumentException if methodName is empty, or the method
86
103
	 * does not appear to be declared in the given object, or object is not a
87
104
	 * class.
88
105
	 */
89
106
	// All unchecked typecasts are safe, and the use of raw types is taken care
90
107
	// of.
91
108
	@SuppressWarnings({"unchecked","rawtypes"})
92
109
	private Method handleReflectionData(String methodName, Object object) {
93
110
		// Null pointer checking:
94
111
		if (methodName == null || object == null) {
95
112
			throw new NullPointerException("One or more of the given parameters are null pointers.");
96
113
		}
97
114
98
115
		// XXX: Some might say the next line should be in an else{} block. But
99
116
		// Scoping rules require that I'd then have to wrap the rest of the
100
117
		// method in the same else to use it.
101
118
		Class methodClass = object.getClass(); 
102
119
		if (methodName.equals("")) {
103
120
			throw new IllegalArgumentException("The given methodName was empty.");
104
121
		}
105
122
		Method method;
106
123
		try { // First: Look if there's a method without parameters.
107
124
			method = methodClass.getMethod(methodName, null);
108
125
		}
109
126
		catch (NoSuchMethodException exception) {
110
127
			try {
111
128
				// It's possible that the method requires an event parameter, so
112
129
				// check for that as well:
113
130
				Class<?>[] parameters = {java.awt.event.ActionEvent.class};
114
131
				method = methodClass.getMethod(methodName, parameters);
115
132
			}
116
133
			catch (NoSuchMethodException e) {
117
134
				throw new IllegalArgumentException("The given method does not appear in the given class. Be aware that the given method mustn't have any parameters, or only 1 parameter, which has to be of type java.awt.event.ActionEvent.");
118
135
			}
119
136
		}
120
137
		// At this stage, the given data has been validated, and we've been able
121
138
		// to retrieve the method itself.
122
139
		return method;
123
140
	}
124
141
125
142
	/**
126
143
	 * Creates a button in the GUI for interaction.
127
144
	 * This function offers a convenient way to create a button, that can be
128
145
	 * directly interacted with by the user. After creation, the button itself
129
146
	 * is returned to the caller, if he wishes to do something else with it.
130
147
	 * @param text The text that will be displayed in the button.
131
148
	 * @param action The action that will be returned to the action listener.
132
149
	 * @param methodName The name of the method that will be called when an
133
150
	 * action is triggered.
134
151
	 * @param objectInstance The object instance that contains the given method.
135
152
	 * This may only be a null pointer if triggerMethod is not an instance
136
153
	 * method.
137
154
	 * performed. This method may accept an ActionEvent parameter as its only
138
155
	 * parameter, or no parameters at all.
139
156
	 * @throws NullPointerException if triggerMethod is a null pointer, or
140
157
	 * the empty String was given.
141
158
	 * @throws IllegalArgumentException if triggerMethod has more than 1
142
159
	 * parameter, or the 1 required parameter is not of type ActionEvent.
143
160
	 * @return The button that was created.
144
161
	 * @see java.awt.event.ActionEvent
145
162
	 * @see java.lang.reflect.Method.invoke()
146
163
	 */
147
164
	public JButton createButton(String text, String action, String methodName, Object triggerObject) {
148
165
		Method triggerMethod = this.handleReflectionData(methodName, triggerObject);
149
166
150
167
		// For starters, we first assert that the given parameters are valid:
151
168
		if (text == null) {
152
169
			text = "";
153
170
		}
154
171
		if (action == null) {
155
172
			action = "";
156
173
		}
157
174
		
158
175
		// When the method gets here, everything's been validated correctly.
159
176
		JButton button = new JButton(text);
160
177
		button.setActionCommand(action);
161
178
		button.addActionListener(
162
179
				new java.awt.event.ActionListener() {
163
180
					public void actionPerformed(java.awt.event.ActionEvent event) {
164
181
						try {
165
182
							triggerMethod.setAccessible(true);
166
183
							if (triggerMethod.getParameterTypes().length == 0) {
167
184
								// FIXME: Next line throws a warning?
168
185
								triggerMethod.invoke(triggerObject, null);
169
186
							}
170
187
							else {
171
188
								triggerMethod.invoke(triggerObject, new Object[]{event});
172
189
							}
173
190
						}
174
191
						catch (Exception useless) {
175
192
							/*
176
193
							 * XXX: Some info on why I don't just throw said
177
194
							 * Exception to the caller:
178
195
							 * Java has this awful language constraint, which
179
196
							 * forces every damn exception that isn't a subclass
180
197
							 * of RuntimeException, to be declared in the method
181
198
							 * declaration. This tends to infect all underlying
182
199
							 * methods as well, and all that for reasons I can't
183
200
							 * comprehend. In order to keep JSugar a simple and
184
201
							 * clean library, I'll rather just handle it here,
185
202
							 * and throw a RuntimeException with appropriate
186
203
							 * details.
187
204
							 */
188
205
							throw new IllegalArgumentException("triggerMethod is not accessible from this context.");
189
206
						}
190
207
					}
191
208
				});
192
209
		this.addComponent(button);
193
210
		return button;
194
211
	}
195
212
196
213
	/**
197
214
	 * Ask the user for input through a dialog box.
198
215
	 * This method presents the user with an input field, that can accept
199
216
	 * textual input. The method will return the given input after the user's
200
217
	 * clicked a button to send.
201
218
	 * @param text The text/question to be asked to the user.
202
219
	 * @return A String, equal to what the user entered.
203
220
	 * @throws NullPointerException if text is a null pointer.
204
221
	 */
205
222
	public String inputDialog(String text) {
206
223
		if (text == null) {
207
224
			throw new NullPointerException("The given text/question was a null pointer.");
208
225
		}
209
226
		return JOptionPane.showInputDialog(text);
210
227
	}
211
228
212
229
	/**
213
230
	 * Give the user a dialog box.
214
231
	 * This method can be used to provide a simple dialog to the user.
215
232
	 * This will show the user the given question, after which a boolean value
216
233
	 * is returned, holding the choice.
217
234
	 * @param text The text/question to be asked to the user.
218
235
	 * @return True if the user confirms, False if he denies.
219
236
	 * @throws NullPointerException if text is a null pointer.
220
237
	 */
221
238
	public boolean confirmDialog(String text) {
222
239
		if (text == null) {
223
240
			throw new NullPointerException("The given text/question was a null pointer.");
224
241
		}
225
242
		final int ACCEPTED = 0;
226
243
		final int DENIED = 1;
227
-
		int result = this.choiceDialog(text, new String[]{"Confirm", "Deny"});
+
244
		int result = this.choiceDialog(text, new String[]{"Confirm", "Deny"});
228
245
		if (result == ACCEPTED) {
229
246
			return true;
230
247
		}
231
248
		else {
232
249
			return false;
233
250
		}
234
251
	}
235
252
236
253
	/**
237
254
	 * Give the user a choice dialog box.
238
255
	 * This method gives the user a simple dialog with predefined choices.
239
256
	 * These choices are to be provided by the caller in a simple array.
240
257
	 * Tip: This method works extremely well with arbitrary created choices.
+
258
	 * Tip: This method works extremely well with arbitrary created choices.
241
259
	 * That is: if the outcome of the dialog is trivial (e.g. Only 1 choice),
242
260
	 * then that value is immediately returned.
243
261
	 * @param text The text/question to be asked to the user.
244
262
	 * @param choices An array of Strings, containing the choices the user can
245
263
	 * pick.
246
264
	 * @return The index value of the picked choice, or -1 if no choices were
247
265
	 * given.
248
266
	 * @throws NullPointerException if text is a null pointer.
249
267
	 */
250
268
	public int choiceDialog(String text, String[] choices) {
251
269
		if (text == null) {
252
270
			throw new NullPointerException("The given text/question was a null pointer.");
253
271
		}
254
272
		// First: handling the trivial cases:
255
273
		if (choices.length == 0) {
256
274
			return -1;
257
275
		}
258
276
		else if (choices.length == 1) {
259
277
			return 0;
260
278
		}
261
279
		int answer = JOptionPane.CLOSED_OPTION;
262
280
		// The dialog needs to be shown again until the user has made a possible
263
281
		// choice, i.e. Chickening out using the close button is not possible
264
282
		// (Because that returns CLOSED_OPTION).
265
283
		while (answer == JOptionPane.CLOSED_OPTION) {
266
284
				JOptionPane.showOptionDialog(
267
285
					null, // The parent component. May become the panel?
268
286
					text, // The text/question to describe the goal
269
287
					"Dialog", // The text in the title bar
270
288
					JOptionPane.DEFAULT_OPTION, // The kind of available options
271
289
					JOptionPane.QUESTION_MESSAGE, // The type of message
272
290
					null, // The icon to show
273
291
					choices, // The possible choices
274
292
					choices[0] // The standard choice
275
293
					);
276
294
		}
277
295
		return answer;
278
296
	}
279
297
		
280
298
281
299
	/**
282
300
	 * Creates a label in the GUI for interaction.
283
301
	 * This function offers a convenient way to create a label, that can be
284
302
	 * directly interacted with by the user. After creation, the label itself
285
303
	 * is returned to the caller, if he wishes to do something else with it.
286
304
	 * @param text The text that will be displayed in the label.
287
305
	 * @return The label that was created.
288
306
	 */
289
307
	public JLabel createLabel(String text) {
290
308
		JLabel label = new JLabel(text);
291
309
		this.addComponent(label);
292
310
		return label;
293
311
	}
294
312
295
313
	/**
+
314
	 * Adds a checkbox to the window.
+
315
	 * By providing a String, you can use this method to easily
+
316
	 * create a checkbox, and add it to the window. 
+
317
	 */
+
318
	public JCheckBox createCheckbox(String text) {
+
319
		JCheckBox checkbox = new JCheckBox(text);
+
320
		this.addComponent(checkbox);
+
321
		return checkbox;
+
322
	}
+
323
+
324
	/**
+
325
	 * Adds radio buttons to the window.
+
326
	 * Given a list of Strings, this method will create the same amount of radio
+
327
	 * buttons.
+
328
	 *
+
329
	 * The radio buttons will silently be grouped in a ButtonGroup object,
+
330
	 * making them automatically disable each other, so only 1 radio button can
+
331
	 * be enabled. This ButtonGroup is immutable.
+
332
	 *
+
333
	 * If you need a mutable ButtonGroup, create your own, and use the {@link
+
334
	 * #addComponent()} method to add the radio buttons manually.
+
335
	 * @param text An array of Strings. The length of the array will determine
+
336
	 * the amount of radio buttons that will be created.
+
337
	 * @return An array of radio buttons, in the same order as text.
+
338
	 */
+
339
	public JRadioButton[] createRadioButtons(String text[]) {
+
340
		JRadioButton[] radioButtons = new JRadioButton[text.length];
+
341
		ButtonGroup buttonGroup = new ButtonGroup();
+
342
		for (int i=0; i<radioButtons.length; i++) {
+
343
			radioButtons[i].setText(text[i]);
+
344
			buttonGroup.add(radioButtons[i]);
+
345
			this.addComponent(radioButtons[i]);
+
346
		}
+
347
+
348
		assert radioButtons.length == buttonGroup.getButtonCount() : "The amount of radio buttons ("+ radioButtons.length +") differs from the amount of buttons in buttonGroup ("+ buttonGroup.getButtonCount() +").";
+
349
		return radioButtons;
+
350
	}
+
351
+
352
	/**
296
353
	 * Adds the given component to the GUI.
297
354
	 * This method allows its caller to give a pre-made component, so that it
298
355
	 * can be added to the GUI. Even though its main use is for the Window class
299
356
	 * itself, the user of JSugar can also use it to create components himself,
300
357
	 * and then add them. As such, this method doesn't provide parameters for
301
358
	 * reflection/action triggering purposes.
302
359
	 * @param component The component to be added to the window.
303
360
	 * @throws NullPointerException if the given component is a null pointer.
304
361
	 */
305
362
	public void addComponent(JComponent component) {
306
363
		this.panel.add(component); // Throws the exception if null.
+
364
		this.panel.add(component); // Throws the exception if null.
307
365
		this.updateWindow();
308
366
	}
+
367
		assert originalSize == this.panel.getComponentCount()-1 : "A component was supposed to be added to the window, but the total amount of components was unchanged after the addition.";
+
368
	}
309
369
310
370
	/**
311
371
	 * Removes the given component from the GUI.
312
372
	 * This method allows its caller to remove a component from the GUI.
313
373
	 * @param component The component to be removed.
314
374
	 * @throws NoSuchElementException if the given component does not exist in
315
375
	 * the GUI.
316
376
	 * @throws NullPointerException if the given component is a null pointer.
317
377
	 */
318
378
	public void removeComponent(JComponent component) {
319
379
		int originalSize = this.panel.getComponentCount();
320
380
		this.panel.remove(component);
321
381
		int newSize = this.panel.getComponentCount();
322
382
		if (originalSize != newSize+1) {
323
383
			throw new NoSuchElementException("The given component does not exist in the GUI.");
324
384
		}
325
385
		this.updateWindow();
326
386
	}
327
387
}
328
388