/******************************************************************************* * Copyright (c) 2015 ARM Ltd. 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: * ARM Ltd and ARM Germany GmbH - Initial API and implementation *******************************************************************************/ package com.arm.cmsis.pack.ui.tree; import org.eclipse.jface.util.Geometry; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.MouseTrackAdapter; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; 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.MenuItem; import com.arm.cmsis.pack.ui.CpPlugInUI; import com.arm.cmsis.pack.ui.OpenURL; /** *Default implementation of IColumnAdvisor interface */ public abstract class ColumnAdvisor implements IColumnAdvisor { protected ColumnViewer columnViewer; protected Control control; protected static final Cursor CURSOR_HAND = Display.getCurrent().getSystemCursor(SWT.CURSOR_HAND); // hand cursor for URL text protected Object selectedItem = null; protected Object selectedUpSpinner = null; protected Object selectedDownSpinner = null; protected Object selectedRightAlignedButton = null; public static final Rectangle EMPTY_RECTANGLE = new Rectangle(0,0,0,0); /** * Constructs advisor for a viewer * @param columnViewer ColumnViewer on which the advisor is installed */ public ColumnAdvisor(ColumnViewer columnViewer) { if (columnViewer == null) { return; } this.columnViewer = columnViewer; this.control = columnViewer.getControl(); this.control.addMouseTrackListener(new MouseTrackAdapter() { @Override public void mouseEnter(MouseEvent e) { handleMouseOver(e); } @Override public void mouseExit(MouseEvent e) { handleMouseExit(e); } }); this.control.addMouseMoveListener(new MouseMoveListener() { @Override public void mouseMove(MouseEvent e) { handleMouseOver(e); } }); this.control.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { handleMouseDown(e); } @Override public void mouseUp(MouseEvent e) { handleMouseUp(e); } }); } /** * Handling mouse over event to display hand cursor when necessary. * Default usage is when cell contains an URL * * @param e mouse event */ protected void handleMouseOver(MouseEvent e) { Cursor cursorToSet = null; Point pt = new Point(e.x, e.y); ViewerCell cell = getViewer().getCell(pt); if (cell != null) { int colIndex = cell.getColumnIndex(); Object element = cell.getElement(); if (getCellControlType(element, colIndex) == CellControlType.BUTTON && isEnabled(element, colIndex)) { Rectangle cellBounds = cell.getBounds(); Image img = getImage(element, colIndex); if (img != null) { cellBounds.x += img.getBounds().width; } if (cellBounds.contains(pt)) { cursorToSet = CURSOR_HAND; } } else if (getCellControlType(element, colIndex) == CellControlType.URL) { String url = getUrl(e.x, e.y); if (url != null && !url.isEmpty()) { cursorToSet = CURSOR_HAND; } } } if (cursorToSet == CURSOR_HAND) { if (this.control.getCursor() != CURSOR_HAND) { this.control.setCursor(CURSOR_HAND); } } else if (this.control.getCursor() == CURSOR_HAND) { this.control.setCursor(null); } } /** * Resets cursor 'hand' * @param e mouse event */ protected void handleMouseExit(MouseEvent e) { String url = getUrl(e.x, e.y); if (url == null || url.isEmpty()) { if(this.control.getCursor() == CURSOR_HAND){ this.control.setCursor(null); } } } /** * Handling mouse down event * @param e */ protected void handleMouseDown(MouseEvent e) { if (e.button != 1) { // must be left key return; } Point pt = new Point(e.x, e.y); ViewerCell cell = getViewer().getCell(pt); if (cell == null) { return; } int colIndex = cell.getColumnIndex(); Object element = cell.getElement(); if (!isEnabled(element, colIndex)) { return; } Rectangle cellBounds = cell.getBounds(); Rectangle buttonBounds = EMPTY_RECTANGLE; if (hasSuffixButton(element, colIndex)) { buttonBounds = getSuffixButtonBounds(cellBounds, element, colIndex); cellBounds.width -= buttonBounds.width; } switch (getCellControlType(element, colIndex)) { case BUTTON: if (cellBounds.contains(pt)) { setButtonPressed(element, colIndex, element); } break; case INPLACE_SPIN: inplaceSpinPressed(cellBounds, pt, element, colIndex); break; case INPLACE_CHECK: if(!suffixButtonPressed(buttonBounds, pt, element, colIndex)) { inplaceCheckboxPressed(cellBounds, pt, element, colIndex); } break; default: break; } this.control.redraw(); } /** * Handling mouse up event to display hand cursor when necessary. * Default usage is to opens URL for an URL control * @param e mouse event */ protected void handleMouseUp(MouseEvent e) { if (e.button != 1) { return; } Point pt = new Point(e.x, e.y); ViewerCell cell = getViewer().getCell(pt); if (cell == null) { return; } int colIndex = cell.getColumnIndex(); Object element = cell.getElement(); if (!isEnabled(element, colIndex)) { return; } Rectangle cellBounds = cell.getBounds(); Rectangle buttonBounds = EMPTY_RECTANGLE; if (hasSuffixButton(element, colIndex)) { buttonBounds = getSuffixButtonBounds(cellBounds, element, colIndex); cellBounds.width -= buttonBounds.width; } switch (getCellControlType(element, colIndex)) { case INPLACE_SPIN: inplaceSpinClicked(cellBounds, pt, element, colIndex); break; case INPLACE_CHECK: suffixButtonClicked(buttonBounds, pt, element, colIndex); break; default: // default is to open the url String url = getUrl(e.x, e.y); if (url != null && !url.isEmpty()) { openUrl(url); } break; } } /** * Action to take when in-place spinner is pressed (after mouse down event) * @param cellBounds cell's bounds excluding suffix button if it exists * @param pt the mouse's point * @param element cell's element * @param colIndex cell's column index */ protected void inplaceSpinPressed(Rectangle cellBounds, Point pt, Object element, int colIndex) { Rectangle upSpinnerBounds = getUpSpinnerBounds(cellBounds, element, colIndex); if (upSpinnerBounds.contains(pt)) { setUpSpinnerPressed(element, colIndex, element); } else { Rectangle downSpinnerBounds = getDownSpinnerBounds(cellBounds, element, colIndex); if (downSpinnerBounds.contains(pt)) { setDownSpinnerPressed(element, colIndex, element); } } } /** * Action to take when in-place spinner is clicked (after mouse up event) * @param cellBounds cell's bounds excluding suffix button if it exists * @param pt the mouse's point * @param element cell's element * @param colIndex cell's column index */ protected void inplaceSpinClicked(Rectangle cellBounds, Point pt, Object element, int colIndex) { Rectangle upBound = getUpSpinnerBounds(cellBounds, element, colIndex); if (isUpSpinnerPressed(element, colIndex) && upBound.contains(pt)) { long newVal = getCurrentSelectedIndex(element, colIndex) + getSpinStep(element, colIndex); if (newVal <= getMaxCount(element, colIndex)) { setCurrentSelectedIndex(element, colIndex, newVal); } } else if (isDownSpinnerPressed(element, colIndex)) { Rectangle downBound = getDownSpinnerBounds(cellBounds, element, colIndex); if (downBound.contains(pt)) { long newVal = getCurrentSelectedIndex(element, colIndex) - getSpinStep(element, colIndex); if (newVal >= getMinCount(element, colIndex)) { setCurrentSelectedIndex(element, colIndex, newVal); } } } setUpSpinnerPressed(element, colIndex, null); setDownSpinnerPressed(element, colIndex, null); this.control.redraw(); } /** * Return true if the checkbox in the cell is pressed * @param cellBounds cell's bounds excluding suffix button if it exists * @param pt the mouse's point * @param element cell's element * @param colIndex cell's column index * @return true if the checkbox in the cell is pressed */ protected boolean inplaceCheckboxPressed(Rectangle cellBounds, Point pt, Object element, int colIndex) { Image image = getCheckboxImage(element, colIndex); if (image == null) { return false; } int x = cellBounds.x; int y = cellBounds.y; int width = image.getBounds().width; int height = image.getBounds().height; Rectangle checkboxBound = new Rectangle(x, y, width, height); if (checkboxBound.contains(pt)) { boolean isChecked = getCheck(element, colIndex); setCheck(element, colIndex, !isChecked); return true; } return false; } /** * Return true if the suffix button in the cell is pressed * @param buttonBounds the suffix button's bounds that the click happens * @param pt the mouse's point * @param element cell's element * @param colIndex cell's column index * @return true if the suffix button in the cell is pressed */ protected boolean suffixButtonPressed(Rectangle buttonBounds, Point pt, Object element, int colIndex) { if (isSuffixButtonEnabled(element, colIndex) && buttonBounds.contains(pt)) { setSuffixButtonPressed(element, colIndex, element); return true; } return false; } /** * Action to take when the suffix button is clicked (after mouse up event) * @param buttonBounds the suffix button's bounds that the click happens * @param pt the mouse's point * @param element cell's element * @param colIndex cell's column index */ protected void suffixButtonClicked(Rectangle buttonBounds, Point pt, Object element, int colIndex) { if (isSuffixButtonPressed(element, colIndex) && buttonBounds.contains(pt)) { Rectangle rect = Geometry.toDisplay(control, buttonBounds); executeSuffixButtonAction(element, colIndex, new Point(rect.x, rect.y)); } setSuffixButtonPressed(element, colIndex, null); this.control.redraw(); } /** * Executes an action associated with the suffix button * @param element cell's element * @param colIndex cell's column index * @param pt point to show menu if needed (in display coordinates) */ protected void executeSuffixButtonAction(Object element, int colIndex, Point pt) { // default does nothing } public String getUrl(int x, int y){ Point pt = new Point(x, y); ViewerCell cell = getViewer().getCell(pt); if (cell != null) { int colIndex = cell.getColumnIndex(); Object element = cell.getElement(); if (getCellControlType(element, colIndex) == CellControlType.URL) { Rectangle cellBounds = cell.getBounds(); Image img = getImage(element, colIndex); if(img != null) { cellBounds.x+=img.getBounds().width; } if(cellBounds.contains(pt)) { return getUrl(element, colIndex); } } } return null; } @Override public ColumnViewer getViewer() { return columnViewer; } @Override public CellControlType getCellControlType(Object obj, int columnIndex) { return CellControlType.TEXT; } @Override public boolean getCheck(Object obj, int columnIndex) { return false; } @Override public String getUrl(Object obj, int columnIndex) { return null; } @Override public void openUrl(String url) { OpenURL.open(url, this.control != null ? this.control.getShell() : null); } @Override public boolean isEnabled(Object obj, int columnIndex) { return true; } @Override public void setString(Object obj, int columnIndex, String newVal) { } @Override public void setCurrentSelectedIndex(Object obj, int columnIndex, long newVal) { } @Override public long getCurrentSelectedIndex(Object element, int columnIndex) { return -1; } @Override public long getMaxCount(Object obj, int columnIndex) { return 0; } @Override public long getMinCount(Object obj, int columnIndex) { return 0; } @Override public long getSpinStep(Object obj, int columnIndex) { return 1; } @Override public int getItemBase(Object obj, int columnIndex) { return 10; } @Override public String[] getStringArray(Object obj, int columnIndex) { return null; } @Override public String getDefaultString(Object obj, int columnIndex) { return null; } @Override public boolean isDefault(Object obj, int columnIndex) { return false; } @Override public boolean canEdit(Object obj, int columnIndex) { return false; } @Override public Image getImage(Object obj, int columnIndex) { return null; } @Override public Image getSuffixButtonImage(Object obj, int columnIndex) { return null; } @Override public Color getBgColor(Object obj, int columnIndex) { return null; } @Override public String getTooltipText(Object obj, int columnIndex) { return null; } @Override public void setCheck(Object element, int columnIndex, boolean newVal) { } @Override public void setButtonPressed(Object obj, int columnIndex, Object newVal) { if (getCellControlType(obj, columnIndex) == CellControlType.BUTTON) { selectedItem = newVal; } } @Override public boolean isButtonPressed(Object obj, int columnIndex) { if (getCellControlType(obj, columnIndex) == CellControlType.BUTTON) { return selectedItem == obj; } return false; } @Override public void setUpSpinnerPressed(Object obj, int columnIndex, Object newVal) { if (getCellControlType(obj, columnIndex) == CellControlType.INPLACE_SPIN) { selectedUpSpinner = newVal; } } @Override public boolean isUpSpinnerPressed(Object obj, int columnIndex) { if (getCellControlType(obj, columnIndex) == CellControlType.INPLACE_SPIN) { return selectedUpSpinner == obj; } return false; } @Override public void setDownSpinnerPressed(Object obj, int columnIndex, Object newVal) { if (getCellControlType(obj, columnIndex) == CellControlType.INPLACE_SPIN) { selectedDownSpinner = newVal; } } @Override public boolean isDownSpinnerPressed(Object obj, int columnIndex) { if (getCellControlType(obj, columnIndex) == CellControlType.INPLACE_SPIN) { return selectedDownSpinner == obj; } return false; } @Override public int getSpinnerWidth(Rectangle cellBounds, Object obj, int columnIndex) { int itWidth = getImageTextWidth(obj, columnIndex); return Math.max(0, Math.min(SPINNER_WIDTH, cellBounds.width - itWidth)); } @Override public boolean hasSuffixButton(Object obj, int columnIndex) { return false; } @Override public void setSuffixButtonPressed(Object obj, int columnIndex, Object newVal) { if (getCellControlType(obj, columnIndex) == CellControlType.INPLACE_CHECK) { selectedRightAlignedButton = newVal; } } @Override public boolean isSuffixButtonPressed(Object obj, int columnIndex) { if (getCellControlType(obj, columnIndex) == CellControlType.INPLACE_CHECK) { return selectedRightAlignedButton == obj; } return false; } @Override public Rectangle getSuffixButtonBounds(Rectangle cellBounds, Object obj, int columnIndex) { if(!hasSuffixButton(obj, columnIndex)) { return EMPTY_RECTANGLE; } int width = Math.min(SUFFIX_BUTTON_WIDTH, cellBounds.width); int height = cellBounds.height; int x = cellBounds.x + cellBounds.width - width; int y = cellBounds.y; return new Rectangle(x, y, width, height); } @Override public boolean isSuffixButtonEnabled(Object obj, int columnIndex) { return isEnabled(obj, columnIndex) && getCheck(obj, columnIndex); } @Override public Menu getMenu(Object obj, int columnIndex) { String[] strings = getStringArray(obj, columnIndex); String selectedString = getString(obj, columnIndex); String defaultString = getDefaultString(obj, columnIndex); boolean bDefault = defaultString !=null && isDefault(obj, columnIndex); return createMenu(strings, selectedString, defaultString, bDefault); } @Override public boolean isEmpty(Object obj, int columnIndex) { return false; } @Override public Image getCheckboxImage(Object obj, int columnIndex) { if (getCellControlType(obj, columnIndex) == CellControlType.INPLACE_CHECK || getCellControlType(obj, columnIndex) == CellControlType.CHECK) { boolean check = getCheck(obj, columnIndex); boolean enabled = canEdit(obj, columnIndex); Image image; if (enabled) { image = check ? CpPlugInUI.getImage(CpPlugInUI.ICON_CHECKED) : CpPlugInUI.getImage(CpPlugInUI.ICON_UNCHECKED); } else { image = check ? CpPlugInUI.getImage(CpPlugInUI.ICON_CHECKED_GREY) : CpPlugInUI.getImage(CpPlugInUI.ICON_UNCHECKED_GREY); } return image; } return null; } /** * Creates menu * @param strings collection of strings * @return Menu */ protected Menu createMenu(String[] strings) { if(strings == null || strings.length == 0) { return null; } Menu menu = new Menu(this.control); for (String s : strings) { MenuItem menuItem = new MenuItem(menu, SWT.NONE); menuItem.setText(s); } return menu; } /** * Creates menu * @param parent parent control * @param strings collection of strings * @param selectedString selected item * @param defaultString default item if any or null * @return Menu */ protected Menu createMenu(String[] strings, String selectedString, String defaultString, boolean bDefault) { if(strings == null || strings.length == 0) { return null; } Menu menu = new Menu(this.control); // insert default value first if(defaultString != null){ MenuItem menuItem = new MenuItem(menu, SWT.RADIO); menuItem.setText(defaultString); menuItem.setSelection(bDefault); menuItem = new MenuItem(menu, SWT.SEPARATOR); } for (String s : strings) { MenuItem menuItem = new MenuItem(menu, SWT.RADIO); menuItem.setText(s); menuItem.setSelection(!bDefault && s.equals(selectedString)); } return menu; } /** * Get the bounds of the up spinner button in this cell * @param cellBounds cell's bounds excluding suffix button if it exists * @return The bounds of the up spinner button in this cell */ protected Rectangle getUpSpinnerBounds(Rectangle cellBounds, Object obj, int columnIndex) { int width = getSpinnerWidth(cellBounds, obj, columnIndex); int height = cellBounds.height / 2; int x = cellBounds.x + cellBounds.width - width; int y = cellBounds.y + 1; return new Rectangle(x, y, width, height); } /** * Get the bounds of the down spinner button in this cell * @param cellBounds cell's bounds excluding suffix button if it exists * @return The bounds of the down spinner button in this cell */ protected Rectangle getDownSpinnerBounds(Rectangle cellBounds, Object obj, int columnIndex) { int width = getSpinnerWidth(cellBounds, obj, columnIndex); int height = cellBounds.height / 2 - 1; int x = cellBounds.x + cellBounds.width - width; int y = cellBounds.y + height + 1; return new Rectangle(x, y, width, height); } /** * Get the added width of image and text together * @param obj cell's object * @param columnIndex cell's columnIndex * @return Get the added width of image and text together */ protected int getImageTextWidth(Object obj, int columnIndex) { Image image = getImage(obj, columnIndex); int imageWidth; if (image == null) { imageWidth = 0; } else { imageWidth = image.getBounds().width; } String text = getString(obj, columnIndex); int textWidth = 0; if (text != null) { textWidth = text.length() * CHAR_WIDTH + 5; } return imageWidth + textWidth; } }