com.xpn.xwiki.watch.client.ui.articles.ArticleListWidget.java Source code

Java tutorial

Introduction

Here is the source code for com.xpn.xwiki.watch.client.ui.articles.ArticleListWidget.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 * <p/>
 * This is free software;you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation;either version2.1of
 * the License,or(at your option)any later version.
 * <p/>
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY;without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the GNU
 * Lesser General Public License for more details.
 * <p/>
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software;if not,write to the Free
 * Software Foundation,Inc.,51 Franklin St,Fifth Floor,Boston,MA
 * 02110-1301 USA,or see the FSF site:http://www.fsf.org.
 */
package com.xpn.xwiki.watch.client.ui.articles;

import com.xpn.xwiki.watch.client.ui.WatchWidget;
import com.xpn.xwiki.watch.client.ui.dialog.TagListSuggestOracle;
import com.xpn.xwiki.watch.client.ui.utils.DefaultLoadingWidget;
import com.xpn.xwiki.watch.client.ui.utils.HTMLMessages;
import com.xpn.xwiki.watch.client.ui.utils.LoadingAsyncCallback;
import com.xpn.xwiki.watch.client.ui.utils.LoadingWidget;
import com.xpn.xwiki.watch.client.Watch;
import com.xpn.xwiki.watch.client.Constants;
import com.xpn.xwiki.watch.client.annotation.Annotation;
import com.xpn.xwiki.watch.client.data.Feed;
import com.xpn.xwiki.watch.client.data.FeedArticle;
import com.xpn.xwiki.watch.client.data.FeedArticleComment;
import com.xpn.xwiki.gwt.api.client.XObject;
import com.xpn.xwiki.gwt.api.client.dialog.Dialog;
import com.xpn.xwiki.gwt.api.client.widgets.WordListSuggestOracle;
import com.google.gwt.user.client.ui.*;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;

import java.util.List;

import org.xwiki.gwt.dom.client.Document;
import org.xwiki.gwt.dom.client.Selection;

public class ArticleListWidget extends WatchWidget {
    private PopupPanel popup;
    private String selection;
    private FeedArticle currentArticle;

    public ArticleListWidget() {
        super();
    }

    public String getName() {
        return "articlelist";
    }

    public ArticleListWidget(Watch watch) {
        super(watch);
        setPanel(new FlowPanel());
        initWidget(panel);
        init();
    }

    public void init() {
        super.init();
        this.popup = buildPopup();
    }

    public void refreshData() {
        List articlesList = watch.getConfig().getArticles();
        showArticles(articlesList);
        //and refresh the contained nav-bar widgets
        watch.getUserInterface().refreshData("navbar");
        watch.getUserInterface().refreshData("navbar-bottom");
        resizeWindow();
    }

    public void showArticles(List feedentries) {
        panel.clear();

        if ((feedentries == null) || (feedentries.size() == 0)) {
            panel.add(HTMLMessages.getInfoHTML(watch.getTranslation("articlelist.noarticles")));
            return;
        }

        panel.add(new ActionBarWidget(watch));
        panel.add(new NavigationBarWidget(watch));

        for (int i = 0; i < feedentries.size(); i++) {
            FeedArticle article = (FeedArticle) feedentries.get(i);
            showArticle(article);
        }
        //put the navbar at the bottom as well
        panel.add(new NavigationBarWidget(watch) {
            public String getName() {
                return "navbar-bottom";
            }
        });
    }

    protected Widget getHeadlinePanel(FeedArticle article, Widget articlePanel, Widget contentZonePanel) {
        FlowPanel p = new FlowPanel();
        p.setStyleName(watch.getStyleName("article", "headline"));
        p.add(getLeftActionsPanel(article));
        p.add(getHeaderPanel(article, articlePanel, contentZonePanel));
        p.add(getRightActionsPanel(article));
        return p;
    }

