2
* Copyright (c) 2007, Sun Microsystems, Inc.
5
* Redistribution and use in source and binary forms, with or without
6
* modification, are permitted provided that the following conditions are met:
8
* * Redistributions of source code must retain the above copyright notice,
9
* this list of conditions and the following disclaimer.
10
* * Redistributions in binary form must reproduce the above copyright
11
* notice, this list of conditions and the following disclaimer in
12
* the documentation and/or other materials provided with the distribution.
13
* * Neither the name of Sun Microsystems, Inc. nor the names of its
14
* contributors may be used to endorse or promote products derived
15
* from this software without specific prior written permission.
17
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
23
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
package documenteditor;
32
import javax.swing.event.DocumentEvent;
33
import org.jdesktop.application.Action;
34
import org.jdesktop.application.ResourceMap;
35
import org.jdesktop.application.SingleFrameApplication;
36
import org.jdesktop.application.FrameView;
37
import org.jdesktop.application.TaskMonitor;
38
import org.jdesktop.application.Task;
39
import java.awt.event.ActionEvent;
40
import java.awt.event.ActionListener;
42
import java.util.EventObject;
43
import java.util.logging.Level;
44
import java.util.logging.Logger;
45
import javax.swing.Timer;
46
import javax.swing.Icon;
47
import javax.swing.JDialog;
48
import javax.swing.JFileChooser;
49
import javax.swing.JFrame;
50
import javax.swing.JOptionPane;
51
import javax.swing.event.DocumentListener;
52
import org.jdesktop.application.Application;
53
import javax.swing.filechooser.FileFilter;
56
* This application is a simple text editor. This class displays the main frame
57
* of the application and provides much of the logic. This class is called by
58
* the main application class, DocumentEditorApp. For an overview of the
59
* application see the comments for the DocumentEditorApp class.
61
public class DocumentEditorView extends FrameView {
63
private File file = new File("untitled.txt");
64
private boolean modified = false;
66
public DocumentEditorView(SingleFrameApplication app) {
69
// generated GUI builder code
72
// status bar initialization - message timeout, idle icon and busy animation, etc
73
ResourceMap resourceMap = getResourceMap();
74
int messageTimeout = resourceMap.getInteger("StatusBar.messageTimeout");
75
messageTimer = new Timer(messageTimeout, new ActionListener() {
76
public void actionPerformed(ActionEvent e) {
77
statusMessageLabel.setText("");
80
messageTimer.setRepeats(false);
81
int busyAnimationRate = resourceMap.getInteger("StatusBar.busyAnimationRate");
82
for (int i = 0; i < busyIcons.length; i++) {
83
busyIcons[i] = resourceMap.getIcon("StatusBar.busyIcons[" + i + "]");
85
busyIconTimer = new Timer(busyAnimationRate, new ActionListener() {
86
public void actionPerformed(ActionEvent e) {
87
busyIconIndex = (busyIconIndex + 1) % busyIcons.length;
88
statusAnimationLabel.setIcon(busyIcons[busyIconIndex]);
91
idleIcon = resourceMap.getIcon("StatusBar.idleIcon");
92
statusAnimationLabel.setIcon(idleIcon);
93
progressBar.setVisible(false);
95
// connect action tasks to status bar via TaskMonitor
96
TaskMonitor taskMonitor = new TaskMonitor(getApplication().getContext());
97
taskMonitor.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
98
public void propertyChange(java.beans.PropertyChangeEvent evt) {
99
String propertyName = evt.getPropertyName();
100
if ("started".equals(propertyName)) {
101
if (!busyIconTimer.isRunning()) {
102
statusAnimationLabel.setIcon(busyIcons[0]);
104
busyIconTimer.start();
106
progressBar.setVisible(true);
107
progressBar.setIndeterminate(true);
108
} else if ("done".equals(propertyName)) {
109
busyIconTimer.stop();
110
statusAnimationLabel.setIcon(idleIcon);
111
progressBar.setVisible(false);
112
progressBar.setValue(0);
113
} else if ("message".equals(propertyName)) {
114
String text = (String)(evt.getNewValue());
115
statusMessageLabel.setText((text == null) ? "" : text);
116
messageTimer.restart();
117
} else if ("progress".equals(propertyName)) {
118
int value = (Integer)(evt.getNewValue());
119
progressBar.setVisible(true);
120
progressBar.setIndeterminate(false);
121
progressBar.setValue(value);
126
// if the document is ever edited, assume that it needs to be saved
127
textArea.getDocument().addDocumentListener(new DocumentListener() {
128
public void changedUpdate(DocumentEvent e) { setModified(true); }
129
public void insertUpdate(DocumentEvent e) { setModified(true); }
130
public void removeUpdate(DocumentEvent e) { setModified(true); }
133
// ask for confirmation on exit
134
getApplication().addExitListener(new ConfirmExit());
138
* The File currently being edited. The default value of this
139
* property is "untitled.txt".
141
* This is a bound read-only property. It is never null.
143
* @return the value of the file property.
146
public File getFile() {
150
/* Set the bound file property and update the GUI.
152
private void setFile(File file) {
153
File oldValue = this.file;
155
String appId = getResourceMap().getString("Application.id");
156
getFrame().setTitle(file.getName() + " - " + appId);
157
firePropertyChange("file", oldValue, this.file);
161
* True if the file value has been modified but not saved. The
162
* default value of this property is false.
164
* This is a bound read-only property.
166
* @return the value of the modified property.
169
public boolean isModified() {
173
/* Set the bound modified property and update the GUI.
175
private void setModified(boolean modified) {
176
boolean oldValue = this.modified;
177
this.modified = modified;
178
firePropertyChange("modified", oldValue, this.modified);
182
* Prompt the user for a filename and then attempt to load the file.
184
* The file is loaded on a worker thread because we don't want to
185
* block the EDT while the file system is accessed. To do that,
186
* this Action method returns a new LoadFileTask instance, if the
187
* user confirms selection of a file. The task is executed when
188
* the "open" Action's actionPerformed method runs. The
189
* LoadFileTask is responsible for updating the GUI after it has
190
* successfully completed loading the file.
192
* @return a new LoadFileTask or null
196
JFileChooser fc = createFileChooser("openFileChooser");
197
int option = fc.showOpenDialog(getFrame());
199
if (JFileChooser.APPROVE_OPTION == option) {
200
task = new LoadFileTask(fc.getSelectedFile());
206
* A Task that loads the contents of a file into a String. The
207
* LoadFileTask constructor runs first, on the EDT, then the
208
* #doInBackground methods runs on a background thread, and finally
209
* a completion method like #succeeded or #failed runs on the EDT.
211
* The resources for this class, like the message format strings are
212
* loaded from resources/LoadFileTask.properties.
214
private class LoadFileTask extends DocumentEditorApp.LoadTextFileTask {
215
/* Construct the LoadFileTask object. The constructor
216
* will run on the EDT, so we capture a reference to the
217
* File to be loaded here. To keep things simple, the
218
* resources for this Task are specified to be in the same
219
* ResourceMap as the DocumentEditorView class's resources.
220
* They're defined in resources/DocumentEditorView.properties.
222
LoadFileTask(File file) {
223
super(DocumentEditorView.this.getApplication(), file);
226
/* Called on the EDT if doInBackground completes without
227
* error and this Task isn't cancelled. We update the
228
* GUI as well as the file and modified properties here.
230
@Override protected void succeeded(String fileContents) {
232
textArea.setText(fileContents);
236
/* Called on the EDT if doInBackground fails because
237
* an uncaught exception is thrown. We show an error
238
* dialog here. The dialog is configured with resources
239
* loaded from this Tasks's ResourceMap.
241
@Override protected void failed(Throwable e) {
242
logger.log(Level.WARNING, "couldn't load " + getFile(), e);
243
String msg = getResourceMap().getString("loadFailedMessage", getFile());
244
String title = getResourceMap().getString("loadFailedTitle");
245
int type = JOptionPane.ERROR_MESSAGE;
246
JOptionPane.showMessageDialog(getFrame(), msg, title, type);
251
* Save the contents of the textArea to the current {@link #getFile file}.
253
* The text is written to the file on a worker thread because we don't want to
254
* block the EDT while the file system is accessed. To do that, this
255
* Action method returns a new SaveFileTask instance. The task
256
* is executed when the "save" Action's actionPerformed method runs.
257
* The SaveFileTask is responsible for updating the GUI after it
258
* has successfully completed saving the file.
262
@Action(enabledProperty = "modified")
264
return new SaveFileTask(getFile());
268
* Save the contents of the textArea to the current file.
270
* This action is nearly identical to {@link #open open}. In
271
* this case, if the user chooses a file, a {@code SaveFileTask}
272
* is returned. Note that the selected file only becomes the
273
* value of the {@code file} property if the file is saved
277
public Task saveAs() {
278
JFileChooser fc = createFileChooser("saveAsFileChooser");
279
int option = fc.showSaveDialog(getFrame());
281
if (JFileChooser.APPROVE_OPTION == option) {
282
task = new SaveFileTask(fc.getSelectedFile());
288
* A Task that saves the contents of the textArea to the current file.
289
* This class is very similar to LoadFileTask, please refer to that
290
* class for more information.
292
private class SaveFileTask extends DocumentEditorApp.SaveTextFileTask {
293
SaveFileTask(File file) {
294
super(DocumentEditorView.this.getApplication(), file, textArea.getText());
297
@Override protected void succeeded(Void ignored) {
302
@Override protected void failed(Throwable e) {
303
logger.log(Level.WARNING, "couldn't save " + getFile(), e);
304
String msg = getResourceMap().getString("saveFailedMessage", getFile());
305
String title = getResourceMap().getString("saveFailedTitle");
306
int type = JOptionPane.ERROR_MESSAGE;
307
JOptionPane.showMessageDialog(getFrame(), msg, title, type);
312
public void showAboutBox() {
313
if (aboutBox == null) {
314
JFrame mainFrame = DocumentEditorApp.getApplication().getMainFrame();
315
aboutBox = new DocumentEditorAboutBox(mainFrame);
316
aboutBox.setLocationRelativeTo(mainFrame);
318
DocumentEditorApp.getApplication().show(aboutBox);
321
private JFileChooser createFileChooser(String name) {
322
JFileChooser fc = new JFileChooser();
323
fc.setDialogTitle(getResourceMap().getString(name + ".dialogTitle"));
324
String textFilesDesc = getResourceMap().getString("txtFileExtensionDescription");
325
fc.setFileFilter(new TextFileFilter(textFilesDesc));
329
/** This is a substitute for FileNameExtensionFilter, which is
330
* only available on Java SE 6.
332
private static class TextFileFilter extends FileFilter {
334
private final String description;
336
TextFileFilter(String description) {
337
this.description = description;
341
public boolean accept(File f) {
342
if (f.isDirectory()) {
345
String fileName = f.getName();
346
int i = fileName.lastIndexOf('.');
347
if ((i > 0) && (i < (fileName.length() - 1))) {
348
String fileExt = fileName.substring(i + 1);
349
if ("txt".equalsIgnoreCase(fileExt)) {
357
public String getDescription() {
362
private class ConfirmExit implements Application.ExitListener {
363
public boolean canExit(EventObject e) {
365
String confirmExitText = getResourceMap().getString("confirmTextExit", getFile());
366
int option = JOptionPane.showConfirmDialog(getFrame(), confirmExitText);
367
return option == JOptionPane.YES_OPTION;
368
// TODO: also offer saving
373
public void willExit(EventObject e) { }
376
/** This method is called from within the constructor to
377
* initialize the form.
378
* WARNING: Do NOT modify this code. The content of this method is
379
* always regenerated by the Form Editor.
381
// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
382
private void initComponents() {
384
mainPanel = new javax.swing.JPanel();
385
scrollPane = new javax.swing.JScrollPane();
386
textArea = new javax.swing.JTextArea();
387
menuBar = new javax.swing.JMenuBar();
388
javax.swing.JMenu fileMenu = new javax.swing.JMenu();
389
javax.swing.JMenuItem openMenuItem = new javax.swing.JMenuItem();
390
javax.swing.JMenuItem saveMenuItem = new javax.swing.JMenuItem();
391
javax.swing.JMenuItem saveAsMenuItem = new javax.swing.JMenuItem();
392
javax.swing.JSeparator fileMenuSeparator = new javax.swing.JSeparator();
393
javax.swing.JMenuItem exitMenuItem = new javax.swing.JMenuItem();
394
javax.swing.JMenu editMenu = new javax.swing.JMenu();
395
javax.swing.JMenuItem cutMenuItem = new javax.swing.JMenuItem();
396
javax.swing.JMenuItem copyMenuItem = new javax.swing.JMenuItem();
397
javax.swing.JMenuItem pasteMenuItem = new javax.swing.JMenuItem();
398
javax.swing.JMenu helpMenu = new javax.swing.JMenu();
399
javax.swing.JMenuItem aboutMenuItem = new javax.swing.JMenuItem();
400
statusPanel = new javax.swing.JPanel();
401
javax.swing.JSeparator statusPanelSeparator = new javax.swing.JSeparator();
402
statusMessageLabel = new javax.swing.JLabel();
403
statusAnimationLabel = new javax.swing.JLabel();
404
progressBar = new javax.swing.JProgressBar();
405
toolBar = new javax.swing.JToolBar();
406
openToolBarButton = new javax.swing.JButton();
407
saveToolBarButton = new javax.swing.JButton();
408
cutToolBarButton = new javax.swing.JButton();
409
copyToolBarButton = new javax.swing.JButton();
410
pasteToolBarButton = new javax.swing.JButton();
412
mainPanel.setName("mainPanel"); // NOI18N
414
scrollPane.setName("scrollPane"); // NOI18N
416
textArea.setColumns(20);
418
textArea.setName("textArea"); // NOI18N
419
scrollPane.setViewportView(textArea);
421
org.jdesktop.layout.GroupLayout mainPanelLayout = new org.jdesktop.layout.GroupLayout(mainPanel);
422
mainPanel.setLayout(mainPanelLayout);
423
mainPanelLayout.setHorizontalGroup(
424
mainPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
425
.add(scrollPane, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 500, Short.MAX_VALUE)
427
mainPanelLayout.setVerticalGroup(
428
mainPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
429
.add(scrollPane, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 358, Short.MAX_VALUE)
431
org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(documenteditor.DocumentEditorApp.class).getContext().getResourceMap(DocumentEditorView.class);
432
resourceMap.injectComponents(mainPanel);
434
menuBar.setName("menuBar"); // NOI18N
436
fileMenu.setName("fileMenu"); // NOI18N
438
javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(documenteditor.DocumentEditorApp.class).getContext().getActionMap(DocumentEditorView.class, this);
439
openMenuItem.setAction(actionMap.get("open")); // NOI18N
440
openMenuItem.setName("openMenuItem"); // NOI18N
441
fileMenu.add(openMenuItem);
443
saveMenuItem.setAction(actionMap.get("save")); // NOI18N
444
saveMenuItem.setName("saveMenuItem"); // NOI18N
445
fileMenu.add(saveMenuItem);
447
saveAsMenuItem.setAction(actionMap.get("saveAs")); // NOI18N
448
saveAsMenuItem.setName("saveAsMenuItem"); // NOI18N
449
fileMenu.add(saveAsMenuItem);
451
fileMenuSeparator.setName("fileMenuSeparator"); // NOI18N
452
fileMenu.add(fileMenuSeparator);
454
exitMenuItem.setAction(actionMap.get("quit")); // NOI18N
455
exitMenuItem.setName("exitMenuItem"); // NOI18N
456
fileMenu.add(exitMenuItem);
458
menuBar.add(fileMenu);
460
editMenu.setName("editMenu"); // NOI18N
462
cutMenuItem.setAction(actionMap.get("cut"));
463
cutMenuItem.setName("cutMenuItem"); // NOI18N
464
editMenu.add(cutMenuItem);
466
copyMenuItem.setAction(actionMap.get("copy"));
467
copyMenuItem.setName("copyMenuItem"); // NOI18N
468
editMenu.add(copyMenuItem);
470
pasteMenuItem.setAction(actionMap.get("paste"));
471
pasteMenuItem.setName("pasteMenuItem"); // NOI18N
472
editMenu.add(pasteMenuItem);
474
menuBar.add(editMenu);
476
helpMenu.setName("helpMenu"); // NOI18N
478
aboutMenuItem.setAction(actionMap.get("showAboutBox")); // NOI18N
479
aboutMenuItem.setName("aboutMenuItem"); // NOI18N
480
helpMenu.add(aboutMenuItem);
482
menuBar.add(helpMenu);
483
resourceMap.injectComponents(menuBar);
485
statusPanel.setName("statusPanel"); // NOI18N
487
statusMessageLabel.setName("statusMessageLabel"); // NOI18N
489
statusAnimationLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
490
statusAnimationLabel.setName("statusAnimationLabel"); // NOI18N
492
progressBar.setName("progressBar"); // NOI18N
494
org.jdesktop.layout.GroupLayout statusPanelLayout = new org.jdesktop.layout.GroupLayout(statusPanel);
495
statusPanel.setLayout(statusPanelLayout);
496
statusPanelLayout.setHorizontalGroup(
497
statusPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
498
.add(statusPanelSeparator, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 500, Short.MAX_VALUE)
499
.add(statusPanelLayout.createSequentialGroup()
501
.add(statusMessageLabel)
502
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, 326, Short.MAX_VALUE)
503
.add(progressBar, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
504
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
505
.add(statusAnimationLabel)
508
statusPanelLayout.setVerticalGroup(
509
statusPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
510
.add(statusPanelLayout.createSequentialGroup()
511
.add(statusPanelSeparator, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
512
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
513
.add(statusPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
514
.add(statusMessageLabel)
515
.add(statusAnimationLabel)
516
.add(progressBar, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
519
resourceMap.injectComponents(statusPanel);
521
toolBar.setFloatable(false);
522
toolBar.setRollover(true);
523
toolBar.setName("toolBar"); // NOI18N
525
openToolBarButton.setAction(actionMap.get("open")); // NOI18N
526
openToolBarButton.setFocusable(false);
527
openToolBarButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
528
openToolBarButton.setName("openToolBarButton"); // NOI18N
529
openToolBarButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
530
toolBar.add(openToolBarButton);
532
saveToolBarButton.setAction(actionMap.get("save")); // NOI18N
533
saveToolBarButton.setFocusable(false);
534
saveToolBarButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
535
saveToolBarButton.setName("saveToolBarButton"); // NOI18N
536
saveToolBarButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
537
toolBar.add(saveToolBarButton);
539
cutToolBarButton.setAction(actionMap.get("cut"));
540
cutToolBarButton.setFocusable(false);
541
cutToolBarButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
542
cutToolBarButton.setName("cutToolBarButton"); // NOI18N
543
cutToolBarButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
544
toolBar.add(cutToolBarButton);
546
copyToolBarButton.setAction(actionMap.get("copy"));
547
copyToolBarButton.setFocusable(false);
548
copyToolBarButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
549
copyToolBarButton.setName("copyToolBarButton"); // NOI18N
550
copyToolBarButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
551
toolBar.add(copyToolBarButton);
553
pasteToolBarButton.setAction(actionMap.get("paste"));
554
pasteToolBarButton.setFocusable(false);
555
pasteToolBarButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
556
pasteToolBarButton.setName("pasteToolBarButton"); // NOI18N
557
pasteToolBarButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
558
toolBar.add(pasteToolBarButton);
559
resourceMap.injectComponents(toolBar);
561
setComponent(mainPanel);
563
setStatusBar(statusPanel);
565
}// </editor-fold>//GEN-END:initComponents
567
// Variables declaration - do not modify//GEN-BEGIN:variables
568
private javax.swing.JButton copyToolBarButton;
569
private javax.swing.JButton cutToolBarButton;
570
private javax.swing.JPanel mainPanel;
571
private javax.swing.JMenuBar menuBar;
572
private javax.swing.JButton openToolBarButton;
573
private javax.swing.JButton pasteToolBarButton;
574
private javax.swing.JProgressBar progressBar;
575
private javax.swing.JButton saveToolBarButton;
576
private javax.swing.JScrollPane scrollPane;
577
private javax.swing.JLabel statusAnimationLabel;
578
private javax.swing.JLabel statusMessageLabel;
579
private javax.swing.JPanel statusPanel;
580
private javax.swing.JTextArea textArea;
581
private javax.swing.JToolBar toolBar;
582
// End of variables declaration//GEN-END:variables
584
private final Timer messageTimer;
585
private final Timer busyIconTimer;
586
private final Icon idleIcon;
587
private final Icon[] busyIcons = new Icon[15];
588
private int busyIconIndex = 0;
590
private JDialog aboutBox;
592
private static final Logger logger = Logger.getLogger(DocumentEditorView.class.getName());