/******************************************************************************* * Copyright (c) 2012 VMWare, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VMWare, Inc. - initial API and implementation *******************************************************************************/ package org.grails.ide.eclipse.ui.internal.inplace; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.internal.ui.JavaPluginImages; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.GroupMarker; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.util.Geometry; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.PopupList; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.MenuEvent; import org.eclipse.swt.events.MenuListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.events.ShellAdapter; import org.eclipse.swt.events.ShellEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.FontMetrics; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Layout; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Monitor; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Tracker; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.PreferencesUtil; import org.grails.ide.eclipse.core.GrailsCoreActivator; import org.grails.ide.eclipse.core.internal.GrailsNature; import org.springsource.ide.eclipse.commons.core.CommandHistoryProvider; import org.springsource.ide.eclipse.commons.core.Entry; import org.springsource.ide.eclipse.commons.core.ICommandHistory; import org.springsource.ide.eclipse.commons.core.SpringCoreUtils; import org.springsource.ide.eclipse.commons.frameworks.ui.internal.contentassist.ContentProposalAdapter; import org.springsource.ide.eclipse.commons.frameworks.ui.internal.contentassist.IContentProposalListener2; import org.springsource.ide.eclipse.commons.ui.CommandHistoryPopupList; import org.grails.ide.eclipse.ui.GrailsUiActivator; import org.grails.ide.eclipse.ui.internal.inplace.GrailsCompletionUtils.GrailsProposalProvider; /** * @author Christian Dupuis * @author Andrew Eisenberg * @author Andy Clement * @author Kris De Volder * @author Nieraj Singh * @since 2.2.0 */ @SuppressWarnings("restriction") public class GrailsInplaceDialog { /** * The maximum number of items that will be shown in the history menu. * (Note that this number is independent from the number of entries * actually stored in the history. This only limits the number of items * shown in the GrailsInplaceDialog) */ public int MAX_HISTORY_ITEMS = 20; /** * Currently active instance or null. Only one instance should be active/open at the * same time. */ private static GrailsInplaceDialog instance; public static String title = "Grails Command Prompt"; /** * Get an instances of GrailsInplaceDialog. This method ensures only one instance * is open at a time, and returns a reference to the existing instance if one is * already/still open. */ public static GrailsInplaceDialog getInstance(Shell parent) { if (instance==null) { instance = new GrailsInplaceDialog(parent); } return instance; } /** * Dialog constants telling whether this control can be resized or move. */ public static final String STORE_DISABLE_RESTORE_SIZE = "DISABLE_RESTORE_SIZE"; //$NON-NLS-1$ public static final String STORE_DISABLE_RESTORE_LOCATION = "DISABLE_RESTORE_LOCATION"; //$NON-NLS-1$ /** * Dialog store constant for the location's x-coordinate, location's y-coordinate and the size's width and height. */ private static final String STORE_LOCATION_X = "location.x"; //$NON-NLS-1$ private static final String STORE_LOCATION_Y = "location.y"; //$NON-NLS-1$ private static final String STORE_SIZE_WIDTH = "size.width"; //$NON-NLS-1$ private static final String STORE_SIZE_HEIGHT = "size.height"; //$NON-NLS-1$ private static final String STORE_PINNED_STATE = "pinned"; /** * The name of the dialog store's section associated with the inplace XReference view. */ private final String sectionName = GrailsInplaceDialog.class.getName(); /** * Fields for text matching and filtering */ private Text commandText; private Font statusTextFont; private static IProject selectedProject; // Static is ok: only one instance exists. // make static to remember last selection for new dialog /** * Remembers the bounds for this information control. */ private Rectangle bounds; private Rectangle trim; /** * Fields for view menu support. */ private ToolBar toolBar; private MenuManager viewMenuManager; private Label statusField; private boolean isDeactivateListenerActive = false; private Composite composite; private Composite line1, line2; private int shellStyle; private Listener deactivateListener; private Shell parentShell; private Shell dialogShell; private Label promptLabel; private Combo projectList; private Label projectLabel; /** * Constructor which takes the parent shell */ private GrailsInplaceDialog(Shell parent) { parentShell = parent; shellStyle = SWT.RESIZE; isPinned = getDialogSettings().getBoolean(STORE_PINNED_STATE); initializeUI(); } /** * Open the dialog */ public void open() { if (dialogShell==null) { initializeUI(); } dialogShell.open(); dialogShell.forceActive(); } private void initializeUI() { if (dialogShell != null) { close(); } if (!grailsInstallAlreadyConfigured()) { // user must configure grails install and then // bring up the in place dialog again askToConfigireGrailsInstall(); return; } createContents(); createShell(); createComposites(); // creates the drop down menu and creates the actions commandText = createCommandText(line1); createViewMenu(line1); createProjectList(line2); createStatusField(line2); // set the tab order line1.setTabList(new Control[] { commandText }); composite.setTabList(new Control[] { line1 }); setInfoSystemColor(); addListenersToShell(); initializeBounds(); } // ---- all to do with the command history --------------- private ICommandHistory commands = getCommandHistory(); public static ICommandHistory getCommandHistory() { return CommandHistoryProvider.getCommandHistory(GrailsUiActivator.PLUGIN_ID, GrailsNature.NATURE_ID); } private boolean isPinned; private IContentProposalListener2 proposalListener; private ContentProposalAdapter contentProposer; private void commandHistoryPopup() { List<Entry> entries = commands.getRecentValid(MAX_HISTORY_ITEMS); Entry chosen = commandHistoryPopup(commandText, entries.toArray(new Entry[entries.size()])); if (chosen!=null) { setSelectedProject(chosen.getProject()); setCommand(chosen.getCommand()); } } private Entry commandHistoryPopup(Control showBelow, Entry[] entries) { if (commands.size()>1) { CommandHistoryPopupList popup = new CommandHistoryPopupList(dialogShell); popup.setLabelProvider(new CommandHistoryPopupList.LabelProvider() { @Override public String getLabel(Entry entry) { if (entry.getProject().equals(getSelectedProjectName())) return entry.getCommand(); else return super.getLabel(entry); } }); popup.setItems(entries); // if (initialSelection!=null) // popup.select(initialSelection); isDeactivateListenerActive = false; // otherwise dialog will be disposed when popup opens Entry selected = popup.open(showBelow.getDisplay().map(showBelow.getParent(), null, showBelow.getBounds())); isDeactivateListenerActive = true; return selected; } else if (commands.isEmpty()) { return null; } else { return commands.getLast(); } } private String popupMenu(Control showBelow, String[] choices, String initialSelection) { if (choices.length>1) { PopupList popup = new PopupList(dialogShell); popup.setItems(choices); if (initialSelection!=null) popup.select(initialSelection); isDeactivateListenerActive = false; // otherwise dialog will be disposed when popup opens String selected = popup.open(showBelow.getDisplay().map(showBelow.getParent(), null, showBelow.getBounds())); isDeactivateListenerActive = true; return selected; } return initialSelection; } private void addCommandToHistory(String text) { if ("".equals(text.trim())) return; commands.add(new Entry(text, getSelectedProjectName())); } // -------------------------------------------------------------------- private void askToConfigireGrailsInstall() { boolean res = MessageDialog.openQuestion(parentShell, "No Grails install found", "No Grails installation has been found.\n" + "Do you want to configure one now?"); if (res) { openPreferences(); } } private void openPreferences() { String id = "org.grails.ide.eclipse.ui.preferencePage"; PreferencesUtil.createPreferenceDialogOn(parentShell, id, new String[] { id }, Collections.EMPTY_MAP).open(); } /** * @return true iff there is at least one grails install already configured */ private boolean grailsInstallAlreadyConfigured() { return GrailsCoreActivator.getDefault().getInstallManager().getDefaultGrailsInstall() != null; } private void createShell() { // Create the shell dialogShell = new Shell(parentShell, shellStyle); dialogShell.setText(title); // To handle "ESC" case dialogShell.addShellListener(new ShellAdapter() { @Override public void shellClosed(ShellEvent event) { event.doit = false; // don't close now dispose(); } }); Display display = dialogShell.getDisplay(); dialogShell.setBackground(display.getSystemColor(SWT.COLOR_BLACK)); int border = ((shellStyle & SWT.NO_TRIM) == 0) ? 0 : 1; dialogShell.setLayout(new BorderFillLayout(border)); } private void createComposites() { // Composite for filter text and tree composite = new Composite(dialogShell, SWT.RESIZE); GridLayout layout = new GridLayout(1, false); composite.setLayout(layout); composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); line1 = new Composite(composite, SWT.NONE); layout = new GridLayout(3, false); layout.marginHeight = 0; layout.marginWidth = 0; line1.setLayout(layout); GridDataFactory.fillDefaults().grab(true, false).applyTo(line1); createHorizontalSeparator(composite); line2 = new Composite(composite, SWT.FILL); layout = new GridLayout(3, false); layout.marginHeight = 0; layout.marginWidth = 0; line2.setLayout(layout); GridDataFactory.fillDefaults().grab(true, false).applyTo(line2); } private void createHorizontalSeparator(Composite parent) { Label separator = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL | SWT.LINE_DOT); separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); } private void setInfoSystemColor() { Display display = dialogShell.getDisplay(); // set the foreground colour commandText.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); promptLabel.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); composite.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); line1.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); toolBar.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); statusField.setForeground(display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW)); line2.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); // set the background colour commandText.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); promptLabel.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); composite.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); line1.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); toolBar.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); statusField.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); line2.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); projectLabel.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); //projectList.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); } // --------------------- adding listeners --------------------------- private void addListenersToShell() { dialogShell.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { if (statusTextFont!=null) statusTextFont.dispose(); close(); // dialogShell = null; composite = null; commandText = null; statusTextFont = null; } }); deactivateListener = new Listener() { public void handleEvent(Event event) { if (isDeactivateListenerActive && !isPinned()) dispose(); } }; dialogShell.addListener(SWT.Deactivate, deactivateListener); isDeactivateListenerActive = true; dialogShell.addShellListener(new ShellAdapter() { @Override public void shellActivated(ShellEvent e) { if (e.widget == dialogShell && dialogShell.getShells().length == 0) { isDeactivateListenerActive = true; refreshProjects(); // force refresh of projects list } } }); dialogShell.addControlListener(new ControlAdapter() { @Override public void controlMoved(ControlEvent e) { bounds = dialogShell.getBounds(); if (trim != null) { Point location = composite.getLocation(); bounds.x = bounds.x - trim.x + location.x; bounds.y = bounds.y - trim.y + location.y; } } @Override public void controlResized(ControlEvent e) { bounds = dialogShell.getBounds(); if (trim != null) { Point location = composite.getLocation(); bounds.x = bounds.x - trim.x + location.x; bounds.y = bounds.y - trim.y + location.y; } } }); } // --------------------- creating and filling the menu // --------------------------- private void createViewMenu(Composite parent) { toolBar = new ToolBar(parent, SWT.FLAT); ToolItem viewMenuButton = new ToolItem(toolBar, SWT.PUSH, 0); GridData data = new GridData(); data.horizontalAlignment = GridData.END; data.verticalAlignment = GridData.BEGINNING; toolBar.setLayoutData(data); viewMenuButton.setImage(JavaPluginImages.get(JavaPluginImages.IMG_ELCL_VIEW_MENU)); viewMenuButton.setDisabledImage(JavaPluginImages.get(JavaPluginImages.IMG_DLCL_VIEW_MENU)); viewMenuButton.setToolTipText("Menu"); viewMenuButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { showViewMenu(); } }); } private void showViewMenu() { isDeactivateListenerActive = false; Menu aMenu = getViewMenuManager().createContextMenu(dialogShell); dialogShell.setMenu(aMenu); Rectangle bounds = toolBar.getBounds(); Point topLeft = new Point(bounds.x, bounds.y + bounds.height); topLeft = dialogShell.toDisplay(topLeft); aMenu.setLocation(topLeft.x, topLeft.y); aMenu.addMenuListener(new MenuListener() { public void menuHidden(MenuEvent e) { isDeactivateListenerActive = true; } public void menuShown(MenuEvent e) { } }); aMenu.setVisible(true); } private MenuManager getViewMenuManager() { if (viewMenuManager == null) { viewMenuManager = new MenuManager(); fillViewMenu(viewMenuManager); } return viewMenuManager; } private void fillViewMenu(IMenuManager viewMenu) { viewMenu.add(new GroupMarker("SystemMenuStart")); //$NON-NLS-1$ viewMenu.add(new MoveAction()); viewMenu.add(new ResizeAction()); viewMenu.add(new PinDownAction()); viewMenu.add(new RememberBoundsAction()); viewMenu.add(new Separator("SystemMenuEnd")); //$NON-NLS-1$ } // --------------------- creating and handling the project selection list private IProject getSelectedProject() { return selectedProject; } private String getSelectedProjectName() { if (selectedProject==null) return null; else return selectedProject.getName(); } private void createProjectList(Composite parent) { projectLabel = new Label(parent, SWT.FILL); projectLabel.setText("Project: "); smallFont(projectLabel); projectList = new Combo(parent, SWT.READ_ONLY); smallFont(projectList); GridDataFactory.swtDefaults().align(SWT.LEFT, SWT.FILL).grab(true, false).applyTo(projectList); projectList.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { handleProjectSelection(); } public void widgetDefaultSelected(SelectionEvent e) { handleProjectSelection(); } private void handleProjectSelection() { if (!projectList.getText().equals(getSelectedProjectName())) { setSelectedProject(projectList.getText()); } } }); refreshProjects(); // force refresh of created projectList } private String[] getGrailsProjectNames() { IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); java.util.List<String> names = new ArrayList<String>(); int i = 0; for (IProject project : projects) { // Test if the selected project has Grails nature if (isValidProject(project)) { names.add(project.getName()); i++; } } if (selectedProject!=null && !isValidProject(selectedProject)) { names.add(selectedProject.getName()); // make sure selected project is always added! or it won't show in the dropdown! } return names.toArray(new String[names.size()]); } private boolean isValidProject(IProject project) { return project!=null && SpringCoreUtils.hasNature(project, GrailsNature.NATURE_ID); } // ----------------- creating and filling the status field ---------- private void createStatusField(Composite parent) { // Composite comp = new Composite(parent, SWT.NONE); // GridLayout layout = new GridLayout(1, false); // layout.marginHeight = 0; // layout.marginWidth = 0; // comp.setLayout(layout); // comp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); // Status field label statusField = new Label(parent, SWT.RIGHT); refreshStatusField(); GridDataFactory.swtDefaults().grab(false, false).align(SWT.RIGHT, SWT.CENTER).applyTo(statusField); smallFont(statusField); } private String getStatusMessage() { if (selectedProject==null) return "Select a Grails Project"; if (!isValidProject(selectedProject)) if (!selectedProject.exists()) return "Selected project does not exist"; else if (!selectedProject.isOpen()) return "Selected project is not open"; else { return "Selected project is not a Grails project"; } if (isPinned()) return "Pinned: Press 'Esc' to close"; else return "Type Grails command and press 'Enter'"; } private void refreshStatusField() { if (statusField!=null) { statusField.setText(getStatusMessage()); statusField.getParent().layout(); } } private void smallFont(Control widget) { if (statusTextFont==null) { Font font = widget.getFont(); Display display = widget.getDisplay(); FontData[] fontDatas = font.getFontData(); for (FontData element : fontDatas) { element.setHeight(element.getHeight() * 9 / 10); } statusTextFont = new Font(display, fontDatas); } widget.setFont(statusTextFont); } // ----------- all to do with setting the bounds of the dialog ------------- /** * Initialize the shell's bounds. */ private void initializeBounds() { // if we don't remember the dialog bounds then reset // to be the defaults (behaves like inplace outline view) Rectangle oldBounds = restoreBounds(); if (oldBounds != null) { Rectangle defaultBounds = getDefaultBounds(); // Only use oldBounds if they are at least as large as the default if (oldBounds.width < defaultBounds.width) { oldBounds.width = defaultBounds.width; oldBounds.x = defaultBounds.x; } if (oldBounds.height < defaultBounds.height) { oldBounds.height = defaultBounds.height; oldBounds.y = defaultBounds.y; } dialogShell.setBounds(oldBounds); return; } dialogShell.setBounds(getDefaultBounds()); } public Rectangle getDefaultBounds() { GC gc = new GC(composite); gc.setFont(composite.getFont()); int width = gc.getFontMetrics().getAverageCharWidth(); gc.dispose(); Point size = dialogShell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); Point location = getDefaultLocation(size); size.x = Math.max(size.x, 65*width); // At least space for 60 something chars return new Rectangle(location.x, location.y, size.x, size.y); } private Point getDefaultLocation(Point initialSize) { Rectangle monitorBounds = getMonitorBounds(); Point centerPoint; if (parentShell != null) { centerPoint = Geometry.centerPoint(parentShell.getBounds()); } else { centerPoint = Geometry.centerPoint(monitorBounds); } return new Point(centerPoint.x - (initialSize.x / 2), Math.max(monitorBounds.y, Math.min(centerPoint.y - (initialSize.y * 2 / 3), monitorBounds.y + monitorBounds.height - initialSize.y))); } private Rectangle getMonitorBounds() { Monitor monitor = dialogShell.getDisplay().getPrimaryMonitor(); if (parentShell != null) { monitor = parentShell.getMonitor(); } Rectangle monitorBounds = monitor.getClientArea(); return monitorBounds; } /** * @return A rectangle that is considered "valid" for the placement of command prompt window with * restored location. */ private Rectangle getValidBounds() { if (parentShell!=null) { return parentShell.getBounds(); } else { return getMonitorBounds(); } } private IDialogSettings getDialogSettings() { IDialogSettings settings = GrailsUiActivator.getDefault().getDialogSettings().getSection(sectionName); if (settings == null) settings = GrailsUiActivator.getDefault().getDialogSettings().addNewSection(sectionName); return settings; } private void storeBounds() { IDialogSettings dialogSettings = getDialogSettings(); boolean controlRestoresSize = !dialogSettings.getBoolean(STORE_DISABLE_RESTORE_SIZE); boolean controlRestoresLocation = !dialogSettings.getBoolean(STORE_DISABLE_RESTORE_LOCATION); if (bounds == null) return; if (controlRestoresSize) { dialogSettings.put(STORE_SIZE_WIDTH, bounds.width); dialogSettings.put(STORE_SIZE_HEIGHT, bounds.height); } if (controlRestoresLocation) { dialogSettings.put(STORE_LOCATION_X, bounds.x); dialogSettings.put(STORE_LOCATION_Y, bounds.y); } } private Rectangle restoreBounds() { IDialogSettings dialogSettings = getDialogSettings(); boolean controlRestoresSize = !dialogSettings.getBoolean(STORE_DISABLE_RESTORE_SIZE); boolean controlRestoresLocation = !dialogSettings.getBoolean(STORE_DISABLE_RESTORE_LOCATION); Rectangle bounds = new Rectangle(-1, -1, -1, -1); if (controlRestoresSize) { try { bounds.width = dialogSettings.getInt(STORE_SIZE_WIDTH); bounds.height = dialogSettings.getInt(STORE_SIZE_HEIGHT); } catch (NumberFormatException ex) { bounds.width = -1; bounds.height = -1; } } if (controlRestoresLocation) { try { bounds.x = dialogSettings.getInt(STORE_LOCATION_X); bounds.y = dialogSettings.getInt(STORE_LOCATION_Y); } catch (NumberFormatException ex) { bounds.x = -1; bounds.y = -1; } } // sanity check if (bounds.x == -1 && bounds.y == -1 && bounds.width == -1 && bounds.height == -1) { return null; } if (!isValid(bounds)) { return null; } else { return bounds; } } /** * When restoring window coordinates persisted in preferences, this validity check is * used to determine whether coordinates look reasonable enough to be reused * (to avoid having the prompt show up on the other, possibly turned off montitor) * <p> * This implementation checks whether the bounds fall completely within the bounds * of the 'parentShell' which is typically the Eclipse workbench window. */ private boolean isValid(Rectangle bounds) { Rectangle validBounds = getValidBounds(); if (validBounds!=null && validBounds.intersects(bounds)) { //Must fall completely inside the valid bounds Rectangle intersection = validBounds.intersection(bounds); return intersection.width==bounds.width && intersection.height == bounds.height; } return false; } // ----------- all to do with filtering text private Text createCommandText(Composite parent) { promptLabel = new Label(parent, SWT.NONE); promptLabel.setText("grails> "); commandText = new Text(parent, SWT.NONE); GridData data = new GridData(GridData.FILL_HORIZONTAL); GC gc = new GC(parent); gc.setFont(parent.getFont()); FontMetrics fontMetrics = gc.getFontMetrics(); gc.dispose(); data.heightHint = Dialog.convertHeightInCharsToPixels(fontMetrics, 1); data.horizontalAlignment = GridData.FILL; data.verticalAlignment = GridData.CENTER; commandText.setLayoutData(data); commandText.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { if (e.character == SWT.ESC) { // ESC dispose(); return; } if (e.doit && (e.keyCode == 0x0D || e.keyCode == SWT.KEYPAD_CR) && isDeactivateListenerActive) { // return e.doit = false; IProject project = getSelectedProject(); if (!isValidProject(project)) { openInformationMessage("Invalid Project ", getStatusMessage()); } else if (explainNewWizardToUser()) { return; } else { addCommandToHistory(commandText.getText()); GrailsLaunchUtils.launch(JavaCore.create(getSelectedProject()), commandText.getText()); if (!isPinned()) dispose(); } return; } if (e.doit && e.keyCode == SWT.ARROW_DOWN || e.keyCode == SWT.ARROW_UP) { e.doit = false; commandHistoryPopup(); return; } } }); refreshContentAssist(); return commandText; } /** * See STS-988: warn user when they are attempting to create plugin or app from the GrailsCommandPrompt. * => Should use a 'new' wizard instead. */ private boolean explainNewWizardToUser() { String command = commandText.getText(); if (command.contains("create-plugin")) { openInformationMessage("Nested projects are not supported in Eclipse", "The Grails Command prompt tool is only intended to run commands in the context of an existing " + "Grails plugin or app project.\n\n" + "To execute the 'create-plugin' command, please use the 'new Grails Plugin Wizard' accessible from the 'New' menu"); return true; } else if (command.contains("create-app")) { openInformationMessage("Nested projects are not supported in Eclipse", "The Grails Command prompt tool is intended to run commands in the context of an existing " + "Grails plugin or app project.\n\n" + "To execute the 'create-app' command, please use the 'new Grails Project Wizard' accessible from the 'New' menu"); return true; } return false; } private void openInformationMessage(String title, String message) { Shell shell = dialogShell; if (shell.isDisposed()) shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); isDeactivateListenerActive = false; try { MessageDialog.openInformation(shell, title, message); } finally { isDeactivateListenerActive = true; } } private boolean isPinned() { return isPinned; } private void setPinned(boolean pinit) { this.isPinned = pinit; refreshStatusField(); getDialogSettings().put(STORE_PINNED_STATE, pinit); // Future dialogs start in same pinned state } /** * Static inner class which sets the layout for the inplace view. Without this, the inplace view will not be * populated. * * @see org.eclipse.jdt.internal.ui.text.AbstractInformationControl */ private static class BorderFillLayout extends Layout { /** The border widths. */ final int fBorderSize; /** * Creates a fill layout with a border. */ public BorderFillLayout(int borderSize) { if (borderSize < 0) throw new IllegalArgumentException(); fBorderSize = borderSize; } /** * Returns the border size. */ @SuppressWarnings("unused") public int getBorderSize() { return fBorderSize; } @Override protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { Control[] children = composite.getChildren(); Point minSize = new Point(0, 0); if (children != null) { for (Control element : children) { Point size = element.computeSize(wHint, hHint, flushCache); minSize.x = Math.max(minSize.x, size.x); minSize.y = Math.max(minSize.y, size.y); } } minSize.x += fBorderSize * 2 + 3; minSize.y += fBorderSize * 2; return minSize; } @Override protected void layout(Composite composite, boolean flushCache) { Control[] children = composite.getChildren(); Point minSize = new Point(composite.getClientArea().width, composite.getClientArea().height); if (children != null) { for (Control child : children) { child.setSize(minSize.x - fBorderSize * 2, minSize.y - fBorderSize * 2); child.setLocation(fBorderSize, fBorderSize); } } } } // ---------- shuts down the dialog --------------- /** * Close the dialog */ public void close() { storeBounds(); // storeCommandHistory(); toolBar = null; viewMenuManager = null; } public void dispose() { instance = null; commandText = null; if (dialogShell != null) { if (!dialogShell.isDisposed()) dialogShell.dispose(); dialogShell = null; parentShell = null; composite = null; } } // ------------------ moving actions -------------------------- /** * Move action for the dialog. */ private class MoveAction extends Action { MoveAction() { super("&Move", IAction.AS_PUSH_BUTTON); } @Override public void run() { performTrackerAction(SWT.NONE); isDeactivateListenerActive = true; } } /** * Remember bounds action for the dialog. */ private class RememberBoundsAction extends Action { RememberBoundsAction() { super("Remember Size and &Location", IAction.AS_CHECK_BOX); setChecked(!getDialogSettings().getBoolean(STORE_DISABLE_RESTORE_LOCATION)); } @Override public void run() { IDialogSettings settings = getDialogSettings(); boolean newValue = !isChecked(); // store new value settings.put(STORE_DISABLE_RESTORE_LOCATION, newValue); settings.put(STORE_DISABLE_RESTORE_SIZE, newValue); isDeactivateListenerActive = true; } } /** * Resize action for the dialog. */ private class ResizeAction extends Action { ResizeAction() { super("&Resize", IAction.AS_PUSH_BUTTON); } @Override public void run() { performTrackerAction(SWT.RESIZE); isDeactivateListenerActive = true; } } /** * Resize action for the dialog. */ private class PinDownAction extends Action { PinDownAction() { super("&Pin", IAction.AS_CHECK_BOX); setChecked(isPinned()); } @Override public void run() { setPinned(isChecked()); } } /** * Perform the requested tracker action (resize or move). * * @param style The track style (resize or move). */ private void performTrackerAction(int style) { Tracker tracker = new Tracker(dialogShell.getDisplay(), style); tracker.setStippled(true); Rectangle[] r = new Rectangle[] { dialogShell.getBounds() }; tracker.setRectangles(r); isDeactivateListenerActive = false; if (tracker.open()) { dialogShell.setBounds(tracker.getRectangles()[0]); isDeactivateListenerActive = true; } } // -------------------- all to do with the contents of the view ------------------ private void createContents() { } public void setSelectedProject(IProject project) { if (project==null) return; // ignore null setting! selectedProject = project; refreshProjects(); refreshContentAssist(); } /** * @param project */ private void refreshContentAssist() { if (isValidProject(selectedProject) && commandText!=null) { if (contentProposer==null) { contentProposer = GrailsCompletionUtils.addTypeFieldAssistToText(this.commandText, selectedProject); if (contentProposer!=null) { contentProposer.addContentProposalListener(getProposalListener()); } } else { GrailsProposalProvider gpp = (GrailsProposalProvider) contentProposer.getContentProposalProvider(); gpp.setProject(selectedProject); } } } /** * This listener is needed so that we can avoid closing the dialog when it gets deactivated during * the selection of a choice from the content proposal popup. Without this listener, when user * clicks in the popup this will deactivate the in place dialog, closing it, unless it is pinned. */ private IContentProposalListener2 getProposalListener() { if (proposalListener==null) { proposalListener = new IContentProposalListener2() { public void proposalPopupOpened(ContentProposalAdapter adapter) { isDeactivateListenerActive = false; } public void proposalPopupClosed(ContentProposalAdapter adapter) { isDeactivateListenerActive = true; } }; } return proposalListener; } /** * Refresh the list of grails projects and put them into the projectList widget. */ private void refreshProjects() { if (projectList!=null) { String[] projectNames = getGrailsProjectNames(); projectList.setItems(projectNames); if (getSelectedProject()==null && projectNames.length>0) { setSelectedProject(projectNames[0]); } if (getSelectedProject()!=null) { projectList.setText(selectedProject.getName()); } projectList.getParent().layout(); refreshStatusField(); } } private void setSelectedProject(String projectName) { setSelectedProject(ResourcesPlugin.getWorkspace().getRoot().getProject(projectName)); } public boolean isOpen() { return dialogShell != null; } public void setupFromHistory(Entry entry) { setSelectedProject(entry.getProject()); String command = entry.getCommand(); setCommand(command); commandText.setSelection(0, command.length()); } private void setCommand(String command) { commandText.setText(command); commandText.setSelection(command.length()); } public static void closeIfNotPinned() { if (instance!=null && !instance.isPinned()) instance.dispose(); } }