/*******************************************************************************
* 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.dialogs;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.forms.FormDialog;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.TableWrapData;
import org.eclipse.ui.forms.widgets.TableWrapLayout;
import de.innot.avreclipse.core.toolinfo.fuses.ByteValues;
import de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart;
import de.innot.avreclipse.ui.editors.ByteValuesMainPart;
import de.innot.avreclipse.ui.editors.ByteValuesTitlePart;
/**
* A Fuse Byte Editor as a Dialog.
* <p>
* This Dialog is called from the AVRDude Fuses Property Dialog to optionally edit fuse byte values
* in the same fashion as the Editor for .fuses files.<br>
* It will show all BitFields of the given ByteValues as {@link BitFieldEditorSectionPart}s for
* editing.
* </p>
* To use this Dialog instantiate it with the <code>ByteValues</code> to edit and call
* {@link #open()}. While this Dialog will only commit any modifications to the given
* <code>ByteValues</code> if the <em>OK</em> was pressed, callers should not depend on this and
* should discard the <code>ByteValues</code> object after the <em>Cancel</em> button has been
* pressed.
* </p>
* <p>
* A safe access pattern could look like this:
*
* <pre>
* ByteValues values = ....
* ByteValuesEditorDialog dialog = new ByteValuesEditorDialog(getShell(), values);
* dialog.create();
* dialog.optimizeSize();
* if (dialog.open() == Dialog.OK) {
* ByteValues newvalues = dialog.getByteValues();
* ....
* }
* </pre>
*
* <p>
* Because the <code>ColumnLayout</code> used in the Dialog has some problems when its size is not
* constrained. It will grab to much screen space, sometimes all of it. The {@link #optimizeSize()}
* method contains a fix to reduce the size of the dialog to the minimum required. Alternatively a
* fixed size can be set with <code>dialog.getShell().setSize(int width, int height)</code>.
* </p>
* <p>
* Note: there is currently an unresolved bug that when the alphabetically first BitField shown is
* of a Radio Button type and its value is undefined (<code>-1</code>) then the first radio
* button gets set to true anyway. This is because SWT (or Windows) will set the first radio button
* to true whenever the control gets the focus, and the first control on the screen will always get
* the focus.
* </p>
*
* @author Thomas Holland
* @since 2.3
*
*/
public class ByteValuesEditorDialog extends FormDialog {
/**
* The <code>ByteValues</code> this dialog works with. Its content will only be modified when
* the <em>OK</em> button is pressed.
*/
private final ByteValues fByteValues;
private IManagedForm fForm;
private ByteValuesMainPart fMainPart;
/**
* Instantiate a new Dialog.
* <p>
* Note that the Dialog will not be shown until the {@link #open()} method is called.
* </p>
*
* @param parentShell
* <code>Shell</code> to associate this Dialog with, so that it always stays on top
* of the given Shell.
* @param values
* The <code>ByteValues</code> to edit.
*/
public ByteValuesEditorDialog(Shell parentShell, ByteValues values) {
super(parentShell);
fByteValues = values;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.Dialog#okPressed()
*/
@Override
protected void okPressed() {
// Only commit the changes if any have been made and the OK button was pressed.
if (fForm.isDirty()) {
fForm.commit(false);
}
super.okPressed();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.forms.FormDialog#createFormContent(org.eclipse.ui.forms.IManagedForm)
*/
@Override
protected void createFormContent(IManagedForm managedForm) {
// The general setup has already been done by the FormDialog superclass.
// We only set the title for the dialog and let fillBody() add the rest of the content.
// Once it is finished we can tell the form about the ByteValues we are supposed to edit.
this.getShell().setText(fByteValues.getType().toString() + " Editor");
fForm = managedForm;
fillBody(managedForm);
}
/**
* Fill the managed Form with the SectionParts for all BitFields.
*
* @param managedForm
* @param toolkit
*/
private void fillBody(IManagedForm managedForm) {
Composite body = managedForm.getForm().getBody();
FormToolkit toolkit = managedForm.getToolkit();
body.setLayout(new TableWrapLayout());
// Add a part that will update the form title to the latest MCU.
ByteValuesTitlePart titlepart = new ByteValuesTitlePart();
managedForm.addPart(titlepart);
// The main section has all BitField sections
Composite main = toolkit.createComposite(body);
main.setLayoutData(new TableWrapData(TableWrapData.FILL));
ByteValuesMainPart mainpart = new ByteValuesMainPart(main, fByteValues);
managedForm.addPart(mainpart);
fMainPart = mainpart;
managedForm.setInput(fByteValues);
}
/**
* Try to optimize the size of the dialog.
* <p>
* This is done by determining the width of the widest BitField section. This is the minimum
* width of the dialog.<br>
* If two sections will fit on the screen, then the width of the dialog is set to twice the the
* minimum width for a two column layout.
* </p>
* <p>
* This method must be called after {@link #create()}, but before {@link #open()}.
* </p>
* <p>
* This method is a solution for the layout problems of the <code>ColumnLayout</code> used for
* the BitField sections. If <code>ColumnLayout</code> is not constrained by the shell size it
* will grab to much screen-space, usually the complete screen.
* <p>
*
*
*/
public void optimizeSize() {
//
// Even after single stepping thru ColumnLayout I am not sure why ColumnLayout grabs to much
// space. It seems like it always tries to get the width for three columns, and as those do
// not fit on a 1024px screen will use the maximum available while falling back to a two
// column layout, thus rendering the columns much wider than required.
//
// With regard to the height of a ColumnLayout I am even more at a loss what ColumnLayout is
// doing and why it sometimes grabs all screen space. It could be because at one point in
// the layout process it calculates the minimum size for each child control, which due to
// label wrapping will result in the minimum height of each section being much higher than
// actually required.
//
Point dialogareasize = getContents().computeSize(SWT.DEFAULT, SWT.DEFAULT);
Rectangle screensize = getShell().getDisplay().getBounds();
// Get the width of the widest Section. As this does not take borders and margins into
// account, I add a arbitrary 12,5% fudge-factor. This value seems to work quite well and is
// much easier to determine as all the margins.
int columnwidth = fMainPart.getMaxWidth();
int width = columnwidth + columnwidth / 8;
// If we can fit two columns on the screen than do it.
boolean twocolumn = screensize.width > width * 2;
width *= twocolumn ? 2 : 1;
// For the height we use the current dialogareasize, which seems to always work, regardless
// of a single or double column layout (I don't know why!).
// The 20 is a little addition for the the top and bottom border. Should try to determine
// this dynamically, but right now I can't be bothered to find out how to determine this.
// Maybe you can?
int height = dialogareasize.y + 20;
// Now that we have a good width and height we can set the shell of the dialog accordingly.
getShell().setSize(width, height);
}
/**
* Get the <code>ByteValues</code> this Dialog has worked with.
* <p>
* This method should only be called after {@link #open()}, otherwise the returned ByteValues
* may not reflect the latest modifications by the user.
* </p>
*
* @return <code>ByteValues</code> with the new settings.
*/
public ByteValues getResult() {
// TODO: Do we really need this?
// This is probably redundant, because we return a reference to an object that we got from
// the caller. So unless the Dialog object has been passed around the caller should still
// have the reference himself.
// (This code is a leftover from when the Dialog would make a copy of the ByteValues. But
// this is not a good idea because the ByteValues could be a subclass, like FileByteValues
// and copying it would change the type - I know, stupid design, but I work on it some other
// time.)
return fByteValues;
}
}