/******************************************************************************* * Copyright (c) 2008, 2011 Thomas Holland (thomas@innot.de) and others. * 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: * Thomas Holland - initial API and implementation *******************************************************************************/ package de.innot.avreclipse.debug.gdbservers; import java.io.File; import org.eclipse.core.runtime.Assert; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.ui.StringVariableSelectionDialog; import org.eclipse.swt.SWT; 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.events.VerifyEvent; import org.eclipse.swt.events.VerifyListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Layout; import org.eclipse.swt.widgets.Text; import de.innot.avreclipse.debug.ui.IGDBServerSettingsContext; import de.innot.avreclipse.debug.ui.IGDBServerSettingsPage; import de.innot.avreclipse.debug.ui.IGdbJtagMapper; /** * Base implementation of the {@link IGDBServerSettingsPage} interface. * <p> * GDBServer providers can extends this class, which contains default implementations for some * methods as well as helper methods common to most subclasses. * </p> * * @author Thomas Holland * @since 2.4 * */ public abstract class AbstractGDBServerSettingsPage implements IGDBServerSettingsPage, IGdbJtagMapper { /** The ID of the gdbserver for which this page manages the settings. */ private String fGDBServerID; /** The parent context. */ private IGDBServerSettingsContext fParent; /** The descriptive name of the launch configuration. */ private String fDescription; /* * (non-Javadoc) * @see de.innot.avreclipse.debug.ui.IGDBServerSettingsPage#getName() */ public final String getDescription() { return fDescription; } /* * (non-Javadoc) * @see de.innot.avreclipse.debug.ui.IGDBServerSettingsPage#setDescription(java.lang.String) */ public final void setDescription(String description) { fDescription = description; } /** * Gets the name of the gdbserver command for this page for the user interface. * * @return */ protected abstract String getGDBServerName(); /* * (non-Javadoc) * @see de.innot.avreclipse.debug.ui.IGDBServerSettingsPage#getGDBServerID() */ public final String getGDBServerID() { return fGDBServerID; } /* * (non-Javadoc) * @see de.innot.avreclipse.debug.ui.IGDBServerSettingsPage#setGDBServerID(java.lang.String) */ public final void setGDBServerID(String gdbserverid) { fGDBServerID = gdbserverid; } /* * (non-Javadoc) * @see de.innot.avreclipse.debug.ui.IGDBServerSettingsPage#setDefaults(org.eclipse.debug.core. * ILaunchConfigurationWorkingCopy) */ public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { // TODO Auto-generated method stub } /* * (non-Javadoc) * @see * de.innot.avreclipse.debug.ui.IGDBServerSettingsPage#setContext(de.innot.avreclipse.debug. * ui.IGDBServerSettingsContext) */ public void setContext(IGDBServerSettingsContext parent) { fParent = parent; } /* * (non-Javadoc) * @see * de.innot.avreclipse.debug.ui.IGDBServerSettingsPage#initializeFrom(org.eclipse.debug.core * .ILaunchConfiguration) */ public abstract void initializeFrom(ILaunchConfiguration configuration); /* * (non-Javadoc) * @see * de.innot.avreclipse.debug.ui.IGDBServerSettingsPage#createSettingsPage(org.eclipse.swt.widgets * .Composite) */ public abstract void createSettingsPage(Composite parent); /* * (non-Javadoc) * @see de.innot.avreclipse.debug.ui.IGDBServerSettingsPage#performApply(org.eclipse.debug.core. * ILaunchConfigurationWorkingCopy) */ public abstract void performApply(ILaunchConfigurationWorkingCopy configuration); /* * (non-Javadoc) * @see de.innot.avreclipse.debug.ui.IGDBServerSettingsPage#isValid(org.eclipse.debug.core. * ILaunchConfiguration) */ abstract public boolean isValid(ILaunchConfiguration configuration); /* * (non-Javadoc) * @see de.innot.avreclipse.debug.ui.IGDBServerSettingsPage#dispose() */ public void dispose() { // Nothing to dispose. } /** * */ protected void updatePage() { fParent.updateDialog(); } /** * Add a standard command name selection group to the parent composite. * <p> * The created <code>Text</code> control has a default ModifyListener that will call * {@link #updatePage()} for each modification. * </p> * <p> * A warning is shown in the user interface if the field is empty. However it is up to the * subclass to set the valid flag in the {@link #isValid(ILaunchConfiguration)} method. * </p> * * @param parent * The parent composite to insert into. Must have an GridLayout with at least four * columns * * @return the <code>Text</code> control. */ protected Text createCommandField(Composite parent) { int columns = getColumns(parent, 4); Label label = new Label(parent, SWT.NONE); label.setText(getGDBServerName() + " command: "); label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false, 1, 1)); final Text text = new Text(parent, SWT.SINGLE | SWT.BORDER); text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, columns - 3, 1)); text.addModifyListener(new ModifyListener() { /* * (non-Javadoc) * @see * org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent) */ public void modifyText(ModifyEvent e) { // the text field may not be empty. if (e.widget instanceof Text) { String command = ((Text) e.widget).getText(); if (command.length() == 0) { setErrorMessage(getGDBServerName() + " command may not be empty."); } else { setErrorMessage(null); } // TODO: could add a check if the selected command name is executable updatePage(); } } }); Button button = new Button(parent, SWT.NONE); button.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); button.setText("Browse..."); button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { browseButtonAction("Select " + getGDBServerName() + " executable", text); } }); button = new Button(parent, SWT.NONE); button.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); button.setText("Variables..."); button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { variablesButtonAction(text); } }); return text; } /** * Create an standard "Hostname or IP address" text control with an appropriate label. * <p> * The created <code>Text</code> control has a default ModifyListener that will call * {@link #updatePage()} for each modification. * </p> * <p> * A warning is shown in the user interface if the field is empty. However it is up to the * subclass to set the valid flag in the {@link #isValid(ILaunchConfiguration)} method. * </p> * * @param parent * The parent composite to insert into. Must have an GridLayout with at least two * columns * * @return the <code>Text</code> control. */ protected Text createHostnameField(Composite parent) { int columns = getColumns(parent, 2); Label label = new Label(parent, SWT.NONE); label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); label.setText("Remote hostname or ip address:"); Text text = new Text(parent, SWT.SINGLE | SWT.BORDER); text.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, columns - 1, 1)); text.addModifyListener(new ModifyListener() { /* * (non-Javadoc) * @see * org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent) */ public void modifyText(ModifyEvent e) { // the text field may not be empty. if (e.widget instanceof Text) { String command = ((Text) e.widget).getText(); if (command.length() == 0) { setErrorMessage("Hostname may not be empty."); } else { setErrorMessage(null); } // TODO: could add a check if the selected hostname is reachable updatePage(); } } }); return text; } /** * Create an standard "Port number" text control with an appropriate label. * <p> * The created <code>Text</code> control has a default ModifyListener that will call * {@link #updatePage()} for each modification. Also a VerifyListener is added that will accept * only digits. * </p> * <p> * A warning is shown in the user interface if the field is empty or its value out of range (0 * to 65535). However it is up to the subclass to set the valid flag in the * {@link #isValid(ILaunchConfiguration)} method. * </p> * * @param parent * The parent composite to insert into. Must have an GridLayout with at least two * columns * * @return the <code>Text</code> control. */ protected Text createPortField(Composite parent) { int columns = getColumns(parent, 2); Label label = new Label(parent, SWT.NONE); label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); label.setText("Port number:"); Text text = new Text(parent, SWT.SINGLE | SWT.BORDER); GridData gd = new GridData(SWT.BEGINNING, SWT.CENTER, true, false); gd.widthHint = 80; text.setLayoutData(gd); text.setTextLimit(5); text.addVerifyListener(new VerifyListener() { // allow only digits public void verifyText(VerifyEvent ve) { String text = ve.text; if (!text.matches("[0-9]*")) { ve.doit = false; } } }); text.addModifyListener(new ModifyListener() { // Check that the port number is between 0 and 65.535 public void modifyText(ModifyEvent e) { String porttext = ((Text) e.widget).getText(); if (porttext.length() == 0) { setErrorMessage("Port number must not be empty"); } else { int newport = Integer.decode(porttext.trim()); if (newport < 0 || newport > 0xffff) { setErrorMessage("Invalid port number. Must be between 0 and 65535"); } else { setErrorMessage(null); } } updatePage(); } }); if (columns > 2) { // fill remaining columns with a dummy label label = new Label(parent, SWT.NONE); label.setLayoutData(new GridData(SWT.NONE, SWT.NONE, false, false, columns - 2, 1)); } return text; } /** * Create an standard "Other options" text control with an appropriate label. * <p> * The created <code>Text</code> control has a default ModifyListener that will call * {@link #updatePage()} for each modification. * </p> * * @param parent * The parent composite to insert into. Must have an GridLayout with at least two * columns * * @return the <code>Text</code> control. */ protected Text createOtherOptionsField(Composite parent) { int columns = getColumns(parent, 2); Label label = new Label(parent, SWT.NONE); label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); label.setText("Other " + getGDBServerName() + " options:"); Text text = new Text(parent, SWT.BORDER | SWT.SINGLE); text.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, columns - 1, 1)); text.setToolTipText("Add other " + getGDBServerName() + " command line options"); text.addModifyListener(new ModifyListener() { /* * (non-Javadoc) * @see * org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent) */ public void modifyText(ModifyEvent e) { updatePage(); } }); return text; } /** * Set the error message of the parent dialog. * <p> * The error message is not shown immediately. The {@link #updatePage()} method needs to be * called afterward to show the error message to the user. * </p> * * @param message * error message or <code>null</code> to clear the message. */ protected void setErrorMessage(String message) { fParent.setErrorMessage(getGDBServerID(), message); } /** * Open a file dialog with the given title and for the given <code>Text</code> Control. * <p> * If the user selects a new file then the full path of the file will written to the * <code>Text</code> control value. If the user cancels the dialog, then the <code>Text</code> * control is untouched. * </p> * * @param title * Dialog title string * @param text * The <code>Text</code> control to be modified */ private void browseButtonAction(String title, Text text) { FileDialog dialog = new FileDialog(getParentControl().getShell(), SWT.NONE); dialog.setText(title); // Check if the previously entered commandname contains a path segment File commandfile = new File(text.getText().trim()); String commandfilepath = commandfile.getParent(); if (commandfilepath != null) { dialog.setFilterPath(commandfilepath); } String newcommand = dialog.open(); if (newcommand != null) text.setText(newcommand); } /** * Open a variables browser dialog and append the selected variable to the content of the given * <code>Text</code> control. * * @param text * The <code>Text</code> control to be modified */ private void variablesButtonAction(Text text) { StringVariableSelectionDialog dialog = new StringVariableSelectionDialog(getParentControl() .getShell()); dialog.open(); text.append(dialog.getVariableExpression()); } /** * Convenience method to get the control of the parent dialog. * <p> * The returned control should only be used to get the parent <code>Shell</code> or * <code>Display</code>. * </p> * * @return <code>Control</code> of the parent dialog */ private Control getParentControl() { return fParent.getControl(); } /** * Checks that the given <code>Composite</code> has a <code>GridLayout</code> with at least * <code>minColumns</code> columns. * <p> * If the parent layout is not an <code>GridLayout</code> or has less than the required number * of columns, then an unchecked Exception is thrown to indicate a bug in the plugin. * </p> * * @param composite * Composite to check * @param minColumns * minimum required number of columns. * @return the actual number of columns */ private int getColumns(Composite composite, int minColumns) { Layout parentlayout = composite.getLayout(); Assert.isTrue(parentlayout instanceof GridLayout); int columns = ((GridLayout) parentlayout).numColumns; Assert.isTrue(columns >= minColumns); return columns; } }