001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU General
007: * Public License Version 2 only ("GPL") or the Common Development and Distribution
008: * License("CDDL") (collectively, the "License"). You may not use this file except in
009: * compliance with the License. You can obtain a copy of the License at
010: * http://www.netbeans.org/cddl-gplv2.html or nbbuild/licenses/CDDL-GPL-2-CP. See the
011: * License for the specific language governing permissions and limitations under the
012: * License. When distributing the software, include this License Header Notice in
013: * each file and include the License file at nbbuild/licenses/CDDL-GPL-2-CP. Sun
014: * designates this particular file as subject to the "Classpath" exception as
015: * provided by Sun in the GPL Version 2 section of the License file that
016: * accompanied this code. If applicable, add the following below the License Header,
017: * with the fields enclosed by brackets [] replaced by your own identifying
018: * information: "Portions Copyrighted [year] [name of copyright owner]"
019: *
020: * Contributor(s):
021: *
022: * The Original Software is NetBeans. The Initial Developer of the Original Software
023: * is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun Microsystems, Inc. All
024: * Rights Reserved.
025: *
026: * If you wish your version of this file to be governed by only the CDDL or only the
027: * GPL Version 2, indicate your decision by adding "[Contributor] elects to include
028: * this software in this distribution under the [CDDL or GPL Version 2] license." If
029: * you do not indicate a single choice of license, a recipient has the option to
030: * distribute your version of this file under either the CDDL, the GPL Version 2 or
031: * to extend the choice of license to its licensees as provided above. However, if
032: * you add GPL Version 2 code and therefore, elected the GPL Version 2 license, then
033: * the option applies only if the new code is made subject to such option by the
034: * copyright holder.
035: */
036:
037: package org.netbeans.installer.wizard.components;
038:
039: import java.awt.GridBagConstraints;
040: import java.awt.Insets;
041: import org.netbeans.installer.utils.ErrorManager;
042: import org.netbeans.installer.utils.ResourceUtils;
043: import org.netbeans.installer.utils.UiUtils;
044: import org.netbeans.installer.utils.helper.NbiThread;
045: import org.netbeans.installer.utils.helper.swing.NbiButton;
046: import org.netbeans.installer.utils.helper.swing.NbiLabel;
047: import org.netbeans.installer.utils.helper.swing.NbiProgressBar;
048: import org.netbeans.installer.utils.progress.Progress;
049: import org.netbeans.installer.utils.progress.ProgressListener;
050: import org.netbeans.installer.wizard.containers.SwingContainer;
051: import org.netbeans.installer.wizard.ui.SwingUi;
052: import org.netbeans.installer.wizard.ui.WizardUi;
053:
054: /**
055: * This class is a specialization of the {@link WizardComponent} which defines
056: * behavior specific to actions.
057: *
058: * <p>
059: * An action is best described by the following characteristics: it represents a
060: * lengthy process (and hence displays a progress bar) and does not require any user
061: * input - it just informs the user that something is happening and the wizard did
062: * not hang.
063: *
064: * <p>
065: * Optionally an action may provide means to cancel without waiting for it to
066: * finish. This behavior is controlled by the {@link #isCancelable()} method.
067: *
068: * <p>
069: * The derivative classes are expected to implement the {@link #execute()} and the
070: * {@link #isCancelable()} methods. If the action is cancelable, then the code in
071: * the {@link #execute()} method should check the cancellation status of the action
072: * via {@link #isCanceled()}. The action will not be interrupted automatically -
073: * canceling is a deliberate process.
074: *
075: * @author Kirill Sorokin
076: * @since 1.0
077: */
078: public abstract class WizardAction extends WizardComponent {
079: /////////////////////////////////////////////////////////////////////////////////
080: // Instance
081: /**
082: * UI of the action.
083: */
084: private WizardUi wizardUi;
085:
086: /**
087: * Whether the action ahs finished execution.
088: */
089: private boolean finished;
090:
091: /**
092: * Whether the action has been canceled. Note this field is simply a
093: * recommendation to the code in {@link #execute()} that it should clean up and
094: * return ASAP, it does not force any operation.
095: */
096: private boolean canceled;
097:
098: /**
099: * Creates a new instance of {@link WizardAction}. This is the default
100: * <code>protected</code> constructor which must be called by the concrete
101: * implementations. It initializes the fields above.
102: */
103: protected WizardAction() {
104: finished = false;
105: canceled = false;
106: }
107:
108: /**
109: * Executes the action when it is read via a call to
110: * {@link org.netbeans.installer.wizard.Wizard#next()}. This method runs the
111: * {@link #execute()} method a new {@link NbiThread}.
112: *
113: * @see WizardComponent#executeForward()
114: */
115: public final void executeForward() {
116: new NbiThread() {
117: @Override
118: public void run() {
119: finished = false;
120: execute();
121: finished = true;
122:
123: if (!canceled) {
124: getWizard().next();
125: }
126: }
127: }.start();
128: }
129:
130: /**
131: * This method has an empty implementation as {@link WizardAction} cannot be
132: * executed when moving backward.
133: *
134: * @see WizardComponent#executeBackward()
135: */
136: public final void executeBackward() {
137: // does nothing
138: }
139:
140: /**
141: * This method always returns <code>false</code>, as {@link WizardAction}s
142: * cannot be executed when moving backward.
143: *
144: * @see WizardComponent#canExecuteBackward()
145: */
146: @Override
147: public final boolean canExecuteBackward() {
148: return false;
149: }
150:
151: /**
152: * {@inheritDoc}
153: */
154: public WizardActionUi getWizardUi() {
155: if (wizardUi == null) {
156: wizardUi = new WizardActionUi(this );
157: }
158:
159: return (WizardActionUi) wizardUi;
160: }
161:
162: /**
163: * The default implementation of this method for {@link WizardAction} has an
164: * empty body. Concrete implementations are expected to override this method
165: * if they require any custom initialization.
166: *
167: * @see WizardComponent#initialize()
168: */
169: public void initialize() {
170: // does nothing
171: }
172:
173: /**
174: * The main business-logic method of the action. It must be implemented by
175: * concrete instances of {@link WizardAction}.
176: *
177: * <p>
178: * The code in this method is expected to update the progress as it is being
179: * executed. The {@link Progress} object for the action should be created by
180: * this method and passed to the UI via the
181: * {@link WizardActionUi#setProgress(Progress)} method.
182: *
183: * <p>
184: * The implementing code is also expected to pay attention to the return value
185: * of the {@link #isCanceled()} method. When the action receives a cancel signal
186: * (if it supports cancelation) the return value of this method will change to
187: * <code>true</code>.
188: */
189: public abstract void execute();
190:
191: /**
192: * Whether this action can be canceled. The default value if <code>true</code>,
193: * concrete implementations of {@link WizardAction} may override this method to
194: * disable the possibility to cancel the action.
195: *
196: * @return <code>true</code> if the action can be canceled, <code>false</code>
197: * otherwise.
198: */
199: public boolean isCancelable() {
200: return true;
201: }
202:
203: /**
204: * Whether this action has been canceled. This informational method is intended
205: * to be called by the code in {@link WizardAction#execute()} in order to
206: * correct its flow in case the action has been canceled.
207: *
208: * @return <code>true</code> is the action has been canceled, <code>false</code>
209: * otherwise.
210: */
211: public boolean isCanceled() {
212: return canceled;
213: }
214:
215: /**
216: * Cancels the action. Note that this method does not explicitly "kill" the
217: * execution of the action, but instead simply sets the cancellation marker and
218: * waits till the action's execution finishes. In case of a not-very-well
219: * behaved action, this can take a while.
220: */
221: public void cancel() {
222: canceled = true;
223:
224: while (!finished) {
225: try {
226: Thread.sleep(50);
227: } catch (InterruptedException e) {
228: ErrorManager.notifyDebug(
229: RESOURCE_INTERRUPTED_EXCEPTION, e);
230: }
231: }
232: }
233:
234: /////////////////////////////////////////////////////////////////////////////////
235: // Inner Classes
236: /**
237: * Implementation of the {@link WizardUi} for {@link WizardAction}.
238: *
239: * @author Kirill Sorokin
240: * @since 1.0
241: */
242: public static class WizardActionUi extends WizardComponentUi
243: implements ProgressListener {
244: /**
245: * Current {@link WizardAction} for this UI.
246: */
247: protected WizardAction action;
248:
249: /**
250: * {@link Progress} object used by the action.
251: */
252: protected Progress progress;
253:
254: /**
255: * Creates a new instance of {@link WizardActionUi}, initializing it with
256: * the specified instance of {@link WizardAction}.
257: *
258: * @param action Instance of {@link WizardAction} which should be used
259: * by this UI.
260: */
261: public WizardActionUi(final WizardAction action) {
262: super (action);
263:
264: this .action = action;
265: }
266:
267: /**
268: * {@inheritDoc}
269: */
270: @Override
271: public SwingUi getSwingUi(final SwingContainer container) {
272: if (swingUi == null) {
273: swingUi = new WizardActionSwingUi(action, container);
274: }
275:
276: return super .getSwingUi(container);
277: }
278:
279: /**
280: * Sets the current progress object for this action. It will be listened for
281: * changes and the UI will be updated accordingly.
282: *
283: * @param progress Current {@link Progress} object for the action.
284: */
285: public void setProgress(final Progress progress) {
286: if (this .progress != null) {
287: this .progress.removeProgressListener(this );
288: }
289:
290: this .progress = progress;
291: this .progress.addProgressListener(this );
292: }
293:
294: /**
295: * This method is called when the progress updates. It performs the update
296: * of all UIs that are created at the moment.
297: *
298: * @param progress {@link Progress} object which was updated.
299: * @see ProgressListener#progressUpdated(Progress)
300: */
301: public void progressUpdated(final Progress progress) {
302: if (swingUi != null) {
303: ((WizardActionSwingUi) swingUi)
304: .progressUpdated(progress);
305: }
306: }
307: }
308:
309: /**
310: * Implementation of {@link SwingUi} for {@link WizardAction}.
311: *
312: * @author Kirill Sorokin
313: * @since 1.0
314: */
315: public static class WizardActionSwingUi extends
316: WizardComponentSwingUi {
317: /**
318: * Current {@link WizardAction} for this UI.
319: */
320: private WizardAction action;
321:
322: /**
323: * {@link NbiLabel} which represents the progress' title.
324: */
325: private NbiLabel titleLabel;
326:
327: /**
328: * {@link NbiLabel} which represents the progress' detailed status.
329: */
330: private NbiLabel detailLabel;
331:
332: /**
333: * {@link NbiProgressBar} which represents the progress' percentage.
334: */
335: private NbiProgressBar progressBar;
336:
337: /**
338: * Creates a new instance of {@link WizardActionSwingUi}, initializing it
339: * with the specified instances of {@link WizardAction} and
340: * {@link SwingContainer}.
341: *
342: * @param action Instance of {@link WizardAction} which should be used
343: * by this UI.
344: * @param container Instance of {@link SwingContainer} which should be used
345: * by this UI.
346: */
347: public WizardActionSwingUi(final WizardAction action,
348: final SwingContainer container) {
349: super (action, container);
350:
351: this .action = action;
352:
353: initComponents();
354: }
355:
356: /**
357: * {@inheritDoc}
358: */
359: @Override
360: public void initializeContainer() {
361: super .initializeContainer();
362:
363: // set up the help button
364: container.getHelpButton().setEnabled(false);
365: container.getHelpButton().setVisible(false);
366:
367: // set up the back button
368: container.getBackButton().setEnabled(false);
369: container.getBackButton().setVisible(false);
370:
371: // set up the next (or finish) button
372: container.getNextButton().setEnabled(false);
373: container.getNextButton().setVisible(true);
374:
375: // set up the cancel button
376: container.getCancelButton().setVisible(true);
377: container.getCancelButton().setEnabled(
378: action.isCancelable());
379: }
380:
381: /**
382: * {@inheritDoc}
383: */
384: @Override
385: public void evaluateCancelButtonClick() {
386: if (action.isCancelable()) {
387: final String cancelDialogTitle = ResourceUtils
388: .getString(WizardAction.class,
389: RESOURCE_CANCEL_DIALOG_TITLE);
390: final String canceldialogText = ResourceUtils
391: .getString(WizardAction.class,
392: RESOURCE_CANCEL_DIALOG_TEXT);
393:
394: if (!UiUtils.showYesNoDialog(cancelDialogTitle,
395: canceldialogText)) {
396: return;
397: }
398:
399: container.getCancelButton().setEnabled(false);
400: titleLabel.setText(ResourceUtils.getString(
401: WizardAction.class,
402: RESOURCE_CANCELING_PROGRESS_TITLE));
403:
404: new NbiThread() {
405: public void run() {
406: ((WizardAction) action).cancel();
407: action.getWizard().getFinishHandler().cancel();
408: }
409: }.start();
410: }
411: }
412:
413: /**
414: * {@inheritDoc}
415: */
416: @Override
417: public NbiButton getDefaultEnterButton() {
418: return container.getCancelButton();
419: }
420:
421: /**
422: * This method is called from the corresponding {@link WizardActionUi} to
423: * inform this class about the fact that the action's progress has been
424: * updated. Thismethod updates the title, detail and progress bar components.
425: *
426: * @param progress {@link Progress} which has been updated.
427: */
428: public void progressUpdated(final Progress progress) {
429: if (progress != null) {
430: if (titleLabel != null) {
431: titleLabel.setText(progress.getTitle());
432: }
433:
434: if (detailLabel != null) {
435: detailLabel.setText(progress.getDetail());
436: }
437:
438: if (progressBar != null) {
439: progressBar.setValue(progress.getPercentage());
440: }
441: }
442: }
443:
444: // private //////////////////////////////////////////////////////////////////
445: /**
446: * Initializes and lays out the swing components in this UI.
447: */
448: private void initComponents() {
449: // titleLabel ///////////////////////////////////////////////////////////
450: titleLabel = new NbiLabel();
451: titleLabel.setFocusable(true);
452:
453: // progressBar //////////////////////////////////////////////////////////
454: progressBar = new NbiProgressBar();
455:
456: // detailLabel //////////////////////////////////////////////////////////
457: detailLabel = new NbiLabel(true);
458: detailLabel.setFocusable(true);
459:
460: // this /////////////////////////////////////////////////////////////////
461: add(titleLabel, new GridBagConstraints(0, 0, // x, y
462: 1, 1, // width, height
463: 1.0, 0.0, // weight-x, weight-y
464: GridBagConstraints.SOUTH, // anchor
465: GridBagConstraints.HORIZONTAL, // fill
466: new Insets(11, 11, 0, 11), // padding
467: 0, 0)); // ??? (padx, pady)
468: add(progressBar, new GridBagConstraints(0, 1, // x, y
469: 1, 1, // width, height
470: 1.0, 0.0, // weight-x, weight-y
471: GridBagConstraints.NORTH, // anchor
472: GridBagConstraints.HORIZONTAL, // fill
473: new Insets(4, 11, 0, 11), // padding
474: 0, 0)); // ??? (padx, pady)
475: add(detailLabel, new GridBagConstraints(0, 2, // x, y
476: 1, 1, // width, height
477: 1.0, 1.0, // weight-x, weight-y
478: GridBagConstraints.PAGE_START, // anchor
479: GridBagConstraints.HORIZONTAL, // fill
480: new Insets(4, 11, 0, 11), // padding
481: 0, 0)); // ??? (padx, pady)
482: }
483: }
484:
485: /////////////////////////////////////////////////////////////////////////////////
486: // Constants
487: /**
488: * Name of a resource bundle entry.
489: */
490: private static final String RESOURCE_CANCEL_DIALOG_TITLE = "WA.cancel.dialog.title"; // NOI18N
491:
492: /**
493: * Name of a resource bundle entry.
494: */
495: private static final String RESOURCE_CANCEL_DIALOG_TEXT = "WA.cancel.dialog.text"; // NOI18N
496:
497: /**
498: * Name of a resource bundle entry.
499: */
500: private static final String RESOURCE_INTERRUPTED_EXCEPTION = "WA.error.interrupted.exception"; // NOI18N
501:
502: /**
503: * Name of a resource bundle entry.
504: */
505: private static final String RESOURCE_CANCELING_PROGRESS_TITLE = "WA.canceling.progress.title"; // NOI18N
506: }
|