    protected Widget getHeaderPanel(FeedArticle article, Widget articlePanel, Widget contentZonePanel) {
        VerticalPanel p = new VerticalPanel();
        p.setStyleName(watch.getStyleName("article", "header"));
        p.add(getTitlePanel(article, articlePanel, contentZonePanel));
        p.add(getDetailsPanel(article));
        p.add(contentZonePanel);
        // add the annotations panel to the article header
        p.add(getAnnotationZonePanel(article));
        HTML commentsStatus = new HTML();
        commentsStatus.setStyleName(watch.getStyleName("article", "comments-status"));
        int nbcomments = article.getCommentsNumber();
        if ((nbcomments > 0) || watch.getConfig().getHasCommentRight()) {
            commentsStatus.addStyleName("clickable");
        }
        String commentTitle = watch.getTranslation("nocomments");
        if (nbcomments != 0) {
            commentTitle = nbcomments + " " + watch.getTranslation("comments");
        }
        commentsStatus.setHTML(commentTitle);
        Widget commentsZonePanel = getCommentsZonePanel(article, commentsStatus);
        p.add(getStatusPanel(article, commentsZonePanel, commentsStatus));
        p.add(commentsZonePanel);
        return p;
    }

    /**
     * @param article the article to build an annotations panel
     * @return the annotations panel for the passed article.
     */
    protected Widget getAnnotationZonePanel(final FeedArticle article) {
        FlowPanel p = new FlowPanel();
        for (final Annotation ann : article.getAnnotations()) {
            FlowPanel annotationPanel = new FlowPanel();
            annotationPanel.addStyleName(watch.getCSSPrefix() + "-article-annotation");
            Label metadataLabel = new Label(ann.getAnnotation());
            Label authorLabel = new Label("by " + ann.getAuthor());
            Image deleteImage = new Image("/xwiki/bin/download/WatchCode/GWT/watch.zip/watch-delete-active.png");
            deleteImage.addClickListener(new ClickListener() {
                public void onClick(Widget arg0) {
                    watch.getXWatchServiceInstance().removeAnnotation(article.getPageName(), ann.getId() + "",
                            new AsyncCallback<String>() {

                                public void onFailure(Throwable arg0) {
                                    showArticle(article);
                                }

                                public void onSuccess(String arg0) {
                                    watch.refreshArticleList();
                                    showArticle(article);
                                }
                            });
                }
            });
            metadataLabel.addStyleName("ann-metadata");
            authorLabel.addStyleName("ann-author");
            deleteImage.addStyleName("ann-delete");

            annotationPanel.add(metadataLabel);
            annotationPanel.add(authorLabel);
            annotationPanel.add(deleteImage);
            p.add(annotationPanel);
        }
        return p;
    }

    protected PopupPanel buildPopup() {
        final PopupPanel popup = new PopupPanel(true);
        popup.setStyleName(watch.getCSSPrefix() + "-add-annotation-popup");
        FlowPanel contents = new FlowPanel();
        popup.setTitle("Add Annotation");
        final TextArea ta = new TextArea();

        Button cancel = new Button("Cancel", new ClickListener() {
            public void onClick(Widget arg0) {
                popup.hide();
            }
        });
        Button annotate = new Button("Annotate", new ClickListener() {
            public void onClick(Widget arg0) {
                if (selection != null) {
                    watch.getXWatchServiceInstance().addAnnotation(selection, ta.getText(),
                            currentArticle.getPageName(), new AsyncCallback<String>() {
                                public void onSuccess(String arg0) {
                                    watch.refreshArticleList();
                                }

                                public void onFailure(Throwable arg0) {
                                    Window.alert("FAILURE : " + arg0.toString());
                                }
                            });
                }
                popup.hide();
            }
        });
        FlowPanel taPanel = new FlowPanel();
        taPanel.addStyleName("popup-textarea");
        taPanel.add(ta);
        contents.add(taPanel);
        FlowPanel holder = new FlowPanel();
        holder.add(cancel);
        holder.add(annotate);
        holder.setStyleName("popup-panel-footer");
        contents.add(holder);
        popup.setWidget(contents);
        return popup;
    }

    protected Widget getDetailsPanel(FeedArticle article) {
        FlowPanel p = new FlowPanel();
        p.setStyleName(watch.getStyleName("article", "details"));
        p.add(getFeedNamePanel(article));
        Label onLabel = new Label(watch.getTranslation("wiki.posted.date"));
        onLabel.addStyleName(watch.getStyleName("article-date-on"));
        p.add(onLabel);
        p.add(getDatePanel(article));
        return p;
    }

    protected Widget getFeedLogoPanel(FeedArticle article) {
        FlowPanel p = new FlowPanel();
        p.setStyleName(watch.getStyleName("article", "logo"));

        Image feedLogo = null;
        String feedName = article.getFeedName();
        if ((feedName != null) && (!feedName.equals(""))) {
            Feed feed = (Feed) watch.getConfig().getFeedsList().get(feedName);
            if (feed != null) {
                String imgurl = watch.getFavIcon(feed);
                if (imgurl != null) {
                    feedLogo = new Image(imgurl);
                }
            }
        }

        // if the image was not found, use the default
        if (feedLogo == null) {
            feedLogo = new Image(watch.getSkinFile(Constants.IMAGE_FEED));
        }

        p.add(feedLogo);

        return p;
    }

