diff --git a/api.debugger/manifest.mf b/api.debugger/manifest.mf --- a/api.debugger/manifest.mf +++ b/api.debugger/manifest.mf @@ -2,3 +2,4 @@ OpenIDE-Module: org.netbeans.api.debugger/1 OpenIDE-Module-Localizing-Bundle: org/netbeans/api/debugger/Bundle.properties OpenIDE-Module-Specification-Version: 1.16 +OpenIDE-Module-Layer: org/netbeans/api/debugger/layer.xml diff --git a/api.debugger/src/org/netbeans/api/debugger/Bundle.properties b/api.debugger/src/org/netbeans/api/debugger/Bundle.properties --- a/api.debugger/src/org/netbeans/api/debugger/Bundle.properties +++ b/api.debugger/src/org/netbeans/api/debugger/Bundle.properties @@ -48,3 +48,6 @@ # {2} supported languages # {3} created engines USG_DEBUG_SESSION_START=Debugger session {0} started at {1} for {2} + +# Options export +Debugger.Options.Export.displayName=Debugger diff --git a/core.ui/src/org/netbeans/core/ui/options/filetypes/Bundle.properties b/core.ui/src/org/netbeans/core/ui/options/filetypes/Bundle.properties --- a/core.ui/src/org/netbeans/core/ui/options/filetypes/Bundle.properties +++ b/core.ui/src/org/netbeans/core/ui/options/filetypes/Bundle.properties @@ -89,3 +89,6 @@ OpenAsPanel.title=Open as... OpenAsPanel.open=Open OpenAsPanel.AD=Choose type of file + +# display name for options export +Files.Options.Export.displayName=Files diff --git a/core.ui/src/org/netbeans/core/ui/options/general/Bundle.properties b/core.ui/src/org/netbeans/core/ui/options/general/Bundle.properties --- a/core.ui/src/org/netbeans/core/ui/options/general/Bundle.properties +++ b/core.ui/src/org/netbeans/core/ui/options/general/Bundle.properties @@ -255,3 +255,9 @@ # Keywords for quick-search KW_General=Proxy, General Options + +# Display name for options export +General.Options.Export.Category.displayName=General +General.Options.Export.displayName=General +Other.Options.Export.displayName=All Other Unspecified +netbeans.conf.Options.Export.displayName=Custom netbeans.conf diff --git a/core.ui/src/org/netbeans/core/ui/resources/layer.xml b/core.ui/src/org/netbeans/core/ui/resources/layer.xml --- a/core.ui/src/org/netbeans/core/ui/resources/layer.xml +++ b/core.ui/src/org/netbeans/core/ui/resources/layer.xml @@ -713,4 +713,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core.windows/src/org/netbeans/core/windows/resources/Bundle.properties b/core.windows/src/org/netbeans/core/windows/resources/Bundle.properties --- a/core.windows/src/org/netbeans/core/windows/resources/Bundle.properties +++ b/core.windows/src/org/netbeans/core/windows/resources/Bundle.properties @@ -45,3 +45,6 @@ Actions/Window=Window Menu/Window=&Window + +# Options export +Toolbars.Options.Export.displayName=Toolbars diff --git a/core.windows/src/org/netbeans/core/windows/resources/layer.xml b/core.windows/src/org/netbeans/core/windows/resources/layer.xml --- a/core.windows/src/org/netbeans/core/windows/resources/layer.xml +++ b/core.windows/src/org/netbeans/core/windows/resources/layer.xml @@ -307,4 +307,13 @@ + + + + + + + + + diff --git a/db/src/org/netbeans/modules/db/explorer/Bundle.properties b/db/src/org/netbeans/modules/db/explorer/Bundle.properties --- a/db/src/org/netbeans/modules/db/explorer/Bundle.properties +++ b/db/src/org/netbeans/modules/db/explorer/Bundle.properties @@ -37,3 +37,6 @@ ERR_CONN_ALREADY_EXISTS = The connection with the name {0} already exists MSG_TestFailed = The connection {0} does not appear to have a valid connection to the database: {1} EXC_DerbyCreateDatabaseUnicode=Unable to create the database because the Derby network driver does not support multibyte characters. + +# Options export +Databases.Options.Export.displayName=Databases diff --git a/db/src/org/netbeans/modules/db/resources/mf-layer.xml b/db/src/org/netbeans/modules/db/resources/mf-layer.xml --- a/db/src/org/netbeans/modules/db/resources/mf-layer.xml +++ b/db/src/org/netbeans/modules/db/resources/mf-layer.xml @@ -614,5 +614,13 @@ - + + + + + + + + + diff --git a/diff/src/org/netbeans/modules/diff/Bundle.properties b/diff/src/org/netbeans/modules/diff/Bundle.properties --- a/diff/src/org/netbeans/modules/diff/Bundle.properties +++ b/diff/src/org/netbeans/modules/diff/Bundle.properties @@ -89,3 +89,6 @@ ACSD_PatchDialog=This dialog allows you to choose patch CTL_PatchDialog_FileFilter = Patch Files (*.diff, *.patch) + +# Options Export +Diff.Options.Export.displayName=Diff diff --git a/diff/src/org/netbeans/modules/diff/mf-layer.xml b/diff/src/org/netbeans/modules/diff/mf-layer.xml --- a/diff/src/org/netbeans/modules/diff/mf-layer.xml +++ b/diff/src/org/netbeans/modules/diff/mf-layer.xml @@ -79,7 +79,16 @@ - + + + + + + + + + + diff --git a/editor/src/org/netbeans/modules/editor/Bundle.properties b/editor/src/org/netbeans/modules/editor/Bundle.properties --- a/editor/src/org/netbeans/modules/editor/Bundle.properties +++ b/editor/src/org/netbeans/modules/editor/Bundle.properties @@ -172,3 +172,7 @@ hyperlinks=Hyperlinks synchronized-text-blocks-ext=Text Boxes (Editable) synchronized-text-blocks-ext-slave=Text Boxes (Synchronized) + +# Options Export +Editor.Export.Category.displayName=Editor +IndentEngine.Options.Export.displayName=Indent Engines diff --git a/editor/src/org/netbeans/modules/editor/resources/layer.xml b/editor/src/org/netbeans/modules/editor/resources/layer.xml --- a/editor/src/org/netbeans/modules/editor/resources/layer.xml +++ b/editor/src/org/netbeans/modules/editor/resources/layer.xml @@ -380,5 +380,14 @@ - + + + + + + + + + + diff --git a/favorites/src/org/netbeans/modules/favorites/Bundle.properties b/favorites/src/org/netbeans/modules/favorites/Bundle.properties --- a/favorites/src/org/netbeans/modules/favorites/Bundle.properties +++ b/favorites/src/org/netbeans/modules/favorites/Bundle.properties @@ -70,3 +70,8 @@ OpenIDE-Module-Display-Category=Infrastructure OpenIDE-Module-Short-Description=Support for organizing favorite files. OpenIDE-Module-Long-Description=Favorites module enables you to create a view of files specifically relevant to your work, simply access them at one common place. + +# Options Export +Templates.Options.Export.Category.displayName=Templates +Templates.Options.Export.displayName=Templates +Favorites.Options.Export.displayName=Favorites diff --git a/favorites/src/org/netbeans/modules/favorites/resources/layer.xml b/favorites/src/org/netbeans/modules/favorites/resources/layer.xml --- a/favorites/src/org/netbeans/modules/favorites/resources/layer.xml +++ b/favorites/src/org/netbeans/modules/favorites/resources/layer.xml @@ -146,5 +146,22 @@ - + + + + + + + + + + + + + + + + + + diff --git a/form/src/org/netbeans/modules/form/Bundle.properties b/form/src/org/netbeans/modules/form/Bundle.properties --- a/form/src/org/netbeans/modules/form/Bundle.properties +++ b/form/src/org/netbeans/modules/form/Bundle.properties @@ -938,3 +938,6 @@ # Null item display name for EnumEditor combobox CTL_NullText=null + +# Options export +Form.Options.Export.displayName=GUI Builder diff --git a/form/src/org/netbeans/modules/form/resources/layer.xml b/form/src/org/netbeans/modules/form/resources/layer.xml --- a/form/src/org/netbeans/modules/form/resources/layer.xml +++ b/form/src/org/netbeans/modules/form/resources/layer.xml @@ -688,4 +688,13 @@ + + + + + + + + + diff --git a/html/src/org/netbeans/modules/html/mf-layer.xml b/html/src/org/netbeans/modules/html/mf-layer.xml --- a/html/src/org/netbeans/modules/html/mf-layer.xml +++ b/html/src/org/netbeans/modules/html/mf-layer.xml @@ -231,5 +231,13 @@ - + + + + + + + + + diff --git a/html/src/org/netbeans/modules/html/palette/Bundle.properties b/html/src/org/netbeans/modules/html/palette/Bundle.properties --- a/html/src/org/netbeans/modules/html/palette/Bundle.properties +++ b/html/src/org/netbeans/modules/html/palette/Bundle.properties @@ -50,4 +50,7 @@ #HTMLPaletteActions MSG_ErrorNoFocusedDocument=No document selected. Please select a document to insert the item into. -ACT_OpenHTMLCustomizer=&HTML/JSP Code Clips \ No newline at end of file +ACT_OpenHTMLCustomizer=&HTML/JSP Code Clips + +# Options export +HTMLPalette.Options.Export.displayName=HTML Palette diff --git a/j2eeserver/src/org/netbeans/modules/j2ee/deployment/impl/Bundle.properties b/j2eeserver/src/org/netbeans/modules/j2ee/deployment/impl/Bundle.properties --- a/j2eeserver/src/org/netbeans/modules/j2ee/deployment/impl/Bundle.properties +++ b/j2eeserver/src/org/netbeans/modules/j2ee/deployment/impl/Bundle.properties @@ -201,3 +201,6 @@ MSG_DeployOnSave_Deployed={0} deployed. MSG_DeployOnSave_Failed={0} failed. MSG_DeployOnSave_Unsupported={0} failed. Server suspended. + +# Options export +J2EE.Options.Export.displayName=Java EE diff --git a/j2eeserver/src/org/netbeans/modules/j2ee/deployment/impl/layer.xml b/j2eeserver/src/org/netbeans/modules/j2ee/deployment/impl/layer.xml --- a/j2eeserver/src/org/netbeans/modules/j2ee/deployment/impl/layer.xml +++ b/j2eeserver/src/org/netbeans/modules/j2ee/deployment/impl/layer.xml @@ -136,4 +136,12 @@ + + + + + + + + diff --git a/java.platform/src/org/netbeans/modules/java/platform/resources/Bundle.properties b/java.platform/src/org/netbeans/modules/java/platform/resources/Bundle.properties --- a/java.platform/src/org/netbeans/modules/java/platform/resources/Bundle.properties +++ b/java.platform/src/org/netbeans/modules/java/platform/resources/Bundle.properties @@ -46,3 +46,6 @@ Templates/Services/Platforms/org-netbeans-api-java-Platform=Java Platforms Templates/Services/Platforms=Platforms Templates/Services=Services + +# Options export +JavaPlatform.Options.Export.displayName=Java Platforms diff --git a/java.platform/src/org/netbeans/modules/java/platform/resources/layer.xml b/java.platform/src/org/netbeans/modules/java/platform/resources/layer.xml --- a/java.platform/src/org/netbeans/modules/java/platform/resources/layer.xml +++ b/java.platform/src/org/netbeans/modules/java/platform/resources/layer.xml @@ -82,4 +82,12 @@ + + + + + + + + diff --git a/localhistory/src/org/netbeans/modules/localhistory/resources/Bundle.properties b/localhistory/src/org/netbeans/modules/localhistory/resources/Bundle.properties --- a/localhistory/src/org/netbeans/modules/localhistory/resources/Bundle.properties +++ b/localhistory/src/org/netbeans/modules/localhistory/resources/Bundle.properties @@ -40,3 +40,4 @@ ## svn-layer.xml Actions/LocalHistory=Local History OptionsDialog/Actions/LocalHistory=Local History +LocalHistory.Options.Export.displayName=Local History diff --git a/localhistory/src/org/netbeans/modules/localhistory/resources/layer.xml b/localhistory/src/org/netbeans/modules/localhistory/resources/layer.xml --- a/localhistory/src/org/netbeans/modules/localhistory/resources/layer.xml +++ b/localhistory/src/org/netbeans/modules/localhistory/resources/layer.xml @@ -81,5 +81,13 @@ - + + + + + + + + + diff --git a/nbbuild/build.xml b/nbbuild/build.xml --- a/nbbuild/build.xml +++ b/nbbuild/build.xml @@ -601,7 +601,7 @@ - + @@ -2070,4 +2070,15 @@ + + + + + + + + + + diff --git a/o.n.upgrader/src/org/netbeans/upgrade/AutoUpgrade.java b/o.n.upgrader/src/org/netbeans/upgrade/AutoUpgrade.java --- a/o.n.upgrader/src/org/netbeans/upgrade/AutoUpgrade.java +++ b/o.n.upgrader/src/org/netbeans/upgrade/AutoUpgrade.java @@ -79,8 +79,12 @@ if (!showUpgradeDialog (sourceFolder)) { throw new org.openide.util.UserCancelException (); } - doUpgrade (sourceFolder, version[0]); - //support for non standard configuration files + // less than 6.5 + if (version[0].compareTo("6.5") < 0) { //NOI18N + doUpgrade (sourceFolder, version[0]); + } + // Till 6.1 support for non standard configuration files and since + // 6.5 it is standard way of import. doNonStandardUpgrade(sourceFolder, version[0]); //#75324 NBplatform settings are not imported upgradeBuildProperties(sourceFolder, version); @@ -215,8 +219,17 @@ File userdir = new File(System.getProperty("netbeans.user", "")); // NOI18N java.util.Set includeExclude; try { - InputStream is = AutoUpgrade.class.getResourceAsStream("nonstandard" + oldVersion); - if (is == null) return; + InputStream is; + if (oldVersion.compareTo("6.5") < 0) { //NOI18N + // less than 6.5 + is = AutoUpgrade.class.getResourceAsStream("nonstandard" + oldVersion); // NOI18N + if (is == null) return; + } else { + // 6.5 or greater + File netBeansDir = InstalledFileLocator.getDefault().locate("modules", null, false).getParentFile().getParentFile(); //NOI18N + File importFile = new File(netBeansDir, "etc/netbeans.import"); //NOI18N + is = new FileInputStream(importFile); + } Reader r = new InputStreamReader(is, "utf-8"); // NOI18N includeExclude = IncludeExclude.create(r); r.close(); diff --git a/options.api/arch.xml b/options.api/arch.xml --- a/options.api/arch.xml +++ b/options.api/arch.xml @@ -45,7 +45,7 @@ ]> @@ -1099,6 +1099,32 @@ your implementation of AdvancedCategory there (*.instance file). Standard file systems sorting mechanism is used. + + Use OptionsExport/<MyCategory> folder for registration of items for export/import + of options. Registration in layers looks as follows +
+ <folder name="OptionsExport">
+     <!-- category -->
+     <folder name="MyCategory">
+         <!-- category display name -->
+         <attr name="displayName"
+          bundlevalue="org.netbeans.modules.mymodule.options.Bundle#Category_Display_Name"/>
+         <!-- item -->
+         <file name="MyItem">
+             <attr name="displayName" stringvalue="org.netbeans.modules.mymodule.options.Bundle#Item_Display_Name"/>
+             <!-- include regex pattern rooted to userdir -->
+             <attr name="include" stringvalue="config/Preferences/org/netbeans/modules/mymodule/.*|config/mymodule/.*"/>
+             <!-- exclude regex pattern rooted to userdir -->
+             <attr name="exclude" stringvalue="config/mymodule/obsolete/.*"/>
+         </file>
+     </folder>
+ </folder>
+ 
+

