summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiles Parker2013-01-09 16:05:43 (EST)
committer Gerrit Code Review @ Eclipse.org2013-01-31 14:33:58 (EST)
commit47cfb690ed76daebc12bd016a3e149bbddd2fa3c (patch)
tree74399831e56a06700cc9d2d01c6084559373c8a3
parentd6269e27f48af03ea86b0ac1563bfd5b828e1ece (diff)
downloadorg.eclipse.mylyn.reviews-47cfb690ed76daebc12bd016a3e149bbddd2fa3c.zip
org.eclipse.mylyn.reviews-47cfb690ed76daebc12bd016a3e149bbddd2fa3c.tar.gz
org.eclipse.mylyn.reviews-47cfb690ed76daebc12bd016a3e149bbddd2fa3c.tar.bz2
334967: Create common navigator for review contentrefs/changes/32/8732/32
•Adds a new Common Navigator Framework view that displays global comments, patch sets, artifacts and comments. Change-Id: Ie74f2a869eaffb15e8759da87ba85078f186e00a Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=334967
-rw-r--r--org.eclipse.mylyn.gerrit.core/src/org/eclipse/mylyn/internal/gerrit/core/GerritUtil.java56
-rw-r--r--org.eclipse.mylyn.gerrit.ui/META-INF/MANIFEST.MF4
-rw-r--r--org.eclipse.mylyn.gerrit.ui/plugin.xml31
-rw-r--r--org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/GerritTaskEditorPage.java56
-rw-r--r--org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/PatchSetSection.java53
-rw-r--r--org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/ReviewItemLabelProvider.java113
-rw-r--r--org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/providers/OpenCompareEditorProvider.java112
-rw-r--r--org.eclipse.mylyn.reviews.ui/META-INF/MANIFEST.MF27
-rw-r--r--org.eclipse.mylyn.reviews.ui/icons/elcl16/flatLayout.gifbin0 -> 97 bytes
-rw-r--r--org.eclipse.mylyn.reviews.ui/icons/elcl16/hierarchicalLayout.gifbin0 -> 101 bytes
-rw-r--r--org.eclipse.mylyn.reviews.ui/icons/elcl16/refresh.gifbin0 -> 330 bytes
-rw-r--r--org.eclipse.mylyn.reviews.ui/icons/obj16/changelog_obj.gifbin0 -> 596 bytes
-rw-r--r--org.eclipse.mylyn.reviews.ui/icons/obj16/commit.gifbin0 -> 932 bytes
-rw-r--r--org.eclipse.mylyn.reviews.ui/icons/obj16/glasses.pngbin0 -> 95 bytes
-rw-r--r--org.eclipse.mylyn.reviews.ui/icons/obj16/quote.pngbin0 -> 3065 bytes
-rw-r--r--org.eclipse.mylyn.reviews.ui/icons/obj16/server.pngbin0 -> 568 bytes
-rw-r--r--org.eclipse.mylyn.reviews.ui/plugin.xml84
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsImages.java63
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsUiConstants.java26
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsUiPlugin.java8
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/GenericTreeContentProvider.java72
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/GlobalCommentsNode.java43
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/NonCommentFilter.java31
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsFlatContentProvider.java40
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsLabelProvider.java630
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsSorter.java99
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsTreeContentProvider.java98
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/TableStyledLabelProvider.java153
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/views/ReviewExplorer.java614
-rw-r--r--org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/reviews/ui/spi/editor/AbstractReviewTaskEditorPage.java34
30 files changed, 2298 insertions, 149 deletions
diff --git a/org.eclipse.mylyn.gerrit.core/src/org/eclipse/mylyn/internal/gerrit/core/GerritUtil.java b/org.eclipse.mylyn.gerrit.core/src/org/eclipse/mylyn/internal/gerrit/core/GerritUtil.java
index cb51027..a287645 100644
--- a/org.eclipse.mylyn.gerrit.core/src/org/eclipse/mylyn/internal/gerrit/core/GerritUtil.java
+++ b/org.eclipse.mylyn.gerrit.core/src/org/eclipse/mylyn/internal/gerrit/core/GerritUtil.java
@@ -22,7 +22,6 @@ import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.mylyn.internal.gerrit.core.client.GerritChange;
import org.eclipse.mylyn.internal.gerrit.core.client.GerritConfiguration;
-import org.eclipse.mylyn.internal.gerrit.core.client.GerritException;
import org.eclipse.mylyn.internal.gerrit.core.client.GerritPatchSetContent;
import org.eclipse.mylyn.internal.gerrit.core.client.JSonSupport;
import org.eclipse.mylyn.internal.gerrit.core.client.compat.ProjectDetailX;
@@ -52,7 +51,9 @@ import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Account.Id;
import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadScheme;
+import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Change.Status;
+import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
@@ -76,8 +77,30 @@ public class GerritUtil {
return null;
}
- public static IReview toReview(ChangeDetail detail) throws GerritException {
+ public static IReview toReview(ChangeDetail detail) {
IReview review = FACTORY.createReview();
+ Change change = detail.getChange();
+ AccountInfo owner = detail.getAccounts().get(change.getOwner());
+ IUser reviewAuthor = createUser(owner.getId(), detail.getAccounts());
+ review.setOwner(reviewAuthor);
+ review.setCreationDate(change.getCreatedOn());
+ review.setModificationDate(change.getLastUpdatedOn());
+ for (ChangeMessage message : detail.getMessages()) {
+ ITopic topic = review.createTopicComment(null, message.getMessage());
+ topic.setDraft(false);
+ topic.setCreationDate(message.getWrittenOn());
+ for (IComment comment : topic.getComments()) {
+ comment.setDraft(false);
+ }
+ if (message.getAuthor() != null) {
+ topic.setAuthor(createUser(message.getAuthor(), detail.getAccounts()));
+ //TODO Should be handled by edit framework
+ for (IComment comment : topic.getComments()) {
+ comment.setAuthor(topic.getAuthor());
+ }
+ }
+ }
+
review.setId(detail.getChange().getId().get() + "");
List<PatchSet> patchSets = detail.getPatchSets();
for (PatchSet patchSet : patchSets) {
@@ -88,6 +111,31 @@ public class GerritUtil {
return review;
}
+ public static void updateMessages(IReview review, ChangeDetail detail) {
+ Change change = detail.getChange();
+ review.setModificationDate(change.getLastUpdatedOn());
+ List<ITopic> existingTopics = review.getTopics();
+ int index = 0;
+ for (ChangeMessage message : detail.getMessages()) {
+ if (index++ < existingTopics.size()) {
+ continue;
+ }
+ ITopic topic = review.createTopicComment(null, message.getMessage());
+ topic.setDraft(false);
+ topic.setCreationDate(message.getWrittenOn());
+ for (IComment comment : topic.getComments()) {
+ comment.setDraft(false);
+ }
+ if (message.getAuthor() != null) {
+ topic.setAuthor(createUser(message.getAuthor(), detail.getAccounts()));
+ //TODO Should be handled by edit framework
+ for (IComment comment : topic.getComments()) {
+ comment.setAuthor(topic.getAuthor());
+ }
+ }
+ }
+ }
+
public static IReviewItemSet toReviewItemSet(ChangeDetail detail, PatchSet base, PatchSet patchSet) {
IReviewItemSet itemSet = FACTORY.createReviewItemSet();
if (base == null) {
@@ -95,6 +143,7 @@ public class GerritUtil {
} else {
itemSet.setName(NLS.bind("Compare Patch Set {0} and {1}", patchSet.getPatchSetId(), base.getPatchSetId()));
}
+ itemSet.setCreationDate(patchSet.getCreatedOn());
itemSet.setId(patchSet.getPatchSetId() + "");
itemSet.setAddedBy(createUser(patchSet.getUploader(), detail.getAccounts()));
itemSet.setRevision(patchSet.getRevision().get());
@@ -134,6 +183,7 @@ public class GerritUtil {
revisionA.setPath(patchScript.getA().getPath());
revisionA.setRevision((content.getBase() != null) ? NLS.bind("Patch Set {0}", content.getBase()
.getPatchSetId()) : "Base");
+ revisionA.setFile(item);
addComments(revisionA, commentDetail.getCommentsA(), commentDetail.getAccounts());
cache.put(revisionA);
}
@@ -149,6 +199,7 @@ public class GerritUtil {
revisionB.setRevision(NLS.bind("Patch Set {0}", content.getTarget()
.getPatchSet()
.getPatchSetId()));
+ revisionB.setFile(item);
addComments(revisionB, commentDetail.getCommentsB(), commentDetail.getAccounts());
cache.put(revisionB);
}
@@ -177,6 +228,7 @@ public class GerritUtil {
topicComment.setAuthor(author);
topicComment.setCreationDate(comment.getWrittenOn());
topicComment.setDescription(comment.getMessage());
+ topicComment.setDraft(PatchLineComment.Status.DRAFT == comment.getStatus());
ITopic topic = FACTORY.createTopic();
topic.setId(comment.getKey().get());
diff --git a/org.eclipse.mylyn.gerrit.ui/META-INF/MANIFEST.MF b/org.eclipse.mylyn.gerrit.ui/META-INF/MANIFEST.MF
index 13d188e..ce110a8 100644
--- a/org.eclipse.mylyn.gerrit.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.mylyn.gerrit.ui/META-INF/MANIFEST.MF
@@ -26,7 +26,9 @@ Require-Bundle: org.eclipse.core.runtime,
org.eclipse.jgit,
org.eclipse.egit.core,
org.eclipse.egit.ui,
- org.eclipse.mylyn.commons.net;bundle-version="3.8.0"
+ org.eclipse.mylyn.commons.net;bundle-version="3.8.0",
+ org.eclipse.mylyn.tasks.ui;bundle-version="3.8.0",
+ org.eclipse.ui.navigator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Bundle-Vendor: Eclipse Mylyn
diff --git a/org.eclipse.mylyn.gerrit.ui/plugin.xml b/org.eclipse.mylyn.gerrit.ui/plugin.xml
index de182a9..9ec92cb 100644
--- a/org.eclipse.mylyn.gerrit.ui/plugin.xml
+++ b/org.eclipse.mylyn.gerrit.ui/plugin.xml
@@ -54,11 +54,32 @@
</repositorySearchPage>
</extension>
<extension
- point="org.eclipse.mylyn.tasks.ui.taskEditorExtensions">
- <repositoryAssociation
- connectorKind="org.eclipse.mylyn.gerrit"
- taskEditorExtension="org.eclipse.mylyn.wikitext.tasks.ui.editor.textileTaskEditorExtension">
- </repositoryAssociation>
+ point="org.eclipse.ui.navigator.navigatorContent">
+ <actionProvider
+ class="org.eclipse.mylyn.internal.gerrit.ui.providers.OpenCompareEditorProvider"
+ id="org.eclipse.mylyn.gerrit.ui.actions.OpenCompareProvider">
+ <enablement>
+ <or>
+ <instanceof
+ value="org.eclipse.mylyn.reviews.core.model.ITopic">
+ </instanceof>
+ <instanceof
+ value="org.eclipse.mylyn.reviews.core.model.IFileItem">
+ </instanceof>
+ </or>
+ </enablement>
+ </actionProvider>
+ </extension>
+ <extension
+ point="org.eclipse.ui.navigator.viewer">
+ <viewerActionBinding
+ viewerId="org.eclipse.mylyn.reviews.Explorer">
+ <includes>
+ <actionExtension
+ pattern="org.eclipse.mylyn.gerrit.ui.actions.*">
+ </actionExtension>
+ </includes>
+ </viewerActionBinding>
</extension>
</plugin>
diff --git a/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/GerritTaskEditorPage.java b/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/GerritTaskEditorPage.java
index c93fa4c..a7f741e 100644
--- a/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/GerritTaskEditorPage.java
+++ b/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/GerritTaskEditorPage.java
@@ -15,9 +15,16 @@ import java.util.Set;
import org.eclipse.mylyn.internal.gerrit.core.GerritConnector;
import org.eclipse.mylyn.internal.gerrit.core.GerritTaskSchema;
+import org.eclipse.mylyn.internal.gerrit.core.GerritUtil;
+import org.eclipse.mylyn.internal.gerrit.core.client.GerritChange;
+import org.eclipse.mylyn.internal.gerrit.core.client.compat.ChangeDetailX;
+import org.eclipse.mylyn.internal.reviews.ui.ReviewsUiConstants;
+import org.eclipse.mylyn.internal.reviews.ui.views.ReviewExplorer;
+import org.eclipse.mylyn.reviews.core.model.IReview;
+import org.eclipse.mylyn.reviews.ui.spi.editor.AbstractReviewTaskEditorPage;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
+import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.ui.editors.AbstractAttributeEditor;
-import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPage;
import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPart;
import org.eclipse.mylyn.tasks.ui.editors.AttributeEditorFactory;
import org.eclipse.mylyn.tasks.ui.editors.LayoutHint;
@@ -25,12 +32,18 @@ import org.eclipse.mylyn.tasks.ui.editors.LayoutHint.ColumnSpan;
import org.eclipse.mylyn.tasks.ui.editors.LayoutHint.RowSpan;
import org.eclipse.mylyn.tasks.ui.editors.TaskEditor;
import org.eclipse.mylyn.tasks.ui.editors.TaskEditorPartDescriptor;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.IViewPart;
+import org.eclipse.ui.PlatformUI;
/**
* @author Mikael Kober
* @author Thomas Westling
*/
-public class GerritTaskEditorPage extends AbstractTaskEditorPage {
+public class GerritTaskEditorPage extends AbstractReviewTaskEditorPage {
+
+ IReview review;
public GerritTaskEditorPage(TaskEditor editor) {
super(editor, GerritConnector.CONNECTOR_KIND);
@@ -100,4 +113,43 @@ public class GerritTaskEditorPage extends AbstractTaskEditorPage {
return descriptors;
}
+ @Override
+ public void init(IEditorSite site, IEditorInput input) {
+ super.init(site, input);
+ TaskData taskData = getModel().getTaskData();
+ if (taskData != null) {
+ GerritChange change = GerritUtil.getChange(taskData);
+ final ChangeDetailX detail = change.getChangeDetail();
+ review = GerritUtil.toReview(detail);
+ }
+ }
+
+ @Override
+ public void refresh() {
+ super.refresh();
+ TaskData taskData = getModel().getTaskData();
+ if (taskData != null) {
+ int oldSize = review.getTopics().size();
+ GerritUtil.updateMessages(review, GerritUtil.getChange(taskData).getChangeDetail());
+ if (review.getTopics().size() > oldSize) {
+ refreshExplorer();
+ }
+ }
+ }
+
+ void refreshExplorer() {
+ //TODO, we really shouldn't be updating the view directly, it should be listening for model change, but that needs to be implemented with care given EMF update and UI threading concerns, etc.
+ IViewPart view = PlatformUI.getWorkbench()
+ .getActiveWorkbenchWindow()
+ .getActivePage()
+ .findView(ReviewsUiConstants.REVIEW_EXPLORER_ID);
+ if (view instanceof ReviewExplorer) {
+ ((ReviewExplorer) view).refreshView();
+ }
+ }
+
+ @Override
+ public IReview getReview() {
+ return review;
+ }
}
diff --git a/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/PatchSetSection.java b/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/PatchSetSection.java
index 54522f4..defd064 100644
--- a/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/PatchSetSection.java
+++ b/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/PatchSetSection.java
@@ -65,9 +65,11 @@ import org.eclipse.mylyn.internal.gerrit.ui.operations.RebaseDialog;
import org.eclipse.mylyn.internal.gerrit.ui.operations.RestoreDialog;
import org.eclipse.mylyn.internal.gerrit.ui.operations.SubmitDialog;
import org.eclipse.mylyn.internal.reviews.ui.compare.FileItemCompareEditorInput;
+import org.eclipse.mylyn.internal.reviews.ui.providers.ReviewsLabelProvider;
import org.eclipse.mylyn.internal.tasks.ui.editors.EditorUtil;
import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal;
import org.eclipse.mylyn.reviews.core.model.IFileItem;
+import org.eclipse.mylyn.reviews.core.model.IReview;
import org.eclipse.mylyn.reviews.core.model.IReviewItem;
import org.eclipse.mylyn.reviews.core.model.IReviewItemSet;
import org.eclipse.mylyn.reviews.internal.core.model.ReviewsPackage;
@@ -89,8 +91,6 @@ import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.IFormColors;
-import org.eclipse.ui.forms.events.ExpansionAdapter;
-import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
@@ -157,6 +157,8 @@ public class PatchSetSection extends AbstractGerritSection {
private final ReviewItemCache cache;
+ private ReviewsLabelProvider labelProvider;
+
public PatchSetSection() {
setPartName("Patch Sets");
this.jobs = new ArrayList<Job>();
@@ -169,6 +171,7 @@ public class PatchSetSection extends AbstractGerritSection {
job.cancel();
}
super.dispose();
+ labelProvider.dispose();
}
public void updateTextClient(Section section, final PatchSetDetail patchSetDetail, boolean cachingInProgress) {
@@ -335,17 +338,17 @@ public class PatchSetSection extends AbstractGerritSection {
addTextClient(toolkit, subSection, "", false); //$NON-NLS-1$
updateTextClient(subSection, patchSetDetail, false);
- if (subSection.isExpanded()) {
- createSubSectionContents(changeDetail, patchSetDetail, publishDetail, subSection);
- }
- subSection.addExpansionListener(new ExpansionAdapter() {
- @Override
- public void expansionStateChanged(ExpansionEvent e) {
- if (subSection.getClient() == null) {
- createSubSectionContents(changeDetail, patchSetDetail, publishDetail, subSection);
- }
- }
- });
+// if (subSection.isExpanded()) {
+ createSubSectionContents(changeDetail, patchSetDetail, publishDetail, subSection);
+// }
+// subSection.addExpansionListener(new ExpansionAdapter() {
+// @Override
+// public void expansionStateChanged(ExpansionEvent e) {
+// if (subSection.getClient() == null) {
+// createSubSectionContents(changeDetail, patchSetDetail, publishDetail, subSection);
+// }
+// }
+// });
}
private int getNumComments(PatchSetDetail patchSetDetail) {
@@ -359,7 +362,11 @@ public class PatchSetSection extends AbstractGerritSection {
private void subSectionExpanded(final ChangeDetail changeDetail, final PatchSetDetail patchSetDetail,
final Section composite, final Viewer viewer) {
updateTextClient(composite, patchSetDetail, true);
+ updateReviewItems(changeDetail, patchSetDetail, composite, viewer);
+ }
+ void updateReviewItems(final ChangeDetail changeDetail, final PatchSetDetail patchSetDetail,
+ final Section composite, final Viewer viewer) {
final GetPatchSetContentJob job = new GetPatchSetContentJob(getTaskEditorPage().getTaskRepository(),
patchSetDetail);
job.addJobChangeListener(new JobChangeAdapter() {
@@ -369,9 +376,24 @@ public class PatchSetSection extends AbstractGerritSection {
public void run() {
if (getControl() != null && !getControl().isDisposed()) {
if (event.getResult().isOK()) {
+ GerritTaskEditorPage editor = (GerritTaskEditorPage) getTaskEditorPage();
+ IReview review = editor.getReview();
GerritPatchSetContent content = job.getPatchSetContent();
if (content != null && content.getPatchScriptByPatchKey() != null) {
- viewer.setInput(GerritUtil.createInput(changeDetail, content, cache));
+ IReviewItemSet reviewItem = GerritUtil.createInput(changeDetail, content, cache);
+ IReviewItem replaceItem = null;
+ for (IReviewItem item : review.getItems()) {
+ if (item.getId().equals(reviewItem.getId())) {
+ replaceItem = item;
+ break;
+ }
+ }
+ if (replaceItem != null) {
+ review.getItems().remove(replaceItem);
+ }
+ review.getItems().add(reviewItem);
+ viewer.setInput(reviewItem);
+ editor.refreshExplorer();
}
}
@@ -613,7 +635,8 @@ public class PatchSetSection extends AbstractGerritSection {
}
}
});
- viewer.setLabelProvider(new DelegatingStyledCellLabelProvider(new ReviewItemLabelProvider()));
+ labelProvider = new ReviewsLabelProvider.Simple();
+ viewer.setLabelProvider(new DelegatingStyledCellLabelProvider(labelProvider));
viewer.addOpenListener(new IOpenListener() {
public void open(OpenEvent event) {
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
diff --git a/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/ReviewItemLabelProvider.java b/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/ReviewItemLabelProvider.java
deleted file mode 100644
index 9b891bf..0000000
--- a/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/editor/ReviewItemLabelProvider.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2011 Tasktop Technologies 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:
- * Tasktop Technologies - initial API and implementation
- * Git Hub, Inc. - fixes for bug 354570
- *******************************************************************************/
-
-package org.eclipse.mylyn.internal.gerrit.ui.editor;
-
-import java.util.List;
-
-import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
-import org.eclipse.jface.viewers.LabelProvider;
-import org.eclipse.jface.viewers.StyledString;
-import org.eclipse.jface.viewers.StyledString.Styler;
-import org.eclipse.mylyn.commons.workbench.CommonImageManger;
-import org.eclipse.mylyn.reviews.core.model.IFileItem;
-import org.eclipse.mylyn.reviews.core.model.IReviewItem;
-import org.eclipse.mylyn.reviews.core.model.ITopic;
-import org.eclipse.osgi.util.NLS;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.TextStyle;
-
-/**
- * @author Steffen Pingel
- * @author Kevin Sawicki
- */
-public class ReviewItemLabelProvider extends LabelProvider implements IStyledLabelProvider {
-
- final Styler NO_STYLE = new Styler() {
- @Override
- public void applyStyles(TextStyle textStyle) {
- }
- };
-
- private final CommonImageManger imageManager;
-
- public ReviewItemLabelProvider() {
- imageManager = new CommonImageManger();
- }
-
- @Override
- public void dispose() {
- super.dispose();
- imageManager.dispose();
- }
-
- @Override
- public Image getImage(Object element) {
- if (element instanceof IReviewItem) {
- IReviewItem item = (IReviewItem) element;
- return imageManager.getFileImage(item.getName());
- }
- return null;
- }
-
- @Override
- public String getText(Object element) {
- if (element instanceof IReviewItem) {
- return ((IReviewItem) element).getName();
- }
- return super.getText(element);
- }
-
- public StyledString getStyledText(Object element) {
- String text = getText(element);
- if (text != null) {
- StyledString styledString = new StyledString(text);
- if (element instanceof IFileItem) {
- IFileItem fileItem = (IFileItem) element;
- if (fileItem.getBase() != null && fileItem.getTarget() != null) {
- Stats stats = new Stats();
- count(stats, fileItem.getTopics());
- count(stats, fileItem.getBase().getTopics());
- count(stats, fileItem.getTarget().getTopics());
- if (stats.comments > 0 && stats.drafts > 0) {
- styledString.append(NLS.bind(" [{0} comments, {1} drafts]", stats.comments, stats.drafts),
- StyledString.DECORATIONS_STYLER);
- } else if (stats.comments > 0) {
- styledString.append(NLS.bind(" [{0} comments]", stats.comments),
- StyledString.DECORATIONS_STYLER);
- } else if (stats.drafts > 0) {
- styledString.append(NLS.bind(" [{0} drafts]", stats.drafts), StyledString.DECORATIONS_STYLER);
- }
- }
- }
- return styledString;
- }
- return new StyledString();
- }
-
- private class Stats {
- int drafts;
-
- int comments;
- }
-
- private void count(Stats stats, List<ITopic> topics) {
- for (ITopic topic : topics) {
- if (topic.isDraft()) {
- stats.drafts++;
- } else {
- stats.comments++;
- }
- }
- }
-
-}
diff --git a/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/providers/OpenCompareEditorProvider.java b/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/providers/OpenCompareEditorProvider.java
new file mode 100644
index 0000000..e17e6ad
--- /dev/null
+++ b/org.eclipse.mylyn.gerrit.ui/src/org/eclipse/mylyn/internal/gerrit/ui/providers/OpenCompareEditorProvider.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * 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:
+ * Miles Parker, Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.gerrit.ui.providers;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareUI;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.mylyn.internal.gerrit.ui.GerritReviewBehavior;
+import org.eclipse.mylyn.internal.reviews.ui.compare.FileItemCompareEditorInput;
+import org.eclipse.mylyn.internal.reviews.ui.views.ReviewExplorer;
+import org.eclipse.mylyn.reviews.core.model.IFileItem;
+import org.eclipse.mylyn.reviews.core.model.IFileRevision;
+import org.eclipse.mylyn.reviews.core.model.IReview;
+import org.eclipse.mylyn.reviews.core.model.ITopic;
+import org.eclipse.mylyn.tasks.ui.editors.TaskEditor;
+import org.eclipse.mylyn.tasks.ui.editors.TaskEditorInput;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.navigator.CommonActionProvider;
+import org.eclipse.ui.navigator.CommonViewer;
+import org.eclipse.ui.navigator.ICommonActionConstants;
+import org.eclipse.ui.navigator.ICommonActionExtensionSite;
+import org.eclipse.ui.navigator.ICommonViewerWorkbenchSite;
+
+/**
+ * @author Miles Parker
+ */
+public class OpenCompareEditorProvider extends CommonActionProvider {
+
+ private Action openAction;
+
+ @Override
+ public void init(ICommonActionExtensionSite site) {
+ if (site.getViewSite() instanceof ICommonViewerWorkbenchSite) {
+ final ICommonViewerWorkbenchSite viewSite = (ICommonViewerWorkbenchSite) site.getViewSite();
+ final ISelectionProvider selectionProvider = viewSite.getSelectionProvider();
+ CommonViewer viewer = (CommonViewer) selectionProvider;
+ final ReviewExplorer explorer = (ReviewExplorer) viewer.getCommonNavigator();
+ openAction = new Action() {
+
+ @Override
+ public void run() {
+ IStructuredSelection selection = (IStructuredSelection) selectionProvider.getSelection();
+ if (selection.size() == 1) {
+ IFileItem fileItem = getFileFor(selection.getFirstElement());
+ if (fileItem != null) {
+ IWorkbenchPart currentPart = explorer.getCurrentPart();
+ if (currentPart instanceof TaskEditor) {
+ TaskEditor part = (TaskEditor) currentPart;
+ TaskEditorInput input = (TaskEditorInput) part.getEditorInput();
+ GerritReviewBehavior behavior = new GerritReviewBehavior(input.getTask());
+ CompareConfiguration configuration = new CompareConfiguration();
+ CompareUI.openCompareEditor(new FileItemCompareEditorInput(configuration, fileItem,
+ behavior));
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+ };
+ }
+ }
+
+ private static IFileItem getFileFor(Object element) {
+ //TODO Move to adapter?
+ if (element instanceof ITopic) {
+ return getFileFor(((ITopic) element).getItem());
+ }
+ if (element instanceof IFileRevision) {
+ return ((IFileRevision) element).getFile();
+ }
+ if (element instanceof IFileItem) {
+ return (IFileItem) element;
+ }
+ return null;
+ }
+
+ private static IReview getReviewFor(Object element) {
+ if (element instanceof ITopic) {
+ return ((ITopic) element).getReview();
+ }
+ if (element instanceof IFileRevision) {
+ return ((IFileRevision) element).getReview();
+ }
+ if (element instanceof IFileItem) {
+ return ((IFileItem) element).getReview();
+ }
+ return null;
+ }
+
+ @Override
+ public void fillActionBars(IActionBars actionBars) {
+ if (openAction.isEnabled()) {
+ actionBars.setGlobalActionHandler(ICommonActionConstants.OPEN, openAction);
+ }
+ }
+}
diff --git a/org.eclipse.mylyn.reviews.ui/META-INF/MANIFEST.MF b/org.eclipse.mylyn.reviews.ui/META-INF/MANIFEST.MF
index eda4174..45034e0 100644
--- a/org.eclipse.mylyn.reviews.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.mylyn.reviews.ui/META-INF/MANIFEST.MF
@@ -5,20 +5,21 @@ Bundle-SymbolicName: org.eclipse.mylyn.reviews.ui;singleton:=true
Bundle-Version: 1.1.0.qualifier
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
- org.eclipse.mylyn.commons.core;bundle-version="3.8.0",
- org.eclipse.mylyn.commons.ui;bundle-version="3.8.0",
- org.eclipse.mylyn.commons.workbench;bundle-version="3.8.0",
- org.eclipse.mylyn.tasks.ui;bundle-version="3.8.0",
- org.eclipse.mylyn.tasks.core;bundle-version="3.8.0",
- org.eclipse.mylyn.reviews.core;bundle-version="1.0.0",
+ org.eclipse.core.expressions,
+ org.eclipse.compare,
+ org.eclipse.emf.ecore,
org.eclipse.jface,
org.eclipse.jface.text,
- org.eclipse.compare,
org.eclipse.ui.editors,
org.eclipse.ui.forms,
+ org.eclipse.ui.navigator,
org.eclipse.ui.workbench.texteditor,
- org.eclipse.emf.ecore,
- org.eclipse.core.expressions
+ org.eclipse.mylyn.commons.core;bundle-version="3.8.0",
+ org.eclipse.mylyn.commons.ui;bundle-version="3.8.0",
+ org.eclipse.mylyn.commons.workbench;bundle-version="3.8.0",
+ org.eclipse.mylyn.tasks.ui;bundle-version="3.8.0",
+ org.eclipse.mylyn.tasks.core;bundle-version="3.8.0",
+ org.eclipse.mylyn.reviews.core;bundle-version="1.0.0"
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Bundle-Vendor: Eclipse Mylyn
@@ -29,6 +30,10 @@ Export-Package: org.eclipse.mylyn.internal.reviews.ui;x-internal:=true,
org.eclipse.mylyn.internal.reviews.ui.dialogs;x-internal:=true,
org.eclipse.mylyn.internal.reviews.ui.editors.parts;x-internal:=true,
org.eclipse.mylyn.internal.reviews.ui.editors.ruler;x-internal:=true,
- org.eclipse.mylyn.reviews.ui;x-internal:=true
-Import-Package: org.apache.commons.io;version="1.4.0"
+ org.eclipse.mylyn.internal.reviews.ui.providers;x-internal:=true,
+ org.eclipse.mylyn.internal.reviews.ui.views;x-internal:=true,
+ org.eclipse.mylyn.reviews.ui;x-internal:=true,
+ org.eclipse.mylyn.reviews.ui.spi.editor;x-internal:=true
+Import-Package: org.apache.commons.io;version="1.4.0",
+ org.apache.commons.lang;version="2.3.0"
Bundle-Activator: org.eclipse.mylyn.internal.reviews.ui.ReviewsUiPlugin
diff --git a/org.eclipse.mylyn.reviews.ui/icons/elcl16/flatLayout.gif b/org.eclipse.mylyn.reviews.ui/icons/elcl16/flatLayout.gif
new file mode 100644
index 0000000..1ef74cf
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/icons/elcl16/flatLayout.gif
Binary files differ
diff --git a/org.eclipse.mylyn.reviews.ui/icons/elcl16/hierarchicalLayout.gif b/org.eclipse.mylyn.reviews.ui/icons/elcl16/hierarchicalLayout.gif
new file mode 100644
index 0000000..2344861
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/icons/elcl16/hierarchicalLayout.gif
Binary files differ
diff --git a/org.eclipse.mylyn.reviews.ui/icons/elcl16/refresh.gif b/org.eclipse.mylyn.reviews.ui/icons/elcl16/refresh.gif
new file mode 100644
index 0000000..e383147
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/icons/elcl16/refresh.gif
Binary files differ
diff --git a/org.eclipse.mylyn.reviews.ui/icons/obj16/changelog_obj.gif b/org.eclipse.mylyn.reviews.ui/icons/obj16/changelog_obj.gif
new file mode 100644
index 0000000..f988003
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/icons/obj16/changelog_obj.gif
Binary files differ
diff --git a/org.eclipse.mylyn.reviews.ui/icons/obj16/commit.gif b/org.eclipse.mylyn.reviews.ui/icons/obj16/commit.gif
new file mode 100644
index 0000000..153ba87
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/icons/obj16/commit.gif
Binary files differ
diff --git a/org.eclipse.mylyn.reviews.ui/icons/obj16/glasses.png b/org.eclipse.mylyn.reviews.ui/icons/obj16/glasses.png
new file mode 100644
index 0000000..c579106
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/icons/obj16/glasses.png
Binary files differ
diff --git a/org.eclipse.mylyn.reviews.ui/icons/obj16/quote.png b/org.eclipse.mylyn.reviews.ui/icons/obj16/quote.png
new file mode 100644
index 0000000..076962b
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/icons/obj16/quote.png
Binary files differ
diff --git a/org.eclipse.mylyn.reviews.ui/icons/obj16/server.png b/org.eclipse.mylyn.reviews.ui/icons/obj16/server.png
new file mode 100644
index 0000000..b51ae39
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/icons/obj16/server.png
Binary files differ
diff --git a/org.eclipse.mylyn.reviews.ui/plugin.xml b/org.eclipse.mylyn.reviews.ui/plugin.xml
index e48b5ce..72009cd 100644
--- a/org.eclipse.mylyn.reviews.ui/plugin.xml
+++ b/org.eclipse.mylyn.reviews.ui/plugin.xml
@@ -49,6 +49,90 @@
verticalRulerPreferenceKey="comment_verticalRuler"
verticalRulerPreferenceValue="true" />
</extension>
+ <extension
+ point="org.eclipse.ui.views">
+ <view
+ category="org.eclipse.mylyn.ui"
+ class="org.eclipse.mylyn.internal.reviews.ui.views.ReviewExplorer"
+ icon="icons/obj16/review.png"
+ id="org.eclipse.mylyn.reviews.Explorer"
+ name="Review">
+ <description>
+ View artifacts and comments associated with reviews.
+ </description>
+ </view>
+ </extension>
+ <extension
+ point="org.eclipse.ui.navigator.viewer">
+ <viewerActionBinding
+ viewerId="org.eclipse.mylyn.reviews.Explorer">
+ <includes>
+ <actionExtension
+ pattern="org.eclipse.mylyn.reviews.ui.actions">
+ </actionExtension>
+ </includes>
+ </viewerActionBinding>
+ <viewerContentBinding
+ viewerId="org.eclipse.mylyn.reviews.Explorer">
+ <includes>
+ <contentExtension
+ isRoot="true"
+ pattern="org.eclipse.mylyn.reviews.ui.ReviewContent">
+ </contentExtension>
+ <contentExtension
+ isRoot="true"
+ pattern="org.eclipse.mylyn.reviews.ui.ReviewFlatContent">
+ </contentExtension>
+ <contentExtension
+ pattern="org.eclipse.mylyn.reviews.ui.CommonFilter">
+ </contentExtension>
+ </includes>
+ </viewerContentBinding>
+ <viewer
+ viewerId="org.eclipse.mylyn.reviews.Explorer">
+ </viewer>
+ </extension>
+ <extension
+ point="org.eclipse.ui.navigator.navigatorContent">
+ <navigatorContent
+ contentProvider="org.eclipse.mylyn.internal.reviews.ui.providers.ReviewsTreeContentProvider"
+ id="org.eclipse.mylyn.reviews.ui.ReviewContent"
+ labelProvider="org.eclipse.mylyn.internal.reviews.ui.providers.ReviewsLabelProvider$Tree"
+ name="Review Artifacts and Comments">
+ <triggerPoints>
+ <instanceof
+ value="org.eclipse.mylyn.reviews.ui.spi.editor.AbstractReviewTaskEditorPage">
+ </instanceof>
+ </triggerPoints>
+ <commonSorter
+ class="org.eclipse.mylyn.internal.reviews.ui.providers.ReviewsSorter"
+ id="sorter.orderable">
+ </commonSorter>
+ </navigatorContent>
+ <navigatorContent
+ contentProvider="org.eclipse.mylyn.internal.reviews.ui.providers.ReviewsFlatContentProvider"
+ id="org.eclipse.mylyn.reviews.ui.ReviewFlatContent"
+ labelProvider="org.eclipse.mylyn.internal.reviews.ui.providers.ReviewsLabelProvider$Flat"
+ name="Review Comments">
+ <triggerPoints>
+ <instanceof
+ value="org.eclipse.mylyn.reviews.ui.spi.editor.AbstractReviewTaskEditorPage">
+ </instanceof>
+ </triggerPoints>
+ <commonSorter
+ class="org.eclipse.mylyn.internal.reviews.ui.providers.ReviewsSorter"
+ id="sorter.orderable">
+ </commonSorter>
+ </navigatorContent>
+ <commonFilter
+ activeByDefault="false"
+ class="org.eclipse.mylyn.internal.reviews.ui.providers.NonCommentFilter"
+ description="Hides all artifacts and containers that do not contain comments as well as machine generated comments."
+ id="org.eclipse.mylyn.reviews.ui.CommonFilter"
+ name="Artifacts without Comments"
+ visibleInUI="true">
+ </commonFilter>
+ </extension>
<!--
<extension point="org.eclipse.ui.workbench.texteditor.rulerColumns">
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsImages.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsImages.java
new file mode 100644
index 0000000..c8c9807
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsImages.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Tasktop Technologies.
+ * 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:
+ * Tasktop Technologies - initial API and implementation
+ * Sascha Scholz (SAP) - improvements
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.reviews.ui;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+
+/**
+ * @author Steffen Pingel
+ * @author Sascha Scholz
+ * @author Miles Parker
+ */
+public class ReviewsImages {
+
+ private static final URL baseURL = ReviewsUiPlugin.getDefault().getBundle().getEntry("/icons/"); //$NON-NLS-1$
+
+ public static final ImageDescriptor OVERLAY_REVIEW = create("eview16/overlay-review.png"); //$NON-NLS-1$
+
+ public static final ImageDescriptor REFRESH = create("elcl16/refresh.gif"); //$NON-NLS-1$
+
+ public static final ImageDescriptor FLAT_LAYOUT = create("elcl16/flatLayout.gif"); //$NON-NLS-1$
+
+ public static final ImageDescriptor HIERARCHICAL_LAYOUT = create("elcl16/hierarchicalLayout.gif"); //$NON-NLS-1$
+
+ public static final ImageDescriptor REVIEW = create("obj16/review.png"); //$NON-NLS-1$
+
+ public static final ImageDescriptor CHANGE_LOG = create("obj16/changelog_obj.gif"); //$NON-NLS-1$
+
+ public static final ImageDescriptor COMMIT = create("obj16/commit.gif"); //$NON-NLS-1$
+
+ public static final ImageDescriptor REVIEW_GLASSES = create("obj16/glasses.png"); //$NON-NLS-1$
+
+ public static final ImageDescriptor REVIEW_QUOTE = create("obj16/quote.png"); //$NON-NLS-1$
+
+ public static final ImageDescriptor SERVER = create("obj16/server.png"); //$NON-NLS-1$
+
+ private static ImageDescriptor create(String path) {
+ try {
+ return ImageDescriptor.createFromURL(makeIconFileURL(path));
+ } catch (MalformedURLException e) {
+ return ImageDescriptor.getMissingImageDescriptor();
+ }
+ }
+
+ private static URL makeIconFileURL(String path) throws MalformedURLException {
+ if (baseURL == null) {
+ throw new MalformedURLException();
+ }
+ return new URL(baseURL, path);
+ }
+}
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsUiConstants.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsUiConstants.java
new file mode 100644
index 0000000..f196fdd
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsUiConstants.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * 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:
+ * Miles Parker (Tasktop Technologies) - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.reviews.ui;
+
+/**
+ * @author Miles Parker
+ */
+public interface ReviewsUiConstants {
+
+ public static final String REVIEW_EXPLORER_ID = "org.eclipse.mylyn.reviews.Explorer";
+
+ public static final String REVIEW_CONTENT_ID = "org.eclipse.mylyn.reviews.ui.ReviewContent";
+
+ public static final String REVIEW_FLAT_CONTENT_ID = "org.eclipse.mylyn.reviews.ui.ReviewFlatContent";
+
+ public static final String REVIEW_FILTER_FOR_COMMENTS = "org.eclipse.mylyn.reviews.ui.CommonFilter";
+}
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsUiPlugin.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsUiPlugin.java
index fdadbbf..6e2a44c 100644
--- a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsUiPlugin.java
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/ReviewsUiPlugin.java
@@ -10,6 +10,7 @@
*********************************************************************/
package org.eclipse.mylyn.internal.reviews.ui;
+import org.eclipse.mylyn.commons.workbench.CommonImageManger;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
@@ -19,6 +20,8 @@ public class ReviewsUiPlugin extends AbstractUIPlugin {
private static ReviewsUiPlugin plugin;
+ CommonImageManger imageManager;
+
public ReviewsUiPlugin() {
}
@@ -26,16 +29,21 @@ public class ReviewsUiPlugin extends AbstractUIPlugin {
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
+ imageManager = new CommonImageManger();
}
@Override
public void stop(BundleContext context) throws Exception {
plugin = null;
super.stop(context);
+ imageManager.dispose();
}
public static ReviewsUiPlugin getDefault() {
return plugin;
}
+ public CommonImageManger getImageManager() {
+ return imageManager;
+ }
}
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/GenericTreeContentProvider.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/GenericTreeContentProvider.java
new file mode 100644
index 0000000..c211d52
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/GenericTreeContentProvider.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Tasktop Technologies 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:
+ * Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.reviews.ui.providers;
+
+import java.util.Collection;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/**
+ * Provides common features for tree content providers including support for gathering children of collections
+ * efficiently.
+ *
+ * @author Miles Parker
+ */
+public abstract class GenericTreeContentProvider implements ITreeContentProvider {
+
+ public Object[] getChildren(Object parentElement) {
+ return getElements(parentElement);
+ }
+
+ public Object getParent(Object element) {
+ return null;
+ }
+
+ public boolean hasChildren(Object element) {
+ return getChildren(element).length > 0;
+ }
+
+ protected Object[] getCollectionChildren(Object element) {
+ if (element instanceof Collection) {
+ Collection<?> collection = (Collection<?>) element;
+ Object[] elements = new Object[] {};
+ for (Object member : collection) {
+ elements = ArrayUtils.addAll(elements, getElements(member));
+ }
+ return elements;
+ }
+ return new Object[0];
+ }
+
+ /**
+ * Optimization over getCollectionChildren() as we don't have to tour each member.
+ */
+ protected boolean hasCollectionChildren(Object element) {
+ if (element instanceof Collection) {
+ Collection<?> collection = (Collection<?>) element;
+ for (Object member : collection) {
+ if (hasChildren(member)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public void dispose() {
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+}
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/GlobalCommentsNode.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/GlobalCommentsNode.java
new file mode 100644
index 0000000..2d3431a
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/GlobalCommentsNode.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * 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:
+ * Miles Parker (Tasktop Technologies) - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.reviews.ui.providers;
+
+import org.apache.commons.lang.ObjectUtils;
+import org.eclipse.mylyn.reviews.core.model.IReview;
+
+/**
+ * Simply represents the node used to contain global comments on the review.
+ *
+ * @author Miles Parker
+ */
+class GlobalCommentsNode {
+ private final IReview review;
+
+ public GlobalCommentsNode(IReview review) {
+ this.review = review;
+ }
+
+ public IReview getReview() {
+ return review;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof GlobalCommentsNode)
+ && ObjectUtils.equals(this.getReview(), ((GlobalCommentsNode) other).getReview());
+ }
+
+ @Override
+ public int hashCode() {
+ return getReview() != null ? getReview().hashCode() : 1;
+ }
+}
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/NonCommentFilter.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/NonCommentFilter.java
new file mode 100644
index 0000000..1808758
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/NonCommentFilter.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * 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:
+ * Miles Parker (Tasktop Technologies) - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.reviews.ui.providers;
+
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.mylyn.reviews.core.model.ITopicContainer;
+
+/**
+ * @author Miles Parker
+ */
+public class NonCommentFilter extends ViewerFilter {
+
+ @Override
+ public boolean select(Viewer viewer, Object parentElement, Object element) {
+ if (element instanceof ITopicContainer) {
+ ITopicContainer container = (ITopicContainer) element;
+ return container.getAllComments().size() > 0;
+ }
+ return true;
+ }
+}
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsFlatContentProvider.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsFlatContentProvider.java
new file mode 100644
index 0000000..a33b690
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsFlatContentProvider.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * 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:
+ * Miles Parker (Tasktop Technologies) - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.reviews.ui.providers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.mylyn.reviews.core.model.ITopicContainer;
+
+/**
+ * Flattens all contents of a review to their lowest level of detail. (In current implementations, this is comments.)
+ *
+ * @author Miles Parker
+ */
+public class ReviewsFlatContentProvider extends GenericTreeContentProvider {
+
+ public Object[] getElements(Object element) {
+ if (element instanceof ITopicContainer) {
+ List<Object> children = new ArrayList<Object>();
+ children.addAll(((ITopicContainer) element).getAllComments());
+ return children.toArray();
+ }
+ return getCollectionChildren(element);
+ }
+
+ @Override
+ public boolean hasChildren(Object element) {
+ return ((element instanceof ITopicContainer) && ((ITopicContainer) element).getAllComments().size() > 0)
+ || hasCollectionChildren(element);
+ }
+}
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsLabelProvider.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsLabelProvider.java
new file mode 100644
index 0000000..6437f82
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsLabelProvider.java
@@ -0,0 +1,630 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * 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:
+ * Miles Parker (Tasktop Technologies) - primary API and implementation
+ * Git Hub, Inc. - fixes for bug 354570
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.reviews.ui.providers;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.eclipse.jface.preference.JFacePreferences;
+import org.eclipse.jface.resource.ColorRegistry;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.viewers.StyledString;
+import org.eclipse.jface.viewers.StyledString.Styler;
+import org.eclipse.mylyn.commons.core.DateUtil;
+import org.eclipse.mylyn.commons.ui.CommonImages;
+import org.eclipse.mylyn.internal.reviews.ui.ReviewsImages;
+import org.eclipse.mylyn.internal.reviews.ui.ReviewsUiPlugin;
+import org.eclipse.mylyn.reviews.core.model.IComment;
+import org.eclipse.mylyn.reviews.core.model.IDated;
+import org.eclipse.mylyn.reviews.core.model.IFileItem;
+import org.eclipse.mylyn.reviews.core.model.IFileRevision;
+import org.eclipse.mylyn.reviews.core.model.ILineLocation;
+import org.eclipse.mylyn.reviews.core.model.ILocation;
+import org.eclipse.mylyn.reviews.core.model.IReview;
+import org.eclipse.mylyn.reviews.core.model.IReviewItem;
+import org.eclipse.mylyn.reviews.core.model.IReviewItemSet;
+import org.eclipse.mylyn.reviews.core.model.ITaskReference;
+import org.eclipse.mylyn.reviews.core.model.ITopic;
+import org.eclipse.mylyn.reviews.core.model.ITopicContainer;
+import org.eclipse.mylyn.reviews.core.model.IUser;
+import org.eclipse.mylyn.tasks.ui.TasksUiImages;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.TextStyle;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Base support for reviews labels.
+ *
+ * @author Miles Parker
+ * @author Steffen Pingel
+ * @author Kevin Sawicki
+ */
+public abstract class ReviewsLabelProvider extends TableStyledLabelProvider {
+
+ private static final String GLOBAL_COMMENTS_NAME = "Global";
+
+ private static final int TOOLTIP_CHAR_WIDTH = 100;
+
+ static final int LINE_NUMBER_WIDTH = 4;
+
+ static final int MAXIMUM_COMMENT_LENGTH = 300;
+
+ static final SimpleDateFormat COMMENT_DATE_FORMAT = new SimpleDateFormat("yyyy.MM.dd HH:mm a");
+
+ static final SimpleDateFormat EXTENDED_DATE_FORMAT = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
+
+ public final static Styler AUTHOR_STYLE = new Styler() {
+ @Override
+ public void applyStyles(TextStyle textStyle) {
+ textStyle.font = JFaceResources.getTextFont();
+ textStyle.foreground = Display.getCurrent().getSystemColor(SWT.COLOR_BLACK);
+ }
+ };
+
+ public final static Styler COMMENT_STYLE = new Styler() {
+ @Override
+ public void applyStyles(TextStyle textStyle) {
+ textStyle.font = JFaceResources.getFontRegistry().getItalic(JFaceResources.DEFAULT_FONT);
+ }
+ };
+
+ public final static Styler COMMENT_DATE_STYLE = new Styler() {
+ @Override
+ public void applyStyles(TextStyle textStyle) {
+ textStyle.font = JFaceResources.getFontRegistry().get(JFaceResources.TEXT_FONT);
+ ColorRegistry colorRegistry = JFaceResources.getColorRegistry();
+ textStyle.foreground = colorRegistry.get(JFacePreferences.DECORATIONS_COLOR);
+ }
+ };
+
+ public final static Styler LINE_NUMBER_STYLE = new Styler() {
+ @Override
+ public void applyStyles(TextStyle textStyle) {
+ ColorRegistry colorRegistry = JFaceResources.getColorRegistry();
+ textStyle.foreground = colorRegistry.get(JFacePreferences.COUNTER_COLOR);
+ textStyle.font = JFaceResources.getFontRegistry().get(JFaceResources.TEXT_FONT);
+ }
+ };
+
+ public static final TableColumnProvider ITEMS_COLUMN = new TableColumnProvider("Item", 80, 800, true) {
+
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof IReview) {
+ return ReviewsUiPlugin.getDefault().getImageManager().getFileImage("review");
+ }
+ if (element instanceof IReviewItemSet) {
+ return CommonImages.getImage(ReviewsImages.CHANGE_LOG);
+
+ }
+ if (element instanceof IReviewItem) {
+ IReviewItem item = (IReviewItem) element;
+ return ReviewsUiPlugin.getDefault().getImageManager().getFileImage(item.getName());
+ }
+ if (element instanceof IComment) {
+ //See https://bugs.eclipse.org/bugs/show_bug.cgi?id=334967#c16
+ // IComment comment = (IComment) element;
+ // if (StringUtils.startsWith(comment.getAuthor().getDisplayName(), "Hudson")) {
+ // return CommonImages.getImage(ReviewsImages.SERVER);
+ // }
+ //TODO: We'd like to return PERSON_ME if user, but need to figure out how to get that w/o creating too much coupling.
+ return CommonImages.getImage(CommonImages.PERSON);
+ }
+ if (element instanceof GlobalCommentsNode) {
+ return CommonImages.getImage(TasksUiImages.TASK);
+ }
+ return null;
+ };
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof GlobalCommentsNode) {
+ return GLOBAL_COMMENTS_NAME;
+ }
+ if (element instanceof Collection) {
+ Collection<?> collection = (Collection<?>) element;
+ if (collection.size() == 1) {
+ return getText(collection.iterator().next());
+ }
+ }
+ if (element instanceof ITaskReference) {
+ ITaskReference ref = (ITaskReference) element;
+ return ref.getTaskId();
+ }
+ if (element instanceof IReview) {
+ IReview review = (IReview) element;
+ ITaskReference reviewTask = review.getReviewTask();
+ if (reviewTask != null) {
+ return getText(reviewTask);
+ }
+ return "Change " + review.getId();
+ }
+ if (element instanceof IFileRevision) {
+ IFileRevision revision = (IFileRevision) element;
+ String text = getText(revision.getFile());
+ text += revision.getName();
+ return text;
+ }
+ if (element instanceof IFileItem) {
+ //Note, we want platform independent separator here.
+ return StringUtils.substringAfterLast(((IFileItem) element).getName(), "/");
+ }
+ if (element instanceof IReviewItem) {
+ return ((IReviewItem) element).getName();
+ }
+ if (element instanceof IComment) {
+ IComment comment = (IComment) element;
+ String desc = comment.getDescription();
+ //desc = StringUtils.normalizeSpace(desc);
+ return desc;
+ }
+ if (element instanceof IUser) {
+ return ((IUser) element).getDisplayName();
+ }
+ if (element instanceof Date) {
+ return DateUtil.getRelativeDuration(System.currentTimeMillis() - ((Date) element).getTime()) + " ago";
+ }
+ if (element instanceof ILineLocation) {
+ int min = ((ILineLocation) element).getRangeMin();
+ int max = ((ILineLocation) element).getRangeMax();
+ String text = min + "";
+ if (min != max) {
+ text += "-" + max;
+ }
+ return text;
+ }
+ return super.getText(element);
+ }
+
+ @Override
+ public StyledString getStyledText(Object element) {
+ Styler styler = null;
+ if (element instanceof IComment) {
+ styler = COMMENT_STYLE;
+ }
+ StyledString styledString = new StyledString(getText(element), styler);
+ if (element instanceof GlobalCommentsNode) {
+ element = ((GlobalCommentsNode) element).getReview();
+ }
+ addTopicContainerStatsStyle(element, styledString);
+ addTopicLineNumberStyle(element, styledString);
+ addFilePathStyle(element, styledString);
+ return styledString;
+ }
+
+ @Override
+ public String getToolTipText(Object element) {
+ if (element instanceof IComment) {
+ IComment comment = (IComment) element;
+ String desc = comment.getDescription();
+ desc = StringUtils.replace(desc, "\r\n", "\n");
+ String[] lines = desc.split("\n");
+ List<String> seperated = new ArrayList<String>();
+ for (String line : lines) {
+ String newLine = "";
+ String[] splitByWholeSeparator = StringUtils.splitByWholeSeparator(line, " ");
+ int count = 0;
+ for (String word : splitByWholeSeparator) {
+ count += word.length();
+ if (count > TOOLTIP_CHAR_WIDTH) {
+ seperated.add(newLine);
+ newLine = word;
+ count = word.length();
+ } else {
+ if (count > word.length()) {
+ newLine += " ";
+ }
+ newLine += word;
+ }
+ }
+ seperated.add(newLine);
+ }
+ return StringUtils.join(seperated, "\n");
+ }
+ if (element instanceof IFileItem) {
+ IFileItem fileItem = (IFileItem) element;
+ return fileItem.getTarget().getPath() + " Revision: " + fileItem.getTarget().getRevision();
+ }
+ if (element instanceof IUser) {
+ IUser user = (IUser) element;
+ String text = user.getDisplayName();
+ if (!StringUtils.isEmpty(user.getEmail())) {
+ text += " <" + user.getEmail() + ">";
+ }
+ return text;
+ }
+ if (element instanceof Date) {
+ return EXTENDED_DATE_FORMAT.format((Date) element);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isStyled() {
+ return true;
+ };
+ };
+
+ public static final TableColumnProvider ARTIFACT_COLUMN = new AdaptingTableColumnProvider(ITEMS_COLUMN, "Artifact",
+ 50, 400, false) {
+
+ @Override
+ public Object adapt(Object element) {
+ if (element instanceof IFileItem) {
+ return element;
+ }
+ if (element instanceof IFileRevision) {
+ return ((IFileRevision) element).getFile();
+ }
+ if (element instanceof ITopic) {
+ ITopic topic = (ITopic) element;
+ return adapt(topic.getItem());
+ }
+ if (element instanceof IReview) {
+ return new GlobalCommentsNode((IReview) element);
+ }
+ return null;
+ }
+
+ @Override
+ public StyledString getStyledText(Object element) {
+ //We don't want to include stats for this usage
+ StyledString styledString = new StyledString(getText(element));
+ addTopicLineNumberStyle(element, styledString);
+ addFilePathStyle(adapt(element), styledString);
+ return styledString;
+ }
+
+ @Override
+ public boolean isStyled() {
+ return true;
+ };
+ };
+
+ public static final TableColumnProvider COMMENTS_COLUMN = new AdaptingTableColumnProvider(ITEMS_COLUMN, "Comment",
+ 50, 400, true) {
+ @Override
+ public Object adapt(Object element) {
+ if (element instanceof ITopic) {
+ return element;
+ }
+ return null;
+ };
+
+ @Override
+ public StyledString getStyledText(Object element) {
+ return new StyledString(getText(element), COMMENT_STYLE);
+ }
+
+ @Override
+ public boolean isStyled() {
+ return true;
+ };
+ };
+
+ public static final TableColumnProvider AUTHORS_COLUMN = new AdaptingTableColumnProvider(ITEMS_COLUMN, "Author",
+ 10, 120, false) {
+
+ @Override
+ public Object adapt(Object element) {
+ if (element instanceof IComment) {
+ IComment comment = (IComment) element;
+ return comment.getAuthor();
+ }
+ if (element instanceof IReviewItem) {
+ IReviewItem item = (IReviewItem) element;
+ return item.getAddedBy();
+ }
+ if (element instanceof IReview) {
+ IReview item = (IReview) element;
+ return item.getOwner();
+ }
+ return null;
+ }
+ };
+
+ public static final TableColumnProvider DATE_COLUMN = new AdaptingTableColumnProvider(ITEMS_COLUMN, "Last Change",
+ 10, 120, false) {
+
+ @Override
+ public Object adapt(Object element) {
+ if (element instanceof IDated) {
+ IDated item = (IDated) element;
+ if (item.getModificationDate() != null) {
+ return item.getModificationDate();
+ }
+ if (item.getCreationDate() != null) {
+ return item.getCreationDate();
+ }
+ }
+ if (element instanceof IFileRevision) {
+ IFileRevision item = (IFileRevision) element;
+ return item.getRevision();
+ }
+ return null;
+ };
+ };
+
+ public static final TableColumnProvider SINGLE_COLUMN = new TableColumnProvider("Items", 10, 120, false) {
+
+ @Override
+ public Image getImage(Object element) {
+ return ITEMS_COLUMN.getImage(element);
+ };
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof GlobalCommentsNode) {
+ return GLOBAL_COMMENTS_NAME;
+ }
+ if (element instanceof Collection) {
+ Collection<?> collection = (Collection<?>) element;
+ if (collection.size() == 1) {
+ return getText(collection.iterator().next());
+ }
+ }
+ if (element instanceof ITaskReference) {
+ ITaskReference ref = (ITaskReference) element;
+ return ref.getTaskId();
+ }
+ if (element instanceof IReview) {
+ IReview review = (IReview) element;
+ ITaskReference reviewTask = review.getReviewTask();
+ if (reviewTask != null) {
+ return getText(reviewTask);
+ }
+ return "Change " + review.getId();
+ }
+ if (element instanceof IFileItem) {
+ IFileItem file = (IFileItem) element;
+ String fileName = StringUtils.substringAfterLast(file.getName(), File.separator);
+ return fileName;
+ }
+ if (element instanceof IReviewItem) {
+ return ((IReviewItem) element).getName();
+ }
+ if (element instanceof IComment) {
+ IComment comment = (IComment) element;
+ String desc = comment.getDescription();
+ return desc;
+ }
+ if (element instanceof IUser) {
+ IUser user = (IUser) element;
+ return user.getDisplayName();
+ }
+ if (element instanceof Date) {
+ return COMMENT_DATE_FORMAT.format(element);
+ }
+ if (element instanceof ILineLocation) {
+ int min = ((ILineLocation) element).getRangeMin();
+ int max = ((ILineLocation) element).getRangeMax();
+ String text = min + "";
+ if (min != max) {
+ text += "-" + max;
+ }
+ return text;
+ }
+ return super.getText(element);
+ }
+
+ @Override
+ public StyledString getStyledText(Object element) {
+ String text = getText(element);
+ StyledString styledString = new StyledString();
+ if (text != null) {
+ if (element instanceof IComment) {
+ IComment comment = (IComment) element;
+ String commentText = text;
+ int textLength = 0;
+ String authorText = "";
+ if (comment.getAuthor() != null) {
+ authorText = getText(comment.getAuthor());
+ textLength += authorText.length();
+ }
+ int descSpaceRemaining = (MAXIMUM_COMMENT_LENGTH - textLength);
+ if (commentText.length() > descSpaceRemaining + 3) { //Account for ellipses
+ commentText = commentText.substring(0, descSpaceRemaining - 3) + "..."; //$NON-NLS-1$
+ }
+ commentText = StringUtils.rightPad(commentText, MAXIMUM_COMMENT_LENGTH - textLength);
+
+ styledString.append(authorText + " ", AUTHOR_STYLE);
+ styledString.append(commentText + " ", COMMENT_STYLE);
+ styledString.append(getText(comment.getCreationDate()) + " ", COMMENT_DATE_STYLE);
+ } else {
+ styledString.append(text);
+ }
+ if (element instanceof GlobalCommentsNode) {
+ element = ((GlobalCommentsNode) element).getReview();
+ }
+ if (element instanceof ITopicContainer) {
+ ITopicContainer container = (ITopicContainer) element;
+ String statsText = getStatsText(container);
+ styledString.append(statsText, StyledString.DECORATIONS_STYLER);
+ }
+ if (element instanceof IFileItem) {
+ IReviewItem item = (IReviewItem) element;
+ styledString.append(" " + item.getName(), StyledString.QUALIFIER_STYLER);
+ }
+ } else {
+ styledString.append(element.toString());
+ }
+ return styledString;
+ }
+ };
+
+ public static class Flat extends ReviewsLabelProvider {
+ TableColumnProvider[] columns = new TableColumnProvider[] { ARTIFACT_COLUMN, COMMENTS_COLUMN, AUTHORS_COLUMN,
+ DATE_COLUMN };
+
+ @Override
+ public TableColumnProvider[] getColumnProviders() {
+ return columns;
+ }
+ }
+
+ public static class Tree extends ReviewsLabelProvider {
+ TableColumnProvider[] columns = new TableColumnProvider[] { ITEMS_COLUMN, AUTHORS_COLUMN, DATE_COLUMN };
+
+ @Override
+ public TableColumnProvider[] getColumnProviders() {
+ return columns;
+ }
+ }
+
+ public static class Single extends ReviewsLabelProvider {
+ TableColumnProvider[] columns = new TableColumnProvider[] { SINGLE_COLUMN };
+
+ @Override
+ public Image getImage(Object element) {
+ return ITEMS_COLUMN.getImage(element);
+ }
+
+ @Override
+ public TableColumnProvider[] getColumnProviders() {
+ return columns;
+ }
+ }
+
+ public static class Simple extends ReviewsLabelProvider {
+ TableColumnProvider[] columns = new TableColumnProvider[] { SINGLE_COLUMN };
+
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof IReviewItem) {
+ IReviewItem item = (IReviewItem) element;
+ return ReviewsUiPlugin.getDefault().getImageManager().getFileImage(item.getName());
+ }
+ return null;
+ }
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof IReviewItem) {
+ return ((IReviewItem) element).getName();
+ }
+ return super.getText(element);
+ }
+
+ @Override
+ public StyledString getStyledText(Object element) {
+ StyledString styledString = new StyledString(getText(element));
+ addTopicContainerStatsStyle(element, styledString);
+ return styledString;
+ }
+
+ @Override
+ public TableColumnProvider[] getColumnProviders() {
+ return columns;
+ }
+ }
+
+ @Override
+ public String getText(Object element) {
+ return ITEMS_COLUMN.getText(element);
+ }
+
+ public StyledString getStyledText(Object element) {
+ return getStyledText(element, true);
+ }
+
+ public StyledString getStyledText(Object element, boolean includeCommentStats) {
+ Styler styler = null;
+ if (element instanceof IComment) {
+ styler = COMMENT_STYLE;
+ }
+ StyledString styledString = new StyledString(getText(element), styler);
+ if (element instanceof GlobalCommentsNode) {
+ element = ((GlobalCommentsNode) element).getReview();
+ }
+ if (includeCommentStats) {
+ addTopicContainerStatsStyle(element, styledString);
+ }
+ addTopicLineNumberStyle(element, styledString);
+ addFilePathStyle(element, styledString);
+ return styledString;
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ return ITEMS_COLUMN.getImage(element);
+ }
+
+ public static void addTopicLineNumberStyle(Object element, StyledString styledString) {
+ if (element instanceof ITopic) {
+ ITopic comment = (ITopic) element;
+ String[] locationStrings = new String[comment.getLocations().size()];
+ int index = 0;
+ for (ILocation location : comment.getLocations()) {
+ locationStrings[index++] = ITEMS_COLUMN.getText(location);
+ }
+ String locationString = StringUtils.join(locationStrings, ", ");
+ styledString.append(" " + locationString, LINE_NUMBER_STYLE);
+ }
+ }
+
+ public static void addFilePathStyle(Object element, StyledString styledString) {
+ if (element instanceof IFileItem) {
+ IReviewItem item = (IReviewItem) element;
+ styledString.append(" " + item.getName(), StyledString.QUALIFIER_STYLER);
+ }
+ }
+
+ public static void addTopicContainerStatsStyle(Object element, StyledString styledString) {
+ if (element instanceof ITopicContainer) {
+ ITopicContainer container = (ITopicContainer) element;
+ String statsText = getStatsText(container);
+ styledString.append(statsText, StyledString.DECORATIONS_STYLER);
+ }
+ }
+
+ public static String getIndexText(IComment comment) {
+ long index = comment.getIndex();
+ return index < Long.MAX_VALUE ? index + "" : "";
+ }
+
+ public static String getStatsText(ITopicContainer container) {
+ List<? extends IComment> comments;
+ if (container instanceof IReview) {
+ comments = container.getTopics();
+ } else {
+ comments = container.getAllComments();
+ }
+ int commentCount = comments.size();
+ int draftCount = 0;
+ for (IComment comment : comments) {
+ if (comment.isDraft()) {
+ draftCount += 1;
+ }
+ }
+ commentCount -= draftCount;
+ String statsText = "";
+ if (commentCount > 0 && draftCount > 0) {
+ statsText = NLS.bind(" [{0} comments, {1} drafts]", commentCount, draftCount);
+ } else if (commentCount > 0) {
+ statsText = NLS.bind(" [{0} comments]", commentCount);
+ } else if (draftCount > 0) {
+ statsText = NLS.bind(" [{0} drafts]", draftCount);
+ }
+ return statsText;
+ }
+
+ @Override
+ public abstract TableColumnProvider[] getColumnProviders();
+}
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsSorter.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsSorter.java
new file mode 100644
index 0000000..ea9249a
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsSorter.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * 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:
+ * Miles Parker (Tasktop Technologies) - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.reviews.ui.providers;
+
+import java.util.Date;
+
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerSorter;
+import org.eclipse.mylyn.reviews.core.model.IDated;
+import org.eclipse.mylyn.reviews.core.model.IIndexed;
+import org.eclipse.mylyn.reviews.core.model.IReviewItem;
+
+/**
+ * Default ordering for review items. Order is in general:
+ * <ol>
+ * <li>Global Comments</i>
+ * <li>Patch Sets, ReviewItemSets, other containers</li>
+ * <li>Dated Items by Change Date (Includes e.g. patch sets, artifacts, etc..)</li>
+ * <li>Orderable Items (Locations, Comments without dates)</li>
+ * <li>Other Review Items (Files, by full path name)</li>
+ * </ol>
+ *
+ * @author Miles Parker
+ */
+public class ReviewsSorter extends ViewerSorter {
+
+ @Override
+ public int compare(Viewer viewer, Object e1, Object e2) {
+ if (e1 instanceof GlobalCommentsNode) {
+ return -1;
+ }
+ if (e2 instanceof GlobalCommentsNode) {
+ return 1;
+ }
+ Date d1 = null;
+ if (e1 instanceof IDated) {
+ IDated ed1 = (IDated) e1;
+ d1 = ed1.getModificationDate();
+ if (d1 == null) {
+ d1 = ed1.getCreationDate();
+ }
+ }
+ Date d2 = null;
+ if (e2 instanceof IDated) {
+ IDated ed2 = (IDated) e2;
+ d2 = ed2.getModificationDate();
+ if (d2 == null) {
+ d2 = ed2.getCreationDate();
+ }
+ }
+
+ if (d1 != null && d2 != null) {
+ return d1.compareTo(d2);
+ }
+
+ if (e1 instanceof IIndexed && e2 instanceof IIndexed) {
+ return IIndexed.COMPARATOR.compare((IIndexed) e1, (IIndexed) e2);
+ }
+
+ if (d1 != null) {
+ return -1;
+ }
+ if (d2 != null) {
+ return 1;
+ }
+ if (e1 instanceof IIndexed) {
+ return -1;
+ }
+ if (e2 instanceof IIndexed) {
+ return 1;
+ }
+
+ //We want to use full path, not the shortened name in UI.
+ if (e1 instanceof IReviewItem && e2 instanceof IReviewItem) {
+ return super.compare(viewer, ((IReviewItem) e1).getName(), ((IReviewItem) e2).getName());
+ }
+ return super.compare(viewer, e1, e2);
+ }
+
+ @Override
+ public int category(Object element) {
+ if (element instanceof IIndexed) {
+ return 1;
+ }
+ if (element instanceof IDated) {
+ return 2;
+ }
+ return super.category(element);
+ }
+}
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsTreeContentProvider.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsTreeContentProvider.java
new file mode 100644
index 0000000..5677d36
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/ReviewsTreeContentProvider.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * 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:
+ * Miles Parker (Tasktop Technologies) - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.reviews.ui.providers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.mylyn.reviews.core.model.IFileItem;
+import org.eclipse.mylyn.reviews.core.model.IReview;
+import org.eclipse.mylyn.reviews.core.model.IReviewItem;
+import org.eclipse.mylyn.reviews.core.model.IReviewItemSet;
+import org.eclipse.mylyn.reviews.core.model.ITopic;
+import org.eclipse.mylyn.reviews.core.model.ITopicContainer;
+
+/**
+ * Provides a common tree hierarchy similar to Gerrit Web UI hierarchy, except that when used with ReviewsSorter, global
+ * comments appear as members of a single "Global" node. Hierarchy is:
+ * <ol>
+ * <li>Global Node</li>
+ * <li>-Comments</li>
+ * <li>Patch Sets</li>
+ * <li>-Artifacts</li>
+ * <li>--Artifact Comments</li>
+ * </ol>
+ *
+ * @see ReviewsSorter
+ * @author Miles Parker
+ */
+public class ReviewsTreeContentProvider extends GenericTreeContentProvider {
+
+ public Object[] getElements(Object element) {
+ if (element instanceof GlobalCommentsNode) {
+ return ((GlobalCommentsNode) element).getReview().getTopics().toArray();
+ } else if (element instanceof EObject) {
+ List<Object> children = new ArrayList<Object>();
+ if (element instanceof IReview) {
+ children.add(new GlobalCommentsNode((IReview) element));
+ children.addAll(((IReview) element).getItems());
+ return children.toArray();
+ }
+ if (element instanceof IReviewItemSet) {
+ IReviewItemSet itemSet = (IReviewItemSet) element;
+ children.addAll(itemSet.getTopics());
+ children.addAll(itemSet.getItems());
+ }
+ if (element instanceof ITopic) {
+ ITopic topic = (ITopic) element;
+ children.addAll(topic.getReplies());
+ }
+ if (element instanceof IFileItem) {
+ IFileItem item = (IFileItem) element;
+ for (ITopic topic : item.getBase().getTopics()) {
+ children.add(topic);
+ }
+ for (ITopic topic : item.getTarget().getTopics()) {
+ children.add(topic);
+ }
+ }
+ return children.toArray();
+ }
+ return getCollectionChildren(element);
+ }
+
+ /**
+ * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
+ */
+ @Override
+ public boolean hasChildren(Object element) {
+ if (element instanceof IReview) {
+ return ((IReview) element).getItems().size() > 0;
+ }
+ if (element instanceof GlobalCommentsNode) {
+ return ((GlobalCommentsNode) element).getReview().getTopics().size() > 0;
+ }
+ return ((element instanceof ITopicContainer) && ((ITopicContainer) element).getAllComments().size() > 0)
+ || (element instanceof IReview && ((IReview) element).getItems().size() > 0)
+ || (element instanceof GlobalCommentsNode && hasChildren(((GlobalCommentsNode) element).getReview()) || (element instanceof IReviewItemSet && ((IReviewItemSet) element).getItems()
+ .size() > 0)) || hasCollectionChildren(element);
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ if (element instanceof IReviewItem) {
+ return ((IReviewItem) element).getReview();
+ }
+ return null;
+ }
+}
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/TableStyledLabelProvider.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/TableStyledLabelProvider.java
new file mode 100644
index 0000000..074326a
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/providers/TableStyledLabelProvider.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * 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:
+ * Miles Parker (Tasktop Technologies) - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.reviews.ui.providers;
+
+import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.StyledString;
+import org.eclipse.swt.graphics.Image;
+
+/**
+ * Supports independent styling of individual table columns.
+ *
+ * @author Miles Parker
+ */
+public abstract class TableStyledLabelProvider extends LabelProvider implements IStyledLabelProvider,
+ ITableLabelProvider {
+
+ public static class TableColumnProvider extends LabelProvider implements IStyledLabelProvider {
+
+ private final String title;
+
+ private final int weight;
+
+ private final int minimumSize;
+
+ private final boolean fill;
+
+ public TableColumnProvider(String title, int weight, int minimumSize, boolean fill) {
+ this.title = title;
+ this.weight = weight;
+ this.minimumSize = minimumSize;
+ this.fill = fill;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public int getWeight() {
+ return weight;
+ }
+
+ public int getMinimumSize() {
+ return minimumSize;
+ }
+
+ public boolean isFillAvailable() {
+ return fill;
+ }
+
+ public StyledString getStyledText(Object element) {
+ String columnText = getText(element);
+ if (columnText == null) {
+ columnText = "";
+ }
+ return new StyledString(columnText);
+ }
+
+ public boolean isStyled() {
+ return false;
+ }
+
+ public String getToolTipText(Object element) {
+ return null;
+ }
+ }
+
+ public static abstract class AdaptingTableColumnProvider extends TableColumnProvider {
+ private final TableColumnProvider targetProvider;
+
+ public AdaptingTableColumnProvider(TableColumnProvider targetProvider, String title, int weight,
+ int minimumSize, boolean fill) {
+ super(title, weight, minimumSize, fill);
+ this.targetProvider = targetProvider;
+ }
+
+ @Override
+ public StyledString getStyledText(Object element) {
+ Object columnObject = adapt(element);
+ if (columnObject != null) {
+ return targetProvider.getStyledText(columnObject);
+ }
+ return new StyledString();
+ };
+
+ @Override
+ public Image getImage(Object element) {
+ Object columnObject = adapt(element);
+ if (columnObject != null) {
+ return targetProvider.getImage(columnObject);
+ }
+ return null;
+ };
+
+ @Override
+ public String getToolTipText(Object element) {
+ Object columnObject = adapt(element);
+ if (columnObject != null) {
+ return targetProvider.getToolTipText(columnObject);
+ }
+ return targetProvider.getToolTipText(element);
+ };
+
+ @Override
+ public String getText(Object element) {
+ Object columnObject = adapt(element);
+ if (columnObject != null) {
+ return targetProvider.getText(columnObject);
+ }
+ return "";
+ };
+
+ public abstract Object adapt(Object element);
+ }
+
+ /**
+ * Noop. Note: Subclasses must manage disposal of any member resources by overriding {@link #doDispose()}. Viewers
+ * are expected to call {@link #doDispose()} from viewer. This is necessary because internal delegating providers
+ * and viewers will call dispose, preventing reuse of this label provider.
+ */
+ @Override
+ public final void dispose() {
+ }
+
+ /**
+ * Override to manage resource disposal.
+ */
+ public void doDispose() {
+ super.dispose();
+ }
+
+ public Image getColumnImage(Object element, int columnIndex) {
+ TableColumnProvider columnProvider = getColumnProviders()[columnIndex];
+ return columnProvider.getImage(element);
+ }
+
+ public String getColumnText(Object element, int columnIndex) {
+ TableColumnProvider columnProvider = getColumnProviders()[columnIndex];
+ return columnProvider.getText(element);
+ }
+
+ public abstract TableColumnProvider[] getColumnProviders();
+}
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/views/ReviewExplorer.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/views/ReviewExplorer.java
new file mode 100644
index 0000000..e59ea3c
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/internal/reviews/ui/views/ReviewExplorer.java
@@ -0,0 +1,614 @@
+/*******************************************************************************
+ * Copyright (c) 2012 SpringSource, a divison of VMware, Inc.
+ * Copyright (c) 2012 Ericsson
+ * 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:
+ * Miles Parker (Tasktop Technologies) - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.reviews.ui.views;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TableLayout;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.mylyn.internal.reviews.ui.ReviewsImages;
+import org.eclipse.mylyn.internal.reviews.ui.ReviewsUiConstants;
+import org.eclipse.mylyn.internal.reviews.ui.providers.ReviewsLabelProvider;
+import org.eclipse.mylyn.internal.reviews.ui.providers.TableStyledLabelProvider;
+import org.eclipse.mylyn.internal.reviews.ui.providers.TableStyledLabelProvider.TableColumnProvider;
+import org.eclipse.mylyn.reviews.core.model.IReview;
+import org.eclipse.mylyn.reviews.ui.spi.editor.AbstractReviewTaskEditorPage;
+import org.eclipse.mylyn.tasks.core.ITask;
+import org.eclipse.mylyn.tasks.ui.editors.TaskEditor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IMemento;
+import org.eclipse.ui.IPageListener;
+import org.eclipse.ui.IPartListener;
+import org.eclipse.ui.ISelectionListener;
+import org.eclipse.ui.IViewSite;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.forms.editor.IFormPage;
+import org.eclipse.ui.navigator.CommonNavigator;
+import org.eclipse.ui.navigator.CommonViewer;
+import org.eclipse.ui.navigator.ICommonFilterDescriptor;
+import org.eclipse.ui.navigator.INavigatorActivationService;
+import org.eclipse.ui.navigator.INavigatorFilterService;
+
+/**
+ * @author Miles Parker
+ */
+public class ReviewExplorer extends CommonNavigator implements ISelectionListener {
+
+ public static final String SHOW_VIEW_LIST = "showViewList";
+
+ public static final String FILTER_FOR_COMMENTS = "filterForComments";
+
+ private static final String TREE_ACTION_GROUP = "tree";
+
+ private static final String FILTER_ACTION_GROUP = "filters";
+
+ private static final String REFRESH_ACTION_GROUP = "refresh";
+
+ private RefreshReviewsAction refreshAction;
+
+ private boolean showList;
+
+ private boolean filterForComments;
+
+ private List<IReview> reviews = Collections.emptyList();
+
+ private TaskEditor currentPart;
+
+ private ReviewsLabelProvider treeLabelProvider;
+
+ private ReviewsLabelProvider flatLabelProvider;
+
+ private TableStyledLabelProvider currentProvider;
+
+ class ShowListAction extends Action {
+ public ShowListAction() {
+ super("", AS_RADIO_BUTTON); //$NON-NLS-1$
+ setText("Show List");
+ setDescription("Show comments in a flat list");
+ setToolTipText("Show all comments in a flat list (Hide files and patch sets)");
+ setImageDescriptor(ReviewsImages.FLAT_LAYOUT);
+ }
+
+ /*
+ * @see Action#actionPerformed
+ */
+ @Override
+ public void run() {
+ if (isChecked()) {
+ if (getMemento() != null) {
+ getMemento().putBoolean(SHOW_VIEW_LIST, true);
+ }
+ showList = true;
+ updateActivations();
+ }
+ }
+ }
+
+ class ShowTreeAction extends Action {
+
+ public ShowTreeAction() {
+ super("", AS_RADIO_BUTTON); //$NON-NLS-1$
+ setText("Show Tree");
+ setDescription("Show all items in a tree");
+ setToolTipText("Show artifacts, files and global comments in a tree");
+ setImageDescriptor(ReviewsImages.HIERARCHICAL_LAYOUT);
+ }
+
+ /*
+ * @see Action#actionPerformed
+ */
+ @Override
+ public void run() {
+ if (isChecked()) {
+ if (getMemento() != null) {
+ getMemento().putBoolean(SHOW_VIEW_LIST, false);
+ }
+ showList = false;
+ updateActivations();
+ }
+ }
+ }
+
+ class RefreshReviewsAction extends Action {
+
+ public RefreshReviewsAction() {
+ super("", AS_PUSH_BUTTON); //$NON-NLS-1$
+ setText("Refresh");
+ setDescription("Refresh Review Items");
+ setToolTipText("Refresh Review Items");
+ setImageDescriptor(ReviewsImages.REFRESH);
+ }
+
+ /*
+ * @see Action#actionPerformed
+ */
+ @Override
+ public void run() {
+ updatePerservingSelection();
+ }
+ }
+
+ class FilterNonCommentsReviewsAction extends Action {
+
+ public FilterNonCommentsReviewsAction() {
+ super("", AS_CHECK_BOX); //$NON-NLS-1$
+ setText("Filter for Comments");
+ setDescription("Filter items for comments.");
+ setToolTipText("Hide items that don't have comments");
+ setImageDescriptor(ReviewsImages.REVIEW_QUOTE);
+ }
+
+ /*
+ * @see Action#actionPerformed
+ */
+ @Override
+ public void run() {
+ filterForComments = isChecked();
+ if (memento != null) {
+ memento.putBoolean(FILTER_FOR_COMMENTS, filterForComments);
+ }
+ updateActivations();
+ }
+ }
+
+ @Override
+ protected CommonViewer createCommonViewer(Composite parent) {
+ flatLabelProvider = new ReviewsLabelProvider.Flat();
+ treeLabelProvider = new ReviewsLabelProvider.Tree();
+ final CommonViewer viewer = super.createCommonViewer(parent);
+ updateTreeViewer(viewer);
+ return viewer;
+ }
+
+ void updateTreeViewer(CommonViewer viewer) {
+ for (TreeColumn column : viewer.getTree().getColumns()) {
+ column.dispose();
+ }
+ if (isShowColumns()) {
+ //Artifact top-level
+ Tree treeTable = viewer.getTree();
+
+ treeTable.setHeaderVisible(true);
+ treeTable.setLinesVisible(false);
+ treeTable.setLayoutData(new GridData(GridData.FILL_BOTH));
+ currentProvider = null;
+ if (isFlat()) {
+ currentProvider = flatLabelProvider;
+ } else {
+ currentProvider = treeLabelProvider;
+ }
+ if (viewer.getLabelProvider() != currentProvider) {
+ viewer.setLabelProvider(currentProvider);
+ }
+ updateTable(viewer);
+ }
+ }
+
+ void updateTable(TreeViewer viewer) {
+ TableLayout layout = new TableLayout();
+ viewer.getTree().setLayout(layout);
+ ColumnViewerToolTipSupport.enableFor(viewer);
+ for (TableColumnProvider columnProvider : currentProvider.getColumnProviders()) {
+ updateColumn(viewer, columnProvider);
+ }
+ }
+
+ void updateColumn(TreeViewer viewer, final TableColumnProvider provider) {
+ TreeColumn column = null;
+ TreeViewerColumn viewerColumn = new TreeViewerColumn(viewer, SWT.NONE);
+ final DelegatingStyledCellLabelProvider styledLabelProvider = new DelegatingStyledCellLabelProvider(provider) {
+ @Override
+ public String getToolTipText(Object element) {
+ //For some reason tooltips are not delegated..
+ return provider.getToolTipText(element);
+ };
+ };
+ viewerColumn.setLabelProvider(styledLabelProvider);
+ column = viewerColumn.getColumn();
+ column.setText(provider.getTitle());
+ if (!provider.isFillAvailable()) {
+ column.setWidth(provider.getMinimumSize());
+ } else {
+ int width = viewer.getTree().getClientArea().width;
+ if (!viewer.getTree().getVerticalBar().isVisible()) {
+ width -= viewer.getTree().getVerticalBar().getSize().x;
+ }
+ int allWidths = 0;
+ for (TableColumnProvider provider2 : currentProvider.getColumnProviders()) {
+ if (provider2 != provider) {
+ allWidths += provider2.getMinimumSize();
+ }
+ }
+ column.setWidth((width - allWidths));
+ }
+ TableLayout layout = (TableLayout) viewer.getTree().getLayout();
+ layout.addColumnData(new ColumnWeightData(provider.getWeight(), provider.getMinimumSize()));
+ }
+
+ @Override
+ protected CommonViewer createCommonViewerObject(Composite parent) {
+ return new CommonViewer(getViewId(), parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
+ }
+
+ private Collection<Object> matchingElements(ITreeContentProvider provider, Object parent,
+ Collection<Object> toMatch, boolean prune) {
+ HashSet<String> matchingLabels = new HashSet<String>();
+ for (Object object : toMatch) {
+ String label = ((ILabelProvider) getCommonViewer().getLabelProvider()).getText(object);
+ matchingLabels.add(label);
+ }
+ return matchingLabelElements(provider, parent, matchingLabels, prune);
+ }
+
+ private Collection<Object> matchingLabelElements(ITreeContentProvider provider, Object parent,
+ Collection<String> toMatch, boolean prune) {
+ HashSet<Object> matches = new HashSet<Object>();
+ String parentLabel = ((ILabelProvider) getCommonViewer().getLabelProvider()).getText(parent);
+ if (toMatch.contains(parentLabel)) {
+ matches.add(parent);
+ }
+ Object[] children = provider.getElements(parent);
+ for (Object object : children) {
+ String childLabel = ((ILabelProvider) getCommonViewer().getLabelProvider()).getText(object);
+ if (!prune || toMatch.contains(childLabel)) {
+ matches.addAll(matchingLabelElements(provider, object, toMatch, prune));
+ }
+ }
+ return matches;
+ }
+
+ public void refreshView() {
+ Display.getDefault().asyncExec(new Runnable() {
+ public void run() {
+ updatePerservingSelection();
+ }
+ });
+ }
+
+ /**
+ * @see org.eclipse.ui.navigator.CommonNavigator#createPartControl(org.eclipse.swt.widgets.Composite)
+ */
+ @Override
+ public void createPartControl(Composite aParent) {
+ IActionBars actionBars = getViewSite().getActionBars();
+ IToolBarManager manager = actionBars.getToolBarManager();
+
+ if (isSupportsListTree()) {
+ showList = false;
+ if (getMemento() != null) {
+ Boolean value = getMemento().getBoolean(SHOW_VIEW_LIST);
+ if (value != null) {
+ showList = value;
+ }
+ }
+ ShowTreeAction showTreeAction = new ShowTreeAction();
+ showTreeAction.setChecked(!showList);
+ ShowListAction showListAction = new ShowListAction();
+ showListAction.setChecked(showList);
+ manager.add(new Separator(TREE_ACTION_GROUP));
+ manager.add(new Separator("presentation")); //$NON-NLS-1$
+ manager.appendToGroup("presentation", showTreeAction); //$NON-NLS-1$
+ manager.appendToGroup("presentation", showListAction); //$NON-NLS-1$
+ }
+ filterForComments = false;
+ if (getMemento() != null) {
+ Boolean value = getMemento().getBoolean(FILTER_FOR_COMMENTS);
+ if (value != null) {
+ filterForComments = value;
+ }
+ }
+ manager.add(new Separator(FILTER_ACTION_GROUP));
+ manager.appendToGroup(FILTER_ACTION_GROUP, new FilterNonCommentsReviewsAction());
+ manager.add(new Separator("Separator"));
+
+ super.createPartControl(aParent);
+
+ manager.add(new Separator(REFRESH_ACTION_GROUP));
+ refreshAction = new RefreshReviewsAction();
+ refreshAction.setEnabled(false);
+ manager.appendToGroup(REFRESH_ACTION_GROUP, refreshAction);
+
+ updateActivations();
+
+ getViewSite().getPage().addPartListener(new IPartListener() {
+
+ public void partOpened(IWorkbenchPart part) {
+ }
+
+ public void partDeactivated(IWorkbenchPart part) {
+ }
+
+ public void partClosed(IWorkbenchPart part) {
+ }
+
+ public void partBroughtToTop(IWorkbenchPart part) {
+ }
+
+ public void partActivated(IWorkbenchPart part) {
+ if (part == ReviewExplorer.this) {
+ activated();
+ }
+ }
+ });
+
+ IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+ if (activePage != null) {
+ handleActivePage(activePage);
+ } else {
+ PlatformUI.getWorkbench().getActiveWorkbenchWindow().addPageListener(new IPageListener() {
+ public void pageOpened(IWorkbenchPage page) {
+ }
+
+ public void pageClosed(IWorkbenchPage page) {
+ }
+
+ public void pageActivated(IWorkbenchPage page) {
+ handleActivePage(page);
+ }
+ });
+ }
+ update();
+ }
+
+ private void handleActivePage(IWorkbenchPage page) {
+ if (page != null) {
+ IEditorPart activeEditor = page.getActiveEditor();
+ if (activeEditor instanceof TaskEditor) {
+ currentPart = (TaskEditor) activeEditor;
+ }
+ page.addPartListener(new IPartListener() {
+
+ public void partOpened(IWorkbenchPart part) {
+ // ignore
+
+ }
+
+ public void partDeactivated(IWorkbenchPart part) {
+ // ignore
+
+ }
+
+ public void partClosed(IWorkbenchPart part) {
+ if (part == currentPart) {
+ clear();
+ }
+ }
+
+ public void partBroughtToTop(IWorkbenchPart part) {
+ // ignore
+
+ }
+
+ public void partActivated(IWorkbenchPart part) {
+ // ignore
+
+ }
+ });
+ activated();
+ }
+ }
+
+ protected void updateContentDescription() {
+ String title = "(No Selection)";
+ Object input = getCommonViewer().getInput();
+ if (input != null && currentPart != null) {
+ ITask task = currentPart.getTaskEditorInput().getTask();
+ title = "Change " + task.getTaskId() + ": " + task.getSummary();
+ }
+ setContentDescription(title);
+ }
+
+ /* (non-Javadoc)
+ * Method declared on ISelectionListener.
+ * Notify the current page that the selection has changed.
+ */
+ public void selectionChanged(IWorkbenchPart part, ISelection sel) {
+ List<IReview> lastReviews = reviews;
+ if (part instanceof TaskEditor) {
+ TaskEditor editor = (TaskEditor) part;
+ IFormPage page = editor.getActivePageInstance();
+ if (page instanceof AbstractReviewTaskEditorPage) {
+ IReview review = ((AbstractReviewTaskEditorPage) page).getReview();
+ if (review != null) {
+ reviews = Collections.singletonList(review);
+ } else {
+ reviews = Collections.emptyList();
+ }
+ currentPart = (TaskEditor) part;
+
+ if (!reviews.equals(lastReviews)) {
+ update();
+ }
+ }
+ }
+ }
+
+ private void update() {
+ getCommonViewer().setInput(reviews);
+ refreshAction.setEnabled(!reviews.isEmpty());
+ updateContentDescription();
+ getCommonViewer().refresh();
+ }
+
+ protected void updatePerservingSelection() {
+ //Because we don't necessarily have the same backing EMF objects, we need to test on equality. We'd also like to restore selection even if the tree structure changes. In this case, using labels will be most consistent with user expectations..
+ Object[] priorExpanded = getCommonViewer().getExpandedElements();
+ Object[] priorSelection = new Object[] {};
+ if (getCommonViewer().getSelection() instanceof IStructuredSelection) {
+ priorSelection = ((IStructuredSelection) getCommonViewer().getSelection()).toArray();
+ }
+ getCommonViewer().getControl().setRedraw(false);
+ update();
+ Collection<Object> newExpanded = matchingElements(
+ (ITreeContentProvider) getCommonViewer().getContentProvider(), reviews,
+ new HashSet<Object>(Arrays.asList(priorExpanded)), true);
+ Collection<Object> newSelection = matchingElements(
+ (ITreeContentProvider) getCommonViewer().getContentProvider(), reviews,
+ new HashSet<Object>(Arrays.asList(priorSelection)), false);
+ getCommonViewer().setExpandedElements(newExpanded.toArray());
+ getCommonViewer().setSelection(new StructuredSelection(newSelection.toArray()), true);
+ getCommonViewer().getControl().setRedraw(true);
+ getCommonViewer().getControl().redraw();
+ }
+
+ private void clear() {
+ reviews = Collections.emptyList();
+ currentPart = null;
+ update();
+ }
+
+ /**
+ * @see org.eclipse.ui.navigator.CommonNavigator#saveState(org.eclipse.ui.IMemento)
+ */
+ @Override
+ public void saveState(IMemento aMemento) {
+ super.saveState(aMemento);
+ if (isSupportsListTree()) {
+ aMemento.putBoolean(SHOW_VIEW_LIST, showList);
+ aMemento.putBoolean(FILTER_ACTION_GROUP, filterForComments);
+ }
+ }
+
+ /**
+ * @see org.eclipse.ui.navigator.CommonNavigator#init(org.eclipse.ui.IViewSite, org.eclipse.ui.IMemento)
+ */
+ @Override
+ public void init(IViewSite site, IMemento memento) throws PartInitException {
+ site.getPage().addPostSelectionListener(this);
+ super.init(site, memento);
+ }
+
+ /* (non-Javadoc)
+ * Method declared on IWorkbenchPart.
+ */
+ @Override
+ public void dispose() {
+ super.dispose();
+ getSite().getPage().removePostSelectionListener(this);
+ //Don't hang on to references
+ flatLabelProvider.doDispose();
+ treeLabelProvider.doDispose();
+ currentPart = null;
+ }
+
+ protected void refreshAll() {
+ refreshView();
+ }
+
+ public boolean isFlat() {
+ return isSupportsListTree() && showList;
+ }
+
+ protected String getTreeContentId() {
+ return ReviewsUiConstants.REVIEW_CONTENT_ID;
+ }
+
+ protected String getListContentId() {
+ return ReviewsUiConstants.REVIEW_FLAT_CONTENT_ID;
+ }
+
+ protected String getViewId() {
+ return ReviewsUiConstants.REVIEW_EXPLORER_ID;
+ }
+
+ protected final boolean isSupportsListTree() {
+ return getListContentId() != null;
+ }
+
+ protected boolean isShowColumns() {
+ return true;
+ }
+
+ public boolean isFilterForComments() {
+ return filterForComments;
+ }
+
+ protected void updateActivations() {
+ INavigatorActivationService activationService = getCommonViewer().getNavigatorContentService()
+ .getActivationService();
+ if (!isSupportsListTree()) {
+ activationService.activateExtensions(new String[] { getTreeContentId() }, false);
+ } else {
+ if (isFlat()) {
+ activationService.deactivateExtensions(new String[] { getTreeContentId() }, false);
+ activationService.activateExtensions(new String[] { getListContentId() }, false);
+ } else {
+ activationService.deactivateExtensions(new String[] { getListContentId() }, false);
+ activationService.activateExtensions(new String[] { getTreeContentId() }, false);
+ }
+ }
+ INavigatorFilterService filterService = getCommonViewer().getNavigatorContentService().getFilterService();
+ ICommonFilterDescriptor[] visibleDescriptors = filterService.getVisibleFilterDescriptors();
+ boolean commentFilterActive = false;
+ for (ICommonFilterDescriptor descriptor : visibleDescriptors) {
+ if (descriptor.getId().equals(ReviewsUiConstants.REVIEW_FILTER_FOR_COMMENTS)
+ && filterService.isActive(descriptor.getId())) {
+ commentFilterActive = true;
+ break;
+ }
+ }
+ boolean filtersModified = false;
+ if (isFilterForComments() && !commentFilterActive) {
+ filterService.setActiveFilterIds(new String[] { ReviewsUiConstants.REVIEW_FILTER_FOR_COMMENTS });
+ filtersModified = true;
+ } else if (!isFilterForComments() && commentFilterActive) {
+ filterService.setActiveFilterIds(new String[] {});
+ filtersModified = true;
+ }
+ if (filtersModified) {
+ filterService.persistFilterActivationState();
+ ViewerFilter[] visibleFilters = filterService.getVisibleFilters(true);
+ getCommonViewer().setFilters(visibleFilters);
+ }
+
+ updateTreeViewer(getCommonViewer());
+ getCommonViewer().refresh();
+ }
+
+ protected void activated() {
+ if (currentPart instanceof IEditorPart) {
+ selectionChanged(currentPart, StructuredSelection.EMPTY);
+ }
+ }
+
+ public IWorkbenchPart getCurrentPart() {
+ return currentPart;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/reviews/ui/spi/editor/AbstractReviewTaskEditorPage.java b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/reviews/ui/spi/editor/AbstractReviewTaskEditorPage.java
new file mode 100644
index 0000000..51dee9b
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.ui/src/org/eclipse/mylyn/reviews/ui/spi/editor/AbstractReviewTaskEditorPage.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * 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:
+ * Miles Parker (Tasktop Technologies) - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.reviews.ui.spi.editor;
+
+import org.eclipse.mylyn.reviews.core.model.IReview;
+import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPage;
+import org.eclipse.mylyn.tasks.ui.editors.TaskEditor;
+
+/**
+ * Marks task editor as providing Review model for extending classes.
+ *
+ * @author Miles Parker
+ */
+public abstract class AbstractReviewTaskEditorPage extends AbstractTaskEditorPage {
+
+ public AbstractReviewTaskEditorPage(TaskEditor editor, String connectorKind) {
+ super(editor, connectorKind);
+ }
+
+ /**
+ * Returns the current review. All instances should provide one open, accessible review model instance at init time,
+ * and that review should be constant throughout the editor life-cycle.
+ */
+ public abstract IReview getReview();
+}