| /******************************************************************************* |
| * Copyright (c) 2017 Exyte |
| * |
| * 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: |
| * Yuri Strot - initial API and Implementation |
| *******************************************************************************/ |
| package org.eclipse.ui.glance.controls.items; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.jface.text.TextPresentation; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StyleRange; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.graphics.TextLayout; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Item; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.ui.glance.sources.ColorManager; |
| import org.eclipse.ui.glance.sources.ITextSourceListener; |
| import org.eclipse.ui.glance.utils.TextUtils; |
| |
| /** |
| * @author Yuri Strot |
| * |
| */ |
| public class ItemDecorator implements Listener { |
| |
| public static final int DEFAULT_STYLE = SWT.BACKGROUND | SWT.FOREGROUND | SWT.SELECTED | SWT.HOT; |
| |
| protected Composite composite; |
| protected ItemProvider provider; |
| protected int style; |
| |
| protected TextLayout textLayout; |
| protected Map<ItemCell, StyleRange[]> itemToMatches; |
| protected Map<ItemCell, StyleRange[]> cacheStyles; |
| protected List<ItemCell> cells; |
| protected HashSet<ItemCell> cellSet; |
| |
| protected boolean customPaintingUsed; |
| |
| private ListenerList<ITextSourceListener> listeners = new ListenerList<>(); |
| |
| public ItemDecorator(Composite composite, ItemProvider provider) { |
| this(composite, provider, DEFAULT_STYLE); |
| } |
| |
| public ItemDecorator(Composite composite, ItemProvider provider, int style) { |
| this.composite = composite; |
| this.provider = provider; |
| this.style = style; |
| clearStyles(); |
| init(); |
| } |
| |
| public void addTextSourceListener(ITextSourceListener listener) { |
| listeners.add(listener); |
| } |
| |
| public void removeTextSourceListener(ITextSourceListener listener) { |
| listeners.remove(listener); |
| } |
| |
| public ITextSourceListener[] getListeners() { |
| Object[] objects = listeners.getListeners(); |
| ITextSourceListener[] listeners = new ITextSourceListener[objects.length]; |
| System.arraycopy(objects, 0, listeners, 0, objects.length); |
| return listeners; |
| } |
| |
| public void blocksChanged(ItemCell[] removed, ItemCell[] added) { |
| for (ItemCell cell : removed) { |
| cells.remove(cell); |
| cellSet.remove(cell); |
| } |
| for (ItemCell cell : added) { |
| cells.add(cell); |
| cellSet.add(cell); |
| cell.getItem().addListener(SWT.Dispose, this); |
| } |
| for (ITextSourceListener listener : getListeners()) { |
| listener.blocksChanged(removed, added); |
| } |
| } |
| |
| public void clearStyles() { |
| itemToMatches = new HashMap<ItemCell, StyleRange[]>(); |
| cacheStyles = new HashMap<ItemCell, StyleRange[]>(); |
| } |
| |
| public List<ItemCell> getCells() { |
| return cells; |
| } |
| |
| public void setCells(List<ItemCell> cells) { |
| this.cells = cells; |
| for (ItemCell cell : cells) { |
| cell.getItem().addListener(SWT.Dispose, this); |
| } |
| cellSet = new HashSet<ItemCell>(cells); |
| } |
| |
| public void setStyles(ItemCell cell, StyleRange[] styles) { |
| if (styles == null) |
| itemToMatches.remove(cell); |
| else |
| itemToMatches.put(cell, styles); |
| cacheStyles.put(cell, calculateStyles(cell)); |
| } |
| |
| public void redraw() { |
| Rectangle rect = composite.getClientArea(); |
| composite.redraw(rect.x, rect.y, rect.width, rect.height, true); |
| } |
| |
| public void redraw(ItemCell cell) { |
| Rectangle rect = provider.getBounds(cell.getItem(), cell.getIndex()); |
| composite.redraw(rect.x, rect.y, rect.width, rect.height, true); |
| } |
| |
| protected TextLayout getTextLayout() { |
| if (textLayout == null) { |
| int orientation = composite.getStyle() & (SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT); |
| textLayout = new TextLayout(composite.getDisplay()); |
| textLayout.setOrientation(orientation); |
| } else { |
| textLayout.setText(""); |
| } |
| return textLayout; |
| } |
| |
| protected StyleRange[] getRanges(ItemCell cell) { |
| StyleRange[] ranges = cacheStyles.get(cell); |
| if (ranges == null) { |
| ranges = calculateStyles(cell); |
| cacheStyles.put(cell, ranges); |
| } |
| return ranges; |
| } |
| |
| protected StyleRange[] calculateStyles(ItemCell cell) { |
| StyleRange[] cellStyles = cell.getStyles(); |
| StyleRange[] matchStyles = itemToMatches.get(cell); |
| if (matchStyles == null || matchStyles.length == 0) |
| return cellStyles; |
| if (cellStyles.length == 0) |
| return matchStyles; |
| Region region = new Region(0, cell.getLength()); |
| int size = cellStyles.length + matchStyles.length; |
| TextPresentation presentation = new TextPresentation(region, size); |
| presentation.replaceStyleRanges(cellStyles); |
| presentation.mergeStyleRanges(matchStyles); |
| return TextUtils.getStyles(presentation); |
| } |
| |
| @Override |
| public void handleEvent(Event event) { |
| switch (event.type) { |
| case SWT.PaintItem: |
| paint(event); |
| break; |
| case SWT.EraseItem: |
| erase(event); |
| break; |
| case SWT.Dispose: |
| if (event.widget instanceof Item) { |
| ItemCell cell = new ItemCell((Item) event.widget, event.index, provider); |
| blocksChanged(new ItemCell[] { cell }, new ItemCell[0]); |
| } |
| break; |
| } |
| } |
| |
| protected void paint(Event event) { |
| Item item = (Item) event.item; |
| GC gc = event.gc; |
| // remember colors to restore the GC later |
| Color oldForeground = gc.getForeground(); |
| Color oldBackground = gc.getBackground(); |
| |
| Color foreground = provider.getForeground(item, event.index); |
| if (foreground != null) { |
| gc.setForeground(foreground); |
| } |
| |
| Color background = provider.getBackground(item, event.index); |
| if (background != null) { |
| gc.setBackground(background); |
| } |
| |
| if (!customPaintingUsed) { |
| if (!ColorManager.getInstance().isUseNative() && (event.detail & SWT.SELECTED) != 0) { |
| gc.setBackground(ColorManager.getInstance().getTreeSelectionBg()); |
| gc.setForeground(ColorManager.getInstance().getTreeSelectionFg()); |
| gc.fillRectangle(provider.getBounds(item, event.index)); |
| } |
| |
| Image image = provider.getImage(item, event.index); |
| if (image != null) { |
| Rectangle imageBounds = provider.getImageBounds(item, event.index); |
| if (imageBounds != null) { |
| Rectangle bounds = image.getBounds(); |
| |
| // center the image in the given space |
| int x = imageBounds.x + Math.max(0, (imageBounds.width - bounds.width) / 2); |
| int y = imageBounds.y + Math.max(0, (imageBounds.height - bounds.height) / 2); |
| gc.drawImage(image, x, y); |
| } |
| } |
| } |
| |
| Rectangle textBounds = provider.getTextBounds(item, event.index); |
| if (textBounds != null) { |
| TextLayout layout = getTextLayout(); |
| layout.setText(provider.getText(item, event.index)); |
| layout.setFont(provider.getFont(item, event.index)); |
| |
| ItemCell cell = new ItemCell(item, event.index, provider); |
| if (!cellSet.contains(cell)) { |
| blocksChanged(new ItemCell[0], new ItemCell[] { cell }); |
| } |
| StyleRange[] ranges = getRanges(cell); |
| for (StyleRange range : ranges) { |
| layout.setStyle(range, range.start, range.start + range.length - 1); |
| } |
| |
| Rectangle layoutBounds = layout.getBounds(); |
| |
| int x = textBounds.x; |
| int avg = (textBounds.height - layoutBounds.height) / 2; |
| int y = textBounds.y + Math.max(0, avg); |
| |
| if (customPaintingUsed) { |
| if (containsMatches(cell)) { |
| Rectangle bounds = provider.getBounds(item, event.index); |
| Color color = ColorManager.getInstance().getBackgroundColor(); |
| if ((event.detail & SWT.SELECTED) != 0) |
| color = ColorManager.getInstance().getSelectedBackgroundColor(); |
| int preAlpha = gc.getAlpha(); |
| gc.setAlpha(100); |
| gc.setBackground(color); |
| gc.fillRectangle(bounds); |
| gc.setAlpha(preAlpha); |
| } |
| } else |
| layout.draw(gc, x, y); |
| } |
| |
| gc.setForeground(oldForeground); |
| gc.setBackground(oldBackground); |
| } |
| |
| private boolean containsMatches(ItemCell cell) { |
| return itemToMatches.get(cell) != null; |
| } |
| |
| public void erase(Event event) { |
| if (customPaintingUsed) |
| return; |
| int style = SWT.BACKGROUND | SWT.FOREGROUND; |
| if (!ColorManager.getInstance().isUseNative()) { |
| style |= SWT.SELECTED | SWT.HOT; |
| } |
| |
| event.detail &= ~style; |
| } |
| |
| protected void init() { |
| // FIXME |
| customPaintingUsed = isCustomPaintingUsed(composite); |
| composite.addListener(SWT.EraseItem, this); |
| composite.addListener(SWT.PaintItem, this); |
| redraw(); |
| } |
| |
| private boolean isCustomPaintingUsed(Composite composite) { |
| Listener[] listeners = composite.getListeners(SWT.PaintItem); |
| if (listeners.length > 0) { |
| Listener listener = listeners[listeners.length - 1]; |
| if (!listener.getClass().getName().startsWith("org.eclipse.swt")) |
| return true; |
| } |
| return false; |
| } |
| |
| public void dispose() { |
| if (!disposed) { |
| clearStyles(); |
| composite.removeListener(SWT.PaintItem, this); |
| composite.removeListener(SWT.EraseItem, this); |
| disposed = true; |
| redraw(); |
| } |
| } |
| |
| public boolean isDisposed() { |
| return disposed; |
| } |
| |
| private boolean disposed; |
| |
| } |