| /******************************************************************************* |
| * Copyright (c) 2004, 2015 Tasktop Technologies and others. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * https://www.eclipse.org/legal/epl-2.0 |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Tasktop Technologies - initial API and implementation |
| * Frank Becker - fixes for bug 169916 |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.internal.tasks.ui.views; |
| |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.mylyn.commons.ui.CommonImages; |
| import org.eclipse.mylyn.commons.ui.PlatformUiUtil; |
| import org.eclipse.mylyn.commons.ui.compatibility.CommonFonts; |
| import org.eclipse.mylyn.internal.tasks.core.AbstractTask; |
| import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; |
| import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; |
| import org.eclipse.mylyn.internal.tasks.core.UnmatchedTaskContainer; |
| import org.eclipse.mylyn.internal.tasks.ui.AbstractTaskListFilter; |
| import org.eclipse.mylyn.internal.tasks.ui.ITasksUiPreferenceConstants; |
| import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; |
| import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal; |
| import org.eclipse.mylyn.internal.tasks.ui.views.TaskScheduleContentProvider.StateTaskContainer; |
| import org.eclipse.mylyn.tasks.core.IRepositoryQuery; |
| import org.eclipse.mylyn.tasks.core.ITask; |
| import org.eclipse.mylyn.tasks.core.ITask.SynchronizationState; |
| import org.eclipse.mylyn.tasks.core.ITaskContainer; |
| import org.eclipse.mylyn.tasks.ui.TasksUiImages; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.TreeItem; |
| |
| /** |
| * @author Mik Kersten |
| * @author Frank Becker |
| */ |
| public class CustomTaskListDecorationDrawer implements Listener { |
| |
| private final int activationImageOffset; |
| |
| private final Image taskActive = CommonImages.getImage(TasksUiImages.CONTEXT_ACTIVE); |
| |
| private final Image taskInactive = CommonImages.getImage(TasksUiImages.CONTEXT_INACTIVE_EMPTY); |
| |
| private final Image taskInactiveContext = CommonImages.getImage(TasksUiImages.CONTEXT_INACTIVE); |
| |
| // see bug 185004 |
| private final int platformSpecificSquish; |
| |
| private boolean useStrikethroughForCompleted; |
| |
| private boolean synchronizationOverlaid; |
| |
| private boolean focusedMode; |
| |
| private final org.eclipse.jface.util.IPropertyChangeListener PROPERTY_LISTENER = new org.eclipse.jface.util.IPropertyChangeListener() { |
| |
| public void propertyChange(org.eclipse.jface.util.PropertyChangeEvent event) { |
| if (event.getProperty().equals(ITasksUiPreferenceConstants.USE_STRIKETHROUGH_FOR_COMPLETED)) { |
| if (event.getNewValue() instanceof Boolean) { |
| useStrikethroughForCompleted = (Boolean) event.getNewValue(); |
| } |
| } else if (event.getProperty().equals(ITasksUiPreferenceConstants.OVERLAYS_INCOMING_TIGHT)) { |
| if (event.getNewValue() instanceof Boolean) { |
| synchronizationOverlaid = (Boolean) event.getNewValue(); |
| } |
| } |
| } |
| }; |
| |
| public CustomTaskListDecorationDrawer(int activationImageOffset, boolean focusedMode) { |
| this.activationImageOffset = activationImageOffset; |
| this.platformSpecificSquish = PlatformUiUtil.getTreeItemSquish(); |
| this.synchronizationOverlaid = TasksUiPlugin.getDefault() |
| .getPreferenceStore() |
| .getBoolean(ITasksUiPreferenceConstants.OVERLAYS_INCOMING_TIGHT); |
| this.useStrikethroughForCompleted = TasksUiPlugin.getDefault() |
| .getPreferenceStore() |
| .getBoolean(ITasksUiPreferenceConstants.USE_STRIKETHROUGH_FOR_COMPLETED); |
| this.focusedMode = focusedMode; |
| TasksUiPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(PROPERTY_LISTENER); |
| } |
| |
| /* |
| * NOTE: MeasureItem, PaintItem and EraseItem are called repeatedly. |
| * Therefore, it is critical for performance that these methods be as |
| * efficient as possible. |
| */ |
| public void handleEvent(Event event) { |
| Object data = event.item.getData(); |
| Image activationImage = null; |
| if (data instanceof ITask) { |
| AbstractTask task = (AbstractTask) data; |
| if (task.isActive()) { |
| activationImage = taskActive; |
| } else if (TasksUiPlugin.getContextStore().hasContext(task)) { |
| activationImage = taskInactiveContext; |
| } else { |
| activationImage = taskInactive; |
| } |
| } |
| if (!CommonFonts.HAS_STRIKETHROUGH) { |
| if (data instanceof AbstractTask & useStrikethroughForCompleted) { |
| AbstractTask task = (AbstractTask) data; |
| if (task.isCompleted()) { |
| Rectangle bounds; |
| //if (isCOCOA) { |
| bounds = ((TreeItem) event.item).getTextBounds(0); |
| // } else { |
| // bounds = ((TreeItem) event.item).getBounds(); |
| // } |
| int lineY = bounds.y + (bounds.height / 2); |
| String itemText = ((TreeItem) event.item).getText(); |
| Point extent = event.gc.textExtent(itemText); |
| event.gc.drawLine(bounds.x, lineY, bounds.x + extent.x, lineY); |
| } |
| } |
| } |
| if (data instanceof ITaskContainer) { |
| switch (event.type) { |
| case SWT.EraseItem: { |
| if ("gtk".equals(SWT.getPlatform())) { //$NON-NLS-1$ |
| // GTK requires drawing on erase event so that images don't disappear when selected. |
| if (activationImage != null) { |
| drawActivationImage(activationImageOffset, event, activationImage); |
| } |
| if (!this.synchronizationOverlaid) { |
| if (data instanceof ITaskContainer) { |
| drawSyncronizationImage((ITaskContainer) data, event); |
| } |
| } |
| } |
| |
| // TODO: would be nice not to do this on each item's painting |
| // String text = tree.getFilterControl().getText(); |
| // if (text != null && !text.equals("") && tree.getViewer().getExpandedElements().length <= 12) { |
| // int offsetY = tree.getViewer().getExpandedElements().length * tree.getViewer().getTree().getItemHeight(); |
| // event.gc.drawText("Open search dialog...", 20, offsetY - 10); |
| // } |
| break; |
| } |
| case SWT.PaintItem: { |
| if (activationImage != null) { |
| drawActivationImage(activationImageOffset, event, activationImage); |
| } |
| if (data instanceof ITaskContainer) { |
| drawSyncronizationImage((ITaskContainer) data, event); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| private void drawSyncronizationImage(ITaskContainer element, Event event) { |
| Image image = null; |
| int offsetX = PlatformUiUtil.getIncomingImageOffset(); |
| int offsetY = (event.height / 2) - 5; |
| if (synchronizationOverlaid) { |
| offsetX = event.x + 18 - platformSpecificSquish; |
| offsetY += 2; |
| } |
| if (element != null) { |
| if (element instanceof ITask) { |
| image = CommonImages.getImage(getSynchronizationImageDescriptor(element, synchronizationOverlaid)); |
| } else { |
| int imageOffset = 0; |
| if (!hideDecorationOnContainer(element, (TreeItem) event.item) |
| && AbstractTaskListFilter.hasDescendantIncoming(element)) { |
| if (synchronizationOverlaid) { |
| image = CommonImages.getImage(CommonImages.OVERLAY_SYNC_OLD_INCOMMING); |
| } else { |
| image = CommonImages.getImage(CommonImages.OVERLAY_SYNC_INCOMMING); |
| } |
| } else if (element instanceof IRepositoryQuery) { |
| RepositoryQuery query = (RepositoryQuery) element; |
| if (query.getStatus() != null) { |
| image = CommonImages.getImage(TasksUiInternal.getIconFromStatusOfQuery(query)); |
| if (synchronizationOverlaid) { |
| imageOffset = 11; |
| } else { |
| imageOffset = 3; |
| } |
| } |
| } |
| |
| int additionalSquish = 0; |
| if (platformSpecificSquish > 0 && synchronizationOverlaid) { |
| additionalSquish = platformSpecificSquish + 3; |
| } else if (platformSpecificSquish > 0) { |
| additionalSquish = platformSpecificSquish / 2; |
| } |
| if (synchronizationOverlaid) { |
| offsetX = 42 - imageOffset - additionalSquish; |
| } else { |
| offsetX = 24 - imageOffset - additionalSquish; |
| } |
| } |
| } |
| |
| if (image != null) { |
| event.gc.drawImage(image, offsetX, event.y + offsetY); |
| } |
| } |
| |
| private boolean hideDecorationOnContainer(ITaskContainer element, TreeItem treeItem) { |
| if (element instanceof StateTaskContainer) { |
| return true; |
| } else if (element instanceof UnmatchedTaskContainer) { |
| if (!focusedMode) { |
| return false; |
| } else if (AbstractTaskListFilter.hasDescendantIncoming(element)) { |
| return true; |
| } |
| } else if (element instanceof IRepositoryQuery) { |
| RepositoryQuery query = (RepositoryQuery) element; |
| if (query.getStatus() != null) { |
| return true; |
| } |
| } |
| |
| if (focusedMode) { |
| return false; |
| } else if (!(element instanceof ITask)) { |
| return treeItem.getExpanded(); |
| } else { |
| return false; |
| } |
| } |
| |
| protected void drawActivationImage(final int activationImageOffset, Event event, Image image) { |
| Rectangle rect = image.getBounds(); |
| int offset = Math.max(0, (event.height - rect.height) / 2); |
| event.gc.drawImage(image, activationImageOffset, event.y + offset); |
| } |
| |
| private ImageDescriptor getSynchronizationImageDescriptor(Object element, boolean synchViewStyle) { |
| if (element instanceof ITask) { |
| ITask repositoryTask = (ITask) element; |
| if (repositoryTask.getSynchronizationState() == SynchronizationState.INCOMING_NEW) { |
| if (synchViewStyle) { |
| return CommonImages.OVERLAY_SYNC_OLD_INCOMMING_NEW; |
| } else { |
| return CommonImages.OVERLAY_SYNC_INCOMMING_NEW; |
| } |
| } else if (repositoryTask.getSynchronizationState() == SynchronizationState.OUTGOING_NEW) { |
| if (synchViewStyle) { |
| return CommonImages.OVERLAY_SYNC_OLD_OUTGOING; |
| } else { |
| return CommonImages.OVERLAY_SYNC_OUTGOING_NEW; |
| } |
| } |
| ImageDescriptor imageDescriptor = null; |
| if (repositoryTask.getSynchronizationState() == SynchronizationState.OUTGOING |
| || repositoryTask.getSynchronizationState() == SynchronizationState.OUTGOING_NEW) { |
| if (synchViewStyle) { |
| imageDescriptor = CommonImages.OVERLAY_SYNC_OLD_OUTGOING; |
| } else { |
| imageDescriptor = CommonImages.OVERLAY_SYNC_OUTGOING; |
| } |
| } else if (repositoryTask.getSynchronizationState() == SynchronizationState.INCOMING) { |
| if (!Boolean.parseBoolean( |
| repositoryTask.getAttribute(ITasksCoreConstants.ATTRIBUTE_TASK_SUPPRESS_INCOMING))) { |
| if (synchViewStyle) { |
| imageDescriptor = CommonImages.OVERLAY_SYNC_OLD_INCOMMING; |
| } else { |
| imageDescriptor = CommonImages.OVERLAY_SYNC_INCOMMING; |
| } |
| } |
| } else if (repositoryTask.getSynchronizationState() == SynchronizationState.CONFLICT) { |
| imageDescriptor = CommonImages.OVERLAY_SYNC_CONFLICT; |
| } |
| if (imageDescriptor == null && repositoryTask instanceof AbstractTask |
| && ((AbstractTask) repositoryTask).getStatus() != null) { |
| return CommonImages.OVERLAY_SYNC_WARNING; |
| } else if (imageDescriptor != null) { |
| return imageDescriptor; |
| } |
| } else if (element instanceof IRepositoryQuery) { |
| RepositoryQuery query = (RepositoryQuery) element; |
| return TasksUiInternal.getIconFromStatusOfQuery(query); |
| } |
| // HACK: need a proper blank image |
| return CommonImages.OVERLAY_CLEAR; |
| } |
| |
| public void dispose() { |
| TasksUiPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(PROPERTY_LISTENER); |
| } |
| |
| public void setUseStrikethroughForCompleted(boolean useStrikethroughForCompleted) { |
| this.useStrikethroughForCompleted = useStrikethroughForCompleted; |
| } |
| |
| public void setSynchronizationOverlaid(boolean synchronizationOverlaid) { |
| this.synchronizationOverlaid = synchronizationOverlaid; |
| } |
| |
| public boolean isFocusedMode() { |
| return focusedMode; |
| } |
| |
| public void setFocusedMode(boolean focusedMode) { |
| this.focusedMode = focusedMode; |
| } |
| |
| } |