    protected Panel getTitlePanel(final FeedArticle article, final Widget articlePanel,
            final Widget contentZonePanel) {
        FlowPanel p = new FlowPanel();
        p.setStyleName(watch.getStyleName("article", "title"));

        HTML titleLabel = new HTML(article.getTitle());
        p.add(titleLabel);

        titleLabel.addClickListener(new ClickListener() {
            public void onClick(Widget widget) {
                ArticleListWidget.this.showContentPanel(!contentZonePanel.isVisible(),
                        (ComplexPanel) contentZonePanel, article);
                resizeWindow();
                watch.getDataManager().updateArticleReadStatus(article, new AsyncCallback() {
                    public void onFailure(Throwable throwable) {
                    }

                    public void onSuccess(Object object) {
                        articlePanel.removeStyleName(watch.getStyleName("article", "unread"));
                        articlePanel.addStyleName(watch.getStyleName("article", "read"));
                    }
                });
            }
        });
        return p;
    }

    protected Widget getFeedNamePanel(FeedArticle article) {
        FlowPanel p = new FlowPanel();
        p.setStyleName(watch.getStyleName("article", "feedname"));
        p.add(getFeedLogoPanel(article));

        HTML htmlFeedName = new HTML();
        htmlFeedName.setStyleName(watch.getStyleName("article", "feedname-text"));
        // Get the feed title from the feed name in the article and display it
        Feed articleFeed = (Feed) watch.getConfig().getFeedsList().get(article.getFeedName());
        htmlFeedName.setHTML(articleFeed == null ? article.getFeedName()
                : (articleFeed.getTitle().trim().length() > 0 ? articleFeed.getTitle() : articleFeed.getName()));
        // TODO: [Proposal] when clicking on a feed name, set FilterStatus to activate the feed
        p.add(htmlFeedName);
        return p;
    }

    protected Widget getDatePanel(FeedArticle article) {
        FlowPanel p = new FlowPanel();
        p.setStyleName(watch.getStyleName("article", "date"));
        HTML htmlDate = new HTML();
        htmlDate.setStyleName(watch.getStyleName("article", "date-text"));
        // TODO: implement me:  Get date in format dd MMM yyyy, day_of_week tt:tt
        // TODO: tt:tt - I don't care about seconds
        // TODO: day of week is necessary for "This Week" Filter
        // TODO: dd MMM yyyy - 10 Feb 2008 format clarifies european/american date format 
        htmlDate.setHTML(article.getDate());
        // TODO: [Proposal] when clicking on a date, set FilterStatus to activate the date period
        p.add(htmlDate);
        return p;
    }

    protected Widget getLeftActionsPanel(final FeedArticle article) {
        final FlowPanel actionsPanel = new FlowPanel();
        actionsPanel.setStyleName(watch.getStyleName("article", "actionsleft"));
        updateLeftActionsPanel(actionsPanel, article);
        return actionsPanel;
    }

