(refs #7362) Creation of new SCL modules in SCL module browser 11/711/1
authorHannu Niemistö <hannu.niemisto@semantum.fi>
Wed, 12 Jul 2017 07:59:06 +0000 (10:59 +0300)
committerHannu Niemistö <hannu.niemisto@semantum.fi>
Wed, 12 Jul 2017 07:59:06 +0000 (10:59 +0300)
Change-Id: Ieb99edbd22db8092edd62449b739f6ae2b5824ab

bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/CreateModuleAction.java [new file with mode: 0644]
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/CreateModuleDialog.java [new file with mode: 0644]
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/CreateModuleValidator.java [new file with mode: 0644]
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/PluginSelectionDialog.java [new file with mode: 0644]
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/SCLModuleBrowser.java

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 (file)
index 0000000..1d70442
--- /dev/null
@@ -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<String,Bundle> findGoodBundles() {
+        BundleContext context = Activator.getInstance().getBundle().getBundleContext();
+        THashMap<String,Bundle> result = new THashMap<String,Bundle>();
+        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<String,Bundle> 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 (file)
index 0000000..875c9ac
--- /dev/null
@@ -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<String,Bundle> 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<Entry> entries = new ArrayList<Entry>(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 (file)
index 0000000..d3236c0
--- /dev/null
@@ -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<moduleName.length();++i) {
+            char c = moduleName.charAt(i);
+            if(!Character.isLetter(c) && !Character.isDigit(c) && c != '_')
+                return false;
+        }
+        return true;
+    }
+}
diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/PluginSelectionDialog.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/modulebrowser/PluginSelectionDialog.java
new file mode 100644 (file)
index 0000000..30e84f8
--- /dev/null
@@ -0,0 +1,133 @@
+package org.simantics.scl.ui.modulebrowser;
+
+import java.util.Collection;
+import java.util.Comparator;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.dialogs.FilteredItemsSelectionDialog;
+import org.eclipse.ui.dialogs.SearchPattern;
+import org.osgi.framework.Bundle;
+import org.simantics.scl.ui.Activator;
+
+public class PluginSelectionDialog extends FilteredItemsSelectionDialog {
+
+    private static final String PLUGIN_SELECTION_DIALOG = "PLUGIN_SELECTION_DIALOG";
+    
+    public static class Entry {
+        Bundle bundle;
+        int packageMatchLength;
+        public Entry(Bundle bundle, int packageMatchLength) {
+            this.bundle = bundle;
+            this.packageMatchLength = packageMatchLength;
+        }
+    }
+    Collection<Entry> entries;
+    
+    public PluginSelectionDialog(Shell shell, Collection<Entry> 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<Entry> comparator = new Comparator<Entry>() {
+        @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();
+    }
+
+}
index 5e08a55a407958d12c0b1b542307074e018559c2..b175a3e600c14cc69db9c7e1a8e6ea23de1b6048 100644 (file)
@@ -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