blob: d2442ccddb68cb467b76f8d81668f89e08a7a48b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2016 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
* Manumitting Technologies Inc - bug 488721
******************************************************************************/
package org.eclipse.ui.internal.quickaccess;
import org.eclipse.jface.resource.DeviceResourceException;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.graphics.TextStyle;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
import org.eclipse.ui.internal.WorkbenchImages;
import org.eclipse.ui.internal.WorkbenchPlugin;
class QuickAccessEntry {
boolean firstInCategory;
boolean lastInCategory;
QuickAccessElement element;
QuickAccessProvider provider;
int[][] elementMatchRegions;
int[][] providerMatchRegions;
/**
* Provides a rough indicator of how good of a match this entry was to its
* filter. Lower values indicate better match quality. A value of 0
* indicates the filter string was an exact match to the label or that there
* is no filter being applied.
*/
private int matchQuality;
/**
* Indicates the filter string was a perfect match to the label or there is
* no filter applied
*
* @see #getMatchQuality()
*/
public static final int MATCH_PERFECT = 0;
/**
* Indicates this entry is very relevant for the filter string. Recommended
* value for when the filter was found at the start of the element's label
* or a complete case sensitive camel case match.
*
* @see #getMatchQuality()
*/
public static final int MATCH_EXCELLENT = 5;
/**
* Indicates this entry is relevant for the filter string. Recommended value
* for when the complete filter was found somewhere inside the element's
* label or provider.
*
* @see #getMatchQuality()
*/
public static final int MATCH_GOOD = 10;
/**
* Indicates only part of the filter string matches to the element's label.
*
* @see #getMatchQuality()
*/
public static final int MATCH_PARTIAL = 15;
/**
* Creates a new quick access entry from the given element and provider. If
* no filter was used to match this entry the element/provider match regions
* may be empty and the match quality should be {@link #MATCH_PERFECT}
*
* @param element
* the element this entry will represent
* @param provider
* the provider that owns this entry
* @param elementMatchRegions
* list of text regions the filter string matched in the element
* label, possibly empty
* @param providerMatchRegions
* list of text regions the filter string matches in the provider
* label, possible empty
* @param matchQuality
* a rough indication of how closely the filter matched, lower
* values indicate a better match. It is recommended to use the
* constants available on this class: {@link #MATCH_PERFECT},
* {@link #MATCH_EXCELLENT}, {@link #MATCH_GOOD},
* {@link #MATCH_PARTIAL}
*/
QuickAccessEntry(QuickAccessElement element, QuickAccessProvider provider,
int[][] elementMatchRegions, int[][] providerMatchRegions, int matchQuality) {
this.element = element;
this.provider = provider;
this.elementMatchRegions = elementMatchRegions;
this.providerMatchRegions = providerMatchRegions;
this.matchQuality = matchQuality;
}
Image getImage(QuickAccessElement element, ResourceManager resourceManager) {
Image image = findOrCreateImage(element.getImageDescriptor(),
resourceManager);
if (image == null) {
image = WorkbenchImages
.getImage(IWorkbenchGraphicConstants.IMG_OBJ_ELEMENT);
}
return image;
}
private Image findOrCreateImage(ImageDescriptor imageDescriptor,
ResourceManager resourceManager) {
if (imageDescriptor == null) {
return null;
}
Image image = (Image) resourceManager.find(imageDescriptor);
if (image == null) {
try {
image = resourceManager.createImage(imageDescriptor);
} catch (DeviceResourceException e) {
WorkbenchPlugin.log(e);
}
}
return image;
}
public void measure(Event event, TextLayout textLayout,
ResourceManager resourceManager, TextStyle boldStyle) {
Table table = ((TableItem) event.item).getParent();
textLayout.setFont(table.getFont());
event.width = 0;
switch (event.index) {
case 0:
if (firstInCategory || providerMatchRegions.length > 0) {
textLayout.setText(provider.getName());
if (boldStyle != null) {
for (int[] matchRegion : providerMatchRegions) {
textLayout.setStyle(boldStyle, matchRegion[0], matchRegion[1]);
}
}
} else {
textLayout.setText(""); //$NON-NLS-1$
}
break;
case 1:
textLayout.setText(element.getLabel());
// Two situations for measuring icons:
// - command with very large icon image (500x500) [scale down]
// - command with normal image (16x16) but small text-height (8pt)
Image image = getImage(element, resourceManager);
Rectangle imageRect = image.getBounds();
Rectangle textBounds = textLayout.getBounds();
int iconSize = imageRect.height;
// Heuristic: only scale image if has double the pixels
if (iconSize > 16 && iconSize >= 2 * textBounds.height) {
// image will be scaled down to fit
iconSize = textBounds.height;
}
// include additional line 1 for category separator
event.height = Math.max(event.height, iconSize + 3);
event.width += iconSize + 4;
if (boldStyle != null) {
for (int[] matchRegion : elementMatchRegions) {
textLayout.setStyle(boldStyle, matchRegion[0], matchRegion[1]);
}
}
break;
}
Rectangle rect = textLayout.getBounds();
event.width += rect.width + 4;
event.height = Math.max(event.height, rect.height + 2);
}
public void paint(Event event, TextLayout textLayout,
ResourceManager resourceManager, TextStyle boldStyle, Color grayColor) {
final Table table = ((TableItem) event.item).getParent();
textLayout.setFont(table.getFont());
switch (event.index) {
case 0:
if (firstInCategory || providerMatchRegions.length > 0) {
textLayout.setText(provider.getName());
if (boldStyle != null) {
for (int[] matchRegion : providerMatchRegions) {
textLayout.setStyle(boldStyle, matchRegion[0],
matchRegion[1]);
}
}
if (grayColor != null && providerMatchRegions.length > 0 && !firstInCategory) {
event.gc.setForeground(grayColor);
}
Rectangle availableBounds = ((TableItem) event.item).getTextBounds(event.index);
Rectangle requiredBounds = textLayout.getBounds();
textLayout.draw(event.gc, availableBounds.x + 1, availableBounds.y
+ (availableBounds.height - requiredBounds.height) / 2);
}
break;
case 1:
String label = element.getLabel();
if (element instanceof CommandElement) {
CommandElement commandElement = (CommandElement) element;
String binding = commandElement.getBinding();
if (binding != null) {
StyledString styledString = StyledCellLabelProvider.styleDecoratedString(label,
StyledString.QUALIFIER_STYLER, new StyledString(commandElement
.getCommand()));
StyleRange[] styleRanges = styledString.getStyleRanges();
for (StyleRange styleRange : styleRanges) {
textLayout.setStyle(styleRange, styleRange.start,
styleRange.start + styleRange.length);
}
}
}
// draw images to fit square area sized by the text area
Image image = getImage(element, resourceManager);
Rectangle availableBounds = ((TableItem) event.item).getTextBounds(event.index);
Rectangle requiredBounds = textLayout.getBounds();
Rectangle imageBounds = image.getBounds();
// 3 = top + bottom + category lines
int maxImageSize = availableBounds.height - 3;
// preserve aspect ratio
int destHeight = Math.min(imageBounds.height, maxImageSize);
int destWidth = destHeight * imageBounds.width / imageBounds.height;
// and centre within available space; remove 1 from height for
// category separator
int startX = (maxImageSize - destWidth) / 2;
int startY = (availableBounds.height - 1 - destHeight) / 2;
event.gc.drawImage(image, 0, 0, imageBounds.width, imageBounds.height,
availableBounds.x + startX, availableBounds.y + startY,
destWidth, destHeight);
textLayout.setText(label);
if (boldStyle != null) {
for (int[] matchRegion : elementMatchRegions) {
textLayout.setStyle(boldStyle, matchRegion[0], matchRegion[1]);
}
}
textLayout.draw(event.gc, availableBounds.x + maxImageSize + 4,
availableBounds.y + (availableBounds.height - requiredBounds.height) / 2);
break;
}
if (lastInCategory) {
if (grayColor != null)
event.gc.setForeground(grayColor);
Rectangle bounds = ((TableItem)event.item).getBounds(event.index);
event.gc.drawLine(Math.max(0, bounds.x - 1), bounds.y + bounds.height - 1, bounds.x + bounds.width, bounds.y
+ bounds.height - 1);
}
}
/**
* @param event
*/
public void erase(Event event) {
// We are only custom drawing the foreground.
event.detail &= ~SWT.FOREGROUND;
}
/**
* Provides a rough indicator of how good of a match this entry was to its
* filter. Lower values indicate better match quality. A value of
* {@link #MATCH_PERFECT} indicates the filter string was an exact match to
* the label or that there is no filter being applied.
*
* @return Returns the match quality
*/
public int getMatchQuality() {
return matchQuality;
}
}