blob: bf526813b0c2aaa05fceffff71b27aa7b146eb13 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.debug.internal.ui.views.memory;
import java.math.BigInteger;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.internal.core.memory.IExtendedMemoryBlock;
import org.eclipse.debug.internal.ui.DebugUIMessages;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.IInternalDebugUIConstants;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableCursor;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
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.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
/**
* Manages the cursor in the table.
* Since table content is dynamic, default TableCursor does not handle dynamic
* content so well. This manager is created to make sure selection is maintainted properly.
* In the future, it should be moidified such that it allows user to do multiple
* cell selection. (use fTrailCursors)
*
* @since 3.0
*/
public class ViewTabCursorManager
{
private TableCursor fTableCursor;
private MemoryViewTab fViewTab;
private MenuManager fMenuManager;
protected int fRow;
protected int fCol;
private TableEditor editor;
private boolean fMenuDisposed = false;
private Table fTable;
private TableViewer fTableViewer;
private String fRenderingId;
private IFixedLengthOutputRenderer fRenderer;
private TraverseEventListener fTraverseEvtListener;
private TextFocusListener fTextFocusListener;
private TextKeyListener fTextKeyListener;
private MouseEventListener fMouseEventListener;
class MouseEventListener implements MouseListener
{
/* (non-Javadoc)
* @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
*/
public void mouseDoubleClick(MouseEvent e)
{
}
/* (non-Javadoc)
* @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent)
*/
public void mouseDown(MouseEvent e)
{
if (e.getSource() instanceof Table)
handleTableMouseEvent(e);
else if (e.getSource() instanceof TableCursor)
handleCursorMouseEvent(e);
}
private void handleCursorMouseEvent(MouseEvent e){
if (e.button == 1)
{
if (fCol > 0 && fCol <= (getNumCol()))
activateCellEditor(null);
}
}
/**
* @param e
*/
private void handleTableMouseEvent(MouseEvent e) {
// figure out new cursor position based on here the mouse is pointing
TableItem[] selections = fTableViewer.getTable().getSelection();
TableItem selectedRow = null;
int colNum = -1;
if (selections.length > 0)
{
selectedRow = selections[0];
int numCol = fTableViewer.getColumnProperties().length;
for (int i=0; i<numCol; i++)
{
Rectangle bound = selectedRow.getBounds(i);
if (bound.contains(e.x, e.y))
{
colNum = i;
break;
}
}
}
// if column position cannot be determined, return
if (colNum < 1)
return;
// handle user mouse click onto table
// move cursor to new position
if (selectedRow != null)
{
int row = fTableViewer.getTable().indexOf(selectedRow);
updateCursorPosition(row, colNum, true);
}
fViewTab.updateSyncTopAddress(true);
// selected address changes because user has selected a new position
fViewTab.updateSelectedAddress(fTableCursor.getRow(), fTableCursor.getColumn());
// keep table selection up to date
fViewTab.updateTableSelection();
setCursorFocus();
}
/* (non-Javadoc)
* @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
*/
public void mouseUp(MouseEvent e)
{
}
}
/**
* Text Traverse Listener for the edit text box in Memory View Tab
* Moves cursor around as user tabs in the view tab.
*/
class TraverseEventListener implements TraverseListener
{
/* (non-Javadoc)
* @see org.eclipse.swt.events.TraverseListener#keyTraversed(org.eclipse.swt.events.TraverseEvent)
*/
public void keyTraversed(TraverseEvent event)
{
if (event.getSource() instanceof Text)
handleTextTraverseEvt(event);
else if (event.getSource() instanceof TableCursor)
handleCursorTraverseEvt(event);
}
/**
* @param event
*/
private void handleTextTraverseEvt(TraverseEvent event) {
final TraverseEvent e = event;
Display.getDefault().asyncExec(new Runnable()
{
public void run()
{
try
{
// if cursor is at boundry, reload
if (fViewTab.getMemoryBlock() instanceof IExtendedMemoryBlock)
{
if (fRow+1 >= fTable.getItemCount() || fRow-1 <= 0)
{
BigInteger topAddress = fViewTab.getTopVisibleAddress();
fViewTab.reloadTable(topAddress, false);
}
}
}
catch (DebugException e1)
{
DebugUIPlugin.log(e1);
return;
}
if (e.detail == SWT.TRAVERSE_TAB_NEXT)
{
if (!(fViewTab.getMemoryBlock() instanceof IExtendedMemoryBlock))
{
if (fRow+1 >= fTable.getItemCount() &&
fCol == getNumCol())
{
return;
}
}
// if the cursor is at the end of the line, move to next line
if (fCol == getNumCol())
{
fCol = 1;
fRow++;
}
else
{
fCol++;
}
}
else if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)
{
if (!(fViewTab.getMemoryBlock() instanceof IExtendedMemoryBlock))
{
if (fRow-1 < 0 && fCol == 1)
{
return;
}
}
// if the cursor is at the beginning of the line, move to previous line
if (fCol == 1)
{
fCol = getNumCol();
fRow--;
}
else
{
fCol--;
}
}
else
return;
// update cursor location and selection in table
updateCursorPosition(fRow, fCol, true);
fViewTab.updateSyncTopAddress(true);
fViewTab.updateSelectedAddress(fTableCursor.getRow(), fTableCursor.getColumn());
fViewTab.updateTableSelection();
Text text = (Text)e.getSource();
removeListeners(text);
activateCellEditor(null);
}
});
}
private void handleCursorTraverseEvt(TraverseEvent e){
if (fCol == getNumCol() && e.keyCode == SWT.ARROW_RIGHT)
{
if (fRow + 1>= fTable.getItemCount())
{
return;
}
fRow = fRow +1;
fCol = 0;
updateCursorPosition(fRow, fCol, true);
}
if (fCol == 1 && e.keyCode == SWT.ARROW_LEFT)
{
if (fRow-1 < 0)
{
return;
}
fRow = fRow - 1;
fCol = getNumCol()+1;
updateCursorPosition(fRow, fCol, true);
}
}
}
/**
* Focus listener for the text editor box in Memory View Tab
* Commit changes to IMemoryBlock when the text box has lost focus.
*/
class TextFocusListener implements FocusListener
{
/* (non-Javadoc)
* @see org.eclipse.swt.events.FocusListener#focusGained(org.eclipse.swt.events.FocusEvent)
*/
public void focusGained(FocusEvent e)
{
}
/* (non-Javadoc)
* @see org.eclipse.swt.events.FocusListener#focusLost(org.eclipse.swt.events.FocusEvent)
*/
public void focusLost(FocusEvent event)
{
final FocusEvent e = event;
Display.getDefault().syncExec(new Runnable() {
public void run()
{
try
{
Text text = (Text)e.getSource();
removeListeners(text);
// get new value
String newValue = text.getText();
// modify memory at fRow and fCol
modifyValue(fRow, fCol, newValue);
// show cursor after modification is completed
showCursor();
}
catch (NumberFormatException e1)
{
MemoryViewUtil.openError(DebugUIMessages.getString(MemoryViewCellModifier.TITLE),
DebugUIMessages.getString(MemoryViewCellModifier.DATA_IS_INVALID), null);
}
}
});
}
}
/**
* Key Listener to the edit text box in Memory View Tab
* Moves the text box arround as user hits the arrow keys.
* Change is not committed as user hits the Escape key.
* Move edit box to the next cell as user has typed in the max
* number of characters for a cell.
*/
class TextKeyListener implements KeyListener
{
/* (non-Javadoc)
* @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
*/
public void keyTyped(KeyEvent e)
{
}
/* (non-Javadoc)
* @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
*/
public void keyPressed(KeyEvent event)
{
if (event.getSource() instanceof Text)
handleTextKeyEvt(event);
}
/**
* @param event
*/
private void handleTextKeyEvt(KeyEvent event) {
final KeyEvent e = event;
Display.getDefault().asyncExec(new Runnable()
{
public void run()
{
Text text = (Text)e.getSource();
try
{
switch (e.keyCode)
{
case SWT.ARROW_UP :
// move text editor box up one row
if (fRow-1 < 0)
return;
// modify value for current cell
modifyValue(fRow, fCol, text.getText());
fRow--;
// update cursor location and selection in table
updateCursorPosition(fRow, fCol, true);
fViewTab.updateSyncTopAddress(true);
fViewTab.updateSelectedAddress(fTableCursor.getRow(), fTableCursor.getColumn());
fViewTab.updateTableSelection();
// remove listeners when focus is lost
removeListeners(text);
activateCellEditor(null);
break;
case SWT.ARROW_DOWN :
// move text editor box down one row
if (fRow+1 >= fTable.getItemCount())
return;
// modify value for current cell
modifyValue(fRow, fCol, text.getText());
fRow++;
// update cursor location and selection in table
updateCursorPosition(fRow, fCol, true);
fViewTab.updateSyncTopAddress(true);
fViewTab.updateSelectedAddress(fTableCursor.getRow(), fTableCursor.getColumn());
fViewTab.updateTableSelection();
// remove traverse listener when focus is lost
removeListeners(text);
activateCellEditor(null);
break;
case 0:
// if user has entered the max number of characters allowed in a cell, move to next cell
// Extra changes will be used as initial value for the next cell
if (fRenderer != null)
{
if (text.getText().length() > fViewTab.getColumnSize()*fRenderer.getNumCharPerByte())
{
String newValue = text.getText();
text.setText(newValue.substring(0, fViewTab.getColumnSize()*fRenderer.getNumCharPerByte()));
modifyValue(fRow, fCol, text.getText());
// if cursor is at the end of a line, move to next line
if (fCol >= getNumCol())
{
fCol = 1;
fRow++;
}
else
{
// move to next column
fCol++;
}
// update cursor position and selected address
updateCursorPosition(fRow, fCol, true);
fViewTab.updateSyncTopAddress(true);
fViewTab.updateSelectedAddress(fTableCursor.getRow(), fTableCursor.getColumn());
fViewTab.updateTableSelection();
removeListeners(text);
// activate text editor at next cell
activateCellEditor(newValue.substring(fViewTab.getColumnSize()*fRenderer.getNumCharPerByte()));
}
}
break;
case SWT.ESC:
// if user has pressed escape, do not commit the changes
// that's why "modifyValue" is not called
updateCursorPosition(fRow, fCol, true);
fViewTab.updateSelectedAddress(fTableCursor.getRow(), fTableCursor.getColumn());
fViewTab.updateTableSelection();
removeListeners(text);
// cursor needs to have focus to remove focus from cell editor
setCursorFocus();
break;
default :
if (fRenderer != null)
{
if (text.getText().length()> fViewTab.getColumnSize()* fRenderer.getNumCharPerByte())
{
String newValue = text.getText();
text.setText(newValue.substring(0,fViewTab.getColumnSize()* fRenderer.getNumCharPerByte()));
modifyValue(fRow, fCol, text.getText());
// if cursor is at the end of a line, move to next line
if (fCol >= getNumCol())
{
fCol = 1;
fRow++;
}
else
{
fCol++;
}
updateCursorPosition(fRow, fCol, true);
fViewTab.updateSyncTopAddress(true);
fViewTab.updateSelectedAddress(fTableCursor.getRow(), fTableCursor.getColumn());
fViewTab.updateTableSelection();
removeListeners(text);
activateCellEditor(
newValue.substring(
fViewTab.getColumnSize()
* fRenderer.getNumCharPerByte()));
}
}
break;
}
}
catch (NumberFormatException e1)
{
MemoryViewUtil.openError(DebugUIMessages.getString(MemoryViewCellModifier.TITLE),
DebugUIMessages.getString(MemoryViewCellModifier.DATA_IS_INVALID), null);
updateCursorPosition(fRow, fCol, true);
fViewTab.updateSelectedAddress(fTableCursor.getRow(), fTableCursor.getColumn());
fViewTab.updateTableSelection();
removeListeners(text);
showCursor();
}
}
});
}
/* (non-Javadoc)
* @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
*/
public void keyReleased(KeyEvent e)
{
}
}
/**
* Create cursor manager for view tab
* @param viewTab
* @param initialRow
* @param initialCol
* @param menuManager
*/
public ViewTabCursorManager(MemoryViewTab viewTab, int initialRow, int initialCol, MenuManager menuManager)
{
fViewTab = viewTab;
fMenuManager = menuManager;
fRow = initialRow;
fCol = initialCol;
fTableViewer = viewTab.getTableViewer();
fTable = fTableViewer.getTable();
editor = new TableEditor (fTable);
fRenderingId = fViewTab.getRenderingId();
IBaseLabelProvider labelProvider = fTableViewer.getLabelProvider();
if (labelProvider instanceof AbstractTableViewTabLabelProvider)
{
AbstractMemoryRenderer renderer = ((AbstractTableViewTabLabelProvider)labelProvider).getRenderer();
if (renderer instanceof IFixedLengthOutputRenderer)
fRenderer = (IFixedLengthOutputRenderer)renderer;
}
fTraverseEvtListener = new TraverseEventListener();
fTextFocusListener = new TextFocusListener();
fTextKeyListener = new TextKeyListener();
fMouseEventListener = new MouseEventListener();
fTable.addMouseListener(fMouseEventListener);
createCursor();
}
private void createCursor()
{
fTableCursor = new TableCursor(fTable, SWT.NONE);
Display display = fTableCursor.getDisplay();
// set up cursor color
fTableCursor.setBackground(display.getSystemColor(SWT.COLOR_LIST_SELECTION));
fTableCursor.setForeground(display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT));
updateCursorPosition(fRow, fCol, true);
fTableCursor.setFont(JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME));
fTableCursor.setVisible(true);
fTableCursor.setFocus();
fTableCursor.addSelectionListener(new SelectionListener() {
// for keeping track of cursor positions
public void widgetSelected(SelectionEvent e)
{
if (fTableCursor != null)
{
fViewTab.updateSyncTopAddress(true);
fRow = fTable.indexOf(fTableCursor.getRow());
fCol = fTableCursor.getColumn();
// cursor position has changed, update selected address
fViewTab.updateSelectedAddress(fTableCursor.getRow(), fTableCursor.getColumn());
fViewTab.updateTableSelection();
}
}
public void widgetDefaultSelected(SelectionEvent e)
{
}});
fTableCursor.addTraverseListener(fTraverseEvtListener);
// set up context menu for the cursor
// otherwise, cell will not show context menu when it is highlighted
if (fMenuManager != null)
{
createContextMenu();
}
// TODO: Moved this to viewTabCursorManager?
// need to revisit this
fTableCursor.addKeyListener(fViewTab);
fTableCursor.addMouseListener(fMouseEventListener);
}
/**
* Hook up context menu with the cursor.
* This is needed when user RMC at cursor. Without this the context
* menu activation will not be detected by the cursor
* @return
*/
private Menu createContextMenu()
{
fMenuDisposed = false;
Menu menu = fMenuManager.createContextMenu(fTable);
fTable.setMenu(menu);
if (fTableCursor != null)
{
Control menuControl = fTableCursor;
menuControl.setMenu(menu);
menu.addDisposeListener(new DisposeListener(){
public void widgetDisposed(DisposeEvent e)
{
fMenuDisposed = true;
Menu disposedMenu = (Menu)e.getSource();
disposedMenu.removeDisposeListener(this);
}});
}
return menu;
}
/**
* Redraw cursors
*/
public void redrawCursors()
{
if (fTableCursor != null && fTableCursor.isVisible())
{
fTableCursor.setSelection(fRow, fCol);
fTableCursor.redraw();
}
}
/**
* Clean up cursor manager
*/
public void dispose()
{
if (fTableCursor != null)
{
// TableCursor changes the way how it is disposed.
// SWT disposes the cursor when the view tab is dispose
// disposing the cursor now causes SWT error
// fTableCursor.dispose();
fTableCursor = null;
}
if (editor != null)
{
editor.dispose();
editor = null;
}
if (fTable != null)
fTable.removeMouseListener(fMouseEventListener);
}
/**
* @return lead cursor
*/
public TableCursor getLeadCursor()
{
return fTableCursor;
}
/**
* Hide cursor
*/
public void hideCursor()
{
fTableCursor.setVisible(false);
}
/**
* When cursor is to be shown again, if the cursor is previously dispose,
* create a new cursor and position at last remembered position
*/
public void showCursor()
{
if (fTableCursor == null)
{
createCursor();
}
if (fMenuDisposed)
{
createContextMenu();
}
if (!fTableCursor.isVisible())
{
fTableCursor.setVisible(true);
}
}
/**
* Causes the cursor to gain focus
*/
public void setCursorFocus()
{
if (fTableCursor != null)
fTableCursor.setFocus();
}
/**
* Set new row/col position in cursor. If cursor is not visible, (cursor is null),
* call showCursor to create new cursor.
* @param row
* @param col
*/
public void updateCursorPosition(int row, int col, boolean showCursor)
{
if (row < 0
|| row >= fTable.getItemCount()
|| col < 0
|| col >= fTable.getColumnCount()) {
return;
}
this.fRow = row;
this.fCol = col;
fTableCursor.setSelection(row, col);
if (showCursor)
showCursor();
}
/**
* Activate celll editor and prefill it with initial value.
* If initialValue is null, use cell content as initial value
* @param initialValue
*/
public void activateCellEditor(String initialValue) {
// do not allow user to edit address column
if (fCol == 0 || fCol > getNumCol())
{
return;
}
ICellModifier cellModifier = null;
if (fTableViewer != null)
{
cellModifier = fTableViewer.getCellModifier();
}
TableItem tableItem = fTable.getItem(fRow);
Object element = tableItem.getData();
Object property = fTableViewer.getColumnProperties()[fCol];
Object value = cellModifier.getValue(element, (String)property);
// The cell modifier canModify function always returns false if the edit action
// is not invoked from here. This is to prevent data to be modified when
// the table cursor loses focus from a cell. By default, data will
// be changed in a table when the cell loses focus. This is to workaround
// this default behaviour and only change data when the cell editor
// is activated.
((MemoryViewCellModifier)cellModifier).setEditActionInvoked(true);
boolean canEdit = cellModifier.canModify(element, (String)property);
((MemoryViewCellModifier)cellModifier).setEditActionInvoked(false);
if (!canEdit)
return;
// activate based on current cursor position
TextCellEditor selectedEditor = (TextCellEditor)fTableViewer.getCellEditors()[fCol];
if (fTableViewer != null && cellModifier != null && selectedEditor != null && tableItem != null)
{
// The control that will be the editor must be a child of the Table
Text text = (Text)selectedEditor.getControl();
String cellValue = null;
if (initialValue != null)
{
cellValue = initialValue;
}
else
{
cellValue = ((String)value);
}
if (fRenderingId.equals(IMemoryViewConstants.RENDERING_RAW_MEMORY))
cellValue = cellValue.toUpperCase();
text.setText(cellValue);
editor.horizontalAlignment = SWT.LEFT;
editor.grabHorizontal = true;
// Open the text editor in selected column of the selected row.
editor.setEditor (text, tableItem, fCol);
// Assign focus to the text control
selectedEditor.setFocus();
if (initialValue != null)
{
text.clearSelection();
}
text.setFont(JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME));
// add listeners for the text control
addListeners(text);
// move cursor below text control
fTableCursor.moveBelow(text);
}
}
/**
* @return
*/
private int getNumCol() {
int bytesPerLine = fViewTab.getBytesPerLine();
int columnSize = fViewTab.getColumnSize();
return bytesPerLine/columnSize;
}
/**
* Modify value and send new value to debug adapter
* @param row
* @param col
* @param newValue
* @throws NumberFormatException
*/
private void modifyValue(int row, int col, String newValue) throws NumberFormatException
{
if (newValue.length() == 0)
{
// do not do anything if user has not entered anything
return;
}
TableItem tableItem = fTable.getItem(row);
Object property = fTableViewer.getColumnProperties()[col];
// get old value
Object element = tableItem.getData();
ICellModifier cellModifier = fTableViewer.getCellModifier();
String oldValue = (String)cellModifier.getValue(element, (String)property);
if (fRenderingId.equals(IMemoryViewConstants.RENDERING_RAW_MEMORY))
{
oldValue = oldValue.toUpperCase();
newValue = newValue.toUpperCase();
// only compare up to the length of new value, leave the rest of the memory unchanged
if (newValue.length() <= oldValue.length())
{
oldValue = oldValue.substring(0, newValue.length());
}
if (!oldValue.equals(newValue))
{
fTableViewer.getCellModifier().modify(tableItem, (String)property, newValue);
}
}
else
{
// just modify
fTableViewer.getCellModifier().modify(tableItem, (String)property, newValue);
}
}
/**
* @param font
*/
protected void setFont(Font font)
{
if (fTableCursor != null)
{
fTableCursor.setFont(font);
}
}
/**
* @param text
*/
private void addListeners(Text text) {
// add listeners to text
text.addFocusListener(fTextFocusListener);
text.addTraverseListener(fTraverseEvtListener);
text.addKeyListener(fTextKeyListener);
text.addKeyListener(fViewTab);
}
/**
* @param text
*/
private void removeListeners(Text text) {
// remove traverse listener when focus is lost
text.removeTraverseListener(fTraverseEvtListener);
text.removeFocusListener(fTextFocusListener);
text.removeKeyListener(fTextKeyListener);
text.removeKeyListener(fViewTab);
}
}