    //TODO: implement me
    protected void updateLeftActionsPanel(final FlowPanel actionsPanel, final FeedArticle article) {
        // TODO: uncomment this when we'll implement checkboxes usage 
        //        CheckBox selectArticle = new CheckBox();
        //        //TODO: replace article.getFlagStatus with article.getSelectStatus
        //        selectArticle.setTitle(watch.getTranslation((article.getFlagStatus()==-1) ? "article.select.remove.caption" : "article.select.add.caption"));
        //        selectArticle.addClickListener(new ClickListener() {
        //            public void onClick(Widget widget) {
        //               //TODO: implement me
        //            }
        //        });
        //        actionsPanel.add(selectArticle);

        Image flagImage = new Image(watch
                .getSkinFile((article.getFlagStatus() == 1) ? Constants.IMAGE_FLAG_ON : Constants.IMAGE_FLAG_OFF));

        // put the flag action only if the user has the right to edit
        if (watch.getConfig().getHasEditRight()) {
            Image loadingFlagImage = new Image(watch.getSkinFile(Constants.IMAGE_LOADING_SPINNER));
            flagImage.setTitle(watch.getTranslation(
                    (article.getFlagStatus() == 1) ? "article.flag.remove.caption" : "article.flag.add.caption"));
            //create a loading widget with the flag image as main widget and loadingFlagImage as loading widget
            final LoadingWidget flagLoadingWidget = new DefaultLoadingWidget(watch, flagImage, loadingFlagImage);
            flagLoadingWidget.addStyleName(watch.getStyleName("article-flag"));
            flagImage.addStyleName("clickable");
            flagImage.addClickListener(new ClickListener() {
                public void onClick(Widget widget) {
                    int flagstatus = article.getFlagStatus();
                    final int newflagstatus = (flagstatus == 1) ? 0 : 1;
                    watch.getDataManager().updateArticleFlagStatus(article, newflagstatus,
                            new LoadingAsyncCallback(flagLoadingWidget) {
                                public void onFailure(Throwable caught) {
                                    super.onFailure(caught);
                                }

                                public void onSuccess(Object result) {
                                    super.onSuccess(result);
                                    article.setFlagStatus(newflagstatus);
                                    actionsPanel.clear();
                                    updateLeftActionsPanel(actionsPanel, article);
                                }
                            });
                }
            });
            actionsPanel.add(flagLoadingWidget);
        } else {
            // otherwise, add only the image 
            actionsPanel.add(flagImage);
        }
    }

    protected Widget getRightActionsPanel(final FeedArticle article) {
        final FlowPanel actionsPanel = new FlowPanel();
        actionsPanel.setStyleName(watch.getStyleName("article", "actionsright"));
        updateRightActionsPanel(actionsPanel, article);
        return actionsPanel;
    }

    protected void updateRightActionsPanel(final FlowPanel actionsPanel, final FeedArticle article) {
        Image extLinkImage = new Image(watch.getSkinFile(Constants.IMAGE_EXT_LINK));
        extLinkImage.setTitle(watch.getTranslation("articlelist.open"));
        extLinkImage.addStyleName("clickable");
        extLinkImage.addClickListener(new ClickListener() {
            public void onClick(Widget widget) {
                Window.open(article.getUrl(), "_blank", "");
            }
        });
        actionsPanel.add(extLinkImage);

        Image trashImage = new Image(watch.getSkinFile(
                (article.getFlagStatus() == -1) ? Constants.IMAGE_TRASH_ON : Constants.IMAGE_TRASH_OFF));

        // add the trash button only if the user has the right to edit
        if (watch.getConfig().getHasEditRight()) {
            Image trashLoadingImage = new Image(watch.getSkinFile(Constants.IMAGE_LOADING_SPINNER));
            trashImage
                    .setTitle(watch.getTranslation((article.getFlagStatus() == -1) ? "article.trash.remove.caption"
                            : "article.trash.add.caption"));
            final LoadingWidget trashLoadingWidget = new DefaultLoadingWidget(watch, trashImage, trashLoadingImage);
            trashLoadingWidget.addStyleName(watch.getStyleName("article-trash"));
            trashImage.addStyleName("clickable");
            trashImage.addClickListener(new ClickListener() {
                public void onClick(Widget widget) {
                    // trash/untrash article
                    int flagstatus = article.getFlagStatus();
                    final int newflagstatus;
                    if (flagstatus == -1) {
                        // the article is trashed, untrash it
                        newflagstatus = 0;
                    } else {
                        //the article isn't trashed, it can be trashed
                        newflagstatus = -1;
                    }
                    watch.getDataManager().updateArticleFlagStatus(article, newflagstatus,
                            new LoadingAsyncCallback(trashLoadingWidget) {
                                public void onFailure(Throwable caught) {
                                    super.onFailure(caught);
                                }

                                public void onSuccess(Object result) {
                                    super.onSuccess(result);
                                    article.setFlagStatus(newflagstatus);
                                    actionsPanel.clear();
                                    updateRightActionsPanel(actionsPanel, article);
                                }
                            });
                }
            });
            actionsPanel.add(trashLoadingWidget);
        } else {
            //otherwise add just the image
            actionsPanel.add(trashImage);
        }
    }

    protected Widget getStatusPanel(FeedArticle article, Widget commentsZonePanel, HTML commentsStatus) {
        FlowPanel p = new FlowPanel();
        p.setStyleName(watch.getStyleName("article", "status"));
        HorizontalPanel statusPanel = new HorizontalPanel();
        statusPanel.add(getCommentsStatusPanel(commentsZonePanel, commentsStatus, article));
        FlowPanel tagsContainer = new FlowPanel();
        HorizontalPanel tagsStatusPanel = new HorizontalPanel();
        prepareTagsPanel(article, tagsStatusPanel, tagsContainer);
        statusPanel.add(tagsStatusPanel);
        statusPanel.add(tagsContainer);
        p.add(statusPanel);
        return p;
    }

