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-2007 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.ruby.options;
042:
043: import java.awt.Component;
044: import java.awt.Container;
045: import java.awt.Font;
046: import java.awt.FontMetrics;
047: import java.awt.event.ActionEvent;
048: import java.awt.event.ActionListener;
049: import java.beans.PropertyChangeListener;
050: import java.beans.PropertyChangeSupport;
051: import java.util.HashMap;
052: import java.util.Map;
053: import java.util.prefs.BackingStoreException;
054: import java.util.prefs.Preferences;
055: import javax.swing.ComboBoxModel;
056: import javax.swing.JCheckBox;
057: import javax.swing.JComboBox;
058: import javax.swing.JComponent;
059: import javax.swing.JComponent;
060: import javax.swing.JEditorPane;
061: import javax.swing.JPanel;
062: import javax.swing.JTextField;
063: import javax.swing.JViewport;
064: import javax.swing.event.DocumentEvent;
065: import javax.swing.event.DocumentListener;
066: import javax.swing.text.EditorKit;
067: import org.netbeans.api.editor.mimelookup.MimeLookup;
068: import org.netbeans.api.editor.mimelookup.MimePath;
069: import org.netbeans.api.ruby.platform.RubyInstallation;
070: import org.netbeans.editor.BaseDocument;
071: import org.netbeans.editor.Settings;
072: import org.netbeans.editor.SettingsNames;
073: import org.netbeans.modules.ruby.Formatter;
074: import org.netbeans.modules.ruby.lexer.RubyTokenId;
075: import org.netbeans.spi.options.OptionsPanelController;
076: import org.openide.util.Exceptions;
077:
078: import static org.netbeans.modules.ruby.options.CodeStyle.*;
079: import org.openide.util.HelpCtx;
080: import org.openide.util.Lookup;
081: import org.openide.util.NbBundle;
082: import org.openide.util.NbPreferences;
083:
084: /**
085: *
086: * @author phrebejk
087: *
088: * @todo Add an RHTML options category, such that I can see the effects of
089: * switching the RHTML toggles?
090: */
091: public class FmtOptions {
092:
093: public static final String expandTabToSpaces = "expandTabToSpaces"; //NOI18N
094: public static final String tabSize = "tabSize"; //NOI18N
095: public static final String indentSize = "indentSize"; //NOI18N
096: public static final String continuationIndentSize = "continuationIndentSize"; //NOI18N
097: public static final String reformatComments = "reformatComments"; //NOI18N
098: public static final String indentHtml = "indentHtml"; //NOI18N
099: public static final String rightMargin = "rightMargin"; //NOI18N
100:
101: public static CodeStyleProducer codeStyleProducer;
102:
103: public static Preferences lastValues;
104:
105: private static Class<? extends EditorKit> kitClass;
106:
107: private static final String DEFAULT_PROFILE = "default"; // NOI18N
108:
109: private FmtOptions() {
110: }
111:
112: public static int getDefaultAsInt(String key) {
113: return Integer.parseInt(defaults.get(key));
114: }
115:
116: public static boolean getDefaultAsBoolean(String key) {
117: return Boolean.parseBoolean(defaults.get(key));
118: }
119:
120: public static String getDefaultAsString(String key) {
121: return defaults.get(key);
122: }
123:
124: public static Preferences getPreferences(String profileId) {
125: return NbPreferences.forModule(CodeStyle.class).node(
126: "CodeStyle").node(profileId);
127: }
128:
129: public static boolean getGlobalExpandTabToSpaces() {
130: org.netbeans.editor.Formatter f = (org.netbeans.editor.Formatter) Settings
131: .getValue(getKitClass(), "formatter");
132: if (f != null)
133: return f.expandTabs();
134: return getDefaultAsBoolean(expandTabToSpaces);
135: }
136:
137: public static int getGlobalTabSize() {
138: Integer i = (Integer) Settings.getValue(getKitClass(),
139: SettingsNames.TAB_SIZE);
140: return i != null ? i.intValue() : getDefaultAsInt(tabSize);
141: }
142:
143: // Ruby needs its own indent size; the global "4" isn't a good match
144: // public static int getGlobalIndentSize() {
145: // org.netbeans.editor.Formatter f = (org.netbeans.editor.Formatter)Settings.getValue(getKitClass(), "formatter");
146: // if (f != null)
147: // return f.getShiftWidth();
148: // return getDefaultAsInt(indentSize);
149: // }
150:
151: public static int getGlobalRightMargin() {
152: Integer i = (Integer) Settings.getValue(getKitClass(),
153: SettingsNames.TEXT_LIMIT_WIDTH);
154: return i != null ? i.intValue() : getDefaultAsInt(rightMargin);
155: }
156:
157: public static Class<? extends EditorKit> getKitClass() {
158: if (kitClass == null) {
159: EditorKit kit = MimeLookup.getLookup(
160: MimePath.get(RubyInstallation.RUBY_MIME_TYPE))
161: .lookup(EditorKit.class); //NOI18N
162: kitClass = kit != null ? kit.getClass() : EditorKit.class;
163: }
164: return kitClass;
165: }
166:
167: public static void flush() {
168: try {
169: getPreferences(getCurrentProfileId()).flush();
170: } catch (BackingStoreException e) {
171: Exceptions.printStackTrace(e);
172: }
173: }
174:
175: public static String getCurrentProfileId() {
176: return DEFAULT_PROFILE;
177: }
178:
179: public static CodeStyle createCodeStyle(Preferences p) {
180: CodeStyle.getDefault(null);
181: return codeStyleProducer.create(p);
182: }
183:
184: public static boolean isInteger(String optionID) {
185: String value = defaults.get(optionID);
186:
187: try {
188: Integer.parseInt(value);
189: return true;
190: } catch (NumberFormatException numberFormatException) {
191: return false;
192: }
193: }
194:
195: public static String getLastValue(String optionID) {
196: Preferences p = lastValues == null ? getPreferences(getCurrentProfileId())
197: : lastValues;
198: return p.get(optionID, getDefaultAsString(optionID));
199: }
200:
201: // Private section ---------------------------------------------------------
202:
203: private static final String TRUE = "true"; // NOI18N
204: private static final String FALSE = "false"; // NOI18N
205:
206: private static Map<String, String> defaults;
207:
208: static {
209: createDefaults();
210: }
211:
212: private static void createDefaults() {
213: String defaultValues[][] = { { expandTabToSpaces, TRUE }, //NOI18N
214: { tabSize, "8" }, //NOI18N
215: { indentSize, "2" }, //NOI18N
216: { continuationIndentSize, "2" }, //NOI18N
217: { reformatComments, FALSE }, //NOI18N
218: { indentHtml, TRUE }, //NOI18N
219: { rightMargin, "80" }, //NOI18N
220: };
221:
222: defaults = new HashMap<String, String>();
223:
224: for (java.lang.String[] strings : defaultValues) {
225: defaults.put(strings[0], strings[1]);
226: }
227:
228: }
229:
230: // Support section ---------------------------------------------------------
231:
232: public static class CategorySupport extends
233: FormatingOptionsPanel.Category implements ActionListener,
234: DocumentListener {
235:
236: public static final String OPTION_ID = "org.netbeans.modules.ruby.options.FormatingOptions.ID";
237:
238: private static final int LOAD = 0;
239: private static final int STORE = 1;
240: private static final int ADD_LISTENERS = 2;
241:
242: private String previewText = NbBundle.getMessage(
243: FmtOptions.class, "SAMPLE_Default");
244: private String forcedOptions[][];
245:
246: private boolean changed = false;
247: private JPanel panel;
248: private final PropertyChangeSupport pcs = new PropertyChangeSupport(
249: this );
250:
251: public CategorySupport(String nameKey, JPanel panel,
252: String previewText, String[]... forcedOptions) {
253: super (nameKey);
254: this .panel = panel;
255: this .previewText = previewText == null ? this .previewText
256: : previewText;
257: this .forcedOptions = forcedOptions;
258: addListeners();
259: }
260:
261: protected void addListeners() {
262: scan(panel, ADD_LISTENERS, null);
263: }
264:
265: public void update() {
266: scan(panel, LOAD, null);
267: }
268:
269: public void applyChanges() {
270: scan(panel, STORE, null);
271: }
272:
273: public void storeTo(Preferences preferences) {
274: scan(panel, STORE, preferences);
275: }
276:
277: public void refreshPreview(JEditorPane pane, Preferences p) {
278:
279: for (String[] option : forcedOptions) {
280: p.put(option[0], option[1]);
281: }
282:
283: int rm = 30;
284: try {
285: rm = p
286: .getInt(rightMargin,
287: getDefaultAsInt(rightMargin));
288:
289: // Estimate text line in preview pane
290:
291: JComponent pc = pane;
292: if (pane.getParent() instanceof JViewport) {
293: pc = (JViewport) pane.getParent();
294: }
295: Font font = pc.getFont();
296: FontMetrics metrics = pc.getFontMetrics(font);
297: int cw = metrics.charWidth('x');
298: if (cw > 0) {
299: int nrm = pc.getWidth() / cw;
300: if (nrm > 3) {
301: rm = nrm - 2;
302: }
303: }
304:
305: //pane.putClientProperty("TextLimitLine", rm); // NOI18N
306: } catch (NumberFormatException e) {
307: // Ignore it
308: }
309:
310: CodeStyle codeStyle = FmtOptions.createCodeStyle(p);
311:
312: try {
313: BaseDocument doc = new BaseDocument(null, false);
314: doc.putProperty(org.netbeans.api.lexer.Language.class,
315: RubyTokenId.language());
316:
317: doc.insertString(0, previewText, null);
318:
319: Formatter formatter = new Formatter(codeStyle, rm);
320: formatter.reformat(doc, 0, doc.getLength(), null);
321:
322: String formatted = doc.getText(0, doc.getLength());
323: pane.setText(formatted);
324: } catch (Exception ex) {
325: Exceptions.printStackTrace(ex);
326: }
327: }
328:
329: public void cancel() {
330: // Usually does not need to do anything
331: }
332:
333: public boolean isValid() {
334: return true; // Should almost always be OK
335: }
336:
337: public boolean isChanged() {
338: return changed;
339: }
340:
341: public JComponent getComponent(Lookup masterLookup) {
342: return panel;
343: }
344:
345: public HelpCtx getHelpCtx() {
346: return null;
347: }
348:
349: public void addPropertyChangeListener(PropertyChangeListener l) {
350: pcs.addPropertyChangeListener(l);
351: }
352:
353: public void removePropertyChangeListener(
354: PropertyChangeListener l) {
355: pcs.removePropertyChangeListener(l);
356: }
357:
358: void changed() {
359: if (!changed) {
360: changed = true;
361: pcs.firePropertyChange(
362: OptionsPanelController.PROP_CHANGED, false,
363: true);
364: }
365: pcs.firePropertyChange(OptionsPanelController.PROP_VALID,
366: null, null);
367: }
368:
369: // ActionListener implementation ---------------------------------------
370:
371: public void actionPerformed(ActionEvent e) {
372: changed();
373: }
374:
375: // DocumentListener implementation -------------------------------------
376:
377: public void insertUpdate(DocumentEvent e) {
378: changed();
379: }
380:
381: public void removeUpdate(DocumentEvent e) {
382: changed();
383: }
384:
385: public void changedUpdate(DocumentEvent e) {
386: changed();
387: }
388:
389: // Private methods -----------------------------------------------------
390:
391: private void scan(Container container, int what, Preferences p) {
392: for (Component c : container.getComponents()) {
393: if (c instanceof JComponent) {
394: JComponent jc = (JComponent) c;
395: Object o = jc.getClientProperty(OPTION_ID);
396: if (o != null && o instanceof String) {
397: switch (what) {
398: case LOAD:
399: loadData(jc, (String) o);
400: break;
401: case STORE:
402: storeData(jc, (String) o, p);
403: break;
404: case ADD_LISTENERS:
405: addListener(jc);
406: break;
407: }
408: }
409: }
410: if (c instanceof Container) {
411: scan((Container) c, what, p);
412: }
413: }
414:
415: }
416:
417: /** Very smart method which tries to set the values in the components correctly
418: */
419: private void loadData(JComponent jc, String optionID) {
420:
421: Preferences node = getPreferences(getCurrentProfileId());
422:
423: if (jc instanceof JTextField) {
424: JTextField field = (JTextField) jc;
425: field.setText(node.get(optionID,
426: getDefaultAsString(optionID)));
427: } else if (jc instanceof JCheckBox) {
428: JCheckBox checkBox = (JCheckBox) jc;
429: boolean df = getDefaultAsBoolean(optionID);
430: checkBox.setSelected(node.getBoolean(optionID, df));
431: } else if (jc instanceof JComboBox) {
432: JComboBox cb = (JComboBox) jc;
433: String value = node.get(optionID,
434: getDefaultAsString(optionID));
435: ComboBoxModel model = createModel(value);
436: cb.setModel(model);
437: ComboItem item = whichItem(value, model);
438: cb.setSelectedItem(item);
439: }
440:
441: }
442:
443: private void storeData(JComponent jc, String optionID,
444: Preferences p) {
445: Preferences node = p == null ? getPreferences(getCurrentProfileId())
446: : p;
447:
448: if (jc instanceof JTextField) {
449: JTextField field = (JTextField) jc;
450:
451: String text = field.getText();
452:
453: if (isInteger(optionID)) {
454: try {
455: int i = Integer.parseInt(text);
456: } catch (NumberFormatException e) {
457: text = getLastValue(optionID);
458: }
459: }
460:
461: // XXX test for numbers
462: node.put(optionID, text);
463: } else if (jc instanceof JCheckBox) {
464: JCheckBox checkBox = (JCheckBox) jc;
465: node.putBoolean(optionID, checkBox.isSelected());
466: } else if (jc instanceof JComboBox) {
467: JComboBox cb = (JComboBox) jc;
468: // Logger.global.info( cb.getSelectedItem() + " " + optionID);
469: node.put(optionID,
470: ((ComboItem) cb.getSelectedItem()).value);
471: }
472: }
473:
474: private void addListener(JComponent jc) {
475: if (jc instanceof JTextField) {
476: JTextField field = (JTextField) jc;
477: field.addActionListener(this );
478: field.getDocument().addDocumentListener(this );
479: } else if (jc instanceof JCheckBox) {
480: JCheckBox checkBox = (JCheckBox) jc;
481: checkBox.addActionListener(this );
482: } else if (jc instanceof JComboBox) {
483: JComboBox cb = (JComboBox) jc;
484: cb.addActionListener(this );
485: }
486: }
487:
488: private ComboBoxModel createModel(String value) {
489: return null;
490: }
491:
492: private static ComboItem whichItem(String value,
493: ComboBoxModel model) {
494:
495: for (int i = 0; i < model.getSize(); i++) {
496: ComboItem item = (ComboItem) model.getElementAt(i);
497: if (value.equals(item.value)) {
498: return item;
499: }
500: }
501: return null;
502: }
503:
504: private static class ComboItem {
505:
506: String value;
507: String displayName;
508:
509: public ComboItem(String value, String key) {
510: this .value = value;
511: this .displayName = NbBundle.getMessage(
512: FmtOptions.class, key);
513: }
514:
515: @Override
516: public String toString() {
517: return displayName;
518: }
519:
520: }
521: }
522:
523: public static interface CodeStyleProducer {
524:
525: public CodeStyle create(Preferences preferences);
526:
527: }
528: }
|