/*******************************************************************************
* 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();
}
}