    protected void prepareTagsPanel(FeedArticle article, HorizontalPanel tagsStatusPanel, FlowPanel tagsContainer) {
        // Create and add the tags icon 
        Image tagsLogo = new Image(watch.getSkinFile(Constants.IMAGE_TAG));
        tagsStatusPanel.add(tagsLogo);
        // Create and add the tags status panel        
        HTML tagsStatus = new HTML(getTagsStatusTitle(article));
        tagsStatus.setStyleName(watch.getStyleName("article", "tags-status"));
        tagsStatusPanel.add(tagsStatus);
        // Create the tags add panel. invisible, to be set to visible / invisible on tags status click
        if (watch.getConfig().getHasEditRight()) {
            tagsStatus.addStyleName("clickable");
            final Widget tagsAdd = getTagsAddZonePanel(tagsContainer, article, tagsStatus);
            tagsStatusPanel.add(tagsAdd);
            tagsStatus.addClickListener(new ClickListener() {
                public void onClick(Widget sender) {
                    tagsAdd.setVisible(!tagsAdd.isVisible());
                    resizeWindow();
                }
            });
        }
        // Populate the tagsContainer panel
        refreshTagsContainer(tagsContainer, article, tagsStatus);
    }

    protected Widget getTagElementPanel(final FeedArticle article, final String tag, final FlowPanel tagsContainer,
            final HTML tagStatus) {
        FlowPanel p = new FlowPanel();
        p.setStyleName(watch.getStyleName("article", "tag"));
        HorizontalPanel tagPanel = new HorizontalPanel();
        Label tagName = new Label(tag);
        tagName.setStyleName(watch.getStyleName("article", "tag-name"));
        Image deleteAction = new Image(watch.getSkinFile((Constants.IMAGE_DELETE)));
        deleteAction.addMouseListener(new MouseListenerAdapter() {
            public void onMouseEnter(Widget sender) {
                ((Image) sender).setUrl(watch.getSkinFile(Constants.IMAGE_DELETE_ACTIVE));
            }

            public void onMouseLeave(Widget sender) {
                ((Image) sender).setUrl(watch.getSkinFile(Constants.IMAGE_DELETE));
            }
        });
        Image loadingImage = new Image(watch.getSkinFile(Constants.IMAGE_LOADING_SPINNER));
        deleteAction.setTitle(watch.getTranslation("article.tag.remove.caption"));
        final LoadingWidget deleteTagWidget = new DefaultLoadingWidget(watch, deleteAction, loadingImage);
        deleteTagWidget.addStyleName(watch.getStyleName("article-tag-remove"));
        deleteAction.addClickListener(new ClickListener() {
            public void onClick(Widget w) {
                watch.getDataManager().removeTag(article, tag, new LoadingAsyncCallback(deleteTagWidget) {
                    public void onSuccess(Object o) {
                        super.onSuccess(o);
                        // success - We need to refreshData the number of tags
                        // Remove the tag from the list
                        article.getTags().remove(tag);
                        tagStatus.setHTML(getTagsStatusTitle(article));
                        refreshTagsContainer(tagsContainer, article, tagStatus);
                        watch.refreshTagCloud();
                    }

                    public void onFailure(Throwable t) {
                        super.onFailure(t);
                    }
                });
            }
        });
        tagPanel.add(tagName);
        tagPanel.add(deleteTagWidget);
        p.add(tagPanel);
        return p;
    }

    protected void refreshTagsContainer(FlowPanel tagsContainer, FeedArticle article, HTML tagStatus) {
        tagsContainer.clear();
        if (article.getTags().size() != 0) {
            for (int i = 0; i < article.getTags().size(); i++) {
                tagsContainer.add(
                        getTagElementPanel(article, (String) article.getTags().get(i), tagsContainer, tagStatus));
            }
        }
    }

    protected Widget getTagsAddZonePanel(FlowPanel tagsContainer, FeedArticle article, HTML tagStatus) {
        FlowPanel p = new FlowPanel();
        p.add(getTagsAddPanel(tagsContainer, p, article, tagStatus));
        // This panel is hidden by default
        p.setVisible(false);
        return p;
    }

