package org.radrails.rails.internal.ui.generators; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.ui.IDebugUIConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; 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.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.ui.part.Page; import org.radrails.rails.core.RailsLog; import org.radrails.rails.internal.core.RailsPlugin; import org.radrails.rails.internal.generators.Generator; import org.radrails.rails.internal.generators.GeneratorLocatorsManager; import org.radrails.rails.internal.ui.console.IRailsShellConstants; import org.radrails.rails.ui.IRailsUIConstants; import org.radrails.rails.ui.RailsUIPlugin; import org.rubypeople.rdt.core.util.Util; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.internal.ui.RubyExplorerTracker.IRubyProjectListener; import org.rubypeople.rdt.internal.ui.text.RubyColorManager; import org.rubypeople.rdt.launching.IRubyLaunchConfigurationConstants; import org.rubypeople.rdt.launching.RubyRuntime; import com.aptana.ide.core.ui.SWTUtils; /** * GeneratorsPage */ public class GeneratorsPage extends Page implements IRubyProjectListener { private static final int MAX_INPUT_HISTORY_SIZE = 25; private static final String PROJECT = "Current Rails Project: "; private Label projectNameLabel; private Composite genComp; private Label genLabel; private Combo genCombo; private Button createButton; private Button destroyButton; private Button pretendButton; private Button skipButton; private Button forceButton; private Button quietButton; private Button backtraceButton; private Label helpButton; private Button svnButton; private Composite paramComp; private Label paramLabel; private Combo paramText; private Composite generateView; private RubyColorManager fColorManager; private IProject project = null; private Cursor hand; private boolean expandedLabel; private static Map<String, String> helps = new HashMap<String, String>(); /** * @see org.eclipse.ui.part.Page#createControl(org.eclipse.swt.widgets.Composite) */ public void createControl(Composite parent) { generateView = new Composite(parent, SWT.NONE); generateView.setLayout(new GridLayout(3, false)); generateView.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false)); fColorManager = new RubyColorManager(true); createGeneratorControls(generateView); RubyPlugin.getDefault().getProjectTracker().addProjectListener(this); IProject project = RubyPlugin.getDefault().getProjectTracker().getSelectedByNatureID( IRailsUIConstants.RAILS_PROJECT_NATURE); Set<IProject> projects = RailsPlugin.getRailsProjects(); if (project != null) { this.projectSelected(project); } else if (projects != null && projects.size() > 0) { this.projectSelected(projects.iterator().next()); } } /** * The <code>Page</code> implementation of this <code>IPage</code> method disposes of this page's control (if it has * one and it has not already been disposed). Subclasses may extend. */ public void dispose() { fColorManager.dispose(); RubyPlugin.getDefault().getProjectTracker().removeProjectListener(this); super.dispose(); } /** * Helper method to create the necessary widgets. * * @param parent * the parent composite */ private void createGeneratorControls(Composite parent) { hand = new Cursor(parent.getDisplay(), SWT.CURSOR_HAND); Composite comp = createMainArea(parent); createAdvancedSection(comp); } private Composite createMainArea(Composite comp) { projectNameLabel = new Label(comp, SWT.LEFT); projectNameLabel.setForeground(fColorManager.getColor(new RGB(128, 128, 128))); projectNameLabel.setText(PROJECT); projectNameLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false, 3, 1)); // Grid for generator selection: label, combo, help button genComp = new Composite(comp, SWT.NONE); genComp.setLayout(new GridLayout(3, false)); genComp.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false)); genLabel = new Label(genComp, SWT.LEFT); genLabel.setText("Generator:"); genCombo = new Combo(genComp, SWT.DROP_DOWN); GridData genComboData = new GridData(); genComboData.widthHint = 200; genCombo.setLayoutData(genComboData); genCombo.setVisibleItemCount(20); helpButton = new Label(genComp, SWT.PUSH); helpButton.setToolTipText("Help"); helpButton.setImage(RailsUIPlugin.getImage("icons/help.png")); // Run the command when the button is clicked helpButton.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { getHelp(getSelectedGenerator(), getProject()); } }); // Create parameters grid: label, text field, 'run' button paramComp = new Composite(comp, SWT.NONE); paramComp.setLayout(new GridLayout(3, false)); paramComp.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false)); paramLabel = new Label(paramComp, SWT.LEFT); paramLabel.setText("Parameters:"); paramText = new Combo(paramComp, SWT.BORDER); paramText.setLayoutData(genComboData); paramText.addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) { // Do nothing } public void keyReleased(KeyEvent e) { // Take action if Enter was pressed if (e.character == SWT.CR) { executeCommand(); } } }); // Create the Go button Button genButton = new Button(comp, SWT.PUSH); genButton.setToolTipText("Run Generator"); genButton.setImage(RailsUIPlugin.getImage("icons/nav_go.gif")); // Run the command when the button is clicked genButton.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { executeCommand(); } public void widgetDefaultSelected(SelectionEvent e) { // Do nothing } }); Label hint = new Label(comp, SWT.WRAP); hint.setText("If dropdown is empty, please hit yellow arrow 'refresh' icon."); hint.setForeground(fColorManager.getColor(new RGB(128, 128, 128))); hint.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, true, false, 3, 1)); return comp; } private Composite createAdvancedSection(final Composite parent) { final Composite advanced = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(2, false); layout.marginHeight = 0; layout.marginWidth = 0; advanced.setLayout(layout); GridData advancedData = new GridData(SWT.FILL, SWT.FILL, true, false); advancedData.horizontalSpan = 3; advanced.setLayoutData(advancedData); final Font boldFont = new Font(advanced.getDisplay(), SWTUtils.boldFont(advanced.getFont())); advanced.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { if (hand != null && !hand.isDisposed()) { hand.dispose(); } if (boldFont != null && !boldFont.isDisposed()) { boldFont.dispose(); } } }); final Label advancedIcon = new Label(advanced, SWT.LEFT); advancedIcon.setImage(RailsUIPlugin.getImage("icons/maximize.png")); //$NON-NLS-1$ advancedIcon.setCursor(hand); advancedIcon.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); Label advancedLabel = new Label(advanced, SWT.LEFT); advancedLabel.setText("Advanced Options"); advancedLabel.setCursor(hand); advancedLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); advancedLabel.setFont(boldFont); final Composite advancedOptions = new Composite(advanced, SWT.NONE); layout = new GridLayout(2, false); layout.marginLeft = 15; advancedOptions.setLayout(layout); GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); gridData.horizontalSpan = 2; gridData.exclude = true; advancedOptions.setLayoutData(gridData); advancedOptions.setVisible(false); MouseAdapter expander = new MouseAdapter() { public void mouseDown(MouseEvent e) { if (advancedOptions.isVisible()) { advancedOptions.setVisible(false); advancedIcon.setImage(RailsUIPlugin.getImage("icons/maximize.png")); //$NON-NLS-1$ ((GridData) advancedOptions.getLayoutData()).exclude = true; } else { advancedOptions.setVisible(true); advancedIcon.setImage(RailsUIPlugin.getImage("icons/minimize.png")); //$NON-NLS-1$ ((GridData) advancedOptions.getLayoutData()).exclude = false; } generateView.layout(true, true); } }; advancedIcon.addMouseListener(expander); advancedLabel.addMouseListener(expander); // Create the radio buttons for create/destroy Group modes = new Group(advancedOptions, SWT.NONE); modes.setText("Modes"); modes.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); modes.setLayout(new GridLayout(2, false)); createButton = new Button(modes, SWT.RADIO); createButton.setText("Create"); createButton.setSelection(true); destroyButton = new Button(modes, SWT.RADIO); destroyButton.setText("Destroy"); // Create the text field for the options information Group optionsGroup = new Group(advancedOptions, SWT.NULL); optionsGroup.setLayout(new GridLayout(7, false)); optionsGroup.setText("Options"); pretendButton = new Button(optionsGroup, SWT.CHECK); pretendButton.setText("Pretend"); forceButton = new Button(optionsGroup, SWT.CHECK); forceButton.setText("Force"); skipButton = new Button(optionsGroup, SWT.CHECK); skipButton.setText("Skip"); quietButton = new Button(optionsGroup, SWT.CHECK); quietButton.setText("Quiet"); backtraceButton = new Button(optionsGroup, SWT.CHECK); backtraceButton.setText("Backtrace"); svnButton = new Button(optionsGroup, SWT.CHECK); svnButton.setText("Use SVN"); return advanced; } /** * Runs the command specified in the view. */ private void executeCommand() { String script = getGenerateScript(); // Check the options boxes String options = ""; if (pretendButton.getSelection()) { options += "p"; } if (forceButton.getSelection()) { options += "f"; } if (skipButton.getSelection()) { options += "s"; } if (quietButton.getSelection()) { options += "q"; } if (backtraceButton.getSelection()) { options += "t"; } if (svnButton.getSelection()) { options += "c"; } if (options.length() > 0) { options = " -" + options; } // Prepare the full command String args = genCombo.getText() + " " + paramText.getText() + options; if (project != null) { if (project.exists() && project.isOpen()) { // Update the input field history LinkedList<String> items = new LinkedList<String>(Arrays.asList(paramText.getItems())); items.addFirst(paramText.getText()); if (items.size() > MAX_INPUT_HISTORY_SIZE) { items.removeLast(); } paramText.setItems(items.toArray(new String[0])); launchTerminal(script.substring(script.indexOf("/")) + genCombo.getText(), script, args); } else { MessageDialog .openError( getControl().getShell(), "Selected project closed/doesn't exist", "The currently selected Rails project is either closed or has been deleted. Please open it or select another project inside the Ruby Explorer"); } } else { MessageDialog.openError(getControl().getShell(), "No project selected", "Unable to get the currently selected Rails project inside the Ruby Explorer"); } } private String getGenerateScript() { if (project == null) { return "script/generate"; } IPath path = RailsPlugin.findRailsRoot(project); path = path.append("script"); if (destroyButton.getSelection()) { return path.append("destroy").toPortableString(); } return path.append("generate").toPortableString(); } private void launchTerminal(String title, final String file, final String args) { // launch the generator command try { ILaunchConfigurationWorkingCopy wc = RubyRuntime.createBasicLaunch(file, args, project); wc.setAttribute(IDebugUIConstants.ATTR_PRIVATE, true); wc.setAttribute(IDebugUIConstants.ATTR_LAUNCH_IN_BACKGROUND, false); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_USE_TERMINAL, IRailsShellConstants.TERMINAL_ID); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_TERMINAL_COMMAND, "script/generate " + args); final ILaunchConfiguration config = wc.doSave(); final String command = file + " " + args; Job job = new Job(command) { @Override protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask(command, 2); monitor.worked(1); ILaunch launch = config.launch(ILaunchManager.RUN_MODE, null); // spin the progress until the launch is complete while (!launch.isTerminated()) { try { Thread.sleep(100); } catch (InterruptedException e) { // ignore } } monitor.worked(1); monitor.done(); } catch (CoreException e) { RailsLog.logError("Error running generator", e); } return Status.OK_STATUS; } }; job.schedule(); } catch (CoreException e) { RailsLog.logError("Error running generator", e); } } /** * Reload the list of generators in the combo box from the generators currently discovered in the * GeneratorsLocatorManager without doing any reparsing */ private void reloadGeneratorsPullDown() { genCombo.removeAll(); List<Generator> generators = GeneratorLocatorsManager.getInstance().getAllGenerators(project); for (Generator generator : generators) { genCombo.add(generator.getName()); } if (genCombo.getItemCount() > 0) genCombo.setText(genCombo.getItem(0)); genCombo.update(); } /** * Refreshes the generators */ public void refreshGenerators() { reloadGeneratorsPullDown(); } /** * @see org.eclipse.ui.part.Page#getControl() */ public Control getControl() { return generateView; } /** * @see org.eclipse.ui.part.Page#setFocus() */ public void setFocus() { genCombo.setFocus(); } boolean pulldownEmpty() { return genCombo != null && genCombo.getItemCount() == 0; } /** * Sets control enablement * * @param enabled */ public void setEnabled(boolean enabled) { genCombo.setEnabled(enabled); paramText.setEnabled(enabled); createButton.setEnabled(enabled); destroyButton.setEnabled(enabled); pretendButton.setEnabled(enabled); skipButton.setEnabled(enabled); forceButton.setEnabled(enabled); quietButton.setEnabled(enabled); backtraceButton.setEnabled(enabled); helpButton.setEnabled(enabled); svnButton.setEnabled(enabled); } /** * @see org.rubypeople.rdt.internal.ui.RubyExplorerTracker.IRubyProjectListener#projectSelected(org.eclipse.core.resources.IProject) */ public void projectSelected(IProject project) { if (projectNameLabel.isDisposed()) { return; } if (RailsPlugin.hasRailsNature(project) && project.exists() && project.isOpen()) { projectNameLabel.setText(PROJECT + project.getName()); this.project = project; setEnabled(true); } else if (project == null || !project.exists()) { projectNameLabel.setText(PROJECT + "<Select a Rails project>"); if (!expandedLabel) // Only once should we expand this label { Point p = projectNameLabel.getSize(); p.x += 100; projectNameLabel.setSize(p); projectNameLabel.redraw(); expandedLabel = true; } setEnabled(false); this.project = null; } refreshGenerators(); } String getSelectedGenerator() { return genCombo.getText(); } IProject getProject() { return this.project; } /** * Gets the help * * @param generatorName * @param project */ private static void getHelp(final String generatorName, final IProject project) { final String command = generatorName + " --help"; final String version = RailsPlugin.getRailsVersion(project); final String uniqueName = generatorName + "_" + version; Job job = new Job("script/generate " + command) { @Override protected IStatus run(IProgressMonitor monitor) { try { String help = helps.get(uniqueName); if (help == null) { File file = RailsUIPlugin.getInstance().getStateLocation().append(uniqueName + "_help.txt") .toFile(); String output = null; if (file.exists()) { try { output = new String(Util.getFileCharContent(file, null)); } catch (IOException e) { // ignore } } if (output == null) { IProject aProject = project; if (aProject == null) { if (RailsPlugin.getRailsProjects().size() == 0) { return new Status(Status.WARNING, RailsUIPlugin.getPluginIdentifier(), -1, "Please select a Rails project in the Ruby Explorer", null); } aProject = RailsPlugin.getRailsProjects().iterator().next(); } ILaunchConfigurationWorkingCopy wc = RubyRuntime.createBasicLaunch("script/generate", command, aProject); output = RubyRuntime.launchInBackgroundAndRead(wc.doSave(), file); } helps.put(uniqueName, output); help = output; } final String toShow = help; Display.getDefault().asyncExec(new Runnable() { public void run() { MessageDialog.openInformation(Display.getDefault().getActiveShell(), "Generators Help", toShow); } }); } catch (IllegalStateException e) { return new Status(Status.ERROR, RailsUIPlugin.getPluginIdentifier(), -1, e.getMessage(), e); } catch (CoreException e) { return e.getStatus(); } return Status.OK_STATUS; } }; job.setPriority(Job.INTERACTIVE); job.schedule(); } }