ro.nextreports.server.web.dashboard.DashboardPanel.java Source code

Java tutorial

Introduction

Here is the source code for ro.nextreports.server.web.dashboard.DashboardPanel.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ro.nextreports.server.web.dashboard;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.wicket.AttributeModifier;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.AjaxSelfUpdatingTimerBehavior;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.image.ContextImage;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.GenericPanel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.apache.wicket.util.time.Duration;
import org.apache.wicket.util.visit.IVisit;
import org.apache.wicket.util.visit.IVisitor;
import org.odlabs.wiquery.core.events.MouseEvent;
import org.odlabs.wiquery.core.events.WiQueryAjaxEventBehavior;
import org.odlabs.wiquery.core.javascript.JsStatement;

import ro.nextreports.server.domain.Entity;
import ro.nextreports.server.exception.NotFoundException;
import ro.nextreports.server.service.DashboardService;
import ro.nextreports.server.service.DataSourceService;
import ro.nextreports.server.service.ReportService;
import ro.nextreports.server.service.SecurityService;
import ro.nextreports.server.service.StorageService;
import ro.nextreports.server.util.ChartUtil;
import ro.nextreports.server.util.PermissionUtil;
import ro.nextreports.server.util.ServerUtil;
import ro.nextreports.server.web.NextServerApplication;
import ro.nextreports.server.web.NextServerSession;
import ro.nextreports.server.web.common.form.FormPanel;
import ro.nextreports.server.web.common.util.PreferencesHelper;
import ro.nextreports.server.web.core.BasePage;
import ro.nextreports.server.web.dashboard.alarm.AlarmWidget;
import ro.nextreports.server.web.dashboard.alarm.AlarmWidgetDescriptor;
import ro.nextreports.server.web.dashboard.chart.ChartHTML5Panel;
import ro.nextreports.server.web.dashboard.chart.ChartWidget;
import ro.nextreports.server.web.dashboard.chart.ChartWidgetDescriptor;
import ro.nextreports.server.web.dashboard.display.DisplayWidget;
import ro.nextreports.server.web.dashboard.display.DisplayWidgetDescriptor;
import ro.nextreports.server.web.dashboard.drilldown.DrillDownWidget;
import ro.nextreports.server.web.dashboard.drilldown.DrillDownWidgetDescriptor;
import ro.nextreports.server.web.dashboard.indicator.IndicatorWidget;
import ro.nextreports.server.web.dashboard.indicator.IndicatorWidgetDescriptor;
import ro.nextreports.server.web.dashboard.model.DashboardColumnModel;
import ro.nextreports.server.web.dashboard.model.SelectedDashboardModel;
import ro.nextreports.server.web.dashboard.pivot.PivotWidget;
import ro.nextreports.server.web.dashboard.pivot.PivotWidgetDescriptor;
import ro.nextreports.server.web.dashboard.table.GeneralWidgetRuntimePanel;
import ro.nextreports.server.web.dashboard.table.TableWidget;
import ro.nextreports.server.web.dashboard.table.TableWidgetDescriptor;
import ro.nextreports.server.web.report.DynamicParameterRuntimePanel;
import ro.nextreports.server.web.report.ParameterRuntimePanel;

/**
 * @author Decebal Suiu
 */
public class DashboardPanel extends GenericPanel<Dashboard> {

    private static final long serialVersionUID = 1L;

    private final ResourceReference HTML2CANVAS_JS = new JavaScriptResourceReference(DashboardPanel.class,
            "html2canvas-0.5.0.js");
    private final ResourceReference JSPDF_JS = new JavaScriptResourceReference(DashboardPanel.class,
            "jspdf.min-1.0.272.js");
    private final ResourceReference NEXTCHARTS_JS = new JavaScriptResourceReference(ChartHTML5Panel.class,
            NextServerApplication.NEXT_CHARTS_JS);

    private List<DashboardColumnPanel> columnPanels;
    private AjaxLink<Void> globalSettingsLink;
    private WebMarkupContainer toolbarContainer;