    protected Widget getTagsAddPanel(final FlowPanel tagsContainer, final FlowPanel tagsAddPanel,
            final FeedArticle article, final HTML tagStatus) {
        FlowPanel p = new FlowPanel();
        p.setStyleName(watch.getStyleName("article", "tags-add"));
        HorizontalPanel addPanel = new HorizontalPanel();
        final TextBox addInput = new TextBox();
        WordListSuggestOracle tagListOracle = new WordListSuggestOracle(new TagListSuggestOracle(watch),
                Constants.PROPERTY_TAGS_SEPARATORS_EDIT, true);
        SuggestBox addInputSuggestBox = new SuggestBox(tagListOracle, addInput);
        // because the addInput is a GWT suggest box, force autocomplete off so that browsers don't suggest too
        DOM.setElementProperty(addInput.getElement(), "autocomplete", "off");
        Button addAction = new Button(watch.getTranslation("button.add"));
        addPanel.add(addInputSuggestBox);
        addPanel.add(addAction);
        Image loadingImage = new Image(watch.getSkinFile(Constants.IMAGE_LOADING_SPINNER));
        FlowPanel loadingPanel = new FlowPanel();
        loadingPanel.add(loadingImage);
        final LoadingWidget addTagLoadingWidget = new DefaultLoadingWidget(watch, addPanel, loadingPanel);
        addTagLoadingWidget.addStyleName(watch.getStyleName("add-tags"));
        addAction.addClickListener(new ClickListener() {
            public void onClick(Widget widget) {
                // Get the new list of tags for the article
                final List newTags = FeedArticle.joinTagsLists(article.getTags(),
                        FeedArticle.parseTagsString(addInput.getText().trim(), true), true);
                if (addInput.getText().trim().length() != 0) {
                    watch.getDataManager().updateTags(article, newTags,
                            new LoadingAsyncCallback(addTagLoadingWidget) {
                                public void onFailure(Throwable caught) {
                                    super.onFailure(caught);
                                }

                                public void onSuccess(Object result) {
                                    super.onSuccess(result);
                                    // success - We need to refreshData the number of tags
                                    article.setTags(newTags);
                                    tagStatus.setHTML(getTagsStatusTitle(article));
                                    refreshTagsContainer(tagsContainer, article, tagStatus);
                                    // Clear the tags add input to prepare it for next adding
                                    addInput.setText("");
                                    tagsAddPanel.setVisible(false);
                                    watch.refreshTagCloud();
                                }
                            });
                }
            }
        });
        p.add(addTagLoadingWidget);
        return p;
    }

    protected String getTagsStatusTitle(FeedArticle article) {
        String tagsTitle = "";
        if (article.getTags().size() == 0) {
            tagsTitle = watch.getTranslation("notags");
        } else {
            tagsTitle = watch.getTranslation("tags");
        }
        return tagsTitle;
    }

    protected Widget getTagsStatusPanel(FlowPanel tagsContainer, final FeedArticle article) {
        final FlowPanel tagsPanel = new FlowPanel();
        HorizontalPanel tagsStatusPanel = new HorizontalPanel();
        Image tagsLogo = new Image(watch.getSkinFile(Constants.IMAGE_TAG));
        HTML tagsStatus = new HTML(getTagsStatusTitle(article));
        tagsStatus.setStyleName(watch.getStyleName("article", "tags-status"));
        tagsStatusPanel.add(tagsLogo);
        tagsStatusPanel.add(tagsStatus);
        final Widget tagsAdd = getTagsAddZonePanel(tagsContainer, article, tagsStatus);
        tagsStatusPanel.add(tagsAdd);
        tagsPanel.add(tagsStatusPanel);
        tagsStatus.addClickListener(new ClickListener() {
            public void onClick(Widget sender) {
                tagsAdd.setVisible(!tagsAdd.isVisible());
                resizeWindow();
            }
        });
        return tagsPanel;
    }

    protected Widget getCommentsStatusPanel(final Widget commentsZonePanel, final HTML commentsStatus,
            final FeedArticle article) {
        FlowPanel p = new FlowPanel();
        HorizontalPanel commentsStatusPanel = new HorizontalPanel();
        Image commentsLogo = new Image(watch.getSkinFile(Constants.IMAGE_COMMENT));
        commentsStatus.addClickListener(new ClickListener() {
            public void onClick(Widget sender) {
                // toggle the comments panel
                ArticleListWidget.this.showCommentsPanel(!commentsZonePanel.isVisible(),
                        (ComplexPanel) commentsZonePanel, article, commentsStatus);
                resizeWindow();
            }
        });
        commentsStatusPanel.add(commentsLogo);
        commentsStatusPanel.add(commentsStatus);
        p.add(commentsStatusPanel);
        return p;
    }

