OOP2

Window.java

1
/**
2
 * Window.java - Module to create a new window with JSugar.
3
 * Copyright © 2016 Maarten "Vngngdn" Vangeneugden
4
 * 
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 * 
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 * 
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
 */
18
19
/*
20
 * TODO list:
21
 * - JSlider (It's the same as the JSpinner, only longer. So an extra.)
22
 * - JTable (And a JScrollBar to accompany it) (extra, because of JList)
23
 * - JFileChooser (?)
24
 * DONE list:
25
 * - JLabel
26
 * - JText
27
 * - JButton
28
 * - JDialogBoxes (you know, everything dialog related)
29
 * - JCheckbox
30
 * - JRadioButton (properly grouping them has been taken care of as well)
31
 * - JSpinner
32
 * - JComboBox
33
 * - JList
34
 */
35
36
import javax.swing.*; // FIXME: Maybe namespacing it to "javax.swing;" is a better idea.
37
import java.util.NoSuchElementException;
38
import java.lang.reflect.Method;
39
import java.io.File;
40
import java.awt.event.WindowEvent; // For programatically closing the window
41
42
/**
43
 * Window class for the program.
44
 *
45
 * Window contains the necessary data and methods to present the user with what
46
 * he's familiar with as being a "window". To make it functional, the developer
47
 * can make use of a series of methods to add components to said window, remove
48
 * components, and so on.
49
 * Currently, Window also contains methods to show dialogs. This will be cleaned
50
 * in the near future.
51
 * @author Maarten Vangeneugden
52
 */
