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
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.vmd.midp.propertyeditors.api.usercode;
042:
043: import org.netbeans.api.java.source.JavaSource;
044: import org.netbeans.api.java.source.ui.DialogBinding;
045: import org.netbeans.modules.vmd.api.io.DataObjectContext;
046: import org.netbeans.modules.vmd.api.io.ProjectUtils;
047: import org.netbeans.modules.vmd.api.model.DesignComponent;
048: import org.netbeans.modules.vmd.api.model.PropertyValue;
049: import org.netbeans.modules.vmd.api.properties.DesignPropertyEditor;
050: import org.netbeans.modules.vmd.midp.propertyeditors.MidpPropertyEditorSupport;
051: import org.openide.awt.Mnemonics;
052: import org.openide.util.NbBundle;
053: import org.openide.util.Utilities;
054: import javax.swing.*;
055: import javax.swing.event.DocumentEvent;
056: import javax.swing.event.DocumentListener;
057: import javax.swing.text.Document;
058: import java.awt.*;
059: import java.awt.event.ActionEvent;
060: import java.awt.event.ActionListener;
061: import java.awt.event.KeyEvent;
062: import java.lang.ref.WeakReference;
063: import java.util.*;
064: import javax.swing.text.Keymap;
065: import javax.swing.undo.UndoManager;
066: import org.netbeans.editor.ActionFactory;
067: import org.netbeans.editor.BaseDocument;
068:
069: /**
070: * This class allows create PropertyEditor which supports User Code.
071: *
072: * See for example PropertyEditorString.
073: *
074: * <b>WARNING - while overriding init method, you have to call super.init() method too.</b>
075: *
076: * @author Anton Chechel
077: */
078: public abstract class PropertyEditorUserCode extends
079: DesignPropertyEditor implements PropertyEditorMessageAwareness {
080:
081: public static final PropertyValue NULL_VALUE = PropertyValue
082: .createNull();
083: public static final String NULL_TEXT = NbBundle.getMessage(
084: PropertyEditorUserCode.class, "LBL_STRING_NULL"); // NOI18N
085: public static final String USER_CODE_TEXT = NbBundle.getMessage(
086: PropertyEditorUserCode.class, "LBL_STRING_USER_CODE"); // NOI18N
087: private static final Icon ICON_WARNING = new ImageIcon(
088: Utilities
089: .loadImage("org/netbeans/modules/vmd/midp/resources/warning.gif")); // NOI18N
090: private static final Icon ICON_ERROR = new ImageIcon(
091: Utilities
092: .loadImage("org/netbeans/modules/vmd/midp/resources/error.gif")); // NOI18N
093: private final CustomEditor customEditor;
094: private JRadioButton userCodeRadioButton;
095: private final JLabel messageLabel;
096: private String userCodeLabel;
097: private String userCode = ""; // NOI18N
098: protected WeakReference<DesignComponent> component;
099:
100: protected PropertyEditorUserCode(String userCodeLabel) {
101: this .userCodeLabel = userCodeLabel;
102: messageLabel = new JLabel(" "); //NOI18N
103: Color nbErrorForeground = UIManager
104: .getColor("nb.errorForeground"); //NOI18N
105: if (nbErrorForeground == null) {
106: nbErrorForeground = new Color(255, 0, 0);
107: }
108: messageLabel.setForeground(nbErrorForeground);
109: customEditor = new CustomEditor();
110: }
111:
112: /**
113: * This method should be invoked from subclass to init elements.
114: */
115: protected void initElements(
116: Collection<PropertyEditorElement> elements) {
117: customEditor.init(elements);
118: }
119:
120: /**
121: * <b>WARNING - while override, you have to call super.init() method too.</b>
122: */
123: @Override
124: public void init(DesignComponent component) {
125: if (component != null) {
126: this .component = new WeakReference<DesignComponent>(
127: component);
128: }
129: }
130:
131: /**
132: * Updates state of custom editor and returns it to edit property value.
133: */
134: @Override
135: public final Component getCustomEditor() {
136: initCustomEditor();
137: return customEditor;
138: }
139:
140: private void initCustomEditor() {
141: PropertyValue value = (PropertyValue) super .getValue();
142: if (isCurrentValueAUserCodeType()) {
143: customEditor.setUserCodeText(value.getUserCode());
144: customEditor.updateState(null);
145: } else {
146: customEditor.setUserCodeText(null);
147: customEditor.updateState(value);
148: }
149: }
150:
151: @Override
152: public boolean isExecuteInsideWriteTransactionUsed() {
153: return false;
154: }
155:
156: /**
157: * Returns text for inplace property editor.
158: * <b>WARNING this method shoud be overriden and if it returns null then overriden getAsText() should return own text</b>
159: */
160: @Override
161: public String getAsText() {
162: if (isCurrentValueAUserCodeType()) {
163: return USER_CODE_TEXT;
164: } else if (isCurrentValueANull()) {
165: return NULL_TEXT;
166: }
167: return null;
168: }
169:
170: /**
171: * Sets property value depending on given text. Invoked from inplace editor.
172: */
173: @Override
174: public void setAsText(String text) {
175: if (canWrite()) {
176: if (text.equals(NULL_TEXT)) {
177: super .setValue(NULL_VALUE);
178: } else {
179: customEditor.setText(text);
180: }
181: }
182: }
183:
184: @Override
185: public Boolean canEditAsText() {
186: if (isCurrentValueAUserCodeType()) {
187: return false;
188: }
189: return MidpPropertyEditorSupport
190: .singleSelectionEditAsTextOnly();
191: }
192:
193: @Override
194: public boolean canWrite() {
195: return MidpPropertyEditorSupport
196: .singleSelectionEditAsTextOnly();
197: }
198:
199: /**
200: * <b>WARNING! If override invoke super.customEditorOKButtonPressed()</b>
201: */
202: @Override
203: public void customEditorOKButtonPressed() {
204: if (userCodeRadioButton.isSelected()) {
205: PropertyEditorUserCode.super .setValue(PropertyValue
206: .createUserCode(userCode));
207: }
208: }
209:
210: /**
211: * @return boolean whether current property value has kind of user code
212: */
213: protected boolean isCurrentValueAUserCodeType() {
214: PropertyValue value = (PropertyValue) super .getValue();
215: return value != null
216: && value.getKind() == PropertyValue.Kind.USERCODE;
217: }
218:
219: /**
220: * @return boolean whether current property value has kind of NULL
221: */
222: protected boolean isCurrentValueANull() {
223: PropertyValue value = (PropertyValue) super .getValue();
224: return value == null
225: || value.getKind() == PropertyValue.Kind.NULL;
226: }
227:
228: /**
229: * Displays warning message on the custom property editor panel
230: * @param message to be displayed
231: */
232: public void displayWarning(String message) {
233: messageLabel.setText(message);
234: messageLabel.setIcon(ICON_WARNING);
235: }
236:
237: /**
238: * Displays error message on the custom property editor panel
239: * @param message to be displayed
240: */
241: public void displayError(String message) {
242: messageLabel.setText(message);
243: messageLabel.setIcon(ICON_ERROR);
244: }
245:
246: /**
247: * Clears error/warning message on the custom property editor panel
248: */
249: public void clearErrorStatus() {
250: messageLabel.setText(" "); //NOI18N
251: messageLabel.setIcon(null);
252: }
253:
254: private static void setupTextUndoRedo(
255: javax.swing.text.JTextComponent editor) {
256: //So smart So nice.... Missis O.R.
257: String os = System.getProperty("os.name").toLowerCase(); //NOI18N
258:
259: KeyStroke[] undoKeys = null;
260: KeyStroke[] redoKeys = null;
261:
262: if (os.indexOf("mac") != -1) { //NOI18N
263: undoKeys = new KeyStroke[] {
264: KeyStroke.getKeyStroke(KeyEvent.VK_UNDO, 0),
265: KeyStroke.getKeyStroke(KeyEvent.VK_Z,
266: Event.META_MASK) };
267: redoKeys = new KeyStroke[] {
268: KeyStroke.getKeyStroke(KeyEvent.VK_AGAIN, 0),
269: KeyStroke.getKeyStroke(KeyEvent.VK_Y,
270: Event.META_MASK) };
271: } else {
272: undoKeys = new KeyStroke[] {
273: KeyStroke.getKeyStroke(KeyEvent.VK_UNDO, 0),
274: KeyStroke.getKeyStroke(KeyEvent.VK_Z, 130) };
275: redoKeys = new KeyStroke[] {
276: KeyStroke.getKeyStroke(KeyEvent.VK_AGAIN, 0),
277: KeyStroke.getKeyStroke(KeyEvent.VK_Y, 130) };
278: }
279:
280: Keymap keymap = editor.getKeymap();
281: Action undoAction = new ActionFactory.UndoAction();
282: for (KeyStroke k : undoKeys) {
283: keymap.removeKeyStrokeBinding(k);
284: keymap.addActionForKeyStroke(k, undoAction);
285: }
286: Action redoAction = new ActionFactory.RedoAction();
287: for (KeyStroke k : redoKeys) {
288: keymap.removeKeyStrokeBinding(k);
289: keymap.addActionForKeyStroke(k, redoAction);
290: }
291: Object currentUM = editor.getDocument().getProperty(
292: BaseDocument.UNDO_MANAGER_PROP);
293: if (currentUM instanceof UndoManager) {
294: editor.getDocument().removeUndoableEditListener(
295: (UndoManager) currentUM);
296: }
297: UndoManager um = new UndoManager();
298: editor.getDocument().addUndoableEditListener(um);
299: editor.getDocument().putProperty(
300: BaseDocument.UNDO_MANAGER_PROP, um);
301: }
302:
303: private final class CustomEditor extends JPanel implements
304: DocumentListener, ActionListener {
305:
306: private Collection<PropertyEditorElement> elements;
307: private JEditorPane userCodeEditorPane;
308:
309: public void init(Collection<PropertyEditorElement> elements) {
310: this .elements = elements;
311: initComponents();
312: }
313:
314: private void initComponents() {
315: setLayout(new GridBagLayout());
316: ButtonGroup buttonGroup = new ButtonGroup();
317: GridBagConstraints constraints = new GridBagConstraints();
318: boolean isAnyElementVerticallyResizable = false;
319: for (PropertyEditorElement element : elements) {
320: JRadioButton rb = element.getRadioButton();
321: buttonGroup.add(rb);
322: constraints.insets = new Insets(12, 12, 6, 12);
323: constraints.anchor = GridBagConstraints.NORTHWEST;
324: constraints.gridx = GridBagConstraints.REMAINDER;
325: constraints.gridy = GridBagConstraints.RELATIVE;
326: constraints.weightx = 1.0;
327: constraints.weighty = 0.0;
328: constraints.fill = GridBagConstraints.HORIZONTAL;
329: add(rb, constraints);
330:
331: constraints.insets = new Insets(0, 32, 12, 12);
332: constraints.anchor = GridBagConstraints.NORTHWEST;
333: constraints.gridx = GridBagConstraints.REMAINDER;
334: constraints.gridy = GridBagConstraints.RELATIVE;
335: constraints.weightx = 1.0;
336: constraints.weighty = element.isVerticallyResizable() ? 1.0
337: : 0.0;
338: constraints.fill = element.isVerticallyResizable() ? GridBagConstraints.BOTH
339: : GridBagConstraints.HORIZONTAL;
340: add(element.getCustomEditorComponent(), constraints);
341:
342: if (element.isVerticallyResizable()) {
343: isAnyElementVerticallyResizable = true;
344: }
345: }
346:
347: userCodeRadioButton = new JRadioButton();
348: Mnemonics.setLocalizedText(userCodeRadioButton, NbBundle
349: .getMessage(PropertyEditorUserCode.class,
350: "LBL_USER_CODE", userCodeLabel)); // NOI18N
351: userCodeRadioButton.getAccessibleContext()
352: .setAccessibleName(
353: NbBundle.getMessage(
354: PropertyEditorUserCode.class,
355: "ACSN_USER_CODE", userCodeLabel));
356: userCodeRadioButton.getAccessibleContext()
357: .setAccessibleDescription(
358: NbBundle.getMessage(
359: PropertyEditorUserCode.class,
360: "ACSD_USER_CODE", userCodeLabel));
361: userCodeRadioButton.addActionListener(this );
362: buttonGroup.add(userCodeRadioButton);
363:
364: constraints.insets = new Insets(12, 12, 6, 12);
365: constraints.anchor = GridBagConstraints.NORTHWEST;
366: constraints.gridx = GridBagConstraints.REMAINDER;
367: constraints.gridy = GridBagConstraints.RELATIVE;
368: constraints.weightx = 1.0;
369: constraints.weighty = 0.0;
370: constraints.fill = GridBagConstraints.HORIZONTAL;
371: add(userCodeRadioButton, constraints);
372:
373: JScrollPane jsp = new JScrollPane();
374: userCodeEditorPane = new JEditorPane();
375: //userCodeEditorPane.setFont(userCodeRadioButton.getFont());
376: SwingUtilities.invokeLater(new Runnable() {
377:
378: //otherwise we get: java.lang.AssertionError: BaseKit.install() incorrectly called from non-AWT thread.
379: public void run() {
380: userCodeEditorPane.setContentType("text/x-java"); // NOI18N
381: userCodeEditorPane.getDocument()
382: .addDocumentListener(CustomEditor.this );
383: }
384: });
385: jsp.setViewportView(userCodeEditorPane);
386: jsp.setPreferredSize(new Dimension(400, 100));
387: jsp.setMinimumSize(new Dimension(400, 100));
388:
389: constraints.insets = new Insets(0, 32, 12, 12);
390: constraints.anchor = GridBagConstraints.NORTHWEST;
391: constraints.gridx = GridBagConstraints.REMAINDER;
392: constraints.gridy = GridBagConstraints.RELATIVE;
393: constraints.weightx = 1.0;
394: constraints.weighty = isAnyElementVerticallyResizable ? 0.0
395: : 1.0;
396: constraints.fill = isAnyElementVerticallyResizable ? GridBagConstraints.HORIZONTAL
397: : GridBagConstraints.BOTH;
398: add(jsp, constraints);
399:
400: constraints.insets = new Insets(0, 12, 0, 12);
401: constraints.anchor = GridBagConstraints.NORTHWEST;
402: constraints.gridx = GridBagConstraints.REMAINDER;
403: constraints.gridy = GridBagConstraints.RELATIVE;
404: constraints.weightx = 1.0;
405: constraints.weighty = 0.0;
406: constraints.fill = GridBagConstraints.HORIZONTAL;
407: add(messageLabel, constraints);
408:
409: selectDefaultRadioButton();
410: }
411:
412: public void initRetoucheStuff() {
413: if (component == null || component.get() == null) {
414: return;
415: }
416: DesignComponent _component = component.get();
417:
418: javax.swing.text.Document swingDoc = userCodeEditorPane
419: .getDocument();
420: if (swingDoc.getProperty(JavaSource.class) == null) {
421: DataObjectContext context = ProjectUtils
422: .getDataObjectContextForDocument(_component
423: .getDocument());
424: swingDoc.putProperty(
425: Document.StreamDescriptionProperty, context
426: .getDataObject());
427: int offset = CodeUtils.getMethodOffset(context);
428: DialogBinding.bindComponentToFile(context
429: .getDataObject().getPrimaryFile(), offset, 0,
430: userCodeEditorPane);
431: PropertyEditorUserCode
432: .setupTextUndoRedo(userCodeEditorPane);
433: }
434: }
435:
436: /**
437: * Updates state of custom editor
438: * @param value if null - clear state
439: */
440: public void updateState(PropertyValue value) {
441: for (PropertyEditorElement element : elements) {
442: element.updateState(value);
443: }
444: }
445:
446: /**
447: * Sets text for each element
448: * @param text to be set
449: */
450: public void setText(String text) {
451: for (PropertyEditorElement element : elements) {
452: element.setTextForPropertyValue(text);
453: }
454: }
455:
456: /**
457: * Sets text for user code pane
458: * @param text to be set
459: */
460: public void setUserCodeText(String text) {
461: if (text != null) {
462: userCodeEditorPane.setText(text);
463: userCodeRadioButton.setSelected(true);
464: userCodeRadioButton.requestFocus();
465: } else {
466: userCodeEditorPane.setText(null);
467: selectDefaultRadioButton();
468: }
469: }
470:
471: public void insertUpdate(DocumentEvent evt) {
472: if (userCodeEditorPane.hasFocus()) {
473: userCodeRadioButton.setSelected(true);
474: setNewValue();
475: }
476: }
477:
478: public void removeUpdate(DocumentEvent evt) {
479: if (userCodeEditorPane.hasFocus()) {
480: userCodeRadioButton.setSelected(true);
481: setNewValue();
482: }
483: }
484:
485: public void changedUpdate(DocumentEvent evt) {
486: }
487:
488: public void actionPerformed(ActionEvent evt) {
489: setNewValue();
490: }
491:
492: @Override
493: public void addNotify() {
494: customEditor.initRetoucheStuff();
495: super .addNotify();
496: }
497:
498: private void setNewValue() {
499: userCode = userCodeEditorPane.getText();
500: }
501:
502: private void selectDefaultRadioButton() {
503: boolean wasSelected = false;
504: for (PropertyEditorElement element : elements) {
505: if (element.isInitiallySelected()) {
506: element.getRadioButton().setSelected(true);
507: element.getRadioButton().requestFocus();
508: wasSelected = true;
509: break;
510: }
511: }
512: userCodeRadioButton.setSelected(!wasSelected);
513: if (!wasSelected) {
514: userCodeEditorPane.requestFocus();
515: }
516: }
517: }
518: }
|