    @SpringBean
    private SecurityService securityService;

    @SpringBean
    private StorageService storageService;

    @SpringBean
    private ReportService reportService;

    @SpringBean
    private DataSourceService dataSourceService;

    @SpringBean
    private DashboardService dashboardService;

    @SpringBean
    private WidgetFactory widgetFactory;

    public DashboardPanel(String id) {
        this(id, new SelectedDashboardModel());
    }

    public DashboardPanel(String id, IModel<Dashboard> model) {
        this(id, model, true);
    }

    public DashboardPanel(String id, IModel<Dashboard> model, boolean viewToolbar) {
        super(id, model);

        setOutputMarkupId(true);

        addToolbar();
        toolbarContainer.setVisible(viewToolbar);
        addColumnsPanel();

        add(AttributeModifier.replace("class", "dashboard"));
    }

    @Override
    public void renderHead(IHeaderResponse response) {
        super.renderHead(response);

        response.render(JavaScriptHeaderItem
                .forReference(new PackageResourceReference(DashboardPanel.class, "dashboard.js")));
        response.render(
                CssHeaderItem.forReference(new PackageResourceReference(DashboardPanel.class, "dashboard.css")));
        if (isInternetExplorer()) {
            response.render(CssHeaderItem
                    .forReference(new PackageResourceReference(DashboardPanel.class, "dashboard-ie.css")));
        }
        response.render(JavaScriptHeaderItem.forReference(HTML2CANVAS_JS));
        response.render(JavaScriptHeaderItem.forReference(JSPDF_JS));
        response.render(JavaScriptHeaderItem.forReference(NEXTCHARTS_JS));
    }

    public Dashboard getDashboard() {
        return getModelObject();
    }

    public DashboardColumnPanel getColumnPanel(int column) {
        return columnPanels.get(column);
    }

    public boolean disableSortable(AjaxRequestTarget target) {
        if (!hasWritePermission()) {
            for (int i = 0; i < getDashboard().getColumnCount(); i++) {
                getColumnPanel(i).getSortableBehavior().disable(target);
            }

            return true;
        }

        return false;
    }

    protected DashboardColumnPanel createColumnPanel(String id, int index) {
        return new DashboardColumnPanel(id, new DashboardColumnModel(getModel(), index));
    }

    private String getUniqueWidgetTitle(String title) {
        return getUniqueWidgetTitle(title, 0);
    }

    private String getUniqueWidgetTitle(String title, int count) {
        String uniqueTitle = title;
        if (count > 0) {
            uniqueTitle = title + " " + count;
        }

        List<Widget> widgets = getDashboard().getWidgets();
        for (Widget widget : widgets) {
            if (widget.getTitle().equals(uniqueTitle)) {
                uniqueTitle = getUniqueWidgetTitle(title, count + 1);
            }
        }

        return uniqueTitle;
    }

    public boolean hasWritePermission() {
        try {
            return securityService.hasPermissionsById(ServerUtil.getUsername(), PermissionUtil.getWrite(),
                    getDashboard().getId());
        } catch (NotFoundException e) {
            // TODO
            e.printStackTrace();
        }

        return false;
    }

    public void refreshGlobalSettings(AjaxRequestTarget target) {
        target.add(globalSettingsLink);
    }

