/******************************************************************************* * Copyright (c) 2006, 2016 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 * Alvaro Sanchez-leon (Ericsson) - Add hovering support to the traditional memory render (Bug 489505) *******************************************************************************/ package org.eclipse.cdt.debug.ui.memory.traditional; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.cdt.debug.core.model.IMemoryBlockAddressInfoRetrieval.IMemoryBlockAddressInfoItem; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.model.MemoryByte; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseTrackAdapter; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; public class DataPane extends AbstractPane { private Shell fToolTipShell; private final static String UNICODE_NORTH_WEST_ARROW = "\u2196"; public DataPane(Rendering parent) { super(parent); } @Override protected String getCellText(MemoryByte bytes[]) { return fRendering.getRadixText(bytes, fRendering.getRadix(), fRendering .isTargetLittleEndian()); } @Override protected void editCell(BigInteger address, int subCellPosition, char character) { try { MemoryByte bytes[] = fRendering.getBytes(fCaretAddress, fRendering .getBytesPerColumn()); String cellText = getCellText(bytes); if(cellText == null) return; StringBuilder cellTextBuffer = new StringBuilder(cellText); cellTextBuffer.setCharAt(subCellPosition, character); BigInteger value = new BigInteger(cellTextBuffer.toString().trim(), fRendering.getNumericRadix(fRendering.getRadix())); final boolean isSignedType = fRendering.getRadix() == Rendering.RADIX_DECIMAL_SIGNED; final boolean isSigned = isSignedType && value.compareTo(BigInteger.valueOf(0)) < 0; int bitCount = value.bitLength(); if(isSignedType) bitCount++; if(bitCount > fRendering.getBytesPerColumn() * 8) return; int byteLen = fRendering.getBytesPerColumn(); byte[] byteData = new byte[byteLen]; for(int i = 0; i < byteLen; i++) { int bits = 255; if(isSignedType && i == byteLen - 1) bits = 127; byteData[i] = (byte) (value.and(BigInteger.valueOf(bits)) .intValue() & bits); value = value.shiftRight(8); } if(isSigned) byteData[byteLen - 1] |= 128; if(!fRendering.isDisplayLittleEndian()) { byte[] byteDataSwapped = new byte[byteData.length]; for(int i = 0; i < byteData.length; i++) byteDataSwapped[i] = byteData[byteData.length - 1 - i]; byteData = byteDataSwapped; } if(byteData.length != bytes.length) return; TraditionalMemoryByte bytesToSet[] = new TraditionalMemoryByte[bytes.length]; for(int i = 0; i < byteData.length; i++) { bytesToSet[i] = new TraditionalMemoryByte(byteData[i]); bytesToSet[i].setBigEndian(bytes[i].isBigEndian()); // for embedded, the user wants feedback that the change will be sent to the target, // even if does not change the value. eventually, maybe we need another color to // indicate change. //if(bytes[i].getValue() != byteData[i]) { bytesToSet[i].setEdited(true); } //else { // if(bytes[i] instanceof TraditionalMemoryByte) // bytesToSet[i].setEdited(((TraditionalMemoryByte) bytes[i]).isEdited()); bytesToSet[i].setChanged(bytes[i].isChanged()); } } fRendering.getViewportCache().setEditedValue(address, bytesToSet); advanceCursor(); redraw(); } catch(Exception e) { // do nothing } } @Override protected int getCellWidth() { return getCellCharacterCount() * getCellCharacterWidth() + (fRendering.getCellPadding() * 2); } /** * @return The width length in pixels needed to draw the characters of an addressable unit */ private int getAddressableWidth() { // derive the number of characters per addressable size e.g. 2 * NumOfOctets for hex representation int charsPerOctet = fRendering.getRadixCharacterCount(fRendering.getRadix(), 1); int addressCharacterCount = fRendering.getAddressableSize() * charsPerOctet; // derive width by multiplying by the size of a character return addressCharacterCount * getCellCharacterWidth(); } @Override protected int getCellCharacterCount() { return fRendering.getRadixCharacterCount(fRendering.getRadix(), fRendering.getBytesPerColumn()); } @Override public Point computeSize(int wHint, int hHint) { return new Point(fRendering.getColumnCount() * getCellWidth() + fRendering.getRenderSpacing(), 100); } private BigInteger getCellAddressAt(int x, int y) throws DebugException { BigInteger address = fRendering.getViewportStartAddress(); int col = x / getCellWidth(); int row = y / getCellHeight(); if(col >= fRendering.getColumnCount()) return null; address = address.add(BigInteger.valueOf(row * fRendering.getColumnCount() * fRendering.getAddressesPerColumn())); address = address.add(BigInteger.valueOf(col * fRendering.getAddressesPerColumn())); return address; } /** * @return The address associated to the hovering location */ private BigInteger getAddressAt(int x, int y) { // Resolve the first address in the cell BigInteger cellBaseAddress; try { cellBaseAddress = getCellAddressAt(x, y); } catch (DebugException e) { fRendering.logError(TraditionalRenderingMessages.getString("TraditionalRendering.FAILURE_DETERMINE_ADDRESS_LOCATION"), e); //$NON-NLS-1$ return null; } if (cellBaseAddress == null) { return null; } // Get the start location of the cell Point cellPosition = getCellLocation(cellBaseAddress); if (cellPosition == null) { return null; } // Resolve the horizontal offset between hover location and // the start of the cell int offset = x - cellPosition.x; if (offset < 0) { return null; } // Resolve the number of addresses between hover location and first address in the cell int addressableOffset = offset / getAddressableWidth(); assert addressableOffset <= getAddressableOctetsPerColumn(); return cellBaseAddress.add(BigInteger.valueOf(addressableOffset)); } private Point getAddressLocation(BigInteger address) { // Resolve the location of the cell Point cellLocation = getCellLocation(address); // Resolve the first address in the cell BigInteger baseAddress; try { baseAddress = getCellAddressAt(cellLocation.x, cellLocation.y); } catch (DebugException e) { return null; } if (baseAddress == null) { return null; } int addressSpan = address.subtract(baseAddress).intValue(); // Resolve the horizontal distance from base address to given address in octets int charsWidth = fRendering.getRadixCharacterCount(fRendering.getRadix(), addressSpan) * getCellCharacterWidth(); return new Point(cellLocation.x + charsWidth, cellLocation.y); } @Override protected Point getCellLocation(BigInteger cellAddress) { try { BigInteger address = fRendering.getViewportStartAddress(); // cell offset from base address in octets int cellOffset = cellAddress.subtract(address).intValue(); cellOffset *= fRendering.getAddressableSize(); int row = cellOffset / (fRendering.getColumnCount() * fRendering.getBytesPerColumn()); cellOffset -= row * fRendering.getColumnCount() * fRendering.getBytesPerColumn(); int col = cellOffset / fRendering.getBytesPerColumn(); int x = col * getCellWidth() + fRendering.getCellPadding(); int y = row * getCellHeight() + fRendering.getCellPadding(); return new Point(x, y); } catch(Exception e) { fRendering .logError( TraditionalRenderingMessages .getString("TraditionalRendering.FAILURE_DETERMINE_CELL_LOCATION"), e); //$NON-NLS-1$ return null; } } private Point getRowFirstCellLocation(BigInteger cellAddress) { try { BigInteger address = fRendering.getViewportStartAddress(); // cell offset from base address in octets int cellOffset = cellAddress.subtract(address).intValue(); cellOffset *= fRendering.getAddressableSize(); int row = cellOffset / (fRendering.getColumnCount() * fRendering.getBytesPerColumn()); // column zero plus cell padding int x = fRendering.getCellPadding(); int y = row * getCellHeight() + fRendering.getCellPadding(); return new Point(x, y); } catch (Exception e) { fRendering.logError( TraditionalRenderingMessages.getString("TraditionalRendering.FAILURE_DETERMINE_CELL_LOCATION"), e); //$NON-NLS-1$ return null; } } private Point getRowLastCellLocation(BigInteger cellAddress) { try { BigInteger address = fRendering.getViewportStartAddress(); // cell offset from base address in octets int cellOffset = cellAddress.subtract(address).intValue(); cellOffset *= fRendering.getAddressableSize(); int row = cellOffset / (fRendering.getColumnCount() * fRendering.getBytesPerColumn()); int col = fRendering.getColumnCount() - 1; int x = col * getCellWidth() + fRendering.getCellPadding(); int y = row * getCellHeight() + fRendering.getCellPadding(); return new Point(x, y); } catch (Exception e) { fRendering.logError( TraditionalRenderingMessages.getString("TraditionalRendering.FAILURE_DETERMINE_CELL_LOCATION"), e); //$NON-NLS-1$ return null; } } @Override protected void positionCaret(int x, int y) { try { BigInteger cellAddress = getCellAddressAt(x, y); if(cellAddress != null) { Point cellPosition = getCellLocation(cellAddress); int offset = x - cellPosition.x; int subCellCharacterPosition = offset / getCellCharacterWidth(); if(subCellCharacterPosition == this.getCellCharacterCount()) { cellAddress = cellAddress.add(BigInteger.valueOf(fRendering .getAddressesPerColumn())); subCellCharacterPosition = 0; cellPosition = getCellLocation(cellAddress); } fCaret.setLocation(cellPosition.x + subCellCharacterPosition * getCellCharacterWidth(), cellPosition.y); this.fCaretAddress = cellAddress; this.fSubCellCaretPosition = subCellCharacterPosition; setCaretAddress(fCaretAddress); } } catch(Exception e) { fRendering .logError( TraditionalRenderingMessages .getString("TraditionalRendering.FAILURE_POSITION_CURSOR"), e); //$NON-NLS-1$ } } @Override protected BigInteger getViewportAddress(int col, int row) throws DebugException { BigInteger address = fRendering.getViewportStartAddress(); address = address.add(BigInteger.valueOf((row * fRendering.getColumnCount() + col) * fRendering.getAddressesPerColumn())); return address; } @Override protected void paint(PaintEvent pe) { super.paint(pe); // Allow subclasses to override this method to do their own painting doPaintData(pe); } // Allow subclasses to override this method to do their own painting protected void doPaintData(PaintEvent pe) { GC gc = pe.gc; int cellHeight = getCellHeight(); int cellWidth = getCellWidth(); int columns = fRendering.getColumnCount(); try { BigInteger startAddress = fRendering.getViewportStartAddress(); for(int i = 0; i < fRendering.getRowCount(); i++) { for(int col = 0; col < columns; col++) { gc.setFont(fRendering.getFont()); if (isOdd(col)) gc.setForeground(fRendering.getTraditionalRendering().getColorText()); else gc.setForeground(fRendering.getTraditionalRendering().getColorTextAlternate()); BigInteger cellAddress = startAddress.add(BigInteger.valueOf((i * fRendering.getColumnCount() + col) * fRendering.getAddressesPerColumn())); TraditionalMemoryByte bytes[] = fRendering.getBytes(cellAddress, fRendering.getBytesPerColumn()); boolean drawBox = false; if(fRendering.getSelection().isSelected(cellAddress)) { gc.setBackground(fRendering.getTraditionalRendering().getColorSelection()); gc.fillRectangle(cellWidth * col + fRendering.getCellPadding(), cellHeight * i, cellWidth, cellHeight); gc.setForeground(fRendering.getTraditionalRendering().getColorBackground()); } else { gc.setBackground(fRendering.getTraditionalRendering().getColorBackground()); gc.fillRectangle(cellWidth * col + fRendering.getCellPadding(), cellHeight * i, cellWidth, cellHeight); // Allow subclasses to override this method to do their own coloring applyCustomColor(gc, bytes, col); drawBox = shouldDrawBox(bytes, col); } gc.drawText(getCellText(bytes), cellWidth * col + fRendering.getCellPadding(), cellHeight * i + fRendering.getCellPadding()); if(drawBox) { gc.setForeground(fRendering.getTraditionalRendering().getColorTextAlternate()); gc.drawRectangle(cellWidth * col, cellHeight * i, cellWidth, cellHeight-1); } BigInteger cellEndAddress = cellAddress.add(BigInteger .valueOf(fRendering.getAddressesPerColumn())); cellEndAddress = cellEndAddress.subtract(BigInteger .valueOf(1)); if(fCaretEnabled) { if(cellAddress.compareTo(fCaretAddress) <= 0 && cellEndAddress.compareTo(fCaretAddress) >= 0) { int x = cellWidth * col + fRendering.getCellPadding() + fSubCellCaretPosition * this.getCellCharacterWidth(); int y = cellHeight * i + fRendering.getCellPadding(); fCaret.setLocation(x, y); } } if(fRendering.isDebug()) gc.drawRectangle(cellWidth * col + fRendering.getCellPadding(), cellHeight * i + fRendering.getCellPadding(), cellWidth, cellHeight); } } markAddressesWithAdditionalInfo(gc); } catch(Exception e) { fRendering.logError(TraditionalRenderingMessages .getString("TraditionalRendering.FAILURE_PAINT"), e); //$NON-NLS-1$ } } private void markAddressesWithAdditionalInfo(GC gc) { if (fRendering.isDisposed() || !fRendering.isVisible() || isDisposed()) { return; } final Map<BigInteger, List<IMemoryBlockAddressInfoItem>> addressToInfoItems = fRendering .getVisibleValueToAddressInfoItems(); // Check if there are information items available if (addressToInfoItems.size() < 1) { return; } // Prepare to enclose addresses with additional info in a rectangle int addressableWidth = getAddressableWidth(); if (fRendering.getAddressableSize() == fRendering.getBytesPerColumn()) { // When the cell size is dimensioned to enclose an addressable size, the width can not be larger // than the containing cell, this adjustment is necessary when using radixes where the number of characters // does not increase in proportion to the number of bytes e.g. octal, decimal, etc. addressableWidth = getAddressableWidth() > getCellWidth() ? getCellWidth() : getAddressableWidth(); } assert addressableWidth > 0; // Initialize the dimensions for the rectangle int width = 1; int leftMargin = 1; int rightMargin = leftMargin + 1; int lowerMargin = 1; int lineWidth = 2; int height = getCellTextHeight() - fRendering.getCellPadding() + lowerMargin; // Save current GC settings Color origColor = gc.getForeground(); int origLineWidth = gc.getLineWidth(); gc.setForeground(fRendering.getTraditionalRendering().getColorChanged()); // Set the thickness of the lines being drawn, i.e. thicker than the default gc.setLineWidth(lineWidth); // Loop for each address from lowest to highest value BigInteger[] sortedAddresses = orderItemsAscending(addressToInfoItems.keySet()); // Define rectangle margin space for (BigInteger startAddress : sortedAddresses) { // Resolve rectangle starting point and start / end row references Point location = getAddressLocation(startAddress); Point firstCellInRow = getRowFirstCellLocation(startAddress); Point lastCellInRow = getRowLastCellLocation(startAddress); // Mark each item even if they point to the same start address, // so the end address is visible on each of them List<IMemoryBlockAddressInfoItem> sameStartAddressitems = addressToInfoItems.get(startAddress); // Sort items starting in the same address to draw longest first, this will give more visibility to the embedded markings IMemoryBlockAddressInfoItem[] sameStartOrderedItems = orderItemsByLengthDescending(sameStartAddressitems); for (IMemoryBlockAddressInfoItem item : sameStartOrderedItems) { BigInteger addressUnits = item.getRangeInAddressableUnits(); // Resolve the color for the rectangle Color rangeColor = resolveColor(item.getRegionRGBColor()); if (rangeColor != null) { gc.setForeground(rangeColor); } // The start and end address are part of the length so we need to decrement / adjust by one BigInteger endAddress = startAddress.add(addressUnits.subtract(BigInteger.ONE)); // End location to the start of next address may change to a different row // So it's best to add the addressable width to the beginning of the last address Point endLocation = getAddressLocation(endAddress); endLocation.x = endLocation.x + addressableWidth; // Resolve the rows index as the selection may span multiple rows int rowsIndex = (endLocation.y - location.y) / getCellHeight(); for (int i = 0; i <= rowsIndex; i++) { Point rowLocation = new Point(firstCellInRow.x, firstCellInRow.y + i * getCellHeight()); if (!isRowVisible(rowLocation.y)) { // No need to draw the portion of lines outside the visible area continue; } if (i == 0) { // Enclosing range in first row if (endLocation.y == location.y) { // End and beginning locations are in the same row width = endLocation.x - location.x + rightMargin; gc.drawRectangle(location.x - leftMargin, location.y, width, height); } else { // The end cell is in a different row, // mark from the location to the end of this row width = lastCellInRow.x + addressableWidth * fRendering.getAddressesPerColumn() - location.x + rightMargin; // open ended first row location.x -= leftMargin; drawRectangleOpenEnd(location, width, height, gc); } } else if (i > 0 && i < rowsIndex) { // The marking started before this row and finishes after this row // we need to mark the whole row with opened ends i.e. two bordering lines top / bottom width = lastCellInRow.x + addressableWidth * fRendering.getAddressesPerColumn() - firstCellInRow.x + rightMargin; // parallel lines row assert width > 0; rowLocation.x -= leftMargin; drawParallelLines(rowLocation, width, height, gc); } else if (i == rowsIndex) { // The last row to highlight width = endLocation.x - firstCellInRow.x + rightMargin; // Draw a colored rectangle around the addressable units rowLocation.x -= leftMargin; drawRectangleOpenStart(rowLocation, width, height, gc); } } // Display the associated textual information String info = fRendering.buildAddressInfoString(startAddress, ",", false); if (info.length() > 0) { // Add one character e.g. up arrow, to indicate the start of the data i.e. upper or lower row gc.drawText(UNICODE_NORTH_WEST_ARROW + info, location.x, location.y + getCellTextHeight()); } if (rangeColor != null) { rangeColor.dispose(); } } } // Restore the original color gc.setForeground(origColor); gc.setLineWidth(origLineWidth); } private IMemoryBlockAddressInfoItem[] orderItemsByLengthDescending( List<IMemoryBlockAddressInfoItem> sameStartAddressitems) { if (sameStartAddressitems.isEmpty() || sameStartAddressitems.size() == 1) { // One item, nothing to sort return sameStartAddressitems .toArray(new IMemoryBlockAddressInfoItem[sameStartAddressitems.size()]); } // Perform a bubble sort boolean swapped = true; IMemoryBlockAddressInfoItem temp; for (int i = 0; i < sameStartAddressitems.size() - 1; i++) { swapped = false; for (int j = 0; j < sameStartAddressitems.size() - i - 1; j++) { // If current index item is smaller then swap to get reverse sorting if (sameStartAddressitems.get(j).getRangeInAddressableUnits() .compareTo(sameStartAddressitems.get(j + 1).getRangeInAddressableUnits()) < 0) { temp = sameStartAddressitems.get(j); sameStartAddressitems.set(j, sameStartAddressitems.get(j + 1)); sameStartAddressitems.set(j + 1, temp); swapped = true; } } if (swapped == false) { // No swaps were needed, we are done! break; } } return sameStartAddressitems.toArray(new IMemoryBlockAddressInfoItem[sameStartAddressitems.size()]); } private BigInteger[] orderItemsAscending(Set<BigInteger> keySet) { List<BigInteger> collection = new ArrayList<>(keySet); Collections.sort(collection); return collection.toArray(new BigInteger[collection.size()]); } /** * Convert from int to RGB octets to then create the corresponding Color */ private Color resolveColor(int intColor) { return new Color(getDisplay(), intColor >> 16, (intColor >> 8) & 0xff, intColor & 0xff); } private boolean isRowVisible(int y) { int firstVisibleRow = getAddressLocation(fRendering.getViewportStartAddress()).y; int lastVisibleRow = getAddressLocation(fRendering.getViewportEndAddress()).y; if (y >= firstVisibleRow && y <= lastVisibleRow) { return true; } return false; } private void drawRectangleOpenStart(Point location, int width, int height, GC gc) { gc.drawRectangle(location.x, location.y, width, height); // clear start border eraseVerticalLine(location, height, gc); } private void drawRectangleOpenEnd(Point location, int width, int height, GC gc) { gc.drawRectangle(location.x, location.y, width, height); // clear end border Point erasep = new Point(location.x + width, location.y); eraseVerticalLine(erasep, height, gc); } private void eraseVerticalLine(Point erasep, int height, GC gc) { Color currentColor = gc.getForeground(); gc.setForeground(fRendering.getTraditionalRendering().getColorBackground()); gc.drawLine(erasep.x, erasep.y, erasep.x, erasep.y + height); gc.setForeground(currentColor); } private void drawParallelLines(Point location, int width, int height, GC gc) { // NOTE: Writing parallel lines would be preferred, however this did not work in my environment // gc.drawLine(location.x, location.y , location.x + width, location.y); // gc.drawLine(location.x, location.y + height, location.x + width, location.y + height); // So we use the work around of writing a rectangle and erase start / end borders gc.drawRectangle(location.x, location.y, width, height); // clear start border eraseVerticalLine(location, height, gc); // clear end border Point erasep = new Point(location.x + width, location.y); eraseVerticalLine(erasep, height, gc); } // Allow subclasses to override this method to do their own coloring protected void applyCustomColor(GC gc, TraditionalMemoryByte bytes[], int col) { // TODO consider adding finer granularity? boolean anyByteEditing = false; for (int n = 0; n < bytes.length && !anyByteEditing; n++) if (bytes[n] instanceof TraditionalMemoryByte) if (bytes[n].isEdited()) anyByteEditing = true; TraditionalRendering ren = fRendering.getTraditionalRendering(); if (isOdd(col)) gc.setForeground(ren.getColorText()); else gc.setForeground(ren.getColorTextAlternate()); gc.setBackground(ren.getColorBackground()); if (anyByteEditing) { gc.setForeground(ren.getColorEdit()); gc.setFont(ren.getFontEdit(gc.getFont())); } else { boolean isColored = false; for (int i = 0; i < fRendering.getHistoryDepth() && !isColored; i++) { // TODO consider adding finer granularity? for (int n = 0; n < bytes.length; n++) { if (bytes[n].isChanged(i)) { if (i == 0) gc.setForeground(ren.getColorsChanged()[i]); else gc.setBackground(ren.getColorsChanged()[i]); gc.setFont(ren.getFontChanged(gc.getFont())); isColored = true; break; } } } } } @Override public void dispose() { super.dispose(); if (fToolTipShell != null) { fToolTipShell.dispose(); fToolTipShell = null; } } @Override protected MouseTrackAdapter createMouseHoverListener() { return new DataPaneMouseHoverListener(); } private int getAddressableOctetsPerColumn() { // Prevent division by zero int addressableSize = (fRendering.getAddressableSize() > 0) ? fRendering.getAddressableSize() : 1; return fRendering.getBytesPerColumn() / addressableSize; } class DataPaneMouseHoverListener extends MouseTrackAdapter { private BigInteger fTooltipAddress = null; private final Label fLabelContent; DataPaneMouseHoverListener() { fLabelContent = createToolTip(); } @Override public void mouseExit(MouseEvent e) { if (fToolTipShell != null && !fToolTipShell.isDisposed()) { fToolTipShell.setVisible(false); fTooltipAddress = null; } } @Override public void mouseHover(MouseEvent e) { if (e.widget == null || !(e.widget instanceof Control) || fToolTipShell == null || fToolTipShell.isDisposed()) { return; } Control control = (Control) e.widget; // Resolve the address associated to the hovering location BigInteger address = getAddressAt(e.x, e.y); if (address == null) { // Invalid Address at location return; } // Display tooltip if there is a change in hover Point hoverPoint = control.toDisplay(new Point(e.x, e.y)); if (!fToolTipShell.isVisible() || !address.equals(fTooltipAddress)) { diplayToolTip(hoverPoint, address); } else { // Still pointing to the same cell return; } // Keep Track of the latest visited address fTooltipAddress = address; } private void diplayToolTip(Point hoverPoint, BigInteger subAddress) { // Show the current hovering address as the first line in the tooltip StringBuilder sb = new StringBuilder("0x").append(subAddress.toString(16)); // Add additional address information, if available if (fRendering.hasAddressInfo(subAddress)) { String info = fRendering.buildAddressInfoString(subAddress, "\n", true); if (info.length() > 0) { sb.append("\n").append(info); } } fLabelContent.setText(sb.toString()); // Setting location of the tool tip Rectangle shellBounds = fToolTipShell.getBounds(); shellBounds.x = hoverPoint.x; shellBounds.y = hoverPoint.y + getCellHeight(); fToolTipShell.setBounds(shellBounds); fToolTipShell.pack(); fToolTipShell.setVisible(true); } private Label createToolTip() { if (fToolTipShell != null) { fToolTipShell.dispose(); } fToolTipShell = new Shell(getShell(), SWT.ON_TOP | SWT.RESIZE); GridLayout gridLayout = new GridLayout(); gridLayout.numColumns = 1; gridLayout.marginWidth = 2; gridLayout.marginHeight = 0; fToolTipShell.setLayout(gridLayout); fToolTipShell.setBackground(fToolTipShell.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); return createToolTipContent(fToolTipShell); } private Label createToolTipContent(Composite composite) { Label toolTipContent = new Label(composite, SWT.NONE); toolTipContent.setForeground(composite.getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND)); toolTipContent.setBackground(composite.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); toolTipContent.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_CENTER)); return toolTipContent; } } }