    protected Widget getCommentsZonePanel(FeedArticle article, HTML commentsStatus) {
        FlowPanel p = new FlowPanel();
        // the comments panel is hidden by default
        showCommentsPanel(false, p, article, commentsStatus);
        return p;
    }

    /**
     * Displays the comments zone panel and populates it if it's the first time it's printed.
     */
    protected void showCommentsPanel(boolean visible, ComplexPanel commentsPanel, FeedArticle article,
            HTML commentsStatus) {
        if (visible && (commentsPanel.getWidgetCount() == 0)) {
            commentsPanel.setStyleName(watch.getStyleName("article", "comments-add"));
            refreshCommentsContainer((FlowPanel) commentsPanel, article, commentsStatus);
        }
        commentsPanel.setVisible(visible);
    }

    protected void refreshCommentsContainer(final FlowPanel commentPanel, final FeedArticle article,
            final HTML commentsStatus) {
        commentPanel.clear();
        int nbcomments = article.getCommentsNumber();
        String commentTitle = watch.getTranslation("nocomments");
        if (nbcomments != 0) {
            commentTitle = nbcomments + " " + watch.getTranslation("comments");
        }
        commentsStatus.setHTML(commentTitle);

        List comments = article.getComments();
        if ((comments != null) && (comments.size() > 0)) {
            FlowPanel commentsZonePanel = new FlowPanel();
            for (int i = 0; i < comments.size(); i++) {
                FeedArticleComment comment = (FeedArticleComment) comments.get(i);
                commentsZonePanel.add(getCommentElementPanel(comment));
            }
            commentPanel.add(commentsZonePanel);
        }

        // add the comments panel only if the user has the right to comment
        if (watch.getConfig().getHasCommentRight()) {
            VerticalPanel g = new VerticalPanel();
            Image commentAddLoadingImage = new Image(watch.getSkinFile(Constants.IMAGE_LOADING_SPINNER));
            Panel commentAddLoadingPanel = new FlowPanel();
            commentAddLoadingPanel.add(commentAddLoadingImage);
            final LoadingWidget commentAddLoadingWidget = new DefaultLoadingWidget(watch, g,
                    commentAddLoadingPanel);
            Label t = new Label(watch.getTranslation("commentadd.caption"));
            g.add(t);
            final TextArea commentTextArea = new TextArea();
            g.add(commentTextArea);
            HorizontalPanel buttonsContainer = new HorizontalPanel();
            Button cancelAction = new Button(watch.getTranslation("button.cancel"));
            cancelAction.addStyleName(watch.getStyleName("article-comment-cancel"));
            cancelAction.addClickListener(new ClickListener() {
                public void onClick(Widget sender) {
                    commentTextArea.setText("");
                }
            });
            Button saveAction = new Button(watch.getTranslation("button.add"));
            saveAction.addStyleName(watch.getStyleName("article-comment-add"));
            buttonsContainer.add(saveAction);
            buttonsContainer.add(cancelAction);
            g.add(buttonsContainer);
            commentPanel.add(commentAddLoadingWidget);

            saveAction.addClickListener(new ClickListener() {
                public void onClick(Widget widget) {
                    // Send the comment
                    final String comment = commentTextArea.getText().trim();
                    if (comment.length() > 0) {
                        watch.getDataManager().addComment(article, comment,
                                new LoadingAsyncCallback(commentAddLoadingWidget) {
                                    public void onFailure(Throwable caught) {
                                        // failure we show the exception
                                        super.onFailure(caught);
                                    }

                                    public void onSuccess(Object result) {
                                        super.onSuccess(result);
                                        // success - We need to refreshData the number of comments
                                        // we reread the article to make sure we get it right
                                        // TODO: it should be an ArticleLoadingWidget here, not a comment one, but the comment
                                        // looks a lot better
                                        watch.getDataManager().getArticle(article.getPageName(),
                                                new LoadingAsyncCallback(commentAddLoadingWidget) {
                                                    public void onFailure(Throwable caught) {
                                                        super.onFailure(caught);
                                                    }

                                                    public void onSuccess(Object result) {
                                                        super.onSuccess(result);
                                                        if (article.getCommentsNumber() != 0) {
                                                            commentsStatus.setHTML(article.getCommentsNumber() + " "
                                                                    + watch.getTranslation("comments"));
                                                        }
                                                        // Refresh the comment panel
                                                        refreshCommentsContainer(commentPanel, (FeedArticle) result,
                                                                commentsStatus);
                                                        // We need to resize in case this brings up a scroll bar
                                                        resizeWindow();
                                                    }
                                                });
                                    }
                                });
                    }
                }
            });
        }
    }