    private void addToolbar() {

        toolbarContainer = new WebMarkupContainer("toolbar");
        add(toolbarContainer);

        IModel<String> toggleImageModel = new LoadableDetachableModel<String>() {

            private static final long serialVersionUID = 1L;

            @Override
            protected String load() {
                String imagePath = "images/left-gray.png";
                Map<String, String> preferences = NextServerSession.get().getPreferences();
                boolean isHidden = !PreferencesHelper.getBoolean("dashboard.navigationToggle", preferences);
                if (isHidden) {
                    imagePath = "images/right-gray.png";
                }

                return imagePath;
            }

        };
        final ContextImage toggle = new ContextImage("toggle", toggleImageModel);
        toggle.add(new WiQueryAjaxEventBehavior(MouseEvent.CLICK) {

            private static final long serialVersionUID = 1L;

            @Override
            protected void onEvent(AjaxRequestTarget target) {
                Map<String, String> preferences = NextServerSession.get().getPreferences();
                boolean toogle = false;
                if (preferences.containsKey("dashboard.navigationToggle")) {
                    toogle = Boolean.parseBoolean(preferences.get("dashboard.navigationToggle"));
                    toogle = !toogle;
                }

                preferences.put("dashboard.navigationToggle", String.valueOf(toogle));
                NextServerSession.get().setPreferences(preferences);

                DashboardBrowserPanel browserPanel = findParent(DashboardBrowserPanel.class);
                target.add(browserPanel.getDashboardNavigationPanel());
                target.add(toggle);
                target.add(DashboardPanel.this);
            }

            public JsStatement statement() {
                return null;
            }

        });
        IModel<String> toggleTooltipModel = new LoadableDetachableModel<String>() {

            private static final long serialVersionUID = 1L;

            @Override
            protected String load() {
                String tooltip = getString("DashboardPanel.hide");
                Map<String, String> preferences = NextServerSession.get().getPreferences();
                boolean isHidden = !PreferencesHelper.getBoolean("dashboard.navigationToggle", preferences);
                if (isHidden) {
                    tooltip = getString("DashboardPanel.show");
                }

                return tooltip;
            }

        };
        toggle.add(new AttributeModifier("title", toggleTooltipModel));
        toolbarContainer.add(toggle);

        //      add(new AjaxLink<Void>("addDashboard") {
        //
        //         private static final long serialVersionUID = 1L;
        //
        //         @Override
        //         public void onClick(AjaxRequestTarget target) {
        //                ModalWindow dialog = findParent(BasePage.class).getDialog();
        //                dialog.setTitle("Add dashboard");
        //                dialog.setInitialWidth(350);
        //                dialog.setUseInitialHeight(false);
        //                
        //                final AddDashboardPanel addDashboardPanel = new AddDashboardPanel(dialog.getContentId()) {
        //
        //               private static final long serialVersionUID = 1L;
        //
        //               @Override
        //                    public void onAdd(AjaxRequestTarget target) {
        //                        ModalWindow.closeCurrent(target);
        //                        String id;
        //                        String title = getTitle();                        
        //                        Dashboard dashboard = new DefaultDashboard(title, getColumnCount());
        //                        id = dashboardService.addOrModifyDashboard(dashboard);
        //
        //                        SectionContext sectionContext = NextServerSession.get().getSectionContext(DashboardSection.ID);
        //                        sectionContext.getData().put(SectionContextConstants.SELECTED_DASHBOARD_ID, id);
        //                        
        //                        target.add(DashboardPanel.this.findParent(DashboardBrowserPanel.class));
        //                    }
        //
        //                    @Override
        //                    public void onCancel(AjaxRequestTarget target) {
        //                        ModalWindow.closeCurrent(target);
        //                    }
        //
        //                };
        //                dialog.setContent(addDashboardPanel);
        //                dialog.show(target);
        //         }
        //         
        //      });

        AjaxLink<Void> refreshLink = new AjaxLink<Void>("refresh") {

            private static final long serialVersionUID = 1L;

            @Override
            public void onClick(AjaxRequestTarget target) {
                target.add(DashboardPanel.this);
            }

        };
        toolbarContainer.add(refreshLink);

        AjaxLink<Void> addWidgetLink = new AjaxLink<Void>("addWidget") {

            private static final long serialVersionUID = 1L;

            @SuppressWarnings("unchecked")
            @Override
            public void onClick(AjaxRequestTarget target) {
                ModalWindow dialog = findParent(BasePage.class).getDialog();
                dialog.setTitle(getString("DashboardPanel.add"));
                dialog.setInitialWidth(350);
                dialog.setUseInitialHeight(false);

                AddWidgetPanel addWidgetPanel = new AddWidgetPanel() {

                    private static final long serialVersionUID = 1L;

                    @Override
                    public void onOk(AjaxRequestTarget target) {
                        if (getEntity() == null) {
                            error(getString("DashboardPanel.select"));
                            target.add(getFeedbackPanel());
                            return;
                        }

                        ModalWindow.closeCurrent(target);

                        Widget widget = null;
                        //!important: first we test for pivot and then for drill-downable
                        if (isPivot()) {
                            PivotWidget pivotWidget = (PivotWidget) widgetFactory
                                    .createWidget(new PivotWidgetDescriptor());
                            pivotWidget.setEntity(getEntity());
                            widget = pivotWidget;
                        } else if (isDrillDownable()) {
                            DrillDownWidget drillWidget = (DrillDownWidget) widgetFactory
                                    .createWidget(new DrillDownWidgetDescriptor());
                            drillWidget.setEntity(getEntity());
                            widget = drillWidget;
                        } else if (isChart()) {
                            ChartWidget chartWidget = (ChartWidget) widgetFactory
                                    .createWidget(new ChartWidgetDescriptor());
                            chartWidget.setEntity(getEntity());
                            widget = chartWidget;
                        } else if (isTable()) {
                            TableWidget tableWidget = (TableWidget) widgetFactory
                                    .createWidget(new TableWidgetDescriptor());
                            tableWidget.setEntity(getEntity());
                            widget = tableWidget;
                        } else if (isAlarm()) {
                            AlarmWidget alarmWidget = (AlarmWidget) widgetFactory
                                    .createWidget(new AlarmWidgetDescriptor());
                            alarmWidget.setEntity(getEntity());
                            widget = alarmWidget;
                        } else if (isIndicator()) {
                            IndicatorWidget indicatorWidget = (IndicatorWidget) widgetFactory
                                    .createWidget(new IndicatorWidgetDescriptor());
                            indicatorWidget.setEntity(getEntity());
                            widget = indicatorWidget;
                        } else if (isDisplay()) {
                            DisplayWidget displayWidget = (DisplayWidget) widgetFactory
                                    .createWidget(new DisplayWidgetDescriptor());
                            displayWidget.setEntity(getEntity());
                            widget = displayWidget;
                        }
                        widget.setTitle(getUniqueWidgetTitle(widget.getTitle()));
                        widget.afterCreate(storageService);
                        Dashboard dashboard = getDashboard();
                        try {
                            dashboardService.addWidget(dashboard.getId(), widget);
                        } catch (NotFoundException e) {
                            // never happening
                            throw new RuntimeException(e);
                        }

                        // TODO
                        /*
                        target.add(getLeftColumnPanel());
                        // @todo
                        // if we do not refresh right panel we have a strange bug :                       
                        // move a W widget from left to right, add a widget (to left), move W from right to left (will look like the added widget) 
                        target.add(getRightColumnPanel());
                        */
                        /*
                        for (int i = 0; i < getDashboard().getColumnCount(); i++) {
                           System.out.println(">>> " + i);
                           System.out.println(getColumnPanel(i));
                           target.add(getColumnPanel(i));
                        }
                        */

                        // globalSettingsLink may be disabled
                        //                       target.add(globalSettingsLink);         

                        // need to to do detach (otherwise after we add a widget to current dashboard, added widget does not appear)!
                        // see also DashboardColumnModel
                        DashboardPanel.this.getModel().detach();
                        target.add(DashboardPanel.this);
                    }

                };
                dialog.setContent(new FormPanel<Void>(dialog.getContentId(), addWidgetPanel, true) {

                    private static final long serialVersionUID = 1L;

                    @Override
                    protected void onConfigure() {
                        super.onConfigure();
                        setOkButtonValue(getString("add"));
                    }

                });
                dialog.show(target);
            }

            @Override
            public boolean isVisible() {
                return hasWritePermission();
            }

        };
        toolbarContainer.add(addWidgetLink);

        globalSettingsLink = new AjaxLink<Void>("globalSettings") {

            private static final long serialVersionUID = 1L;

            @Override
            public void onClick(AjaxRequestTarget target) {
                ModalWindow dialog = findParent(BasePage.class).getDialog();
                dialog.setTitle(getString("DashboardPanel.global"));

                final WidgetRuntimeModel runtimeModel = ChartUtil.getGlobalRuntimeModel(
                        storageService.getSettings(), getDashboard().getId(), reportService, dataSourceService,
                        dashboardService);
                Entity entity = null;
                if (runtimeModel.getParameters().size() > 0) {
                    entity = ((EntityWidget) getDashboard().getWidgets().get(0)).getEntity();
                }
                if (getDashboard().getWidgets().size() > 0) {
                    final ParameterRuntimePanel paramRuntimePanel = new GeneralWidgetRuntimePanel(
                            "chartRuntimePanel", entity, runtimeModel, true);

                    boolean isDynamic = false;
                    if (paramRuntimePanel instanceof DynamicParameterRuntimePanel) {
                        if (((DynamicParameterRuntimePanel) paramRuntimePanel).hasDynamicParameter()) {
                            isDynamic = true;
                        }
                    }

                    if (paramRuntimePanel.hasPalette()) {
                        if (isDynamic) {
                            dialog.setInitialWidth(720);
                        } else {
                            dialog.setInitialWidth(685);
                        }
                    } else {
                        if (isDynamic) {
                            dialog.setInitialWidth(445);
                        } else {
                            dialog.setInitialWidth(435);
                        }
                    }
                    dialog.setUseInitialHeight(false);
                    dialog.setContent(new WidgetSettingsPanel(dialog.getContentId(), paramRuntimePanel) {

                        private static final long serialVersionUID = 1L;

                        @Override
                        public void onChange(AjaxRequestTarget target) {
                            changeGlobalSettings(runtimeModel, target);
                        }

                        @Override
                        public void onCancel(AjaxRequestTarget target) {
                            ModalWindow.closeCurrent(target);
                        }

                        @Override
                        public void onReset(AjaxRequestTarget target) {
                            // resetSettings(widget, target);
                        }

                    });
                } else {
                    dialog.setContent(new Label(dialog.getContentId(), getString("DashboardPanel.empty")));
                    dialog.setInitialWidth(300);
                    dialog.setInitialHeight(40);
                }
                dialog.show(target);
            }

            @Override
            public boolean isVisible() {
                return hasWritePermission()
                        && (DashboardUtil.getDashboard(DashboardUtil.getSelectedDashboardId(), dashboardService)
                                .getWidgets().size() > 0);
            }

        };
        globalSettingsLink.setOutputMarkupId(true);
        globalSettingsLink.setOutputMarkupPlaceholderTag(true);
        toolbarContainer.add(globalSettingsLink);

        toolbarContainer.add(new AjaxLink<Void>("pdf") {

            private static final long serialVersionUID = 1L;

            @Override
            public void onClick(AjaxRequestTarget target) {
                target.appendJavaScript(getPdfJavascriptCall());
            }

            // a landscape pdf with the entire visible part of dashboard
            private String getPdfJavascriptCall() {
                StringBuilder sb = new StringBuilder();
                sb.append("var doc = new jsPDF('l', 'mm');");
                //sb.append("var list = $('li[id^=\"widget\"]');");
                //sb.append("pdfSettings.position = 1;");
                sb.append("var list = $('div[class=\"dashboardCapture\"]');");
                sb.append("var pdfSettings = new Object();");
                sb.append("pdfSettings.doc = doc;");
                sb.append("pdfSettings.elements = list;");
                sb.append("pdfSettings.title = \"" + getDashboard().getTitle() + "\";");
                sb.append("pdfSettings.showFooter = true;");
                sb.append("pdfSettings.footerText = \"Generated by NextReports "
                        + new SimpleDateFormat().format(new Date()) + "\";");
                sb.append("capturePdf(pdfSettings);");
                return sb.toString();
            }

        });

    }