53
public class Window { // Must be public, in order to generate Javadoc.
54
	private JPanel panel; // The panel that contains all the components.
55
	private JFrame frame; // The "window" being presented to the user.
56
57
	/**
58
	 * Constructor of Window.
59
	 * By creating a new Window instance, this constructor will automatically
60
	 * start the initialization of the GUI. After doing so, the caller can
61
	 * start adding components to the window as pleased.
62
	 * @param title The title to be shown in the window's title bar.
63
	 */
64
	public Window(String title) {
65
		// Setting the UI style to the platform's UI style. Fuck Swing's,
66
		// really.
67
		try {
68
		UIManager.setLookAndFeel(
69
				UIManager.getSystemLookAndFeelClassName());
70
		} catch(Exception e) {
71
			e.printStackTrace();
72
		}
73
74
		if(title == null || title.equals("")) { // If the title was omitted:
75
			title = "JSugar";
76
		}
77
		this.panel = new JPanel();
78
		// TODO: The current title is "Hello world!" but that will become caller
79
		// defined soon.
80
		JFrame frame = new JFrame(title);
81
		// Makes it so that if the user clicks the X in the titlebar, the window
82
		// closes:
83
		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // XXX: Closes
84
		//ALL open windows!
85
		//frame.getContentPane().add(lblHelloWorld); // So you use a get() in order to set() data? #JavaWTF
86
		frame.setContentPane(this.panel); // Connecting the component panel to the window.
87
		// Makes the window fit to the necessary width and height, so it can show all "subcomponents".
88
		frame.pack(); 	
89
		frame.setVisible(true); // Makes the window visible to the user.
90
		this.frame = frame;
91
	}
92
93
	/**
94
	 * Resizes the window to fit all components.
95
	 * By calling this method, the window will evaluate the currently visible
96
	 * components, and resize itself so that all components become properly
97
	 * visible.
98
	 */
99
	private void updateWindow() {
100
		this.frame.pack();
101
	}
102
103
	/**
104
	 * A series of tests for method and class handling.
105
	 * When a caller presents certain methods with data concerning reflection,
106
	 * the Java classes you need to use for that are quite opaque, and don't
107
	 * offer much safety in any way.
108
	 * The solution therefore, is run some validation checks, but these take up
109
	 * a decent amount of space in terms of LoC.
110
	 * This method takes care of all that. Call this function whenever data
111
	 * needs to be validated.
112
	 * @param methodName The name of the method, as it is declared in object.
113
	 * @param object The class instance in where this method will be called.
114
	 * @return The method that could be derived from the supplied data, or null
115
	 * if that wasn't possible.
116
	 * @throws NullPointerException if either methodName or object are null
117
	 * pointers.
118
	 * @throws IllegalArgumentException if methodName is empty, or the method
119
	 * does not appear to be declared in the given object, or object is not a
120
	 * class.
121
	 */
122
	// All unchecked typecasts are safe, and the use of raw types is taken care
123
	// of.
124
	@SuppressWarnings({"unchecked","rawtypes"})
125
	private Method handleReflectionData(String methodName, Object object) {
126
		// Null pointer checking:
127
		if (methodName == null || object == null) {
128
			throw new NullPointerException("One or more of the given parameters are null pointers.");
129
		}
130
131
		// XXX: Some might say the next line should be in an else{} block. But
132
		// Scoping rules require that I'd then have to wrap the rest of the
133
		// method in the same else to use it.
134
		Class methodClass = object.getClass(); 
135
		if (methodName.equals("")) {
136
			throw new IllegalArgumentException("The given methodName was empty.");
137
		}
138
		Method method;
139
		try { // First: Look if there's a method without parameters.
140
			method = methodClass.getMethod(methodName, null);
141
		}
142
		catch (NoSuchMethodException exception) {
143
			try {
144
				// It's possible that the method requires an event parameter, so
145
				// check for that as well:
146
				Class<?>[] parameters = {java.awt.event.ActionEvent.class};
147
				method = methodClass.getMethod(methodName, parameters);
148
			}
149
			catch (NoSuchMethodException e) {
150
				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.");
151
			}
152
		}
153
		// At this stage, the given data has been validated, and we've been able
154
		// to retrieve the method itself.
155
		return method;
156
	}
157
158
	/**
159
	 * Creates a button in the GUI for interaction.
160
	 * This function offers a convenient way to create a button, that can be
161
	 * directly interacted with by the user. After creation, the button itself
162
	 * is returned to the caller, if he wishes to do something else with it.
163
	 * @param text The text that will be displayed in the button.
164
	 * @param action The action that will be returned to the action listener.
165
	 * @param methodName The name of the method that will be called when an
166
	 * action is triggered.
167
	 * @param triggerObject The object instance that contains the given method.
168
	 * This may only be a null pointer if triggerMethod is not an instance
169
	 * method.
170
	 * performed. This method may accept an ActionEvent parameter as its only
171
	 * parameter, or no parameters at all.
172
	 * @throws NullPointerException if triggerMethod is a null pointer, or
173
	 * the empty String was given.
174
	 * @throws IllegalArgumentException if triggerMethod has more than 1
175
	 * parameter, or the 1 required parameter is not of type ActionEvent.
176
	 * @return The button that was created.
177
	 * @see java.awt.event.ActionEvent
178
	 * @see java.lang.reflect.Method#invoke
179
	 */
180
	public JButton createButton(String text, String action, String methodName, Object triggerObject) {
181
		Method triggerMethod = this.handleReflectionData(methodName, triggerObject);
182
183
		// For starters, we first assert that the given parameters are valid:
184
		if (text == null) {
185
			text = "";
186
		}
187
		if (action == null) {
188
			action = "";
189
		}
190
		
191
		// When the method gets here, everything's been validated correctly.
192
		JButton button = new JButton(text);
193
		button.setActionCommand(action);
194
		button.addActionListener(
195
				new java.awt.event.ActionListener() {
196
					public void actionPerformed(java.awt.event.ActionEvent event) {
197
						try {
198
							triggerMethod.setAccessible(true);
199
							if (triggerMethod.getParameterTypes().length == 0) {
200
								// FIXME: Next line throws a warning?
201
								triggerMethod.invoke(triggerObject, null);
202
							}
203
							else {
204
								triggerMethod.invoke(triggerObject, new Object[]{event});
205
							}
206
						}
207
						catch (Exception useless) {
208
							useless.printStackTrace();
209
							/*
210
							 * XXX: Some info on why I don't just throw said
211
							 * Exception to the caller:
212
							 * Java has this awful language constraint, which
213
							 * forces every damn exception that isn't a subclass
214
							 * of RuntimeException, to be declared in the method
215
							 * declaration. This tends to infect all underlying
216
							 * methods as well, and all that for reasons I can't
217
							 * comprehend. In order to keep JSugar a simple and
218
							 * clean library, I'll rather just handle it here,
219
							 * and throw a RuntimeException with appropriate
220
							 * details.
221
							 */
222
							throw new IllegalArgumentException("triggerMethod is not accessible from this context.");
223
						}
224
					}
225
				});
226
		this.addComponent(button);
227
		return button;
228
	}
229
230
	/**
231
	 * Ask the user for input through a dialog box.
232
	 * This method presents the user with an input field, that can accept
233
	 * textual input. The method will return the given input after the user's
234
	 * clicked a button to send.
235
	 * @param text The text/question to be asked to the user.
236
	 * @return A String, equal to what the user entered.
237
	 * @throws NullPointerException if text is a null pointer.
238
	 */
239
	public String inputDialog(String text) {
240
		if (text == null) {
241
			throw new NullPointerException("The given text/question was a null pointer.");
242
		}
243
		return JOptionPane.showInputDialog(text);
244
	}
245
246
	/**
247
	 * Give the user a dialog box.
248
	 * This method can be used to provide a simple dialog to the user.
249
	 * This will show the user the given question, after which a boolean value
250
	 * is returned, holding the choice.
251
	 * @param text The text/question to be asked to the user.
252
	 * @return True if the user confirms, False if he denies.
253
	 * @throws NullPointerException if text is a null pointer.
254
	 */
255
	public boolean confirmDialog(String text) {
256
		if (text == null) {
257
			throw new NullPointerException("The given text/question was a null pointer.");
258
		}
259
		final int ACCEPTED = 0;
260
		//final int DENIED = 1; // Not used in the current context.
261
		int result = this.choiceDialog(text, new String[]{"Confirm", "Deny"});
262
		if (result == ACCEPTED) {
263
			return true;
264
		}
265
		else {
266
			return false;
267
		}
268
	}
269
270
	/**
271
	 * Give the user a choice dialog box.
272
	 * This method gives the user a simple dialog with predefined choices.
273
	 * These choices are to be provided by the caller in a simple array.
274
	 *
275
	 * Tip: This method works extremely well with arbitrary created choices.
276
	 * That is: if the outcome of the dialog is trivial (e.g. Only 1 choice),
277
	 * then that value is immediately returned.
278
	 * @param text The text/question to be asked to the user.
279
	 * @param choices An array of Strings, containing the choices the user can
280
	 * pick.
281
	 * @return The index value of the picked choice, or -1 if no choices were
282
	 * given.
283
	 * @throws NullPointerException if text is a null pointer.
284
	 */
285
	public int choiceDialog(String text, String[] choices) {
286
		if (text == null) {
287
			throw new NullPointerException("The given text/question was a null pointer.");
288
		}
289
		// First: handling the trivial cases:
290
		if (choices.length == 0) {
291
			return -1;
292
		}
293
		else if (choices.length == 1) {
294
			return 0;
295
		}
296
		int answer = JOptionPane.CLOSED_OPTION;
297
		// The dialog needs to be shown again until the user has made a possible
298
		// choice, i.e. Chickening out using the close button is not possible
299
		// (Because that returns CLOSED_OPTION).
300
		while (answer == JOptionPane.CLOSED_OPTION) {
301
				answer = JOptionPane.showOptionDialog(
302
					null, // The parent component. May become the panel?
303
					text, // The text/question to describe the goal
304
					"Dialog", // The text in the title bar
305
					JOptionPane.DEFAULT_OPTION, // The kind of available options
306
					JOptionPane.QUESTION_MESSAGE, // The type of message
307
					null, // The icon to show
308
					choices, // The possible choices
309
					choices[0] // The standard choice
310
					);
311
		}
312
		return answer;
313
	}
314
		
315
316
	/**
317
	 * Creates a label in the GUI for interaction.
318
	 * This function offers a convenient way to create a label, that can be
319
	 * directly interacted with by the user. After creation, the label itself
320
	 * is returned to the caller, if he wishes to do something else with it.
321
	 * @param text The text that will be displayed in the label.
322
	 * @return The label that was created.
323
	 */
324
	public JLabel createLabel(String text) {
325
		JLabel label = new JLabel(text);
326
		this.addComponent(label);
327
		return label;
328
	}
329
330
	/**
331
	 * Adds a checkbox to the window.
332
	 * By providing a String, you can use this method to easily
333
	 * create a checkbox, and add it to the window. 
334
	 * @param text The text to put next to the checkbox.
335
	 * @return The checkbox that was created and added to the GUI.
336
	 */
337
	public JCheckBox createCheckbox(String text) {
338
		JCheckBox checkbox = new JCheckBox(text);
339
		this.addComponent(checkbox);
340
		return checkbox;
341
	}
342
343
	/**
344
	 * Adds radio buttons to the window.
345
	 * Given a list of Strings, this method will create the same amount of radio
346
	 * buttons.
347
	 *
348
	 * The radio buttons will silently be grouped in a ButtonGroup object,
349
	 * making them automatically disable each other, so only 1 radio button can
350
	 * be enabled. This ButtonGroup is immutable.
351
	 *
352
	 * If you need a mutable ButtonGroup, create your own, and use the 
353
	 * {@link #addComponent} method to add the radio buttons manually.
354
	 * @param text An array of Strings. The length of the array will determine
355
	 * the amount of radio buttons that will be created.
356
	 * @return An array of radio buttons, in the same order as text.
357
	 */
358
	public JRadioButton[] createRadioButtons(String text[]) {
359
		JRadioButton[] radioButtons = new JRadioButton[text.length];
360
		ButtonGroup buttonGroup = new ButtonGroup();
361
		for (int i=0; i<radioButtons.length; i++) {
362
			radioButtons[i] = new JRadioButton(text[i]);
363
			//radioButtons[i].setText(text[i]);
364
			buttonGroup.add(radioButtons[i]);
365
			this.addComponent(radioButtons[i]);
366
		}
367
368
		assert radioButtons.length == buttonGroup.getButtonCount() : "The amount of radio buttons ("+ radioButtons.length +") differs from the amount of buttons in buttonGroup ("+ buttonGroup.getButtonCount() +").";
369
		return radioButtons;
370
	}
371
372
	public JTextField createTextField(String text) {
373
		JTextField textField = new JTextField(text);
374
		this.addComponent(textField);
375
		return textField;
376
	}
377
378
	/**
379
	 * Adds a number spinner component to the GUI.
380
	 * This method allows the caller to create a so-called "spinner component"
381
	 * to the window. This spinner is an input box, in which only integers can
382
	 * be put.
383
	 *
384
	 * The caller can set a range, a start value, and a step size.
385
	 *
386
	 * The spinner created with this method may modify itself based on the
387
	 * parameters.
388
	 * For example: If the minimum and maximum value are equal, the spinner will
389
	 * be disabled.
390
	 *
391
	 * @param minimum The minimum value that can be selected.
392
	 * @param maximum The maximum value that can be selected.
393
	 * @param start The value that will initially be shown in the component.
394
	 * @param stepSize The step size when the user increases/decreases the
395
	 * value.
396
	 * @throws IllegalArgumentException if minimum is larger than maximum, 
397
	 * start is not in the range of the selectable values, or stepsize is not a
398
	 * natural number.
399
	 * @return The JSpinner that was added to the window.
400
	 */
401
	public JSpinner createSpinner(int minimum, int maximum, int start, int stepSize) {
402
		// As usual, we begin with checking the contract:
403
		if(minimum > maximum) {
404
			throw new IllegalArgumentException("The minimum value ("+ minimum +") was larger than the maximum value ("+ maximum +")");
405
		}
406
		if(start < minimum || start > maximum) {
407
			throw new IllegalArgumentException("The start value("+ start +") was not in the given range ("+ minimum +", "+ maximum +").");
408
		}
409
		// The "start ∉ [minimum, maximum]" is done by the SpinnerNumberModel
410
		// constructor, which will be constructed later.
411
		if(stepSize <= 0) { // stepSize ∉ ℕ¹ (In Belgium: ℕ₀)
412
			throw new IllegalArgumentException("The stepSize ("+ stepSize +") is not a natural number (excluding 0).");
413
		}
414
		// If the contract is valid, we can begin:
415
		/*
416
		 * I'd like to interject here, because this is a nice example of why
417
		 * JSugar was a good idea:
418
		 * If you want a spinner, you'll typically want an integer spinner. But
419
		 * in Swing, when you create a JSpinner, it creates a JSpinner, with a
420
		 * predefined 'SpinnerNumberModel' attached to it.
421
		 * It's this model you then have to extract from the created spinner, on
422
		 * which you need to apply the configuration.
423
		 * What you actually have to do, is create a SpinnerNumberModel
424
		 * yourself, put your settings on that, and then, create a JSpinner to
425
		 * which you give that SpinnerNumberModel.
426
		 * In essence: The entire Java framework is shit.
427
		 */
428
		SpinnerNumberModel spinnerSettings = new SpinnerNumberModel(
429
				start,
430
				minimum,
431
				maximum,
432
				stepSize
433
				);
434
		JSpinner spinner = new JSpinner(spinnerSettings);
435
		if(minimum == maximum) { // Trivial value is set already, --> disable.
436
			spinner.setEnabled(false);
437
		}
438
		this.addComponent(spinner);
439
		return spinner;
440
	}
441
442
	/**
443
	 * Adds a number spinner component to the GUI.
444
	 * This method allows the caller to create a so-called "spinner component"
445
	 * to the window. This spinner is an input box, in which only integers can
446
	 * be put.
447
	 * 
448
	 * Tip: This method is a convenience method, and works extremely well with
449
	 * arbitrary data.
450
	 * For example: The start value is automatically set to the minimal possible
451
	 * value, and the step size defaults to 1.
452
	 * If the minimum and maximum are equal, the component will be disabled, and
453
	 * thus, be locked on the only (trivially) possible value.
454
	 * If minimum is larger than maximum, the method will notify you of this,
455
	 * but also swap the values. So you can rest assured that the spinner will
456
	 * handle errors, but also, inform you about it.
457
	 * @param minimum The minimum value that can be selected.
458
	 * @param maximum The maximum value that can be selected.
459
	 * @return The JSpinner component that was added to the window.
460
	 */
461
	public JSpinner createSpinner(int minimum, int maximum) {
462
		// The disabling of equal values is done in the full createSpinner(), so
463
		// this is merely switching values if they need to be swapped.
464
		if(minimum > maximum) {
465
			System.err.println("minimum ("+ minimum +") was larger than maximum ("+ maximum +").");
466
			// FIXME: Consider whether it's appropriate to print a stacktrace
467
			// here, which may be convenient for debugging.
468
			
469
			// XXX: I know you don't need the help variable when swapping
470
			// integers, because you can also do basic arithmetics. Change it if
471
			// it causes too much eye burn for you.
472
			int swapValue = minimum;
473
			minimum = maximum;
474
			maximum = swapValue;
475
		}
476
477
		// Yeah, these 2 variables make you cringe huh, performance addicts?
478
		// Drown me in the tears of your useless performance-related opinions.
479
		int startValue = minimum;
480
		int stepSize = 1;
481
		return this.createSpinner(minimum, maximum, startValue, stepSize);
482
	}
483
484
	/**
485
	 * Adds a combobox to the GUI.
486
	 * Allows the caller to create a combobox by providing the values that
487
	 * should be put in it.
488
	 *
489
	 * This method can only be used for String values. If that is not what you
490
	 * need, consider creating your own combobox and adding it manually. Or, if
491
	 * you need a combobox for integers, consider {@link #createSpinner}.
492
	 *
493
	 * WARNING: {@link JComboBox#getSelectedItem} returns an object, not a
494
	 * String. You need to manually typecast this. This is a constraint of the
495
	 * Swing framework.
496
	 * @param items An array of Strings that will be put in the combobox.
497
	 * @throws NullPointerException if one of the values in items is a null
498
	 * pointer.
499
	 * @throws IllegalArgumentException if items is empty.
500
	 * @return The JCombobox that was added to the window.
501
	 */
502
	public JComboBox<String> addComboBox(String[] items) {
503
		// Contract validation:
504
		if(items.length == 0) {
505
			throw new IllegalArgumentException("The given array of items was empty.");
506
		}
507
		for(String item : items) {
508
			if(item == null) {
509
				throw new NullPointerException("One of the given Strings is a null pointer.");
510
			}
511
		}
512
		// Contract validated, create the component:
513
		JComboBox<String> comboBox = new JComboBox<String>(items);
514
		comboBox.setSelectedIndex(0);
515
		if(comboBox.getItemCount() == 1) { // Trivial selection
516
			comboBox.setEnabled(false);
517
		}
518
		this.addComponent(comboBox);
519
		return comboBox;
520
	}
521
522
	/**
523
	 * Creates a list of the given data, and adds it to the GUI.
524
	 * This will create a JList component, containing the given data. 
525
	 * To jar up your memory: A list in this context, is a component in which
526
	 * data of the same type is printed out. The user of said list, can then
527
	 * select a subset of these items.
528
	 *
529
	 * @see JList for a collection of possible operations.
530
	 * @param items The String items that will be put in the list.
531
	 * @throws NullPointerException if one of the values in items is a null
532
	 * pointer.
533
	 * @throws IllegalArgumentException if items is empty.
534
	 * @return A JList component, that was a added to the GUI.
535
	 */
536
	public JList createList(String[] items) {
537
		// Contract validation:
538
		if(items.length == 0) {
539
			throw new IllegalArgumentException("The given array of items was empty.");
540
		}
541
		for(String item : items) {
542
			if(item == null) {
543
				throw new NullPointerException("One of the given Strings is a null pointer.");
544
			}
545
		}
546
		// Contract validated, create the component:
547
		JList list = new JList(items);
548
		this.addComponent(list);
549
		return list;
550
	}
551
552
	/**
553
	 * Creates a table of the given data, and adds it to the GUI.
554
	 * This method allows you to create a table with sorting functionality in
555
	 * the GUI.
556
	 * This method relies on implications, deducted from the given data. That
557
	 * is, the length of the rows and columns is calculated by the longest
558
	 * length it can find in the nested array.
559
	 * To change the data, take a look at the JTable documentation.
560
	 *
561
	 * @see JTable for a collection of possible operations.
562
	 * @param items The String items that will be put in the list.
563
	 * @throws NullPointerException if one of the values in items is a null
564
	 * pointer.
565
	 * @throws IllegalArgumentException if items is empty, or the amount of
566
	 * column names does not correspond with the given amount of items.
567
	 * @return A JTable component, that was a added to the GUI.
568
	 */
569
	public JTable createTable(String[] columns, String[][] items) {
570
		// Contract validation:
571
		if(items.length == 0) {
572
			throw new IllegalArgumentException("The given array of items was empty.");
573
		}
574
		if(columns.length != items[0].length) {
575
			throw new IllegalArgumentException("The amount of columns does not correspond to the given amount of items.");
576
		}
577
		for(int i=0; i<items.length; i++) {
578
			if(items[i] == null) {
579
				throw new NullPointerException("One of the given Strings is a null pointer.");
580
			}
581
		}
582
		// Contract validated, create the component:
583
		// Deducting max length:
584
		int columnCount = columns.length;
585
		int rowCount = items.length;
586
		
587
		JTable table = new JTable(items, columns);
588
		this.addComponent(table);
589
		return table;
590
	}
591
592
	/**
593
	 * Adds the given component to the GUI.
594
	 * This method allows its caller to give a pre-made component, so that it
595
	 * can be added to the GUI. Even though its main use is for the Window class
596
	 * itself, the user of JSugar can also use it to create components himself,
597
	 * and then add them. As such, this method doesn't provide parameters for
598
	 * reflection/action triggering purposes.
599
	 * @param component The component to be added to the window.
600
	 * @throws NullPointerException if the given component is a null pointer.
601
	 */
602
	public void addComponent(JComponent component) {
603
		int originalSize = this.panel.getComponentCount();
604
		this.panel.add(component); // Throws the exception if null.
605
		this.updateWindow();
606
607
		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.";
608
	}
609
610
	/**
611
	 * Removes the given component from the GUI.
612
	 * This method allows its caller to remove a component from the GUI.
613
	 * @param component The component to be removed.
614
	 * @throws NoSuchElementException if the given component does not exist in
615
	 * the GUI.
616
	 * @throws NullPointerException if the given component is a null pointer.
617
	 */
618
	public void removeComponent(JComponent component) {
619
		int originalSize = this.panel.getComponentCount();
620
		this.panel.remove(component);
621
		int newSize = this.panel.getComponentCount();
622
		if (originalSize != newSize+1) {
623
			throw new NoSuchElementException("The given component does not exist in the GUI.");
624
		}
625
		this.updateWindow();
626
	}
627
	/**
628
	 * Prompts the user with a file chooser dialog.
629
	 * By calling this method, the user will be presented with a file chooser
630
	 * dialog, out of which a single file can be selected. If the selected file
631
	 * exists, a File object is returned, a null pointer if the user cancelled.
632
	 * @return A File object representing the file the user selected, or null
633
	 * otherwise.
634
	 */
635
	public File openFileChooserDialog() {
636
		JFileChooser fileDialog = new JFileChooser();
637
		fileDialog.setFileSelectionMode(JFileChooser.FILES_ONLY);
638
639
		int userResponse = fileDialog.showOpenDialog(this.panel);
640
		if(userResponse == JFileChooser.APPROVE_OPTION) {
641
			return fileDialog.getSelectedFile();
642
		}
643
		else {
644
			return null;
645
		}
646
	}
647
648
	/** Prompts the user with a message dialog.
649
	 * This method creates a dialog that is purely informative. As such, it
650
	 * only presents the user with the option to confirm it.
651
	 * @param text The text to be presented in the dialog box.
652
	 * @pre text mustn't be null or an empty string.
653
	 * @throws NullPointerException if text is a null pointer.
654
	 * @throws IllegalArgumentException if text is an empty string.
655
	 */
656
	public void messageDialog(String text) {
657
		if(text.isEmpty())
658
			throw new IllegalArgumentException("text mustn't be an empty string.");
659
		JOptionPane.showMessageDialog(this.frame, text);
660
	}
661
662
	/**
663
	 * Closes this window.
664
	 * This method tells this Window's JFrame that a closing event has been
665
	 * fired.
666
	 * Use this method when you need to programatically close this window.
667
	 */
668
	public void close() {
669
		this.frame.dispatchEvent(new WindowEvent(this.frame, WindowEvent.WINDOW_CLOSING));
670
	}
671
}
672