blob: 38841f51eec013a9c92a74d7b366dd06bf493ee5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010-2014 SAP AG 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:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.skalli.view.ext.impl.internal.infobox;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.eclipse.skalli.commons.HtmlUtils;
import org.eclipse.skalli.model.Project;
import org.eclipse.skalli.model.User;
import org.eclipse.skalli.model.ext.misc.ProjectRating;
import org.eclipse.skalli.model.ext.misc.ProjectRatingStyle;
import org.eclipse.skalli.model.ext.misc.ReviewEntry;
import org.eclipse.skalli.model.ext.misc.ReviewProjectExt;
import org.eclipse.skalli.view.ext.ExtensionUtil;
import com.vaadin.terminal.ThemeResource;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Component;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.Embedded;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Layout;
import com.vaadin.ui.OptionGroup;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
public class ReviewComponent extends CustomComponent {
private static final long serialVersionUID = 5778729327534523225L;
private final ThemeResource ICON_THUMB_UP = new ThemeResource("icons/rating/thumb-up.png"); //$NON-NLS-1$
private final ThemeResource ICON_THUMB_DOWN = new ThemeResource("icons/rating/thumb-down.png"); //$NON-NLS-1$
private final ThemeResource ICON_FACE_CRYING = new ThemeResource("icons/rating/face-crying.png"); //$NON-NLS-1$
private final ThemeResource ICON_FACE_SAD = new ThemeResource("icons/rating/face-sad.png"); //$NON-NLS-1$
private final ThemeResource ICON_FACE_PLAIN = new ThemeResource("icons/rating/face-plain.png"); //$NON-NLS-1$
private final ThemeResource ICON_FACE_SMILE = new ThemeResource("icons/rating/face-smile.png"); //$NON-NLS-1$
private final ThemeResource ICON_FACE_SMILE_BIG = new ThemeResource("icons/rating/face-smile-big.png"); //$NON-NLS-1$
private final ThemeResource ICON_BUTTON_OK = new ThemeResource("icons/button/ok.png"); //$NON-NLS-1$
private final ThemeResource ICON_BUTTON_CANCEL = new ThemeResource("icons/button/cancel.png"); //$NON-NLS-1$
private static final int MILLIS_PER_SECOND = 1000;
private static final int MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60;
private static final int MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60;
private static final int MILLIS_PER_DAY = MILLIS_PER_HOUR * 24;
private static final String HSPACE = "    "; //$NON-NLS-1$
private Project project;
private ReviewProjectExt extension;
private List<ReviewEntry> reviews;
private int size;
private int defaultPageLength;
private int maxPageLength;
private int currentPageLength;
private boolean showAll;
private ExtensionUtil util;
private ProjectRatingStyle ratingStyle;
private Layout layout;
private GridLayout reviewGrid;
private Button morelessButton;
private Button nextButton;
private Button prevButton;
private CssLayout reviewButtons;
private Window reviewPopup;
private Label ratioLabel;
private int currentPage;
private int lastPage;
@SuppressWarnings("serial")
public ReviewComponent(Project project, int defaultPageLength, int maxPageLength, ExtensionUtil util) {
this.project = project;
this.defaultPageLength = defaultPageLength;
this.maxPageLength = maxPageLength;
this.util = util;
extension = project.getExtension(ReviewProjectExt.class);
if (extension == null) {
extension = new ReviewProjectExt();
extension.setExtensibleEntity(project);
project.addExtension(extension);
}
reviews = extension.getReviews();
ratingStyle = extension.getRatingStyle();
showAll = false;
size = reviews.size();
currentPage = 0;
currentPageLength = defaultPageLength;
lastPage = size / maxPageLength;
layout = new CssLayout() {
@Override
protected String getCss(Component c) {
if (c instanceof Button) {
return "padding-top:3px;padding-right:15px"; //$NON-NLS-1$
}
if (c instanceof Label) {
return "padding-bottom:10px"; //$NON-NLS-1$
}
return "padding-top:3px"; //$NON-NLS-1$
}
};
layout.setSizeFull();
paintReviewButtons();
paintReviewList();
setCompositionRoot(layout);
}
private void paintReviewList() {
if (size > 0) {
painRatioLabel();
paintReviewGrid();
paintPageButtons();
}
}
private void painRatioLabel() {
String ratioLabelValue = null;
if (ProjectRatingStyle.TWO_STATES.equals(ratingStyle)) {
ratioLabelValue = "<span style=\"font-weight:bold\">" //$NON-NLS-1$
+ extension.getRecommendedRatio() + " of " + extension.getNumberVotes()
+ " users recommend this project"
+ "</span>"; //$NON-NLS-1$
} else if (ProjectRatingStyle.FIVE_STATES.equals(ratingStyle)) {
ratioLabelValue = "<span style=\"font-weight:bold\">" //$NON-NLS-1$
+ "Average rating of this project by " + extension.getNumberVotes()
+ " users: "
+ getDescription(extension.getAverageRating()) + "</span>"; //$NON-NLS-1$
}
if (ratioLabel == null) {
ratioLabel = new Label(ratioLabelValue, Label.CONTENT_XHTML);
layout.addComponent(ratioLabel);
} else {
ratioLabel.setValue(ratioLabelValue);
}
}
private void paintReviewGrid() {
if (reviewGrid == null) {
reviewGrid = new GridLayout(2, currentPageLength);
reviewGrid.setSizeFull();
reviewGrid.setSpacing(true);
layout.addComponent(reviewGrid);
} else {
reviewGrid.removeAllComponents();
}
int rows = reviewGrid.getRows();
if (rows != currentPageLength) {
reviewGrid.setRows(Math.min(size, currentPageLength));
}
int row = 0;
List<ReviewEntry> latestReviews = getLatestReviews(currentPage, currentPageLength);
for (ReviewEntry review : latestReviews) {
ProjectRating rating = review.getRating();
Embedded e = new Embedded(null, getIcon(rating));
e.setDescription(getDescription(rating));
e.setWidth("22px"); //$NON-NLS-1$
e.setHeight("22px"); //$NON-NLS-1$
reviewGrid.addComponent(e, 0, row);
StringBuilder sb = new StringBuilder();
sb.append("<span style=\"white-space:normal\">"); //$NON-NLS-1$
sb.append(HtmlUtils.clean(review.getComment()));
sb.append("<br>"); //$NON-NLS-1$
sb.append("<span style=\"font-size:x-small\">").append(" posted by "); //$NON-NLS-1$
sb.append(StringEscapeUtils.escapeHtml(review.getVoter()));
sb.append(" "); //$NON-NLS-1$
long deltaMillis = System.currentTimeMillis() - review.getTimestamp();
long deltaDays = deltaMillis / MILLIS_PER_DAY;
if (deltaDays > 0) {
sb.append(deltaDays);
sb.append(" days ago");
} else {
long deltaHours = deltaMillis / MILLIS_PER_HOUR;
if (deltaHours > 0) {
sb.append(deltaHours).append(" hours ago");
} else {
long deltaMinutes = deltaMillis / MILLIS_PER_MINUTE;
if (deltaMinutes > 0) {
sb.append(deltaMinutes).append(" minutes ago");
} else {
sb.append(" just now");
}
}
sb.append("</span></span>"); //$NON-NLS-1$
}
CssLayout css = new CssLayout();
css.setSizeFull();
Label comment = new Label(sb.toString(), Label.CONTENT_XHTML);
comment.setSizeUndefined();
css.addComponent(comment);
reviewGrid.addComponent(css, 1, row);
reviewGrid.setColumnExpandRatio(1, 1.0f);
++row;
}
}
private ThemeResource getIcon(ProjectRating rating) {
ThemeResource icon = null;
switch (rating) {
case UP:
icon = ICON_THUMB_UP;
break;
case DOWN:
icon = ICON_THUMB_DOWN;
break;
case FACE_CRYING:
icon = ICON_FACE_CRYING;
break;
case FACE_SAD:
icon = ICON_FACE_SAD;
break;
case FACE_PLAIN:
icon = ICON_FACE_PLAIN;
break;
case FACE_SMILE:
icon = ICON_FACE_SMILE;
break;
case FACE_SMILE_BIG:
icon = ICON_FACE_SMILE_BIG;
break;
}
return icon;
}
private String getDescription(ProjectRating rating) {
String description = null;
switch (rating) {
case UP:
description = "Recommended";
break;
case DOWN:
description = "Not Recommended";
break;
case FACE_CRYING:
description = "Lousy!";
break;
case FACE_SAD:
description = "Poor";
break;
case FACE_PLAIN:
description = "Average";
break;
case FACE_SMILE:
description = "Good";
break;
case FACE_SMILE_BIG:
description = "Excellent!";
break;
}
return description;
}
private String getRatingQuestion() {
switch (ratingStyle) {
case TWO_STATES:
return "Would you recommend this project?";
case FIVE_STATES:
return "How would you rate this project?";
}
return null;
}
private String getReviewButtonsHeight() {
switch (ratingStyle) {
case TWO_STATES:
return "80px"; //$NON-NLS-1$
case FIVE_STATES:
return "70px"; //$NON-NLS-1$
}
return "0px"; //$NON-NLS-1$
}
private String getReviewComment(ProjectRating rating) {
String comment = null;
switch (rating) {
case UP:
comment = "I recommend this project!";
break;
case DOWN:
comment = "I do not recommend this project!";
break;
case FACE_CRYING:
comment = "I think this project is lousy!";
break;
case FACE_SAD:
comment = "I think this project is poor!";
break;
case FACE_PLAIN:
comment = "I think this project is average!";
break;
case FACE_SMILE:
comment = "I think this project is good!";
break;
case FACE_SMILE_BIG:
comment = "I think this project is excellent!";
break;
}
return comment;
}
private String getReviewCommentQuestion(ProjectRating rating) {
String question = null;
switch (rating) {
case UP:
question = "Why do you recommend this project?";
break;
case DOWN:
question = "Why do you not recommend this project?";
break;
case FACE_CRYING:
question = "Why do you think this project is lousy?";
break;
case FACE_SAD:
question = "Why do you think this project is poor?";
break;
case FACE_PLAIN:
question = "Why do you think this project is average?";
break;
case FACE_SMILE:
question = "Why do you think this project is good?";
break;
case FACE_SMILE_BIG:
question = "Why do you think this project is excellent?";
break;
}
return question;
}
private void paintPageButtons() {
paintMoreLessButton();
paintPrevNextButtons();
paintButtonStates();
}
@SuppressWarnings({ "deprecation", "serial" })
private void paintMoreLessButton() {
if (morelessButton == null) {
morelessButton = new Button();
morelessButton.setStyleName(Button.STYLE_LINK);
morelessButton.addListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
if (showAll) {
showAll = false;
currentPage = 0;
currentPageLength = defaultPageLength;
lastPage = size / currentPageLength;
paintButtonStates();
} else {
showAll = true;
currentPage = 0;
currentPageLength = maxPageLength;
lastPage = size / currentPageLength;
paintButtonStates();
}
paintReviewGrid();
}
});
layout.addComponent(morelessButton);
}
}
@SuppressWarnings({ "serial", "deprecation" })
private void paintPrevNextButtons() {
if (prevButton == null) {
prevButton = new Button();
prevButton.setStyleName(Button.STYLE_LINK);
prevButton.addListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
--currentPage;
paintReviewGrid();
paintButtonStates();
}
});
layout.addComponent(prevButton);
}
if (nextButton == null) {
nextButton = new Button();
nextButton.setStyleName(Button.STYLE_LINK);
nextButton.addListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
++currentPage;
paintReviewGrid();
paintButtonStates();
}
});
layout.addComponent(nextButton);
}
}
private void paintButtonStates() {
String caption = showAll ? "Show Latest Reviews" : "Show All Reviews";
morelessButton.setCaption(caption);
if (showAll || size > currentPageLength) {
morelessButton.setEnabled(true);
} else {
morelessButton.setEnabled(false);
}
if (showAll && size > currentPageLength) {
nextButton.setVisible(true);
nextButton.setCaption("Next " + currentPageLength + " Reviews");
prevButton.setVisible(true);
prevButton.setCaption("Previous " + currentPageLength + " Reviews");
if (currentPage == lastPage || size <= currentPageLength) {
nextButton.setEnabled(false);
} else {
nextButton.setEnabled(true);
}
if (currentPage == 0 || size <= currentPageLength) {
prevButton.setEnabled(false);
} else {
prevButton.setEnabled(true);
}
} else {
nextButton.setVisible(false);
prevButton.setVisible(false);
}
}
@SuppressWarnings("serial")
private void paintReviewButtons() {
reviewButtons = new CssLayout() {
@Override
protected String getCss(Component c) {
if (c instanceof HorizontalLayout) {
return "padding-left: 15px; padding-top: 10px;"; //$NON-NLS-1$
} else {
return StringUtils.EMPTY;
}
}
};
reviewButtons.setWidth("300px"); //$NON-NLS-1$
reviewButtons.setHeight(getReviewButtonsHeight());
Label label = new Label("<b>" + getRatingQuestion() + "</b>", Label.CONTENT_XHTML); //$NON-NLS-1$ //$NON-NLS-2$
reviewButtons.addComponent(label);
HorizontalLayout hl = new HorizontalLayout();
hl.setSizeUndefined();
final String separatorLabelCaption = "<b>" + HSPACE + "or" + HSPACE + "</b>"; //$NON-NLS-1$ //$NON-NLS-3$
ProjectRating[] ratings = ProjectRating.getRatings(ratingStyle);
int i = 0;
for (ProjectRating rating : ratings) {
if (i > 0) {
Label separatorLabel = new Label(separatorLabelCaption, Label.CONTENT_XHTML);
hl.addComponent(separatorLabel);
hl.setComponentAlignment(separatorLabel, Alignment.MIDDLE_LEFT);
}
paintReviewButton(hl, rating);
++i;
}
reviewButtons.addComponent(hl);
layout.addComponent(reviewButtons);
}
@SuppressWarnings({ "serial", "deprecation" })
private void paintReviewButton(HorizontalLayout hl, final ProjectRating rating) {
Button btn = new Button();
if (util.getLoggedInUser() != null) {
btn.addListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
reviewPopup = createReviewWindow(rating);
getWindow().addWindow(reviewPopup);
}
});
btn.setDescription(getDescription(rating));
} else {
btn.setEnabled(false);
btn.setDescription("Login to rate this project.");
}
btn.setStyleName(Button.STYLE_LINK);
btn.setIcon(getIcon(rating));
hl.addComponent(btn);
}
@SuppressWarnings("serial")
private Window createReviewWindow(final ProjectRating rating) {
final Window subwindow = new Window("Rate and Review");
subwindow.setModal(true);
subwindow.setWidth("420px"); //$NON-NLS-1$
subwindow.setHeight("320px"); //$NON-NLS-1$
VerticalLayout vl = (VerticalLayout) subwindow.getContent();
vl.setSpacing(true);
vl.setSizeFull();
HorizontalLayout hl = new HorizontalLayout();
hl.setSizeUndefined();
Embedded icon = new Embedded(null, getIcon(rating));
Label iconLabel = new Label("<b>" + HSPACE + getReviewComment(rating) + "</b>", Label.CONTENT_XHTML); //$NON-NLS-1$ //$NON-NLS-2$
String captionTextField = getReviewCommentQuestion(rating);
hl.addComponent(icon);
hl.addComponent(iconLabel);
hl.setComponentAlignment(iconLabel, Alignment.MIDDLE_LEFT);
vl.addComponent(hl);
final TextField editor = new TextField(captionTextField);
editor.setRows(3);
editor.setColumns(30);
editor.setImmediate(true);
vl.addComponent(editor);
final User user = util.getLoggedInUser();
final ArrayList<String> userSelects = new ArrayList<String>(2);
userSelects.add("I want to vote as " + user.getDisplayName());
if (extension.getAllowAnonymous()) {
userSelects.add("I want to vote as Anonymous!");
}
final OptionGroup userSelect = new OptionGroup(null, userSelects);
userSelect.setNullSelectionAllowed(false);
userSelect.select(userSelects.get(0));
vl.addComponent(userSelect);
CssLayout css = new CssLayout() {
@Override
protected String getCss(Component c) {
return "margin-left:5px;margin-right:5px;margin-top:10px"; //$NON-NLS-1$
}
};
Button okButton = new Button("OK");
okButton.setIcon(ICON_BUTTON_OK);
okButton.setDescription("Commit changes");
okButton.addListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
String comment = (String) editor.getValue();
if (StringUtils.isBlank(comment)) {
comment = "No Comment";
}
((Window) subwindow.getParent()).removeWindow(subwindow);
String userName = "Anonymous";
if (userSelects.get(0).equals(userSelect.getValue())) {
userName = user.getDisplayName();
}
ReviewEntry review = new ReviewEntry(rating, comment, userName, System.currentTimeMillis());
extension.addReview(review);
util.persist(project);
reviews = extension.getReviews();
size = reviews.size();
currentPage = 0;
lastPage = size / currentPageLength;
paintReviewList();
}
});
css.addComponent(okButton);
Button cancelButton = new Button("Cancel");
cancelButton.setIcon(ICON_BUTTON_CANCEL);
cancelButton.setDescription("Discard changes");
cancelButton.addListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
((Window) subwindow.getParent()).removeWindow(subwindow);
}
});
css.addComponent(cancelButton);
vl.addComponent(css);
vl.setComponentAlignment(css, Alignment.MIDDLE_CENTER);
return subwindow;
}
private List<ReviewEntry> getLatestReviews(int currentPage, int len) {
int first = size - currentPage * currentPageLength - 1;
if (first >= size) {
first = size - 1;
}
int last = first - len + 1;
if (last < 0) {
last = 0;
}
ArrayList<ReviewEntry> result = new ArrayList<ReviewEntry>();
for (int i = first; i >= last; --i) {
result.add(reviews.get(i));
}
return result;
}
}