    private void addColumnsPanel() {
        final IModel<List<DashboardColumn>> columnsModel = new LoadableDetachableModel<List<DashboardColumn>>() {

            private static final long serialVersionUID = 1L;

            @Override
            protected List<DashboardColumn> load() {
                List<DashboardColumn> dashboardColumns = new ArrayList<DashboardColumn>();
                int columnCount = getDashboard().getColumnCount();
                //             System.out.println("columnCount = " + columnCount);
                for (int i = 0; i < columnCount; i++) {
                    dashboardColumns.add(new DashboardColumnModel(getModel(), i).getObject());
                }

                //             System.out.println("dashboardColumns = " + dashboardColumns);
                return dashboardColumns;
            }

        };
        ListView<DashboardColumn> columnsView = new ListView<DashboardColumn>("columns", columnsModel) {

            private static final long serialVersionUID = 1L;

            @Override
            protected void onBeforeRender() {
                if (!hasBeenRendered()) {
                    //               System.out.println(".....................");
                    columnPanels = new ArrayList<DashboardColumnPanel>();
                }

                super.onBeforeRender();
            }

            @Override
            protected void populateItem(ListItem<DashboardColumn> item) {
                // TODO
                float columnPanelWidth = 100f / columnsModel.getObject().size();
                DashboardColumnPanel columnPanel = createColumnPanel("column", item.getIndex());
                columnPanel.getColumnContainer()
                        .add(AttributeModifier.replace("style", "width: " + columnPanelWidth + "%;"));
                item.add(columnPanel);

                columnPanels.add(columnPanel);
                System.out.println("... " + columnPanel);
            }

        };
        add(columnsView);
    }

