From: Hannu Niemistö Date: Wed, 12 Jul 2017 07:59:06 +0000 (+0300) Subject: (refs #7362) Creation of new SCL modules in SCL module browser X-Git-Tag: v1.31.0~264^2~54 X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=commitdiff_plain;h=0d50e7ddf3d00d7ceabf109848139531dafffd6a;hp=56f843ece1f6c8cbe55a5dc3495d828a1163c2fe (refs #7362) Creation of new SCL modules in SCL module browser Change-Id: Ieb99edbd22db8092edd62449b739f6ae2b5824ab --- diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/CreateModuleAction.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/CreateModuleAction.java new file mode 100644 index 000000000..1d70442c8 --- /dev/null +++ b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/CreateModuleAction.java @@ -0,0 +1,72 @@ +package org.simantics.scl.ui.modulebrowser; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.simantics.scl.ui.Activator; + +import gnu.trove.map.hash.THashMap; + +public class CreateModuleAction { + public static final String PREFIX = "reference:file:/"; + + public static final void createModule(Bundle bundle, String packageName, String moduleName) throws IOException { + String bundleLocation = bundle.getLocation(); + bundleLocation = bundleLocation.substring(PREFIX.length()); + + Path packagePath = Paths.get(bundleLocation).resolve("scl").resolve(packageName); + Files.createDirectories(packagePath); + Path modulePath = packagePath.resolve(moduleName + ".scl"); + if(Files.exists(modulePath)) + return; + Files.createFile(modulePath); + } + + public static THashMap findGoodBundles() { + BundleContext context = Activator.getInstance().getBundle().getBundleContext(); + THashMap result = new THashMap(); + for(Bundle bundle : context.getBundles()) { + String location = bundle.getLocation(); + if(location.endsWith(".jar") || !location.startsWith(PREFIX)) + continue; + location = location.substring(PREFIX.length()); + Path bundlePath = Paths.get(location); + if(!Files.isDirectory(bundlePath)) + continue; + result.put(bundle.getSymbolicName(), bundle); + } + return result; + } + + public static String findBestPlugin(THashMap bundles, String packageName) { + for(Bundle bundle : bundles.values()) { + String location = bundle.getLocation(); + location = location.substring(PREFIX.length()); + Path packagePath = Paths.get(location).resolve("scl").resolve(packageName); + if(Files.exists(packagePath)) + return bundle.getSymbolicName(); + } + int p = packageName.lastIndexOf('/'); + if(p == -1) + return ""; + else + return findBestPlugin(bundles, packageName.substring(0, p)); + } + + public static int packageMatchLength(Bundle bundle, String packageName) { + if(bundle.getEntry("scl") == null) + return 0; + packageName = "scl/" + packageName; + while(packageName.length() > 3) { + if(bundle.getEntry(packageName) != null) + return packageName.length(); + int p = packageName.lastIndexOf('/'); + packageName = packageName.substring(0, p); + } + return 3; + } +} diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/CreateModuleDialog.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/CreateModuleDialog.java new file mode 100644 index 000000000..875c9ace4 --- /dev/null +++ b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/CreateModuleDialog.java @@ -0,0 +1,186 @@ +package org.simantics.scl.ui.modulebrowser; + +import java.io.IOException; +import java.util.ArrayList; + +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.osgi.framework.Bundle; +import org.simantics.scl.osgi.SCLOsgi; +import org.simantics.scl.ui.editor2.OpenSCLModule; +import org.simantics.scl.ui.modulebrowser.PluginSelectionDialog.Entry; + +import gnu.trove.map.hash.THashMap; + +public class CreateModuleDialog extends Dialog { + + SCLModuleBrowser parentBrowser; + + String initialPackageName = ""; + String initialPluginName = ""; + + Text packageName; + Text moduleName; + Text pluginName; + private THashMap bundles; + + private Color normalBackground; + private Color errorBackground; + + protected CreateModuleDialog(Shell parentShell, SCLModuleBrowser parentBrowser) { + super(parentShell); + this.parentBrowser = parentBrowser; + setShellStyle(SWT.RESIZE | SWT.TITLE | SWT.BORDER); + bundles = CreateModuleAction.findGoodBundles(); + + normalBackground = parentShell.getDisplay().getSystemColor(SWT.COLOR_WHITE); + errorBackground = new Color(parentShell.getDisplay(), 255, 128, 128); + } + + @Override + protected Control createDialogArea(Composite parent) { + getShell().setText("Create New Module"); + getShell().addDisposeListener(new DisposeListener() { + @Override + public void widgetDisposed(DisposeEvent e) { + errorBackground.dispose(); + } + }); + + final Composite composite = (Composite) super.createDialogArea(parent); + GridLayoutFactory.fillDefaults().margins(10,10).numColumns(2).applyTo(composite); + GridDataFactory.fillDefaults().grab(true,true).applyTo(composite); + + // Package name + Label packageNameLabel = new Label(composite, SWT.NONE); + packageNameLabel.setText("Package"); + GridDataFactory.fillDefaults().applyTo(packageNameLabel); + + packageName = new Text(composite, SWT.BORDER); + GridDataFactory.fillDefaults().grab(true, false).minSize(500, SWT.DEFAULT).applyTo(packageName); + packageName.setText(initialPackageName); + packageName.addModifyListener(modifyListener); + + // Module name + Label moduleNameLabel = new Label(composite, SWT.NONE); + moduleNameLabel.setText("Module name"); + GridDataFactory.fillDefaults().applyTo(moduleNameLabel); + + moduleName = new Text(composite, SWT.BORDER); + GridDataFactory.fillDefaults().grab(true, false).applyTo(moduleName); + moduleName.addModifyListener(modifyListener); + + // Plugin + Label pluginNameLabel = new Label(composite, SWT.NONE); + pluginNameLabel.setText("Plugin"); + GridDataFactory.fillDefaults().applyTo(pluginNameLabel); + + Composite pluginNameComposite = new Composite(composite, SWT.NONE); + GridDataFactory.fillDefaults().grab(true, false).applyTo(pluginNameComposite); + GridLayoutFactory.fillDefaults().numColumns(2).applyTo(pluginNameComposite); + + pluginName = new Text(pluginNameComposite, SWT.BORDER); + GridDataFactory.fillDefaults().grab(true, false).applyTo(pluginName); + pluginName.setText(initialPluginName); + pluginName.addModifyListener(modifyListener); + + Button browsePlugins = new Button(pluginNameComposite, SWT.PUSH); + browsePlugins.setText("Browse..."); + GridDataFactory.fillDefaults().applyTo(browsePlugins); + browsePlugins.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + browsePlugins(); + } + }); + + // Focus + moduleName.setFocus(); + parent.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + validate(); + } + }); + + return composite; + } + + private void browsePlugins() { + ArrayList entries = new ArrayList(bundles.size()); + String currentPackageName = packageName.getText(); + for(Bundle bundle : bundles.values()) + entries.add(new Entry(bundle, CreateModuleAction.packageMatchLength(bundle, currentPackageName))); + PluginSelectionDialog dialog = new PluginSelectionDialog(getShell(), entries); + if(dialog.open() == Dialog.OK) { + Entry result = (Entry)dialog.getFirstResult(); + if(result != null) { + pluginName.setText(result.bundle.getSymbolicName()); + validate(); + } + } + } + + private void validate() { + boolean validPackageName = CreateModuleValidator.isValidPackageName(packageName.getText()); + packageName.setBackground(validPackageName ? normalBackground : errorBackground); + + boolean validModuleName = CreateModuleValidator.isValidModuleName(moduleName.getText()); + if(validModuleName && validPackageName) { + String fullModuleName = packageName.getText() + "/" + moduleName.getText(); + validModuleName = SCLOsgi.SOURCE_REPOSITORY.getModuleSource(fullModuleName, null) == null; + } + moduleName.setBackground(validModuleName ? normalBackground : errorBackground); + + boolean validPluginName = bundles.containsKey(pluginName.getText()); + pluginName.setBackground(validPluginName ? normalBackground : errorBackground); + + getButton(IDialogConstants.OK_ID).setEnabled(validPackageName && validModuleName && validPackageName); + } + + private ModifyListener modifyListener = new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + validate(); + } + }; + + public void setPackage(String initialPackageName) { + this.initialPackageName = initialPackageName; + this.initialPluginName = CreateModuleAction.findBestPlugin(bundles, initialPackageName); + } + + @Override + protected void okPressed() { + try { + Bundle bundle = bundles.get(pluginName.getText()); + if(bundle != null) { + CreateModuleAction.createModule(bundle, packageName.getText(), moduleName.getText()); + parentBrowser.refresh(); + OpenSCLModule.openModule(packageName.getText() + "/" + moduleName.getText()); + } + } catch (IOException e) { + ErrorDialog.openError(getParentShell(), "Module creation failed", e.getMessage(), + new Status(Status.ERROR, "org.simantics.scl.ui", e.getMessage())); + } + super.okPressed(); + } +} diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/CreateModuleValidator.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/CreateModuleValidator.java new file mode 100644 index 000000000..d3236c0dd --- /dev/null +++ b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/CreateModuleValidator.java @@ -0,0 +1,28 @@ +package org.simantics.scl.ui.modulebrowser; + +public class CreateModuleValidator { + public static boolean isValidPackageName(String packageName) { + if(packageName.isEmpty()) + return true; + for(String part : packageName.split("/", -1)) + if(!isValidModuleName(part)) + return false; + return true; + } + + public static boolean isValidModuleName(String moduleName) { + if(moduleName.isEmpty()) + return false; + { + char c = moduleName.charAt(0); + if(!Character.isLetter(c)) + return false; + } + for(int i=1;i entries; + + public PluginSelectionDialog(Shell shell, Collection entries) { + super(shell, true); + this.entries = entries; + setListLabelProvider(new LabelProvider() { + @Override + public String getText(Object element) { + Entry entry = (Entry)element; + if(entry == null) + return "NULL"; + return entry.bundle.getSymbolicName(); + } + }); + } + + @Override + protected Control createExtendedContentArea(Composite parent) { + return null; + } + + @Override + protected IDialogSettings getDialogSettings() { + IDialogSettings settings = org.simantics.scl.ui.Activator.getInstance() + .getDialogSettings().getSection(PLUGIN_SELECTION_DIALOG); + if (settings == null) + settings = Activator.getInstance() + .getDialogSettings().addNewSection(PLUGIN_SELECTION_DIALOG); + return settings; + } + + @Override + protected IStatus validateItem(Object item) { + return Status.OK_STATUS; + } + + @Override + protected ItemsFilter createFilter() { + return new ItemsFilter() { + { + String patternText = getPattern(); + patternMatcher = new SearchPattern(); + if(patternText != null && patternText.length() > 0) + patternMatcher.setPattern(patternText); + else + patternMatcher.setPattern("*"); + } + + @Override + public boolean matchItem(Object item) { + Entry entry = (Entry)item; + String name = entry.bundle.getSymbolicName(); + if(getPattern().indexOf('/') > 0) + return matches(name); + else { + for(String part : name.split("/")) + if(matches(part)) + return true; + return false; + } + } + + @Override + public boolean isConsistentItem(Object item) { + return true; + } + + }; + } + + Comparator comparator = new Comparator() { + @Override + public int compare(Entry o1, Entry o2) { + if(o1.packageMatchLength != o2.packageMatchLength) + return Integer.compare(o2.packageMatchLength, o1.packageMatchLength); + return o1.bundle.getSymbolicName().compareTo(o2.bundle.getSymbolicName()); + } + }; + + @SuppressWarnings("rawtypes") + @Override + protected Comparator getItemsComparator() { + return comparator; + } + + @Override + protected void fillContentProvider(final AbstractContentProvider contentProvider, + final ItemsFilter itemsFilter, IProgressMonitor progressMonitor) + throws CoreException { + for(Entry entry : entries) + contentProvider.add(entry, itemsFilter); + if(progressMonitor != null) + progressMonitor.done(); + } + + @Override + public String getElementName(Object item) { + Entry entry = (Entry)item; + return entry.bundle.getSymbolicName(); + } + +} diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/SCLModuleBrowser.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/SCLModuleBrowser.java index 5e08a55a4..b175a3e60 100644 --- a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/SCLModuleBrowser.java +++ b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/SCLModuleBrowser.java @@ -1,13 +1,17 @@ package org.simantics.scl.ui.modulebrowser; import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Menu; import org.eclipse.ui.part.ViewPart; import org.simantics.scl.osgi.SCLOsgi; import org.simantics.scl.ui.Activator; @@ -21,6 +25,8 @@ public class SCLModuleBrowser extends ViewPart { public void createPartControl(Composite parent) { this.content = new SCLModuleTree(parent, SWT.NONE, SCLOsgi.MODULE_REPOSITORY); setPartName("SCL Modules"); + + // Opening modules content.addDoubleClickListener(new IDoubleClickListener() { @Override public void doubleClick(DoubleClickEvent event) { @@ -33,15 +39,41 @@ public class SCLModuleBrowser extends ViewPart { } }); + // Toolbar IToolBarManager toolBarManager = getViewSite().getActionBars().getToolBarManager(); toolBarManager.add(new Action("Refresh modules", Activator.imageDescriptorFromPlugin("org.simantics.scl.ui", "icons/arrow_refresh.png")) { @Override public void run() { - SCLOsgi.MODULE_REPOSITORY.getSourceRepository().checkUpdates(); - content.recalculateInput(); + refresh(); + } + }); + + // Context menu + MenuManager menuMgr = new MenuManager(); + menuMgr.setRemoveAllWhenShown(true); + menuMgr.addMenuListener(new IMenuListener() { + public void menuAboutToShow(IMenuManager manager) { + ModuleNameTreeEntry entry = (ModuleNameTreeEntry)content.getStructuredSelection().getFirstElement(); + if(!entry.isModule) + manager.add(new Action("New Module...") { + @Override + public void run() { + CreateModuleDialog dialog = new CreateModuleDialog(content.getControl().getShell(), SCLModuleBrowser.this); + dialog.setPackage(entry.fullName); + dialog.open(); + } + }); } }); + Menu menu = menuMgr.createContextMenu(content.getControl()); + content.getControl().setMenu(menu); + getSite().registerContextMenu(menuMgr, content); + } + + public void refresh() { + SCLOsgi.MODULE_REPOSITORY.getSourceRepository().checkUpdates(); + content.recalculateInput(); } @Override