@@ -1137,4 +1163,49 @@

+ + + + + +

+ No deprecation. +

+
+ + + + + +

+ Uses org.netbeans.modules.options.api.OptionsHeight and OptionsWidth properties. +

+
+
diff --git a/options.api/nbproject/project.xml b/options.api/nbproject/project.xml --- a/options.api/nbproject/project.xml +++ b/options.api/nbproject/project.xml @@ -47,6 +47,15 @@ org.netbeans.modules.options.api + org.jdesktop.layout + + + + 1 + 1.7 + + + org.netbeans.spi.quicksearch @@ -55,6 +64,14 @@ + org.netbeans.swing.outline + + + + 1.3 + + + org.openide.awt diff --git a/options.api/src/org/netbeans/modules/options/Bundle.properties b/options.api/src/org/netbeans/modules/options/Bundle.properties --- a/options.api/src/org/netbeans/modules/options/Bundle.properties +++ b/options.api/src/org/netbeans/modules/options/Bundle.properties @@ -63,9 +63,15 @@ CTL_Font_And_Color_Options_Title=Fonts & Colors CTL_Keymap_Options=&Keymap CTL_Keymap_Options_Title=Keymap + +CTL_Export=Expor&t +CTL_Import=&Import + #A11Y ACS_OKButton=OK ACS_ClassicButton= Invokes Advanced Options dialog ACS_OptionsPanel=Options Dialog +ACS_Export=Export Options +ACS_Import=Import Options QuickSearch/GoToOption=Options diff --git a/options.api/src/org/netbeans/modules/options/OptionsDisplayerImpl.java b/options.api/src/org/netbeans/modules/options/OptionsDisplayerImpl.java --- a/options.api/src/org/netbeans/modules/options/OptionsDisplayerImpl.java +++ b/options.api/src/org/netbeans/modules/options/OptionsDisplayerImpl.java @@ -57,6 +57,7 @@ import javax.swing.JLabel; import javax.swing.SwingUtilities; import org.netbeans.api.options.OptionsDisplayer; +import org.netbeans.modules.options.export.OptionsChooserPanel; import org.netbeans.spi.options.OptionsPanelController; import org.openide.DialogDescriptor; @@ -93,6 +94,10 @@ static final LookupListener lookupListener = new LookupListenerImpl(); /** Advanced Options button. */ private JButton bClassic; + /** Export Options button */ + private JButton btnExport; + /** Import Options button */ + private JButton btnImport; public OptionsDisplayerImpl (boolean modal) { this.modal = modal; @@ -149,14 +154,18 @@ bClassic = (JButton) loc(new JButton(), "CTL_Classic");//NOI18N bClassic.getAccessibleContext().setAccessibleDescription(loc("ACS_ClassicButton"));//NOI18N setVisibleAdvancedOptionsButton(); + btnExport = (JButton) loc(new JButton(), "CTL_Export");//NOI18N + btnExport.getAccessibleContext().setAccessibleDescription(loc("ACS_Export"));//NOI18N + btnImport = (JButton) loc(new JButton(), "CTL_Import");//NOI18N + btnImport.getAccessibleContext().setAccessibleDescription(loc("ACS_Import"));//NOI18N boolean isMac = Utilities.isMac(); Object[] options = new Object[2]; options[0] = isMac ? DialogDescriptor.CANCEL_OPTION : bOK; options[1] = isMac ? bOK : DialogDescriptor.CANCEL_OPTION; descriptor = new DialogDescriptor(optionsPanel,title,modal,options,DialogDescriptor.OK_OPTION,DialogDescriptor.DEFAULT_ALIGN, null, null, false); - descriptor.setAdditionalOptions(new Object[] {bClassic}); + descriptor.setAdditionalOptions(new Object[] {bClassic, btnExport, btnImport}); descriptor.setHelpCtx(optionsPanel.getHelpCtx()); - OptionsPanelListener listener = new OptionsPanelListener(descriptor, optionsPanel, bOK, bClassic); + OptionsPanelListener listener = new OptionsPanelListener(descriptor, optionsPanel, bOK, bClassic, btnExport, btnImport); descriptor.setButtonListener(listener); optionsPanel.addPropertyChangeListener(listener); synchronized(lookupListener) { @@ -227,18 +236,24 @@ private OptionsPanel optionsPanel; private JButton bOK; private JButton bClassic; + private JButton btnExport; + private JButton btnImport; OptionsPanelListener ( DialogDescriptor descriptor, OptionsPanel optionsPanel, JButton bOK, - JButton bClassic + JButton bClassic, + JButton btnExport, + JButton btnImport ) { this.descriptor = descriptor; this.optionsPanel = optionsPanel; this.bOK = bOK; this.bClassic = bClassic; + this.btnExport = btnExport; + this.btnImport = btnImport; } public void propertyChange (PropertyChangeEvent ev) { @@ -309,6 +324,11 @@ Exceptions.printStackTrace(ex); } } // classic + else if (e.getSource() == btnExport) { + OptionsChooserPanel.showExportDialog(); + } else if (e.getSource() == btnImport) { + OptionsChooserPanel.showImportDialog(); + } } } diff --git a/options.api/src/org/netbeans/modules/options/advanced/Bundle.properties b/options.api/src/org/netbeans/modules/options/advanced/Bundle.properties --- a/options.api/src/org/netbeans/modules/options/advanced/Bundle.properties +++ b/options.api/src/org/netbeans/modules/options/advanced/Bundle.properties @@ -43,3 +43,4 @@ CTL_Advanced_Options_Title=Miscellaneous CTL_Advanced_Options_Description=Miscellaneous AdvancedPanel.tabbedPanel.AD=Miscellaneous tabbed panel +Advanced.Export.Category=Miscellaneous diff --git a/options.api/src/org/netbeans/modules/options/export/Bundle.properties b/options.api/src/org/netbeans/modules/options/export/Bundle.properties new file mode 100644 --- /dev/null +++ b/options.api/src/org/netbeans/modules/options/export/Bundle.properties @@ -0,0 +1,63 @@ +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. +# +# The contents of this file are subject to the terms of either the GNU +# General Public License Version 2 only ("GPL") or the Common +# Development and Distribution License("CDDL") (collectively, the +# "License"). You may not use this file except in compliance with the +# License. You can obtain a copy of the License at +# http://www.netbeans.org/cddl-gplv2.html +# or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the +# specific language governing permissions and limitations under the +# License. When distributing the software, include this License Header +# Notice in each file and include the License file at +# nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the GPL Version 2 section of the License file that +# accompanied this code. If applicable, add the following below the +# License Header, with the fields enclosed by brackets [] replaced by +# your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# +# Contributor(s): +# +# The Original Software is NetBeans. The Initial Developer of the Original +# Software is Sun Microsystems, Inc. Portions Copyright 1997-2009 Sun +# Microsystems, Inc. All Rights Reserved. +# +# If you wish your version of this file to be governed by only the CDDL +# or only the GPL Version 2, indicate your decision by adding +# "[Contributor] elects to include this software in this distribution +# under the [CDDL or GPL Version 2] license." If you do not indicate a +# single choice of license, a recipient has the option to distribute +# your version of this file under either the CDDL, the GPL Version 2 or +# to extend the choice of license to its licensees as provided above. +# However, if you add GPL Version 2 code and therefore, elected the GPL +# Version 2 license, then the option applies only if the new code is +# made subject to such option by the copyright holder. + +OptionsChooserPanel.lblHint.text=Select Options for Export: +OptionsChooserPanel.outline.header.tree=Available Options +OptionsChooserPanel.outline.header.included=Included +OptionsChooserPanel.lblFile.text=Target zip file: +OptionsChooserPanel.btnBrowse=Browse... +OptionsChooserPanel.file.warning=Please, select a valid target zip file +OptionsChooserPanel.nooption.warning=Please, select something to export +OptionsChooserPanel.file.chooser.title=Select target zip file +OptionsChooserPanel.file.chooser.approve=OK +# 0 - zip file path +OptionsChooserPanel.invalid.zipfile=Invalid zip file: {0} +# 0 - file path +OptionsChooserPanel.export.zip.error=Writing to file {0} failed. +OptionsChooserPanel.export.title=Select Options to Export +OptionsChooserPanel.export.status.text=Options Export Finished. +OptionsChooserPanel.import.title=Select Options to Import +OptionsChooserPanel.import.lblFile.text=Import Source: +OptionsChooserPanel.import.lblHint.text=Select Options for Import: +OptionsChooserPanel.import.file.warning=Please, select a valid zip file or userdir +OptionsChooserPanel.import.nooption.warning=Please, select something to import +OptionsChooserPanel.import.error=Error while importing options. +OptionsChooserPanel.import.file.chooser.title=Select zip file or userdir +OptionsChooserPanel.restart.title=Import successfully finished +OptionsChooserPanel.restart.message=Restart of application is needed to initiate imported options. Would you like to restart now? diff --git a/options.api/src/org/netbeans/modules/options/export/OptionsChooserPanel.form b/options.api/src/org/netbeans/modules/options/export/OptionsChooserPanel.form new file mode 100644 --- /dev/null +++ b/options.api/src/org/netbeans/modules/options/export/OptionsChooserPanel.form @@ -0,0 +1,86 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/options.api/src/org/netbeans/modules/options/export/OptionsChooserPanel.java b/options.api/src/org/netbeans/modules/options/export/OptionsChooserPanel.java new file mode 100644 --- /dev/null +++ b/options.api/src/org/netbeans/modules/options/export/OptionsChooserPanel.java @@ -0,0 +1,571 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ +package org.netbeans.modules.options.export; + +import java.awt.Color; +import java.awt.Component; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.logging.Logger; +import javax.swing.Icon; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.filechooser.FileFilter; +import javax.swing.plaf.UIResource; +import javax.swing.table.TableCellRenderer; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreeNode; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.netbeans.swing.outline.RenderDataProvider; +import org.netbeans.swing.outline.RowModel; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.LifecycleManager; +import org.openide.awt.StatusDisplayer; +import org.openide.filesystems.FileChooserBuilder; +import org.openide.util.Exceptions; +import org.openide.util.ImageUtilities; +import org.openide.util.NbBundle; + +/** + * Export/import options panel. + * @author Jiri Skrivanek + */ +public final class OptionsChooserPanel extends JPanel { + + private static final Logger LOGGER = Logger.getLogger(OptionsChooserPanel.class.getName()); + private DialogDescriptor dialogDescriptor; + private PanelType panelType; + + /** To distinguish between import and export panels. */ + private enum PanelType { + + EXPORT, IMPORT + }; + + private OptionsChooserPanel() { + initComponents(); + } + + /** Shows panel for export of options. */ + public static void showExportDialog() { + LOGGER.fine("showExportDialog"); //NOI18N + File sourceUserdir = new File(System.getProperty("netbeans.user")); // NOI18N + final OptionsChooserPanel optionsChooserPanel = new OptionsChooserPanel(); + optionsChooserPanel.scrollPaneOptions.setViewportView(optionsChooserPanel.getOutline(sourceUserdir)); + optionsChooserPanel.txtFile.getDocument().addDocumentListener(new DocumentListener() { + + public void insertUpdate(DocumentEvent e) { + optionsChooserPanel.dialogDescriptor.setValid(optionsChooserPanel.isPanelValid()); + } + + public void removeUpdate(DocumentEvent e) { + optionsChooserPanel.dialogDescriptor.setValid(optionsChooserPanel.isPanelValid()); + } + + public void changedUpdate(DocumentEvent e) { + optionsChooserPanel.dialogDescriptor.setValid(optionsChooserPanel.isPanelValid()); + } + }); + + DialogDescriptor dd = new DialogDescriptor( + optionsChooserPanel, + NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.export.title"), + true, + new Object[]{DialogDescriptor.OK_OPTION, DialogDescriptor.CANCEL_OPTION}, + DialogDescriptor.OK_OPTION, + DialogDescriptor.DEFAULT_ALIGN, + null, + null); + // add bottom user notification area + dd.createNotificationLineSupport(); + dd.setValid(false); + optionsChooserPanel.setDialogDescriptor(dd); + DialogDisplayer.getDefault().createDialog(dd).setVisible(true); + + if (DialogDescriptor.OK_OPTION.equals(dd.getValue())) { + String targetPath = optionsChooserPanel.getSelectedFilePath(); + try { + OptionsExportModel.getInstance().createZipFile(new File(targetPath), sourceUserdir); + } catch (IOException ex) { + Exceptions.attachLocalizedMessage(ex, + NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.export.zip.error", targetPath)); + Exceptions.printStackTrace(ex); + } + StatusDisplayer.getDefault().setStatusText( + NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.export.status.text")); + LOGGER.fine("Export finished."); //NOI18N + } + } + + /** Shows panel for import of options. */ + public static void showImportDialog() { + LOGGER.fine("showImportDialog"); //NOI18N + OptionsChooserPanel optionsChooserPanel = new OptionsChooserPanel(); + optionsChooserPanel.txtFile.setEditable(false); + optionsChooserPanel.lblFile.setText(NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.import.lblFile.text")); + optionsChooserPanel.lblHint.setText(NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.import.lblHint.text")); + optionsChooserPanel.panelType = PanelType.IMPORT; + + DialogDescriptor dd = new DialogDescriptor( + optionsChooserPanel, + NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.import.title"), + true, + new Object[]{DialogDescriptor.OK_OPTION, DialogDescriptor.CANCEL_OPTION}, + DialogDescriptor.OK_OPTION, + DialogDescriptor.DEFAULT_ALIGN, + null, + null); + dd.createNotificationLineSupport(); + dd.setValid(false); + optionsChooserPanel.setDialogDescriptor(dd); + DialogDisplayer.getDefault().createDialog(dd).setVisible(true); + + if (DialogDescriptor.OK_OPTION.equals(dd.getValue())) { + File targetUserdir = new File(System.getProperty("netbeans.user")); // NOI18N + try { + String sourcePath = optionsChooserPanel.getSelectedFilePath(); + File source = new File(sourcePath); + if (source.isFile()) { + // zip file + OptionsExportModel.getInstance().extractZipFile(source, targetUserdir); + } else { + // userdir + OptionsExportModel.getInstance().copy(source, targetUserdir); + } + } catch (IOException ex) { + Exceptions.attachLocalizedMessage(ex, + NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.import.error")); + Exceptions.printStackTrace(ex); + } + LOGGER.fine("Import finished."); //NOI18N + // restart IDE + String title = NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.restart.title"); + String message = NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.restart.message"); + int restartNow = JOptionPane.showConfirmDialog(null, message, title, JOptionPane.YES_NO_OPTION); + if (restartNow == JOptionPane.YES_OPTION) { + try { + new File(targetUserdir, "var/restart").createNewFile(); //NOI18N + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + LifecycleManager.getDefault().exit(); + } + } + } + + /** Returns outline view for displaying options for export/import. */ + private Outline getOutline(File source) { + Outline outline = new Outline(); + outline.setModel(DefaultOutlineModel.createOutlineModel( + getOptionsTreeModel(source), + new OptionsRowModel(), + true, + NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.outline.header.tree"))); + outline.setRenderDataProvider(new OptionsTreeDataProvider()); + outline.setRootVisible(false); + outline.getTableHeader().setReorderingAllowed(false); + outline.setColumnHidingAllowed(false); + outline.setDefaultRenderer(Boolean.class, new BooleanRenderer()); + return outline; + } + + /** Returns tree model based on current state of this model. */ + private TreeModel getOptionsTreeModel(File source) { + LOGGER.fine("getOptionsTreeModel - source=" + source); //NOI18N + List relativePaths = null; + if (source.isFile()) { + try { + // zip file + relativePaths = OptionsExportModel.listZipFile(source); + } catch (IOException ex) { + Exceptions.attachLocalizedMessage(ex, NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.invalid.zipfile", source)); + Exceptions.printStackTrace(ex); + } + } else { + // userdir + relativePaths = OptionsExportModel.getRelativePaths(source); + } + LOGGER.fine("relativePaths=" + relativePaths); //NOI18N + DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); + for (OptionsExportModel.Category category : OptionsExportModel.getInstance().getCategories()) { + LOGGER.fine("category=" + category); //NOI18N + DefaultMutableTreeNode categoryNode = new DefaultMutableTreeNode(category); + List items = category.getItems(); + for (OptionsExportModel.Item item : items) { + LOGGER.fine(" item=" + item); //NOI18N + if (item.isApplicable(relativePaths)) { + categoryNode.add(new DefaultMutableTreeNode(item)); + } + } + if (categoryNode.getChildCount() != 0) { + // do not show category node if it has no children + rootNode.add(categoryNode); + updateCategoryNode(categoryNode); + } + } + return new DefaultTreeModel(rootNode); + } + + private String getSelectedFilePath() { + return txtFile.getText(); + } + + private void setDialogDescriptor(DialogDescriptor dd) { + this.dialogDescriptor = dd; + } + + /** Returns true if all user inputs in this panel are valid. */ + private boolean isPanelValid() { + if (panelType == PanelType.IMPORT) { + if (txtFile.getText().length() == 0) { + dialogDescriptor.getNotificationLineSupport().setWarningMessage(NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.import.file.warning")); + } else if (!OptionsExportModel.getInstance().isEnabled()) { + dialogDescriptor.getNotificationLineSupport().setWarningMessage(NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.import.nooption.warning")); + } else { + dialogDescriptor.getNotificationLineSupport().clearMessages(); + return true; + } + } else { + if (txtFile.getText().length() == 0 || !txtFile.getText().endsWith(".zip")) { //NOI18N + dialogDescriptor.getNotificationLineSupport().setWarningMessage(NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.file.warning")); + } else if (!OptionsExportModel.getInstance().isEnabled()) { + dialogDescriptor.getNotificationLineSupport().setWarningMessage(NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.nooption.warning")); + } else { + dialogDescriptor.getNotificationLineSupport().clearMessages(); + return true; + } + } + return false; + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + lblHint = new javax.swing.JLabel(); + scrollPaneOptions = new javax.swing.JScrollPane(); + lblFile = new javax.swing.JLabel(); + txtFile = new javax.swing.JTextField(); + btnBrowse = new javax.swing.JButton(); + + lblHint.setText(org.openide.util.NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.lblHint.text")); // NOI18N + + lblFile.setText(org.openide.util.NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.lblFile.text")); // NOI18N + + btnBrowse.setText(org.openide.util.NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.btnBrowse")); // NOI18N + btnBrowse.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnBrowseActionPerformed(evt); + } + }); + + org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .addContainerGap() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(scrollPaneOptions, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 580, Short.MAX_VALUE) + .add(layout.createSequentialGroup() + .add(lblFile) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(txtFile, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 422, Short.MAX_VALUE) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(btnBrowse)) + .add(lblHint, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 580, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .addContainerGap() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(lblFile) + .add(txtFile, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .add(btnBrowse)) + .add(18, 18, 18) + .add(lblHint) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(scrollPaneOptions, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 370, Short.MAX_VALUE) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void btnBrowseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnBrowseActionPerformed + FileChooserBuilder fileChooserBuilder = new FileChooserBuilder(OptionsChooserPanel.class); + fileChooserBuilder.setDefaultWorkingDirectory(new File(System.getProperty("user.home"))); //NOI18N + fileChooserBuilder.setFileFilter(new FileNameExtensionFilter("*.zip", "zip")); //NOI18N + fileChooserBuilder.setApproveText(NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.file.chooser.approve")); + if (panelType == PanelType.IMPORT) { + fileChooserBuilder.setTitle(NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.import.file.chooser.title")); + } else { + fileChooserBuilder.setTitle(NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.file.chooser.title")); + } + + JFileChooser fileChooser = fileChooserBuilder.createFileChooser(); + if (JFileChooser.APPROVE_OPTION == fileChooser.showOpenDialog(this)) { + txtFile.setText(fileChooser.getSelectedFile().getAbsolutePath()); + if (panelType == PanelType.IMPORT) { + scrollPaneOptions.setViewportView(getOutline(fileChooser.getSelectedFile())); + } + dialogDescriptor.setValid(isPanelValid()); + } + }//GEN-LAST:event_btnBrowseActionPerformed + + + /** Custom BooleanRenderer to visualize PARTIAL state. Inspired by org.openide.explorer.propertysheet.Boolean3WayEditor. */ + private static class BooleanRenderer extends JCheckBox implements TableCellRenderer, UIResource { + + private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1); + + public BooleanRenderer() { + super(); + setHorizontalAlignment(JLabel.CENTER); + setBorderPainted(true); + } + + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + if (isSelected) { + setForeground(table.getSelectionForeground()); + super.setBackground(table.getSelectionBackground()); + } else { + setForeground(table.getForeground()); + setBackground(table.getBackground()); + } + setSelected((value != null && ((Boolean) value).booleanValue())); + if (value == null) { + // visualize PARTIAL state + getModel().setArmed(true); + getModel().setPressed(true); + setSelected(true); + } else { + getModel().setArmed(false); + getModel().setPressed(false); + } + + if (hasFocus) { + setBorder(UIManager.getBorder("Table.focusCellHighlightBorder")); //NOI18N + } else { + setBorder(noFocusBorder); + } + + return this; + } + } + + /** Defines presentation of table. */ + private class OptionsRowModel implements RowModel { + + public Class getColumnClass(int column) { + return Boolean.class; + } + + public int getColumnCount() { + return 1; + } + + public String getColumnName(int column) { + return NbBundle.getMessage(OptionsChooserPanel.class, "OptionsChooserPanel.outline.header.included"); + } + + public Object getValueFor(Object node, int column) { + Object userObject = ((DefaultMutableTreeNode) node).getUserObject(); + if (userObject instanceof OptionsExportModel.Category) { + // 3 states - see BooleanRenderer + if (((OptionsExportModel.Category) userObject).getState() == OptionsExportModel.State.ENABLED) { + return true; + } else if (((OptionsExportModel.Category) userObject).getState() == OptionsExportModel.State.DISABLED) { + return false; + } else { + return null; + } + } else if (userObject instanceof OptionsExportModel.Item) { + return ((OptionsExportModel.Item) userObject).isEnabled(); + } + // root node + return null; + } + + public boolean isCellEditable(Object node, int column) { + return column == 0; + } + + public void setValueFor(Object node, int column, Object value) { + assert column == 0; + Object userObject = ((DefaultMutableTreeNode) node).getUserObject(); + if (userObject instanceof OptionsExportModel.Category) { + if (Boolean.valueOf(value.toString())) { + ((OptionsExportModel.Category) userObject).setState(OptionsExportModel.State.ENABLED); + } else { + ((OptionsExportModel.Category) userObject).setState(OptionsExportModel.State.DISABLED); + } + } else if (userObject instanceof OptionsExportModel.Item) { + ((OptionsExportModel.Item) userObject).setEnabled(Boolean.valueOf(value.toString())); + // update parent category + Object parent = ((TreeNode) node).getParent(); + updateCategoryNode((DefaultMutableTreeNode) parent); + + } + dialogDescriptor.setValid(isPanelValid()); + } + } + + /** Update state of category node according to state of sub items. */ + private static void updateCategoryNode(DefaultMutableTreeNode categoryNode) { + int enabledCount = 0; + for (int i = 0; i < categoryNode.getChildCount(); i++) { + Object userObject = ((DefaultMutableTreeNode) categoryNode.getChildAt(i)).getUserObject(); + if (((OptionsExportModel.Item) userObject).isEnabled()) { + enabledCount++; + } + } + Object userObject = categoryNode.getUserObject(); + OptionsExportModel.Category category = ((OptionsExportModel.Category) userObject); + if (enabledCount == 0) { + category.setState(OptionsExportModel.State.DISABLED); + } else if (enabledCount == categoryNode.getChildCount()) { + category.setState(OptionsExportModel.State.ENABLED); + } else { + category.setState(OptionsExportModel.State.PARTIAL); + } + } + + /** Defines visual appearance of tree. */ + private static class OptionsTreeDataProvider implements RenderDataProvider { + + private static final Icon ICON = ImageUtilities.loadImageIcon("org/netbeans/modules/options/export/defaultNode.gif", true); //NOI18N + + public Color getBackground(Object node) { + return null; + } + + public String getDisplayName(Object node) { + Object userObject = ((DefaultMutableTreeNode) node).getUserObject(); + if (userObject instanceof OptionsExportModel.Category) { + return ((OptionsExportModel.Category) userObject).getDisplayName(); + } + if (userObject instanceof OptionsExportModel.Item) { + return ((OptionsExportModel.Item) userObject).getDisplayName(); + } + // root node + return node.toString(); + } + + public Color getForeground(Object node) { + return null; + } + + public Icon getIcon(Object o) { + return ICON; + } + + public String getTooltipText(Object o) { + return null; + } + + public boolean isHtmlDisplayName(Object o) { + return false; + } + } + + /** FileFile for single extension. Remove it when JDK5 is obsolete and + * use FileNameExtensionFilter from JDK6. */ + private static class FileNameExtensionFilter extends FileFilter { + + private final String description; + private final String lowerCaseExtension; + + public FileNameExtensionFilter(String description, String extension) { + assert extension != null; + this.description = description; + this.lowerCaseExtension = extension.toLowerCase(); + } + + @Override + public boolean accept(File f) { + if (f != null) { + if (f.isDirectory()) { + return true; + } + String fileName = f.getName(); + int i = fileName.lastIndexOf('.'); + if (i > 0 && i < fileName.length() - 1) { + String desiredExtension = fileName.substring(i + 1).toLowerCase(); + if (desiredExtension.equals(lowerCaseExtension)) { + return true; + } + } + } + return false; + } + + @Override + public String getDescription() { + return description; + } + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton btnBrowse; + private javax.swing.JLabel lblFile; + private javax.swing.JLabel lblHint; + private javax.swing.JScrollPane scrollPaneOptions; + private javax.swing.JTextField txtFile; + // End of variables declaration//GEN-END:variables +} diff --git a/options.api/src/org/netbeans/modules/options/export/OptionsExportModel.java b/options.api/src/org/netbeans/modules/options/export/OptionsExportModel.java new file mode 100644 --- /dev/null +++ b/options.api/src/org/netbeans/modules/options/export/OptionsExportModel.java @@ -0,0 +1,588 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ +package org.netbeans.modules.options.export; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileChangeListener; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileRenameEvent; +import org.openide.filesystems.FileUtil; + +/** + * Model for export/import options. It reads {@code OptionsExport//} + * from layers and evaluates whether items are applicable for export/import. + * + * @author Jiri Skrivanek + */ +public final class OptionsExportModel { + + private static final Logger LOGGER = Logger.getLogger(OptionsExportModel.class.getName()); + /** folder in layer file system where provider are searched for */ + private static final String OPTIONS_EXPORT_FOLDER = "OptionsExport"; //NOI18N + private static OptionsExportModel instance; + // keep instance to not loose listener + private FileObject exportFolderFO; + private List categories; + // listen to changes in system filesystem (e.g. new module installed) + private final FileChangeListener exportFolderListener = new FileChangeAdapter() { + + @Override + public void fileDeleted(FileEvent fe) { + categories = null; + } + + @Override + public void fileRenamed(FileRenameEvent fe) { + categories = null; + } + + @Override + public void fileDataCreated(FileEvent fe) { + categories = null; + } + + @Override + public void fileChanged(FileEvent fe) { + categories = null; + } + }; + + private OptionsExportModel() { + exportFolderFO = FileUtil.getConfigFile(OPTIONS_EXPORT_FOLDER); + if (exportFolderFO != null) { + exportFolderFO.addFileChangeListener(FileUtil.weakFileChangeListener(exportFolderListener, exportFolderFO)); + } + } + + /** Returns instance of export options model. + * @return instance of export options model + */ + static synchronized OptionsExportModel getInstance() { + if (instance == null) { + instance = new OptionsExportModel(); + } + return instance; + } + + /** + * Gets list of categories + * @return list of categories + */ + List getCategories() { + if (categories == null) { + categories = loadCategories(); + } + return categories; + } + + /** Returns true if at least one item in this model is enabled. + * @return true if at least one item in this model is enabled. + */ + boolean isEnabled() { + for (OptionsExportModel.Category category : getCategories()) { + for (OptionsExportModel.Item item : category.getItems()) { + if (item.isEnabled()) { + return true; + } + } + } + return false; + } + + /** Copies files from source dir to target dir according to current state + * of model, i.e. only include/exclude patterns from enabled items are + * considered. + * @param targetUserdir target userdir + * @param sourceUserdir source userdir + * @throws java.io.IOException + */ + void copy(File sourceUserdir, File targetUserdir) throws IOException { + LOGGER.fine("Copying from: "+ sourceUserdir + "\nto:" + targetUserdir); //NOI18N + List relativePaths = getApplicablePaths(getRelativePaths(sourceUserdir), getIncludePatterns(), getExcludePatterns()); + if (!relativePaths.isEmpty() && !targetUserdir.exists()) { + if (!targetUserdir.mkdirs()) { + throw new IOException("Cannot create folder: " + targetUserdir.getAbsolutePath()); //NOI18N + } + } + for (String path : relativePaths) { + LOGGER.fine("Path=" + path); //NOI18N + copyFile(new File(sourceUserdir, path), new File(targetUserdir, path)); + } + } + + /** Extracts files from source zip file to target dir according to current state + * of model, i.e. only include/exclude patterns from enabled items are + * considered. + * @param sourceFile source zip file + * @param targetUserdir target userdir + */ + void extractZipFile(File sourceFile, File targetUserdir) throws IOException { + LOGGER.fine("Extracting from:" + sourceFile + " to:" + targetUserdir); //NOI18N + List applicablePaths = getApplicablePaths(listZipFile(sourceFile), getIncludePatterns(), getExcludePatterns()); + extractZipFile(sourceFile, targetUserdir, applicablePaths); + } + + /** Creates zip file from source userdir according to current state + * of model, i.e. only include/exclude patterns from enabled items are + * considered. + * @param sourceUserdir source userdir + * @param targetFile target zip file + */ + void createZipFile(File targetFile, File sourceUserdir) throws IOException { + LOGGER.fine("Creating file:" + targetFile + " from:" + sourceUserdir); //NOI18N + List applicablePaths = getApplicablePaths(getRelativePaths(sourceUserdir), getIncludePatterns(), getExcludePatterns()); + createZipFile(targetFile, sourceUserdir, applicablePaths); + } + + /** Returns set of include patterns in this model. */ + private Set getIncludePatterns() { + Set includePatterns = new HashSet(); + for (OptionsExportModel.Category category : getCategories()) { + for (OptionsExportModel.Item item : category.getItems()) { + if (item.isEnabled()) { + String include = item.getInclude(); + if (include != null && include.length() > 0) { + includePatterns.add(Pattern.compile(include)); + } + } + } + } + return includePatterns; + } + + /** Returns set of exclude patterns in this model. */ + private Set getExcludePatterns() { + Set excludePatterns = new HashSet(); + for (OptionsExportModel.Category category : getCategories()) { + for (OptionsExportModel.Item item : category.getItems()) { + if (item.isEnabled()) { + String exclude = item.getExclude(); + if (exclude != null && exclude.length() > 0) { + excludePatterns.add(Pattern.compile(exclude)); + } + } + } + } + return excludePatterns; + } + + /** Represents one item in UI and hold include/exclude patterns. */ + static class Item { + + private String displayName; + private String include; + private String exclude; + private boolean enabled = false; + + public Item(String displayName, String include, String exclude) { + assert displayName != null : "Display name of export option item not defined in layer."; //NOI18N + this.displayName = displayName; + if (include == null) { + include = ""; //NOI18N + } + this.include = include; + if (exclude == null) { + exclude = ""; //NOI18N + } + this.exclude = exclude; + } + + public String getDisplayName() { + return displayName; + } + + public String getInclude() { + return include; + } + + public String getExclude() { + return exclude; + } + + /** Returns true if at least one path in given list of relative paths + * matches include/exclude patterns. + * @param relativePaths list of paths relative to userdir root + * @return true if at least one path in given list of relative paths + * matches include/exclude patterns, false otherwise + */ + public boolean isApplicable(List relativePaths) { + List applicablePaths = getApplicablePaths(relativePaths, + Collections.singleton(Pattern.compile(include)), + Collections.singleton(Pattern.compile(exclude))); + LOGGER.fine(" applicablePaths=" + applicablePaths); //NOI18N + return !applicablePaths.isEmpty(); + } + + /** Returns true if user selected this item for export/import. + * @return returns true if user selected this item for export/import, + * false otherwise + */ + public boolean isEnabled() { + return enabled; + } + + /** Sets whether user selects this item for export/import. + * @param newState if selected or not + */ + public void setEnabled(boolean newState) { + enabled = newState; + } + + /** Just for debugging. */ + @Override + public String toString() { + return getDisplayName() + ", enabled=" + isEnabled(); //NOI18N + } + } + + /** Represents 3 state of category. */ + static enum State { + + ENABLED, DISABLED, PARTIAL + }; + + /** Represents category in UI holding several items. */ + static class Category { + + //xml entry names + private static final String INCLUDE = "include"; // NOI18N + private static final String EXCLUDE = "exclude"; // NOI18N + private static final String DISPLAY_NAME = "displayName"; // NOI18N + private FileObject categoryFO; + private String displayName; + private List items; + private State state = State.DISABLED; + + public Category(FileObject fo, String displayName) { + this.categoryFO = fo; + this.displayName = displayName; + } + + private void addItem(String displayName, String includes, String excludes) { + items.add(new Item(displayName, includes, excludes)); + } + + /** Returns items under OptionsExport/. **/ + public List getItems() { + if (items == null) { + items = new ArrayList(); + FileObject[] itemsFOs = categoryFO.getChildren(); + for (FileObject itemFO : itemsFOs) { + addItem((String) itemFO.getAttribute(DISPLAY_NAME), + (String) itemFO.getAttribute(INCLUDE), + (String) itemFO.getAttribute(EXCLUDE)); + } + } + return items; + } + + public String getName() { + return categoryFO.getNameExt(); + } + + public String getDisplayName() { + return displayName; + } + + public void setState(State state) { + this.state = state; + updateItems(state); + } + + public State getState() { + return state; + } + + /** Just for debugging. */ + @Override + public String toString() { + return getDisplayName() + ", state=" + getState(); //NOI18N + } + + private void updateItems(State state) { + for (Item item : getItems()) { + if (state == State.DISABLED) { + item.setEnabled(false); + } else if (state == State.ENABLED) { + item.setEnabled(true); + } + } + } + } // end of Category + + /** Load categories from filesystem. */ + private static List loadCategories() { + FileObject[] categoryFOs = FileUtil.getConfigFile(OPTIONS_EXPORT_FOLDER).getChildren(); + // respect ordering defined in layers + List sortedCats = FileUtil.getOrder(Arrays.asList(categoryFOs), false); + List categories = new ArrayList(sortedCats.size()); + for (FileObject curFO : sortedCats) { + String displayName = (String) curFO.getAttribute(Category.DISPLAY_NAME); + categories.add(new Category(curFO, displayName)); + } + return categories; + } + + /** Filters given relative paths and returns only ones which match given + * include/exclude patterns. + * @param relativePaths relative paths + * @param includePatterns include patterns + * @param excludePatterns exclude patterns + * @return relative patsh which match include/exclude patterns + */ + private static List getApplicablePaths(List relativePaths, Set includePatterns, Set excludePatterns) { + List applicablePaths = new ArrayList(); + for (String relativePath : relativePaths) { + if (include(relativePath, includePatterns, excludePatterns)) { + applicablePaths.add(relativePath); + } + } + return applicablePaths; + } + + /** Returns list of file path relative to given source root. It scans + * sub folders recursively. + * @param sourceRoot source root + * @return list of file path relative to given source root + */ + static List getRelativePaths(File sourceRoot) { + List relativePaths = new ArrayList(); + getRelativePaths(sourceRoot, sourceRoot, relativePaths); + return relativePaths; + } + + private static void getRelativePaths(File source, File sourceRoot, List relativePaths) { + if (source.isDirectory()) { + File[] children = source.listFiles(); + if (children == null) { + return; + } + for (File child : children) { + getRelativePaths(child, sourceRoot, relativePaths); + } + } else { + relativePaths.add(getRelativePath(sourceRoot, source)); + } + } + + /** Returns slash separated path relative to given root. */ + private static String getRelativePath(File root, File file) { + String result = file.getAbsolutePath().substring(root.getAbsolutePath().length()); + result = result.replace('\\', '/'); //NOI18N + if (result.startsWith("/") && !result.startsWith("//")) { //NOI18N + result = result.substring(1); + } + return result; + } + + /** Returns true if given relative path matches at least one of given include + * patterns and doesn't match all exclude patterns. + * @param relativePath relative path + * @param includePatterns include patterns + * @param excludePatterns exclude patterns + * @return true if given relative path matches at least one of given include + * patterns and doesn't match all exclude patterns, false otherwise + */ + private static boolean include(String relativePath, Set includePatterns, Set excludePatterns) { + boolean include = false; + for (Pattern pattern : includePatterns) { + Matcher matcher = pattern.matcher(relativePath); + if (matcher.matches()) { + include = true; + break; + } + } + if (include) { + // check excludes + for (Pattern pattern : excludePatterns) { + Matcher matcher = pattern.matcher(relativePath); + if (matcher.matches()) { + return false; + } + } + } + return include; + } + + /** Copy source file to target file. It creates necessary sub folders. + * @param sourceFile source file + * @param targetFile target file + * @throws java.io.IOException if copying fails + */ + private static void copyFile(File sourceFile, File targetFile) throws IOException { + ensureParent(targetFile); + InputStream ins = null; + OutputStream out = null; + try { + ins = new FileInputStream(sourceFile); + out = new FileOutputStream(targetFile); + FileUtil.copy(ins, out); + } finally { + if (ins != null) { + ins.close(); + } + if (out != null) { + out.close(); + } + } + } + + /** Creates parent of given file, if doesn't exist. */ + private static void ensureParent(File file) throws IOException { + final File parent = file.getParentFile(); + if (parent != null && !parent.exists()) { + if (!parent.mkdirs()) { + throw new IOException("Cannot create folder: " + parent.getAbsolutePath()); //NOI18N + } + } + } + + /** Extracts given zip file to target directory but only those files which + * match given list. + * @param sourceFile source zip file + * @param targetDir target directory + * @param applicablePaths list of files to be extracted + */ + private static void extractZipFile(File sourceFile, File targetDir, List applicablePaths) throws IOException { + ZipFile zipFile = new ZipFile(sourceFile); + Enumeration enumeration = zipFile.entries(); + while (enumeration.hasMoreElements()) { + ZipEntry zipEntry = (ZipEntry) enumeration.nextElement(); + if (!applicablePaths.contains(zipEntry.getName())) { + // skip not matching entries + continue; + } + LOGGER.fine("Extracting:" + zipEntry.getName()); //NOI18N + InputStream in = null; + FileOutputStream out = null; + try { + in = zipFile.getInputStream(zipEntry); + File outFile = new File(targetDir, zipEntry.getName()); + ensureParent(outFile); + out = new FileOutputStream(outFile); + FileUtil.copy(in, out); + } finally { + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + } + } + } + + /** Returns list of paths from given zip file. + * @param file zip file + * @return list of paths from given zip file + * @throws java.io.IOException + */ + static List listZipFile(File file) throws IOException { + List relativePaths = new ArrayList(); + // Open the ZIP file + ZipFile zipFile = new ZipFile(file); + // Enumerate each entry + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + // Get the entry name + String zipEntryName = ((ZipEntry) entries.nextElement()).getName(); + relativePaths.add(zipEntryName); + } + return relativePaths; + } + + /** Creates zip file containing only selected files from given source dir. + * @param targetFile target zip file + * @param sourceDir source dir + * @param relativePaths paths to be added to zip file + * @throws java.io.IOException + */ + private static void createZipFile(File targetFile, File sourceDir, List relativePaths) throws IOException { + ensureParent(targetFile); + ZipOutputStream out = null; + try { + // Create the ZIP file + out = new ZipOutputStream(new FileOutputStream(targetFile)); + // Compress the files + for (String relativePath : relativePaths) { + LOGGER.fine("Adding to zip:" + relativePath); //NOI18N + // Add ZIP entry to output stream. + out.putNextEntry(new ZipEntry(relativePath)); + // Transfer bytes from the file to the ZIP file + FileInputStream in = null; + try { + in = new FileInputStream(new File(sourceDir, relativePath)); + FileUtil.copy(in, out); + } finally { + if (in != null) { + in.close(); + } + } + // Complete the entry + out.closeEntry(); + } + // Complete the ZIP file + out.close(); + } finally { + if (out != null) { + out.close(); + } + } + } +} diff --git a/options.api/src/org/netbeans/modules/options/export/defaultNode.gif b/options.api/src/org/netbeans/modules/options/export/defaultNode.gif new file mode 100644 index 0000000000000000000000000000000000000000..3ebebf55d4269fc64082d1156c78b0a34864ee99 GIT binary patch literal 401 zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+AuIMDto#(hE&{2`t$$4J+mrCDalx69uDj1P+A6-sgo;}>wa!8B3uNsd6p&sV00kDs3}apUGq$%u#ui3N{(PINIc zaYRe}VgLE_+umxP_xI}=vj6@3tbTaDef@#NM!624?H#I$jWI_9W3T_Xdh8rqhOu$L z=lC||_CA9xW>=e;KCzo990dAqgCs+L>!mKK3Z0VjEeXA>4Gc*~CQj+}V+T4=TQO1N p2rC0q$D<`Y0uCb53=krLA#eU>X8WlJvVdXA;OXk;vd$@?2>|yUo7?~Z diff --git a/options.api/src/org/netbeans/modules/options/resources/mf-layer.xml b/options.api/src/org/netbeans/modules/options/resources/mf-layer.xml --- a/options.api/src/org/netbeans/modules/options/resources/mf-layer.xml +++ b/options.api/src/org/netbeans/modules/options/resources/mf-layer.xml @@ -69,6 +69,13 @@ + + + + + + + diff --git a/options.editor/src/org/netbeans/modules/options/colors/Bundle.properties b/options.editor/src/org/netbeans/modules/options/colors/Bundle.properties --- a/options.editor/src/org/netbeans/modules/options/colors/Bundle.properties +++ b/options.editor/src/org/netbeans/modules/options/colors/Bundle.properties @@ -132,4 +132,6 @@ # QuickSearch keywords KW_FontsAndColorsOptions=Fonts & Colors Options - +# Options Export +FontsAndColors.Options.Export.Category.displayName=Fonts & Colors +FontsAndColors.Options.Export.displayName=Fonts & Colors diff --git a/options.editor/src/org/netbeans/modules/options/editor/Bundle.properties b/options.editor/src/org/netbeans/modules/options/editor/Bundle.properties --- a/options.editor/src/org/netbeans/modules/options/editor/Bundle.properties +++ b/options.editor/src/org/netbeans/modules/options/editor/Bundle.properties @@ -93,3 +93,9 @@ OptionsDialog/Actions/Window=Window OptionsDialog/Actions/System=System OptionsDialog/Actions/XML=XML + +# Options Export +Editor.Options.Export.Category.displayName=Editor +Editor.Options.Export.Macros.displayName=Macros +Editor.Options.Export.CodeTemplates.displayName=Code Templates +Editor.Options.Export.Other.displayName=Other diff --git a/options.editor/src/org/netbeans/modules/options/editor/mf-layer.xml b/options.editor/src/org/netbeans/modules/options/editor/mf-layer.xml --- a/options.editor/src/org/netbeans/modules/options/editor/mf-layer.xml +++ b/options.editor/src/org/netbeans/modules/options/editor/mf-layer.xml @@ -228,6 +228,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/options.keymap/src/org/netbeans/modules/options/keymap/Bundle.properties b/options.keymap/src/org/netbeans/modules/options/keymap/Bundle.properties --- a/options.keymap/src/org/netbeans/modules/options/keymap/Bundle.properties +++ b/options.keymap/src/org/netbeans/modules/options/keymap/Bundle.properties @@ -178,3 +178,7 @@ SpecialkeyPanel.enterButton.text= SpecialkeyPanel.leftButton.text_1= SpecialkeyPanel.rightButton.text= + +# Keymap Options Export +Keymaps.Options.Export.Category.displayName=Keymaps +Keymaps.Options.Export.Profiles.displayName=Keymap Profiles diff --git a/options.keymap/src/org/netbeans/modules/options/keymap/mf-layer.xml b/options.keymap/src/org/netbeans/modules/options/keymap/mf-layer.xml --- a/options.keymap/src/org/netbeans/modules/options/keymap/mf-layer.xml +++ b/options.keymap/src/org/netbeans/modules/options/keymap/mf-layer.xml @@ -91,9 +91,20 @@ - + + + + + + + + + + + - + + diff --git a/project.libraries/src/org/netbeans/modules/project/libraries/Bundle.properties b/project.libraries/src/org/netbeans/modules/project/libraries/Bundle.properties --- a/project.libraries/src/org/netbeans/modules/project/libraries/Bundle.properties +++ b/project.libraries/src/org/netbeans/modules/project/libraries/Bundle.properties @@ -39,3 +39,6 @@ MSG_libraryCreatedError=The library provider has thrown the following exception during library initialization, the created library may not work correctly. MSG_libraryDeletedError=The library provider has thrown the following exception during library removal. + +# Options export +ProjectLibraries.Options.Export.displayName=Project Libraries diff --git a/project.libraries/src/org/netbeans/modules/project/libraries/resources/mf-layer.xml b/project.libraries/src/org/netbeans/modules/project/libraries/resources/mf-layer.xml --- a/project.libraries/src/org/netbeans/modules/project/libraries/resources/mf-layer.xml +++ b/project.libraries/src/org/netbeans/modules/project/libraries/resources/mf-layer.xml @@ -68,4 +68,12 @@ + + + + + + + + diff --git a/vmd.midpnb/src/org/netbeans/modules/vmd/midpnb/Bundle.properties b/vmd.midpnb/src/org/netbeans/modules/vmd/midpnb/Bundle.properties --- a/vmd.midpnb/src/org/netbeans/modules/vmd/midpnb/Bundle.properties +++ b/vmd.midpnb/src/org/netbeans/modules/vmd/midpnb/Bundle.properties @@ -40,3 +40,6 @@ OpenIDE-Module-Display-Category=Mobility OpenIDE-Module-Name=VMD MIDP NB OpenIDE-Module-Short-Description=Visual Mobile Designer - MIDP NetBeans Components + +# Options export +VmdMidp.Options.Export.displayName=MIDP Palette diff --git a/vmd.midpnb/src/org/netbeans/modules/vmd/midpnb/layer.xml b/vmd.midpnb/src/org/netbeans/modules/vmd/midpnb/layer.xml --- a/vmd.midpnb/src/org/netbeans/modules/vmd/midpnb/layer.xml +++ b/vmd.midpnb/src/org/netbeans/modules/vmd/midpnb/layer.xml @@ -145,4 +145,13 @@ + + + + + + + + + diff --git a/web.core/src/org/netbeans/modules/web/core/Bundle.properties b/web.core/src/org/netbeans/modules/web/core/Bundle.properties --- a/web.core/src/org/netbeans/modules/web/core/Bundle.properties +++ b/web.core/src/org/netbeans/modules/web/core/Bundle.properties @@ -111,3 +111,6 @@ Templates/JSP_Servlet/TagLibrary.tld=Tag Library Descriptor Templates/JSP_Servlet/TagHandler.java=Tag Handler + +# Options export +JSPPalette.Options.Export.displayName=JSP Palette diff --git a/web.core/src/org/netbeans/modules/web/core/resources/layer.xml b/web.core/src/org/netbeans/modules/web/core/resources/layer.xml --- a/web.core/src/org/netbeans/modules/web/core/resources/layer.xml +++ b/web.core/src/org/netbeans/modules/web/core/resources/layer.xml @@ -614,4 +614,12 @@ + + + + + + + + diff --git a/welcome/src/org/netbeans/modules/welcome/Bundle.properties b/welcome/src/org/netbeans/modules/welcome/Bundle.properties --- a/welcome/src/org/netbeans/modules/welcome/Bundle.properties +++ b/welcome/src/org/netbeans/modules/welcome/Bundle.properties @@ -73,3 +73,6 @@ ACSD_FeedbackSurvey_Yes=Goes to survey ACSD_FeedbackSurvey_Later=Reminds later ACSD_FeedbackSurvey_Never=Skips survey + +# Display name for options export +Welcome.Options.Export.displayName=Start Page diff --git a/welcome/src/org/netbeans/modules/welcome/resources/layer.xml b/welcome/src/org/netbeans/modules/welcome/resources/layer.xml --- a/welcome/src/org/netbeans/modules/welcome/resources/layer.xml +++ b/welcome/src/org/netbeans/modules/welcome/resources/layer.xml @@ -178,4 +178,14 @@ + + + + + + + + + +