    private boolean isInternetExplorer() {
        return NextServerSession.get().getClientInfo().getProperties().isBrowserInternetExplorer();
    }

    private void changeGlobalSettings(WidgetRuntimeModel runtimeModel, AjaxRequestTarget target) {
        ModalWindow.closeCurrent(target);

        WidgetPanelVisitor visitor = new WidgetPanelVisitor();
        visitChildren(WidgetPanel.class, visitor);

        for (WidgetPanel widgetPanel : visitor.getWidgetPanels()) {
            Widget widget = widgetPanel.getWidget();
            if (widget == null) {
                continue;
            }
            int oldRefreshTime = widget.getRefreshTime();
            WidgetRuntimeModel storedRuntimeModel = ChartUtil.getRuntimeModel(storageService.getSettings(),
                    (EntityWidget) widget, reportService, dataSourceService, true);
            ChartUtil.updateGlobalWidget(widget, storedRuntimeModel, runtimeModel);
            try {
                dashboardService.modifyWidget(getDashboard().getId(), widget);
            } catch (NotFoundException e) {
                // never happening
                throw new RuntimeException(e);
            }
            int refreshTime = widget.getRefreshTime();
            if (oldRefreshTime != refreshTime) {
                for (Behavior behavior : widgetPanel.getBehaviors()) {
                    if (behavior instanceof AjaxSelfUpdatingTimerBehavior) {
                        ((AjaxSelfUpdatingTimerBehavior) behavior).stop(target);
                        // do not remove the behavior : after changing , the
                        // event is called one more
                        // time on the client so it has to be present ...
                    }
                }
                if (refreshTime > 0) {
                    widgetPanel.add(new AjaxSelfUpdatingTimerBehavior(Duration.seconds(refreshTime)));
                }
            }
        }

        /*
          for (int i = 0; i < getDashboard().getColumnCount(); i++) {
             target.add(getColumnPanel(i));
          }
          */
        target.add(this);
    }

    private class WidgetPanelVisitor implements IVisitor<WidgetPanel, Void>, Serializable {

        private static final long serialVersionUID = 1L;

        private final Set<WidgetPanel> visited = new HashSet<WidgetPanel>();

        public Set<WidgetPanel> getWidgetPanels() {
            return visited;
        }

        @Override
        public void component(WidgetPanel object, IVisit<Void> visit) {
            if (!visited.contains(object)) {
                visited.add(object);
            }
        }

    }

}