/******************************************************************************* * 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.ui.propertypages; import org.eclipse.cdt.core.settings.model.ICConfigurationDescription; import org.eclipse.cdt.core.settings.model.ICResourceDescription; import org.eclipse.cdt.managedbuilder.core.IConfiguration; import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager; import org.eclipse.cdt.managedbuilder.ui.properties.AbstractCBuildPropertyTab; import org.eclipse.cdt.ui.newui.ICPropertyTab; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; 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.Shell; import org.eclipse.swt.widgets.Text; import org.osgi.service.prefs.BackingStoreException; import de.innot.avreclipse.AVRPlugin; import de.innot.avreclipse.core.properties.AVRProjectProperties; import de.innot.avreclipse.core.properties.ProjectPropertyManager; import de.innot.avreclipse.mbs.scannerconfig.ScannerConfigUtil; /** * Abstract parent class for all AVR Property tabs. * <p> * This class is an interface between <code>ICPropertyTab</code>, which works on * ICResourceDescriptions, and the {@link AVRProjectProperties} where all AVR specific settings are * stored, either per project or - at user discretion - per build configuration. * </p> * <p> * {@link #performApply(AVRProjectProperties)} and {@link #updateData(AVRProjectProperties)} are * almost identical to the methods in <code>ICPropertyTab</code>, while * <code>performDefaults()</code> is replaced by {@link #performCopy(AVRProjectProperties)}, which * enables this class to send different default properties to the implementor. * </p> * * @author Thomas Holland * @since 2.2 * */ public abstract class AbstractAVRPropertyTab extends AbstractCBuildPropertyTab { /** * special Tab message to indicate that the given Properties should be copied. This is very * similar to {@link ICPropertyTab#DEFAULTS} message. */ public final static int COPY = 200; /** * Flag to indicate that something affecting the build-in paths or symbols has changed. */ private boolean isDiscoveryRequired = false; /** * Action for an Apply event. * <p> * The implementation must copy the values relevant to the current page to the given destination * properties. * </p> * The given properties are fresh, unmodified props from the properties storage. They will be * saved once this method returns. </p> * * @param dstprops * Destination properties. */ protected abstract void performApply(AVRProjectProperties dstprops); /** * Action for a Copy event. * <p> * The implementation must copy the values relevant to the current page from the given source * properties. * </p> * <p> * This method is called with either the default properties or with the project properties, * depending on whether the "Defaults" or the "Copy from Project" Button has been clicked by the * user. * </p> * <p> * It is up to the implementor to call {@link #updateData(AVRProjectProperties)} to update the * representation after the copy has taken place. * </p> * * @param srcprops * Source properties. */ protected abstract void performCopy(AVRProjectProperties srcprops); /** * Update the tab to the values of the given properties. * <p> * This method is called whenever a different build configuration is selected by the user or the * "per Config Settings" flag has changed. The props parameter has the properties for the * configuration / project. * </p> * <p> * Implementing classes should update their controls to the values of the properties and can * must make all future modifications directly to the given properties. * </p> * * @param props * <code>AVRProjectProperties</code> the tab must work with. */ protected abstract void updateData(AVRProjectProperties props); /** * Action for a defaults event. * <p> * This is called in addition to {@link #performCopy(AVRProjectProperties)}, so that subclasses * can override to add any special handling for the defaults case, which does not apply to the * copy event. E.g. the main page overrides this to reset the list of available programmers. * </p> * * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#performDefaults() */ @Override protected void performDefaults() { // Subclasse }; /* * (non-Javadoc) * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#handleTabEvent(int, java.lang.Object) */ @Override public void handleTabEvent(int kind, Object data) { // Override handleTabEvent to handle the COPY and DEFAULTS messages. // // The DEFAULTS message is intercepted here, because the handling of // defaults is different in the AVR tabs compared to the standard // CPropertyTabs. The default properties are not writable (at least not // saveable), so we cannot pass the default properties directly to the // updateData() method. // Instead the default properties are passed to the new performCopy(), // which is implemented in subclasses and in which they copy their // relevant properties from the given default/project properties. // // The same method is used to reset a tab to the project settings. If // the "Copy Project Settings" Button is pressed, the parent // AbstractAVRPage will get the Project properties and generate a COPY // message with the project properties attached. // // Both handlers call updateData(getResDesc()) first, because // updateData() is used to pass a valid AVRProjectProperties to the // subclass and might not have been called when the handler is executed. switch (kind) { case COPY: updateData(getResDesc()); AVRProjectProperties projectprops = (AVRProjectProperties) data; performCopy(projectprops); break; case ICPropertyTab.DEFAULTS: updateData(getResDesc()); AVRProjectProperties defaultprops = ProjectPropertyManager.getDefaultProperties(); performDefaults(); performCopy(defaultprops); break; default: // All other messages (APPLY, DISPOSE etc.) are handled by the // superclass. super.handleTabEvent(kind, data); } } /* * (non-Javadoc) * @see * org.eclipse.cdt.ui.newui.AbstractCPropertyTab#performApply(org.eclipse.cdt.core.settings. * model.ICResourceDescription, org.eclipse.cdt.core.settings.model.ICResourceDescription) */ @Override protected void performApply(ICResourceDescription src, ICResourceDescription dst) { // Apply should only save the values of this Tab. // To do this, we get a fresh Property Element, which is filled with the // values from the property storage. // Then this new Element is passed on to the subclass, which // modifies only its own values. // Finally the Element is saved again to the property storage. AVRProjectProperties freshprops; freshprops = AVRPropertyPageManager.getConfigPropertiesNoCache(src); performApply(freshprops); try { freshprops.save(); } catch (BackingStoreException e) { IStatus status = new Status(IStatus.ERROR, AVRPlugin.PLUGIN_ID, "Could not write to the preferences.", e); ErrorDialog.openError(super.usercomp.getShell(), "AVR Properties Error", null, status); e.printStackTrace(); } if (isDiscoveryRequired) { // Now we need to invalidate all discovered paths and symbols, because they still // contain stale data. clearDiscovery(); isDiscoveryRequired = true; } } /* * (non-Javadoc) * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#performOK() */ @Override protected void performOK() { if (isDiscoveryRequired) { // Now we need to invalidate all discovered paths and symbols, because they still // contain stale data. clearDiscovery(); runDiscovery(); isDiscoveryRequired = false; } } /* * (non-Javadoc) * @see * org.eclipse.cdt.ui.newui.AbstractCPropertyTab#updateData(org.eclipse.cdt.core.settings.model * .ICResourceDescription) */ @Override protected void updateData(ICResourceDescription resdesc) { // Translate ICResourceDescription to AVRProjectProperties and pass them // to the subclass. AVRProjectProperties props = AVRPropertyPageManager.getConfigProperties(resdesc); if (props != null) { updateData(props); } } /* * (non-Javadoc) * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#updateButtons() */ @Override protected void updateButtons() { // Why was this method made Abstract by the superclass? // As only few pages actually need this, it could have been declared as // an empty method. // Like we do here, to spare extending classes of implementing this // useless method. }; /** * Sets the rebuild flag for the current configuration or the complete project. * <p> * Passing <code>false</code> is not recommended, as it might prevent necessary rebuilds caused * by changes outside of the AVR property world. * </p> * * @param rebuild * <code>true</code> if a complete rebuild is required. */ protected void setRebuildState(boolean rebuild) { // Check if we have per project or per config setting AbstractAVRPage avrpage = (AbstractAVRPage) page; if (avrpage.isPerConfig()) { // Set the rebuild flag for the current configuration getCfg().setRebuildState(rebuild); } else { // MNL: This doesn't work, rebuild state for project isn't reset by builder // ---- // Set the rebuild flag for the complete project // ManagedBuildManager.getBuildInfo(getCfg().getOwner()).setRebuildState(rebuild); // ---- // Make sure that all configurations are loaded getCfg(); for (ICConfigurationDescription cfgd: page.getCfgsEditable()) { getCfg(cfgd).setRebuildState(rebuild); } } } /** * Set the "Discovery required" flag. * <p> * This method must be called whenever something has changed that requires a re-build of the * built-in paths and symbols. The actual re-build is performed by this class at the appropriate * time (after a performApply() or performOK() event). * </p> */ protected void setDiscoveryRequired() { isDiscoveryRequired = true; } /** * Convenience method to add a separator bar to the composite. * <p> * The parent composite must have a <code>GridLayout</code>. The separator bar will span all * columns of the parent grid layout. * </p> * * @param parent * <code>Composite</code> */ protected void addSeparator(Composite parent) { Label separator = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); Layout parentlayout = parent.getLayout(); if (parentlayout instanceof GridLayout) { int columns = ((GridLayout) parentlayout).numColumns; GridData gridData = new GridData(SWT.FILL, SWT.NONE, true, false, columns, 1); separator.setLayoutData(gridData); } } /** * Returns the value of the "per config" flag for this project. * * @return <code>true</code> if each build configuration has its own properties. */ protected boolean isPerConfig() { if (page instanceof AbstractAVRPage) { AbstractAVRPage avrpage = (AbstractAVRPage) page; return avrpage.isPerConfig(); } return true; } /** * Create and return a "Workplace" browse Button. * <p> * Clicking the Button will open a Workplace file selector Dialog and the result is copied to * the supplied <code>Text</code> Control. * </p> * * @param parent * Parent <code>Composite</code>, which needs to have <code>GridLayout</code> * @param text * Target <code>Text</code> Control * @return <code>Button</code> Control with the created Button. */ protected Button setupWorkplaceButton(Composite parent, final Text text) { Button button = new Button(parent, SWT.PUSH); button.setText(WORKSPACEBUTTON_NAME); GridData gd = new GridData(SWT.CENTER, SWT.NONE, false, false); // make all Buttons the same size gd.minimumWidth = BUTTON_WIDTH; button.setLayoutData(gd); button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { String location = getWorkspaceFileDialog(text.getShell(), EMPTY_STR); if (location != null) { text.setText(location); } } }); return button; } /** * Create and return a "Filesystem" browse Button. * <p> * Clicking the Button will open a file selector Dialog and the result is copied to the supplied * <code>Text</code> Control. * </p> * * @param parent * Parent <code>Composite</code>, which needs to have <code>GridLayout</code> * @param text * Target <code>Text</code> Control * @param exts * <code>String[]</code> with all valid file extensions. Files with other extensions * will be filtered. * @return <code>Button</code> Control with the created Button. */ protected Button setupFilesystemButton(Composite parent, final Text text, String[] exts) { Button button = new Button(parent, SWT.PUSH); button.setText(FILESYSTEMBUTTON_NAME); GridData gd = new GridData(SWT.CENTER, SWT.NONE, false, false); // make all Buttons the same size gd.minimumWidth = BUTTON_WIDTH; button.setLayoutData(gd); button.setData(exts); button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { String[] exts = (String[]) event.widget.getData(); String location = getFileSystemFileDialog(text.getShell(), EMPTY_STR, exts); if (location != null) { text.setText(location); } } }); return button; } /** * Create and return a "Variable" browse Button. * <p> * Clicking the Button will open a variable selector Dialog and the result is inserted into the * supplied <code>Text</code> Control at the current cursor position. * </p> * * @param parent * Parent <code>Composite</code>, which needs to have <code>GridLayout</code> * @param text * Target <code>Text</code> Control * @return <code>Button</code> Control with the created Button. */ protected Button setupVariableButton(Composite parent, final Text text) { Button button = new Button(parent, SWT.PUSH); button.setText(VARIABLESBUTTON_NAME); GridData gd = new GridData(SWT.CENTER, SWT.NONE, false, false); // make all Buttons the same size gd.minimumWidth = BUTTON_WIDTH; button.setLayoutData(gd); button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { String var = getVariableDialog(text.getShell(), getResDesc().getConfiguration()); if (var != null) { text.insert(var); } } }); return button; } /** * Open a FileSystem Dialog and return the selected file as a <code>String</code>. * * @param shell * Shell in which to open the Dialog * @param text * Root file name * @param exts * <code>String[]</code> with all valid file extensions. Files with other extensions * will be filtered. * @return <code>String</code> with the selected filename or <cod>null</code> if the user has * cancelled or an error occured. */ public static String getFileSystemFileDialog(Shell shell, String text, String[] exts) { // Why has the AbstractCPropertyTab.getFileSystemDialog() a hardcoded // list of extensions? // This is basically the same method, but with a list of valid // extensions as parameter. FileDialog dialog = new FileDialog(shell); if (text != null && text.trim().length() != 0) dialog.setFilterPath(text); dialog.setFilterExtensions(exts); dialog.setText(FILESYSTEM_FILE_DIALOG_TITLE); return dialog.open(); } /** * Enable / Disable the given Composite. * * @param compo * A <code>Composite</code> with some controls. * @param value * <code>true</code> to enable, <code>false</code> to disable the given group. */ protected void setEnabled(Composite compo, boolean value) { Control[] children = compo.getChildren(); for (Control child : children) { child.setEnabled(value); } } /** * Clear all discovered paths and symbols. * <p> * Depending on the perConfig flag this method will either clear the current config or all * configs. * </p> */ protected void clearDiscovery() { try { AbstractAVRPage avrpage = (AbstractAVRPage) page; if (avrpage.isPerConfig()) { // Only clear the current config IConfiguration currcfg = getCfg(); ScannerConfigUtil.clearDiscovery(currcfg); } else { // Clear all configurations. // While there are many ways to get a list of all configurations, this is the only // one I could find that would actually work, i.e. get the scanner info of the // configuration actually cleared. ICConfigurationDescription[] alldesc = page.getCfgsEditable(); for (ICConfigurationDescription desc : alldesc) { IConfiguration cfg = getCfg(desc); ScannerConfigUtil.clearDiscovery(cfg); } } } catch (CoreException e) { final Shell shell = super.buttoncomp.getShell(); MessageDialog.openError(shell, "MCU Change Error", "Could not clear discovered path & symbols.\n\n" + e.getLocalizedMessage()); } } /** * Re-discover all build in paths and symbols. * <p> * This method will first clear all previously discovered paths and symbols and then re-run the * ScannerConfigBuilder to re-discover the Symbols (which might have changed due to some AVR * property changes (like MCU or AVR toolchain). * </p> * <p> * The <code>perConfig</code> flag is honored. If it is set, only the current config is cleared * and rebuild, otherwise all configs of the current project are cleared/rebuild. * </p> * <p> * A error dialog is shown upon for any errors during the clearing. The builder is run * asynchronously, so errors there will only be logged. * </p> */ protected void runDiscovery() { // Run the Scanner Config Builder to re-Discover all paths and symbols. ScannerConfigUtil.runDiscovery((IProject) getCfg().getOwner()); } }