/*******************************************************************************
* Copyright (c) 2006, 2010 Wind River Systems, Inc. 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:
* Ted R Williams (Wind River Systems, Inc.) - initial implementation
*******************************************************************************/
package org.eclipse.cdt.debug.ui.memory.traditional;
import java.math.BigInteger;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.MemoryByte;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Caret;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.ScrollBar;
public abstract class AbstractPane extends Canvas
{
protected Rendering fRendering;
// selection state
protected boolean fSelectionStarted = false;
protected boolean fSelectionInProgress = false;
protected BigInteger fSelectionStartAddress = null;
protected int fSelectionStartAddressSubPosition;
// caret
protected Caret fCaret = null;
// character may not fall on byte boundary
protected int fSubCellCaretPosition = 0;
protected int fOldSubCellCaretPosition = 0;
protected boolean fCaretEnabled = false;
protected BigInteger fCaretAddress = null;
// storage
protected int fRowCount = 0;
protected boolean fPaneVisible = true;
class AbstractPaneMouseListener implements MouseListener
{
public void mouseUp(MouseEvent me)
{
positionCaret(me.x, me.y);
fCaret.setVisible(true);
if(fSelectionInProgress && me.button == 1)
{
endSelection(me.x, me.y);
}
fSelectionInProgress = fSelectionStarted = false;
}
public void mouseDown(MouseEvent me)
{
AbstractPane.this.forceFocus();
positionCaret(me.x, me.y);
fCaret.setVisible(false);
if(me.button == 1)
{
// if shift is down and we have an existing start address,
// append selection
if((me.stateMask & SWT.SHIFT) != 0
&& fRendering.getSelection().getStart() != null)
{
// if the pane doesn't have a selection start (the
// selection was created in a different pane)
// then initialize the pane's selection start to the
// rendering's selection start
if(AbstractPane.this.fSelectionStartAddress == null)
AbstractPane.this.fSelectionStartAddress = fRendering
.getSelection().getStart();
AbstractPane.this.fSelectionStarted = true;
AbstractPane.this.appendSelection(me.x, me.y);
}
else
{
// start a new selection
AbstractPane.this.startSelection(me.x, me.y);
}
}
}
public void mouseDoubleClick(MouseEvent me)
{
handleMouseDoubleClick(me);
}
}
class AbstractPaneMouseMoveListener implements MouseMoveListener
{
public void mouseMove(MouseEvent me)
{
if(fSelectionStarted)
{
fSelectionInProgress = true;
appendSelection(me.x, me.y);
}
}
}
class AbstractPaneFocusListener implements FocusListener
{
public void focusLost(FocusEvent fe)
{
IPreferenceStore store = TraditionalRenderingPlugin.getDefault().getPreferenceStore();
if(TraditionalRenderingPreferenceConstants.MEM_EDIT_BUFFER_SAVE_ON_ENTER_ONLY
.equals(store.getString(TraditionalRenderingPreferenceConstants.MEM_EDIT_BUFFER_SAVE)))
{
fRendering.getViewportCache().clearEditBuffer();
}
else
{
fRendering.getViewportCache().writeEditBuffer();
}
// clear the pane local selection start
AbstractPane.this.fSelectionStartAddress = null;
}
public void focusGained(FocusEvent fe)
{
}
}
class AbstractPaneKeyListener implements KeyListener
{
public void keyPressed(KeyEvent ke)
{
fOldSubCellCaretPosition = fSubCellCaretPosition;
if((ke.stateMask & SWT.SHIFT) != 0)
{
switch(ke.keyCode)
{
case SWT.ARROW_RIGHT:
case SWT.ARROW_LEFT:
case SWT.ARROW_UP:
case SWT.ARROW_DOWN:
case SWT.PAGE_DOWN:
case SWT.PAGE_UP:
if(fRendering.getSelection().getStart() == null)
{
fRendering.getSelection().setStart(fCaretAddress.add(BigInteger.valueOf(
fRendering.getAddressesPerColumn())), fCaretAddress);
}
break;
}
}
if(ke.keyCode == SWT.ARROW_RIGHT)
{
handleRightArrowKey();
}
else if(ke.keyCode == SWT.ARROW_LEFT || ke.keyCode == SWT.BS)
{
handleLeftArrowKey();
}
else if(ke.keyCode == SWT.ARROW_DOWN)
{
handleDownArrowKey();
}
else if(ke.keyCode == SWT.ARROW_UP)
{
handleUpArrowKey();
}
else if(ke.keyCode == SWT.PAGE_DOWN)
{
handlePageDownKey();
}
else if(ke.keyCode == SWT.PAGE_UP)
{
handlePageUpKey();
}
else if(ke.keyCode == SWT.ESC)
{
fRendering.getViewportCache().clearEditBuffer();
}
else if(ke.character == '\r')
{
fRendering.getViewportCache().writeEditBuffer();
}
else if(Rendering.isValidEditCharacter(ke.character))
{
if(fRendering.getSelection().hasSelection())
{
setCaretAddress(fRendering.getSelection().getLow());
fSubCellCaretPosition = 0;
}
editCell(fCaretAddress, fSubCellCaretPosition, ke.character);
}
if((ke.stateMask & SWT.SHIFT) != 0)
{
switch(ke.keyCode)
{
case SWT.ARROW_RIGHT:
case SWT.ARROW_LEFT:
case SWT.ARROW_UP:
case SWT.ARROW_DOWN:
case SWT.PAGE_DOWN:
case SWT.PAGE_UP:
fRendering.getSelection().setEnd(fCaretAddress.add(BigInteger.valueOf(
fRendering.getAddressesPerColumn())),
fCaretAddress);
break;
}
}
else if(ke.keyCode != SWT.SHIFT && ke.keyCode != SWT.CTRL && ke.keyCode != SWT.COMMAND)
// if shift key, keep selection, we might add to it
{
fRendering.getSelection().clear();
}
}
public void keyReleased(KeyEvent ke)
{
// do nothing
}
}
class AbstractPanePaintListener implements PaintListener
{
public void paintControl(PaintEvent pe)
{
AbstractPane.this.paint(pe);
}
}
public AbstractPane(Rendering rendering)
{
super(rendering, SWT.DOUBLE_BUFFERED);
fRendering = rendering;
try
{
fCaretAddress = rendering.getBigBaseAddress();
}
catch(Exception e)
{
// do nothing
}
// pref
this.setFont(fRendering.getFont());
GC gc = new GC(this);
gc.setFont(this.getFont());
fCaret = new Caret(this, SWT.NONE);
fCaret.setSize(1, gc.stringExtent("|").y); //$NON-NLS-1$
gc.dispose();
this.addPaintListener(createPaintListener());
this.addMouseListener(createMouseListener());
this.addMouseMoveListener(createMouseMoveListener());
this.addKeyListener(createKeyListener());
this.addFocusListener(createFocusListener());
}
protected MouseListener createMouseListener(){
return new AbstractPaneMouseListener();
}
protected MouseMoveListener createMouseMoveListener(){
return new AbstractPaneMouseMoveListener();
}
protected FocusListener createFocusListener() {
return new AbstractPaneFocusListener();
}
protected KeyListener createKeyListener(){
return new AbstractPaneKeyListener();
}
protected PaintListener createPaintListener(){
return new AbstractPanePaintListener();
}
protected void handleRightArrowKey()
{
fSubCellCaretPosition++;
if(fSubCellCaretPosition >= getCellCharacterCount())
{
fSubCellCaretPosition = 0;
// Ensure that caret is within the addressable range
BigInteger newCaretAddress = fCaretAddress.add(BigInteger
.valueOf(getNumberOfBytesRepresentedByColumn() / fRendering.getAddressableSize()));
if(newCaretAddress.compareTo(fRendering.getMemoryBlockEndAddress()) > 0)
{
fSubCellCaretPosition = getCellCharacterCount();
}
else
{
setCaretAddress(newCaretAddress);
}
}
updateCaret();
ensureCaretWithinViewport();
}
protected void handleLeftArrowKey()
{
fSubCellCaretPosition--;
if(fSubCellCaretPosition < 0)
{
fSubCellCaretPosition = getCellCharacterCount() - 1;
// Ensure that caret is within the addressable range
BigInteger newCaretAddress = fCaretAddress.subtract(BigInteger
.valueOf(getNumberOfBytesRepresentedByColumn() / fRendering.getAddressableSize()));
if(newCaretAddress.compareTo(fRendering.getMemoryBlockStartAddress()) < 0)
{
fSubCellCaretPosition = 0;
}
else
{
setCaretAddress(newCaretAddress);
}
}
updateCaret();
ensureCaretWithinViewport();
}
protected void handleDownArrowKey()
{
// Ensure that caret is within the addressable range
BigInteger newCaretAddress = fCaretAddress.add(BigInteger
.valueOf(fRendering.getAddressableCellsPerRow()));
setCaretAddress(newCaretAddress);
updateCaret();
ensureCaretWithinViewport();
}
protected void handleUpArrowKey()
{
// Ensure that caret is within the addressable range
BigInteger newCaretAddress = fCaretAddress.subtract(BigInteger
.valueOf(fRendering.getAddressableCellsPerRow()));
setCaretAddress(newCaretAddress);
updateCaret();
ensureCaretWithinViewport();
}
protected void handlePageDownKey()
{
// Ensure that caret is within the addressable range
BigInteger newCaretAddress = fCaretAddress.add(BigInteger
.valueOf(fRendering.getAddressableCellsPerRow()
* (fRendering.getRowCount() - 1)));
setCaretAddress(newCaretAddress);
updateCaret();
ensureCaretWithinViewport();
}
protected void handlePageUpKey()
{
// Ensure that caret is within the addressable range
BigInteger newCaretAddress = fCaretAddress.subtract(BigInteger
.valueOf(fRendering.getAddressableCellsPerRow()
* (fRendering.getRowCount() - 1)));
setCaretAddress(newCaretAddress);
updateCaret();
ensureCaretWithinViewport();
}
protected void handleMouseDoubleClick(MouseEvent me)
{
try
{
BigInteger address = getViewportAddress(me.x / getCellWidth(), me.y
/ getCellHeight());
fRendering.getSelection().clear();
fRendering.getSelection().setStart(address.add(BigInteger
.valueOf(fRendering.getAddressesPerColumn())), address);
fRendering.getSelection().setEnd(address.add(BigInteger
.valueOf(fRendering.getAddressesPerColumn())), address);
}
catch(DebugException de)
{
// do nothing
}
}
protected boolean isPaneVisible()
{
return fPaneVisible;
}
protected void setPaneVisible(boolean visible)
{
fPaneVisible = visible;
this.setVisible(visible);
}
protected int getNumberOfBytesRepresentedByColumn()
{
return fRendering.getBytesPerColumn();
}
protected void editCell(BigInteger address, int subCellPosition,
char character)
{
// do nothing
}
// Set the caret address
protected void setCaretAddress(BigInteger caretAddress)
{
// Ensure that caret is within the addressable range
if((caretAddress.compareTo(fRendering.getMemoryBlockStartAddress()) >= 0) &&
(caretAddress.compareTo(fRendering.getMemoryBlockEndAddress()) <= 0))
{
fCaretAddress = caretAddress;
}
else if(caretAddress.compareTo(fRendering.getMemoryBlockStartAddress()) < 0)
{
// calculate offset from the beginning of the row
int cellOffset = fCaretAddress.subtract(fRendering.getViewportStartAddress()).intValue();
int row = cellOffset / (fRendering.getBytesPerRow() / fRendering.getBytesPerCharacter());
cellOffset -= row * fRendering.getBytesPerRow() / fRendering.getBytesPerCharacter();
fCaretAddress = fRendering.getMemoryBlockStartAddress().add(
BigInteger.valueOf(cellOffset / fRendering.getAddressableSize()));
}
else if(caretAddress.compareTo(fRendering.getMemoryBlockEndAddress()) > 0)
{
// calculate offset from the end of the row
int cellOffset = fCaretAddress.subtract(fRendering.getViewportEndAddress()).intValue() + 1;
int row = cellOffset / (fRendering.getBytesPerRow() / fRendering.getBytesPerCharacter());
cellOffset -= row * fRendering.getBytesPerRow()/ fRendering.getBytesPerCharacter();
fCaretAddress = fRendering.getMemoryBlockEndAddress().add(
BigInteger.valueOf(cellOffset / fRendering.getAddressableSize()));
}
fRendering.setCaretAddress(fCaretAddress);
}
protected boolean isOdd(int value)
{
return (value / 2) * 2 == value;
}
@SuppressWarnings("all")
protected void updateCaret()
{
try
{
if(fCaretAddress != null)
{
Point cellPosition = getCellLocation(fCaretAddress);
if(cellPosition != null)
fCaret.setLocation(cellPosition.x + fSubCellCaretPosition * getCellCharacterWidth(), cellPosition.y);
}
}
catch(Exception e)
{
fRendering
.logError(
TraditionalRenderingMessages
.getString("TraditionalRendering.FAILURE_POSITION_CURSOR"), e); //$NON-NLS-1$
}
}
protected void ensureCaretWithinViewport() // TODO getAddressableSize() > 1 ?
{
BigInteger vpStart = fRendering.getViewportStartAddress();
BigInteger vpEnd = fRendering.getViewportEndAddress();
Rectangle vpBounds = fRendering.getBounds();
Rectangle apBounds = fRendering.fAddressPane.getBounds();
Rectangle dpBounds = fRendering.fBinaryPane.getBounds();
Rectangle tpBounds = fRendering.fTextPane.getBounds();
ScrollBar hBar = fRendering.getHorizontalBar();
Point adjustedCaret = null;
int leftPaneEdge = 0;
int rightPaneEdge = 0;
int eolLocation = 0;
int bolSelection = 0;
int eolSelection = 0;
// Determine if we're in the address, data (binary) or text panes; return if none of 'em.
if (this instanceof AddressPane)
{
adjustedCaret = new Point(fCaret.getLocation().x, fCaret.getLocation().y);
}
else if (this instanceof DataPane)
{
leftPaneEdge = Math.max(vpBounds.x, dpBounds.x);
rightPaneEdge = vpBounds.x + vpBounds.width;
bolSelection = hBar.getMinimum();
eolSelection = apBounds.width + dpBounds.width - (vpBounds.width/2);
eolLocation = apBounds.width + dpBounds.width - 10;
adjustedCaret = new Point(fCaret.getLocation().x + dpBounds.x + 16, fCaret.getLocation().y);
}
else if (this instanceof TextPane)
{
leftPaneEdge = apBounds.width + dpBounds.width;
rightPaneEdge = leftPaneEdge + (vpBounds.width - tpBounds.x);
bolSelection = apBounds.width + dpBounds.width - 36;
eolSelection = hBar.getMaximum();
eolLocation = apBounds.width + dpBounds.width + tpBounds.width - 22;
adjustedCaret = new Point(fCaret.getLocation().x + apBounds.width + dpBounds.width, fCaret.getLocation().y);
}
else
return;
if (fCaretAddress.compareTo(vpStart) < 0 || fCaretAddress.compareTo(vpEnd) >= 0)
{
// The caret was moved outside the viewport bounds: Scroll the
// viewport up or down by a row, depending on where the caret is
boolean upArrow = fCaretAddress.compareTo(vpStart) <= 0;
ScrollBar vBar = fRendering.getVerticalBar();
vBar.setSelection(vBar.getSelection() + (upArrow ? -1 : 1));
vBar.notifyListeners(SWT.Selection, new Event());
// Check to see if we're at the beginning or end of a line, and
// move the scrollbar, if necessary, to keep the caret in view.
int currentCaretLocation = fCaret.getLocation().x + dpBounds.x + 16;
int lowEolLimit = eolLocation - 1;
int highEolLimit = eolLocation + 1;
if (fCaret.getLocation().x == 2)
{
hBar.setSelection(bolSelection);
hBar.notifyListeners(SWT.Selection, new Event());
}
else if (upArrow && ((currentCaretLocation >= lowEolLimit && currentCaretLocation <= highEolLimit)))
{
hBar.setSelection(eolSelection);
hBar.notifyListeners(SWT.Selection, new Event());
}
}
else if (!vpBounds.contains(adjustedCaret))
{
// Left or Right arrow: The caret is now outside the viewport and beyond the pane. Calculate
// a new pane position at [up to] 33% left or right in the viewport, to center the caret; use a
// positive or negative offset depending on which direction we're scrolling.
int hBarOffset = (rightPaneEdge - leftPaneEdge) / 3;
int newHBarSel = hBar.getSelection() + (adjustedCaret.x > rightPaneEdge ? hBarOffset : -hBarOffset);
if (fCaret.getLocation().x == 2)
{
// Beginning of a line
hBar.setSelection(bolSelection);
}
else if (adjustedCaret.x == eolLocation)
{
// End of a line
hBar.setSelection(eolSelection);
}
else if (adjustedCaret.x > rightPaneEdge)
{
// Caret was moved by the user beyond the right edge of the pane
hBar.setSelection(newHBarSel < hBar.getMaximum() ? newHBarSel : hBar.getMaximum());
}
else if (adjustedCaret.x < leftPaneEdge)
{
// Caret was moved by the user beyond the left edge of the pane
hBar.setSelection(newHBarSel > hBar.getMinimum() ? newHBarSel : hBar.getMinimum());
}
else
return;
hBar.notifyListeners(SWT.Selection, new Event());
}
else
{
// Caret is inside the viewport
return;
}
fRendering.ensureViewportAddressDisplayable();
fRendering.setCaretAddress(fCaretAddress);
}
protected void advanceCursor()
{
handleRightArrowKey();
}
protected void positionCaret(int x, int y)
{
// do nothing
}
protected int getRowCount()
{
return fRowCount;
}
protected void setRowCount()
{
fRowCount = getBounds().height / getCellHeight();
}
protected void settingsChanged()
{
fSubCellCaretPosition = 0;
}
protected void startSelection(int x, int y)
{
try
{
BigInteger address = getViewportAddress(x / getCellWidth(), y
/ getCellHeight());
if(address != null)
{
this.fSelectionStartAddress = address;
Point cellPosition = getCellLocation(address);
if(cellPosition != null)
{
int offset = x - cellPosition.x;
fSelectionStartAddressSubPosition = offset
/ getCellCharacterWidth();
}
fRendering.getSelection().clear();
fRendering.getSelection().setStart(address.add(BigInteger.valueOf(
fRendering.getBytesPerColumn() / fRendering.getAddressableSize())), address);
fSelectionStarted = true;
new CopyDefaultAction(fRendering, DND.SELECTION_CLIPBOARD).run();
}
}
catch(DebugException e)
{
fRendering
.logError(
TraditionalRenderingMessages
.getString("TraditionalRendering.FAILURE_START_SELECTION"), e); //$NON-NLS-1$
}
}
protected void endSelection(int x, int y)
{
appendSelection(x, y);
fSelectionInProgress = false;
}
protected void appendSelection(int x, int y)
{
try
{
if(this.fSelectionStartAddress == null)
return;
BigInteger address = getViewportAddress(x / getCellWidth(), y
/ getCellHeight());
if(address.compareTo(this.fSelectionStartAddress) == 0)
{
// deal with sub cell selection
Point cellPosition = getCellLocation(address);
int offset = x - cellPosition.x;
int subCellCharacterPosition = offset / getCellCharacterWidth();
if(Math.abs(subCellCharacterPosition
- this.fSelectionStartAddressSubPosition) > this
.getCellCharacterCount() / 4)
{
fRendering.getSelection().setEnd(address.add(BigInteger
.valueOf(fRendering.getAddressesPerColumn())), address);
}
else
{
fRendering.getSelection().setEnd(null, null);
}
}
else
{
fRendering.getSelection().setEnd(address.add(BigInteger
.valueOf(fRendering.getAddressesPerColumn())), address);
}
if(fRendering.getSelection().getEnd() != null)
{
this.fCaretAddress = fRendering.getSelection().getEnd();
this.fSubCellCaretPosition = 0;
}
updateCaret();
new CopyDefaultAction(fRendering, DND.SELECTION_CLIPBOARD).run();
}
catch(DebugException e)
{
fRendering
.logError(
TraditionalRenderingMessages
.getString("TraditionalRendering.FAILURE_APPEND_SELECTION"), e); //$NON-NLS-1$
}
}
protected void paint(PaintEvent pe)
{
fRowCount = getBounds().height / getCellHeight();
if(fRendering.isDirty())
{
fRendering.setDirty(false);
fRendering.refresh();
}
}
abstract protected BigInteger getViewportAddress(int col, int row)
throws DebugException;
protected Point getCellLocation(BigInteger address)
{
return null;
}
protected String getCellText(MemoryByte bytes[])
{
return null;
}
abstract protected int getCellWidth();
abstract protected int getCellCharacterCount();
public void setFont(Font font)
{
super.setFont(font);
fCharacterWidth = -1;
fCellHeight = -1;
fTextHeight = -1;
}
private int fCellHeight = -1; // called often, cache
protected int getCellHeight()
{
if(fCellHeight == -1)
{
fCellHeight = getCellTextHeight()
+ (fRendering.getCellPadding() * 2);
}
return fCellHeight;
}
private int fCharacterWidth = -1; // called often, cache
protected int getCellCharacterWidth()
{
if(fCharacterWidth == -1)
{
GC gc = new GC(this);
gc.setFont(fRendering.getFont());
fCharacterWidth = gc.getAdvanceWidth('F');
gc.dispose();
}
return fCharacterWidth;
}
private int fTextHeight = -1; // called often, cache
protected int getCellTextHeight()
{
if(fTextHeight == -1)
{
GC gc = new GC(this);
gc.setFont(fRendering.getFont());
FontMetrics fontMetrics = gc.getFontMetrics();
fTextHeight = fontMetrics.getHeight();
gc.dispose();
}
return fTextHeight;
}
}