    protected Widget getCommentElementPanel(FeedArticleComment comment) {
        FlowPanel pp = new FlowPanel();
        pp.setStyleName(watch.getStyleName("article", "comment"));
        HorizontalPanel p = new HorizontalPanel();
        FlowPanel detailsPanel = new FlowPanel();
        detailsPanel.setStyleName(watch.getStyleName("article", "comment-details"));
        HorizontalPanel authorPanel = new HorizontalPanel();
        Image authorLogo = new Image(watch.getSkinFile(Constants.IMAGE_USER));
        HTML author = new HTML(comment.getAuthor());
        authorPanel.add(authorLogo);
        authorPanel.add(author);
        author.setStyleName(watch.getStyleName("article", "comment-author"));
        detailsPanel.add(authorPanel);
        HTML date = new HTML(comment.getDate());
        date.setStyleName(watch.getStyleName("article", "comment-date"));
        detailsPanel.add(date);
        p.add(detailsPanel);
        HTML content = new HTML(comment.getContent());
        content.setStyleName(watch.getStyleName("article", "comment-content"));
        p.add(content);
        pp.add(p);
        return pp;
    }

    protected Widget getContentZonePanel(FeedArticle article) {
        FlowPanel p = new FlowPanel();
        // the content panel is invisible by default
        showContentPanel(false, p, article);
        return p;
    }

    /**
     * Set the content panel visible, also handling panel initialization. Initially, if the panel is not visible, 
     * it will be empty and will be initialized from the feed article upon first show, to optimize DOM manipulation 
     * on list fill. 
     */
    protected void showContentPanel(boolean visible, ComplexPanel contentPanel, FeedArticle article) {
        if (visible && (contentPanel.getWidgetCount() == 0)) {
            // populate this panel
            HTML content = new HTML(article.getContent());
            content.setStyleName(watch.getStyleName("article", "content"));
            contentPanel.add(content);
            final FeedArticle myArticle = article;
            content.addMouseListener(new MouseListener() {
                public void onMouseUp(Widget arg0, int arg1, int arg2) {
                    Document doc = (Document) Document.get();
                    Selection sel = doc.getSelection();
                    if (sel.toString() == "") {
                        return;
                    }
                    selection = sel.toString();
                    currentArticle = myArticle;
                    popup.center();
                }

                public void onMouseMove(Widget arg0, int arg1, int arg2) {
                }

                public void onMouseLeave(Widget arg0) {
                }

                public void onMouseEnter(Widget arg0) {
                }

                public void onMouseDown(Widget arg0, int arg1, int arg2) {
                }
            });
        }
        contentPanel.setVisible(visible);
    }

    public void showArticle(FeedArticle article) {
        FlowPanel articlepanel = new FlowPanel();
        articlepanel.setStyleName(watch.getStyleName("article"));
        if (article.getReadStatus() == 1) {
            articlepanel.addStyleName(watch.getStyleName("article", "read"));
        } else {
            articlepanel.addStyleName(watch.getStyleName("article", "unread"));
        }
        Widget contentZonePanel = getContentZonePanel(article);
        articlepanel.add(getHeadlinePanel(article, articlepanel, contentZonePanel));
        panel.add(articlepanel);
    }

    public void resizeWindow() {
        int windowWidth = watch.getUserInterface().getOffsetWidth();
        int feedTreeWidth = watch.getUserInterface().getFeedTreeWidth();
        int filterBarWidth = watch.getUserInterface().getFilterBarWidth();
        int newWidth = windowWidth - feedTreeWidth - filterBarWidth;
        // Handle floating point widths in FF3: decrease by one, to be sure it fits
        // TODO: remove this ugly and unreliable hack when we'll be able to get floating point widths
        if (Watch.getUserAgent().toLowerCase().indexOf("firefox/3.0") != -1) {
            newWidth = newWidth - 1;
        }
        if (newWidth < 0)
            newWidth = 0;
        setWidth(newWidth + "px");
    }
}