/*******************************************************************************
* Copyright (c) 2009, 2010 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package com.aptana.ruby.debug.ui.breakpoints;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.internal.ui.SWTFactory;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.TextViewerUndoManager;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IPropertyListener;
import com.aptana.core.util.StringUtil;
import com.aptana.ruby.debug.core.IRubyLineBreakpoint;
import com.aptana.ruby.debug.ui.RubyDebugUIPlugin;
import com.aptana.ruby.internal.debug.ui.breakpoints.AbstractRubyBreakpointEditor;
import com.aptana.ruby.internal.debug.ui.propertypages.PropertyPageMessages;
/**
* Controls to edit a breakpoint's conditional expression, condition enabled state, and suspend policy (suspend when
* condition is <code>true</code> or when the value of the conditional expression changes).
* <p>
* The controls are intended to be embedded in a composite provided by the client - for example, in a dialog. Clients
* must call {@link #createControl(Composite)} as the first life cycle method after instantiation. Clients may then call
* {@link #setInput(Object)} with the breakpoint object to be displayed/edited. Changes are not applied to the
* breakpoint until {@link #doSave()} is called. The method {@link #isDirty()} may be used to determine if any changes
* have been made in the editor, and {@link #getStatus()} may be used to determine if the editor settings are valid.
* Clients can register for property change notification ({@link #addPropertyListener(IPropertyListener)}). The editor
* will fire a property change each time a setting is modified. The same editor can be used to display different
* breakpoints by calling {@link #setInput(Object)} with different breakpoint objects.
* </p>
*
* @since 1.0
*/
@SuppressWarnings("restriction")
public final class RubyBreakpointConditionEditor extends AbstractRubyBreakpointEditor
{
private Button fConditional;
private SourceViewer fViewer;
private IRubyLineBreakpoint fBreakpoint;
private IDocumentListener fDocumentListener;
/**
* Property id for breakpoint condition expression.
*/
public static final int PROP_CONDITION = 0x1001;
/**
* Property id for breakpoint condition enabled state.
*/
public static final int PROP_CONDITION_ENABLED = 0x1002;
/**
* Adds the given property listener to this editor. Property changes are reported on the breakpoint being edited.
* Property identifiers are breakpoint attribute keys.
*
* @param listener
* listener
*/
public void addPropertyListener(IPropertyListener listener)
{
super.addPropertyListener(listener);
}
/**
* Removes the property listener from this editor.
*
* @param listener
* listener
*/
public void removePropertyListener(IPropertyListener listener)
{
super.removePropertyListener(listener);
}
/**
* Sets the breakpoint to editor or <code>null</code> if none.
*
* @param input
* breakpoint or <code>null</code>
* @throws CoreException
* if unable to access breakpoint attributes
*/
public void setInput(Object input) throws CoreException
{
if (input instanceof IRubyLineBreakpoint)
{
setBreakpoint((IRubyLineBreakpoint) input);
}
else
{
setBreakpoint(null);
}
}
/**
* Sets the breakpoint to edit. Has no effect if the breakpoint responds <code>false</code> to
* {@link IRubyLineBreakpoint#supportsCondition()}. The same editor can be used iteratively for different
* breakpoints.
*
* @param breakpoint
* the breakpoint to edit or <code>null</code> if none
* @exception CoreException
* if unable to access breakpoint attributes
*/
private void setBreakpoint(IRubyLineBreakpoint breakpoint) throws CoreException
{
fBreakpoint = breakpoint;
if (fDocumentListener != null)
{
fViewer.getDocument().removeDocumentListener(fDocumentListener);
fDocumentListener = null;
}
fViewer.unconfigure();
IDocument document = new Document();
fViewer.setInput(document);
String condition = null;
boolean controlsEnabled = false;
boolean conditionEnabled = false;
if (breakpoint != null)
{
controlsEnabled = true;
if (breakpoint.supportsCondition())
{
condition = breakpoint.getCondition();
conditionEnabled = breakpoint.isConditionEnabled();
}
}
document.set((condition == null ? StringUtil.EMPTY : condition));
fViewer.setUndoManager(new TextViewerUndoManager(10));
fViewer.getUndoManager().connect(fViewer);
fDocumentListener = new IDocumentListener()
{
public void documentAboutToBeChanged(DocumentEvent event)
{
}
public void documentChanged(DocumentEvent event)
{
setDirty(PROP_CONDITION);
}
};
fViewer.getDocument().addDocumentListener(fDocumentListener);
fConditional.setEnabled(controlsEnabled);
fConditional.setSelection(conditionEnabled);
setEnabled(conditionEnabled && breakpoint != null && breakpoint.supportsCondition(), false);
setDirty(false);
}
/**
* Creates the condition editor widgets and returns the top level control.
*
* @param parent
* composite to embed the editor controls in
* @return top level control
*/
public Control createControl(Composite parent)
{
Composite controls = SWTFactory.createComposite(parent, parent.getFont(), 2, 1, GridData.FILL_HORIZONTAL, 0, 0);
fConditional = new Button(controls, SWT.CHECK);
fConditional.setText(processMnemonics(PropertyPageMessages.RubyBreakpointConditionEditor_0));
fConditional.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
fConditional.addSelectionListener(new SelectionAdapter()
{
public void widgetSelected(SelectionEvent e)
{
boolean checked = fConditional.getSelection();
setEnabled(checked, true);
setDirty(PROP_CONDITION_ENABLED);
}
});
fViewer = new SourceViewer(parent, null, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.LEFT_TO_RIGHT);
fViewer.setEditable(false);
ControlDecoration decoration = new ControlDecoration(fViewer.getControl(), SWT.TOP | SWT.LEFT);
decoration.setShowOnlyOnFocus(true);
FieldDecoration dec = FieldDecorationRegistry.getDefault().getFieldDecoration(
FieldDecorationRegistry.DEC_CONTENT_PROPOSAL);
decoration.setImage(dec.getImage());
decoration.setDescriptionText(dec.getDescription());
GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
// set height/width hints based on font
GC gc = new GC(fViewer.getTextWidget());
gc.setFont(fViewer.getTextWidget().getFont());
FontMetrics fontMetrics = gc.getFontMetrics();
// gd.heightHint = Dialog.convertHeightInCharsToPixels(fontMetrics, 10);
gd.widthHint = Dialog.convertWidthInCharsToPixels(fontMetrics, 40);
gc.dispose();
fViewer.getControl().setLayoutData(gd);
parent.addDisposeListener(new DisposeListener()
{
public void widgetDisposed(DisposeEvent e)
{
dispose();
}
});
return parent;
}
/**
* Disposes this editor and its controls. Once disposed, the editor can no longer be used.
*/
protected void dispose()
{
super.dispose();
if (fDocumentListener != null)
{
fViewer.getDocument().removeDocumentListener(fDocumentListener);
}
// fViewer.dispose();
}
/**
* Gives focus to an appropriate control in the editor.
*/
public void setFocus()
{
fViewer.getControl().setFocus();
}
/**
* Saves current settings to the breakpoint being edited. Has no effect if a breakpoint is not currently being
* edited or if this editor is not dirty.
*
* @exception CoreException
* if unable to update the breakpoint.
*/
public void doSave() throws CoreException
{
if (fBreakpoint != null && isDirty())
{
fBreakpoint.setCondition(fViewer.getDocument().get().trim());
fBreakpoint.setConditionEnabled(fConditional.getSelection());
fBreakpoint.setConditionSuspendOnTrue(true);
setDirty(false);
}
}
/**
* Returns a status describing whether the condition editor is in a valid state. Returns an OK status when all is
* good. For example, an error status is returned when the conditional expression is empty but enabled.
*
* @return editor status.
*/
public IStatus getStatus()
{
if (fBreakpoint != null && fBreakpoint.supportsCondition())
{
if (fConditional.getSelection())
{
if (fViewer.getDocument().get().trim().length() == 0)
{
return new Status(IStatus.ERROR, RubyDebugUIPlugin.getUniqueIdentifier(),
PropertyPageMessages.BreakpointConditionEditor_1);
}
}
}
return Status.OK_STATUS;
}
/**
* Returns whether the editor needs saving.
*
* @return whether the editor needs saving
*/
public boolean isDirty()
{
return super.isDirty();
}
/**
* Sets whether mnemonics should be displayed in editor controls. Only has an effect if set before
* {@link #createControl(Composite)} is called. By default, mnemonics are displayed.
*
* @param mnemonics
* whether to display mnemonics
*/
public void setMnemonics(boolean mnemonics)
{
super.setMnemonics(mnemonics);
}
/**
* Enables controls based on whether the breakpoint's condition is enabled.
*
* @param enabled
* whether to enable
*/
private void setEnabled(boolean enabled, boolean focus)
{
fViewer.setEditable(enabled);
fViewer.getTextWidget().setEnabled(enabled);
if (enabled)
{
fViewer.getTextWidget().setBackground(null);
if (focus)
{
setFocus();
}
}
else
{
Color color = fViewer.getControl().getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
fViewer.getTextWidget().setBackground(color);
}
}
/**
* Returns the breakpoint being edited or <code>null</code> if none.
*
* @return breakpoint or <code>null</code>
*/
public Object getInput()
{
return fBreakpoint;
}
}