org.nuclos.client.customcomp.resplan.ResPlanPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.nuclos.client.customcomp.resplan.ResPlanPanel.java

Source

//Copyright (C) 2010  Novabit Informationssysteme GmbH
//
//This file is part of Nuclos.
//
//Nuclos is free software: you can redistribute it and/or modify
//it under the terms of the GNU Affero General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//Nuclos 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 Affero General Public License for more details.
//
//You should have received a copy of the GNU Affero General Public License
//along with Nuclos.  If not, see <http://www.gnu.org/licenses/>.

package org.nuclos.client.customcomp.resplan;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;

import org.apache.commons.lang.ObjectUtils;
import org.apache.log4j.Logger;
import org.jdesktop.swingx.JXFindPanel;
import org.jdesktop.swingx.combobox.ListComboBoxModel;
import org.jdesktop.swingx.painter.Painter;
import org.jdesktop.swingx.renderer.ComponentProvider;
import org.jdesktop.swingx.search.SearchFactory;
import org.nuclos.client.common.ClientParameterProvider;
import org.nuclos.client.common.KeyBindingProvider;
import org.nuclos.client.common.NuclosCollectController;
import org.nuclos.client.common.NuclosCollectControllerFactory;
import org.nuclos.client.customcomp.resplan.ResPlanController.GranularityType;
import org.nuclos.client.customcomp.resplan.ResPlanController.RestorePreferences;
import org.nuclos.client.customcomp.resplan.ResPlanController.TimeGranularity;
import org.nuclos.client.main.Main;
import org.nuclos.client.main.mainframe.MainFrameTab;
import org.nuclos.client.searchfilter.EntitySearchFilter;
import org.nuclos.client.searchfilter.SearchFilterListCellRenderer;
import org.nuclos.client.ui.Bubble;
import org.nuclos.client.ui.CommonAbstractAction;
import org.nuclos.client.ui.DateChooser;
import org.nuclos.client.ui.Errors;
import org.nuclos.client.ui.Icons;
import org.nuclos.client.ui.UIUtils;
import org.nuclos.client.ui.collect.CollectController.CollectableEventListener;
import org.nuclos.client.ui.collect.CollectController.MessageType;
import org.nuclos.client.ui.labeled.LabeledComponentSupport;
import org.nuclos.client.ui.popupmenu.AbstractJPopupMenuListener;
import org.nuclos.client.ui.resplan.Interval;
import org.nuclos.client.ui.resplan.JResPlanComponent;
import org.nuclos.client.ui.resplan.JResPlanComponent.Area;
import org.nuclos.client.ui.resplan.ResPlanModel;
import org.nuclos.client.ui.resplan.header.JHeaderGrid;
import org.nuclos.client.ui.util.Orientation;
import org.nuclos.common.NuclosFatalException;
import org.nuclos.common.ParameterProvider;
import org.nuclos.common.collect.collectable.Collectable;
import org.nuclos.common.collect.collectable.searchcondition.CollectableSearchCondition;
import org.nuclos.common.collect.exception.CollectableFieldFormatException;
import org.nuclos.common2.CommonRunnable;
import org.nuclos.common2.DateUtils;
import org.nuclos.common2.LangUtils;
import org.nuclos.common2.SpringLocaleDelegate;
import org.nuclos.common2.StringUtils;
import org.nuclos.common2.exception.CommonBusinessException;
import org.nuclos.common2.exception.CommonValidationException;
import org.nuclos.common2.exception.PreferencesException;

public class ResPlanPanel extends JPanel {

    private static final Logger LOG = Logger.getLogger(ResPlanPanel.class);

    public static final String TIME_HORIZON_PROPERTY = "timeHorizon";

    public static final String SEARCH_CONDITION_PROPERTY = "searchCondition";

    //

    private ResPlanController controller;

    private DateChooser startDateChooser;
    private DateChooser endDateChooser;

    private JResPlanComponent<Collectable, Date, Collectable, Collectable> resPlan;
    private CollectableResPlanModel resPlanModel;
    private DateTimeModel timeModel;

    private CollectableSearchCondition searchCondition;
    private Interval<Date> timeHorizon;

    private List<EntitySearchFilter> entitySearchFilters;
    private JComboBox searchFilterComboBox;
    private ListComboBoxModel<ResPlanController.TimeGranularity> timeGranularityModel;
    private JComboBox timeGranularityComboBox;
    private JScrollPane scrollPane;
    private final JButton infoButton;

    private List<String> infoMessages;

    // Last known user-specified extents
    private Dimension userResourceCellExtent = new Dimension(-1, -1);
    //   private Dimension userTimelineCellExtent = new Dimension(-1, -1);

    public ResPlanPanel(ResPlanController cntrl, CollectableResPlanModel model, DateTimeModel timeModel) {
        final SpringLocaleDelegate localeDelegate = SpringLocaleDelegate.getInstance();

        this.controller = cntrl;
        this.timeModel = timeModel;
        this.resPlanModel = model;
        setLayout(new BorderLayout());

        JToolBar tb = UIUtils.createNonFloatableToolBar();
        tb.setFloatable(false);
        tb.add(new AbstractAction(localeDelegate.getText("nuclos.resplan.action.refresh"),
                Icons.getInstance().getIconRefresh16()) {

            @Override
            public void actionPerformed(ActionEvent e) {
                controller.refresh();
            }
        });
        tb.add(exportAction);
        tb.addSeparator();

        this.timeHorizon = new Interval<Date>(model.getDefaultViewFrom(), model.getDefaultViewUntil());

        final LabeledComponentSupport support = new LabeledComponentSupport();
        startDateChooser = new DateChooser(support, timeHorizon.getStart());
        startDateChooser.setMinimumSize(startDateChooser.getPreferredSize());
        startDateChooser.setMaximumSize(startDateChooser.getPreferredSize());
        endDateChooser = new DateChooser(support, timeHorizon.getEnd());
        endDateChooser.setMinimumSize(endDateChooser.getPreferredSize());
        endDateChooser.setMaximumSize(endDateChooser.getPreferredSize());

        tb.add(new JLabel(localeDelegate.getText("nuclos.resplan.toolbar.from")));
        tb.add(startDateChooser);
        tb.add(Box.createHorizontalStrut(5));
        tb.add(new JLabel(localeDelegate.getText("nuclos.resplan.toolbar.until")));
        tb.add(endDateChooser);

        timeGranularityModel = new ListComboBoxModel<ResPlanController.TimeGranularity>(
                controller.getTimeGranularityOptions());
        tb.addSeparator();
        tb.add(new JLabel(localeDelegate.getText("nuclos.resplan.toolbar.granularity")));
        timeGranularityComboBox = new JComboBox(timeGranularityModel);
        tb.add(timeGranularityComboBox);
        timeGranularityComboBox
                .setMaximumSize(Orientation.VERTICAL.updateExtent(timeGranularityComboBox.getPreferredSize(), 20));

        tb.addSeparator();
        tb.add(new JLabel(localeDelegate.getText("nuclos.resplan.toolbar.resourceFilter")));
        searchFilterComboBox = new JComboBox();
        searchFilterComboBox.setRenderer(new SearchFilterListCellRenderer());
        refreshSearchFilter();
        tb.add(searchFilterComboBox);
        searchFilterComboBox
                .setMaximumSize(Orientation.VERTICAL.updateExtent(searchFilterComboBox.getPreferredSize(), 20));

        tb.add(Box.createGlue());

        infoButton = new JButton(infoAction);
        infoButton.setVisible(false);
        tb.add(infoButton);

        tb.add(Box.createHorizontalStrut(3));

        initJResPlan();

        ActionListener dateChooserListener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                timeHorzionChanged();
            }
        };
        startDateChooser.addActionListener(dateChooserListener);
        endDateChooser.addActionListener(dateChooserListener);

        timeGranularityComboBox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    Runnable runnable = createScrollToCurrentAreaRunnable();
                    ResPlanController.TimeGranularity granularity = timeGranularityModel.getSelectedItem();
                    resPlan.setTimeModel(granularity.getTimeModel());
                    resPlan.getTimelineHeader().setCategoryModel(granularity.getHeaderCategories());
                    SwingUtilities.invokeLater(runnable);
                }
            }
        });
        searchFilterComboBox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    EntitySearchFilter filter = (EntitySearchFilter) searchFilterComboBox.getSelectedItem();
                    if (filter instanceof NewCustomSearchFilter) {
                        runCustomSearch();
                        return;
                    }
                    setSearchCondition(filter.getSearchCondition());
                }
            }
        });

        scrollPane = new JScrollPane(resPlan);

        JButton corner = new JButton(switchOrientationAction);
        corner.setBorderPainted(false);
        scrollPane.setCorner(JScrollPane.LOWER_RIGHT_CORNER, corner);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        add(tb, BorderLayout.NORTH);
        add(scrollPane, BorderLayout.CENTER);

        resPlan.setTimeHorizon(this.timeHorizon);
        resPlan.invalidate();

        setFocusable(true);
        setFocusCycleRoot(true);
        getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("DELETE"), "delete");
        //getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("control F"), "find");
        getActionMap().put("delete", removeAction);
        //getActionMap().put("find", findAction);
        KeyBindingProvider.bindActionToComponent(KeyBindingProvider.ACTIVATE_SEARCH_PANEL_2, findAction, this);
    }

    JResPlanComponent<Collectable, Date, Collectable, Collectable> getResPlan() {
        return resPlan;
    }

    ResPlanController getController() {
        return controller;
    }

    public ListComboBoxModel<EntitySearchFilter> getSearchFilterModel() {
        return (ListComboBoxModel<EntitySearchFilter>) searchFilterComboBox.getModel();
    }

    public void refreshSearchFilter() {
        this.refreshSearchFilter(controller.getSearchFilters());
    }

    public void refreshSearchFilter(List<EntitySearchFilter> searchFilters) {
        Object selectedItem = searchFilterComboBox.getSelectedItem();
        entitySearchFilters = searchFilters;
        searchFilterComboBox.setModel(new ListComboBoxModel<EntitySearchFilter>(searchFilters));
        searchFilterComboBox
                .setMaximumSize(Orientation.VERTICAL.updateExtent(searchFilterComboBox.getPreferredSize(), 20));
        if (selectedItem != null)
            searchFilterComboBox.setSelectedItem(selectedItem);
    }

    public CollectableSearchCondition getSearchCondition() {
        return searchCondition;
    }

    private void setSearchCondition(CollectableSearchCondition newSearchCondition) {
        CollectableSearchCondition oldSearchCondition = this.searchCondition;
        if (!ObjectUtils.equals(oldSearchCondition, newSearchCondition)) {
            this.searchCondition = newSearchCondition;
            firePropertyChange(SEARCH_CONDITION_PROPERTY, oldSearchCondition, newSearchCondition);
        }
    }

    public String getSearchFilter() {
        EntitySearchFilter filter = (EntitySearchFilter) searchFilterComboBox.getSelectedItem();
        if (filter == null || filter.getSearchCondition() == null || filter instanceof CustomSearchFilter) {
            return null;
        }
        return filter.getName();
    }

    public void setSearchFilter(String name) {
        for (EntitySearchFilter filter : entitySearchFilters) {
            if ((name == null && filter.isDefaultFilter()) || name.equals(filter.getName())) {
                searchFilterComboBox.setSelectedItem(filter);
                return;
            }
        }
    }

    public void setCustomSearchFilter(CollectableSearchCondition searchCondition) {
        CustomSearchFilter customFilter = new CustomSearchFilter(searchCondition);
        List<EntitySearchFilter> filters = new ArrayList<EntitySearchFilter>(entitySearchFilters);
        filters.add(filters.size() - 1, customFilter);
        ListComboBoxModel<EntitySearchFilter> model = new ListComboBoxModel<EntitySearchFilter>(filters);
        searchFilterComboBox.setModel(model);
        searchFilterComboBox.setSelectedItem(customFilter);
        // -> fires event -> setSearchCondition
    }

    public GranularityType getTimeGranularity() {
        ResPlanController.TimeGranularity granularity = timeGranularityModel.getSelectedItem();
        return (granularity != null) ? granularity.getType() : null;
    }

    public void setTimeGranularity(GranularityType granularity) {
        for (int i = 0; i < timeGranularityModel.getSize(); i++) {
            ResPlanController.TimeGranularity option = timeGranularityModel.getElementAt(i);
            if (option.getType() == granularity) {
                timeGranularityModel.setSelectedItem(option);
                break;
            }
        }
    }

    public Interval<Date> getTimeHorizon() {
        return timeHorizon;
    }

    public void setTimeHorizon(Interval<Date> newTimeHorizon) {
        Interval<Date> oldTimeHorizon = this.timeHorizon;
        if (!ObjectUtils.equals(oldTimeHorizon, newTimeHorizon)) {
            this.timeHorizon = newTimeHorizon;
            startDateChooser.setDate(newTimeHorizon.getStart());
            endDateChooser.setDate(newTimeHorizon.getEnd());
            resPlan.setTimeHorizon(timeHorizon);
            firePropertyChange(TIME_HORIZON_PROPERTY, oldTimeHorizon, newTimeHorizon);
        }
    }

    private void timeHorzionChanged() {
        try {
            Date startDate = startDateChooser.getDate();
            Date endDate = endDateChooser.getDate();
            setTimeHorizon(new Interval<Date>(startDate, endDate, true));
        } catch (CommonValidationException ex) {
            Errors.getInstance().showExceptionDialog(ResPlanPanel.this, ex);
            return;
        }
    }

    public void setInfoMessages(List<String> messages, boolean show) {
        this.infoMessages = messages == null || messages.isEmpty() ? null : messages;
        infoButton.setVisible(infoMessages != null);
        if (show)
            showInfoMessages();
    }

    private void showInfoMessages() {
        if (infoMessages != null && infoButton.isVisible()) {
            StringBuilder sb = new StringBuilder("<html>");
            for (String message : infoMessages) {
                String escapedMessage = StringUtils.xmlEncode(message).replaceAll("\n", "<br/>");
                sb.append("<p>").append(escapedMessage).append("</p>");
            }
            final String message = sb.toString();
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        Bubble bubble = new Bubble(infoButton, message, 8, Bubble.Position.SE);
                        bubble.setVisible(true);
                    } catch (Exception e) {
                        LOG.error("showInfoMessages failed: " + e, e);
                    }
                }
            });
        }
    }

    private void initJResPlan() {
        resPlan = new JResPlanComponent<Collectable, Date, Collectable, Collectable>(resPlanModel, timeModel);
        resPlan.getTimelineHeader().setCategoryModel(timeGranularityModel.getSelectedItem());
        resPlan.addMouseListener(new AbstractJPopupMenuListener() {
            @Override
            protected JPopupMenu getJPopupMenu(MouseEvent evt) {
                JPopupMenu popupMenu = new JPopupMenu();
                Point pt = evt.getPoint();

                Area<Collectable, Date> blankSelection = resPlan.getSelectedBlankArea();
                if (blankSelection != null) {
                    popupMenu.add(new AddAction(blankSelection.getResource(), blankSelection.getInterval()));
                } else {
                    popupMenu.add(new AddAction(resPlan.getResourceAt(pt), resPlan.getTimeIntervalAt(pt)));
                }

                List<Collectable> selectedEntries = selectEntriesForEvent(pt);
                List<Collectable> selectedRelations = selectRelationsForEvent(pt);
                if (resPlan.isEditable() && (!selectedEntries.isEmpty() || !selectedRelations.isEmpty())) {
                    JMenuItem menuItem = popupMenu.add(removeAction);
                    boolean enabled = true;
                    for (Collectable clct : selectedEntries) {
                        if (!resPlanModel.isRemoveEntryAllowed(clct)) {
                            enabled = false;
                            break;
                        }
                    }
                    for (Collectable clct : selectedRelations) {
                        if (!resPlanModel.isRemoveRelationAllowed(clct)) {
                            enabled = false;
                            break;
                        }
                    }
                    // Note: just change the state of the menu item (and leave the action as is)
                    menuItem.setEnabled(enabled);
                }
                if (!selectedEntries.isEmpty() || !selectedRelations.isEmpty()) {
                    popupMenu.add(detailsAction);
                }

                if (selectedEntries.size() == 1 && resPlanModel.getRelationEntity() != null
                        && resPlanModel.isCreateRelationAllowed()) {
                    popupMenu.addSeparator();
                    if (resPlan.getRelateBegin() != null) {
                        Collectable to = selectedEntries.get(0);
                        if (to != resPlan.getRelateBegin()) {
                            popupMenu.add(relateFinishAction);
                        }
                    }
                    popupMenu.add(relateBeginAction);
                }

                return popupMenu;
            }

            private List<Collectable> selectEntriesForEvent(Point pt) {
                List<Collectable> selection = resPlan.getSelectedEntries();
                Collectable entryAt = resPlan.getEntryAt(pt);
                if (entryAt != null && (selection.isEmpty() || !selection.contains(entryAt))) {
                    selection = Collections.singletonList(entryAt);
                    resPlan.setSelectedEntries(selection);
                }
                return selection;
            }

            private List<Collectable> selectRelationsForEvent(Point pt) {
                List<Collectable> selection = resPlan.getSelectedRelations();
                Collectable relAt = resPlan.getRelationAt(pt);
                if (relAt != null && (selection.isEmpty() || !selection.contains(relAt))) {
                    selection = Collections.singletonList(relAt);
                    resPlan.setSelectedRelations(selection);
                }
                return selection;
            }

            @Override
            public void mouseClicked(MouseEvent evt) {
                if (evt.getClickCount() == 2) {
                    Collectable clct = resPlan.getEntryAt(evt.getPoint());
                    if (clct == null) {
                        clct = resPlan.getRelationAt(evt.getPoint());
                        if (clct != null) {
                            runDetailsCollectable(resPlanModel.getRelationEntity().getEntityName(), clct);
                        }
                    } else {
                        runDetailsCollectable(resPlanModel.getEntryEntity().getEntityName(), clct);
                    }
                    evt.consume();
                }
            }
        });
        resPlan.getResourceHeader().addMouseListener(new AbstractJPopupMenuListener() {
            @Override
            public void mouseClicked(MouseEvent evt) {
                if (evt.getClickCount() == 2) {
                    Collectable clct = resPlan.getResourceHeader().getValueAt(evt.getPoint());
                    runDetailsCollectable(resPlanModel.getResourceEntity().getEntityName(), clct);
                    evt.consume();
                }
            }

            @Override
            protected JPopupMenu getJPopupMenu(MouseEvent evt) {
                final Collectable clct = resPlan.getResourceHeader().getValueAt(evt.getPoint());
                if (clct != null) {
                    JPopupMenu popupMenu = new JPopupMenu();
                    popupMenu.add(new AbstractAction(
                            SpringLocaleDelegate.getInstance().getText("nuclos.resplan.action.showDetails")) {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            runDetailsCollectable(resPlanModel.getResourceEntity().getEntityName(), clct);
                        }
                    });
                    return popupMenu;
                }
                return null;
            }
        });
        Date start = DateUtils.addDays(DateUtils.getPureDate(new Date()), -5);
        Date end = DateUtils.addDays(start, 30);
        resPlan.setTimeHorizon(new Interval<Date>(start, end));
    }

    public void setResourceRenderer(ComponentProvider<?> renderer) {
        resPlan.getResourceHeader().setCellRendererProvider(renderer);
    }

    public void setEntryRenderer(ComponentProvider<?> renderer) {
        resPlan.setCellRendererProvider(renderer);
    }

    public void setBackgroundPainter(Painter<? super Area<Collectable, Date>> painter) {
        resPlan.setTimeslotBackgroundPainter(painter);
    }

    public void setCaptionComponent(Component c) {
        scrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER, c);
        resPlan.getResourceHeader().setCornerComponent(c);
        resPlan.getTimelineHeader().setCornerComponent(c);
    }

    private void runCustomSearch() {
        // Set-up new custom filter with the current search condition
        // (since the underlying search conditon does not change, no events are triggered)
        setCustomSearchFilter(searchCondition);

        try {
            final NuclosCollectController<?> ctl = NuclosCollectControllerFactory.getInstance()
                    .newCollectController(resPlanModel.getResourceEntity().getEntityName(), null,
                            ClientParameterProvider.getInstance()
                                    .getValue(ParameterProvider.KEY_LAYOUT_CUSTOM_KEY));
            ctl.getSearchPanel().btnSearch.setAction(new CommonAbstractAction(Icons.getInstance().getIconFind16(),
                    SpringLocaleDelegate.getInstance().getText("CollectController.30")) {
                @Override
                public void actionPerformed(ActionEvent ev) {
                    UIUtils.runCommand(getParent(), new CommonRunnable() {
                        @Override
                        public void run() throws CommonBusinessException {
                            try {
                                ctl.makeConsistent(true);
                                CollectableSearchCondition result = ctl.getSearchStrategy()
                                        .getCollectableSearchCondition();
                                setCustomSearchFilter(result);
                            } catch (CollectableFieldFormatException e) {
                                throw new NuclosFatalException(e);
                            }
                            ctl.getTab().dispose();
                        }
                    });
                }
            });
            ctl.runSearch();
        } catch (CommonBusinessException e) {
            LOG.warn("runCustomSearch failed: " + e, e);
        }
    }

    public Rectangle getViewRect() {
        return scrollPane.getViewport().getViewRect();
    }

    public void setViewRect(Rectangle rect) {
        scrollPane.getViewport().setViewPosition(rect.getLocation());
    }

    public void storeViewPreferences(Preferences prefs, RestorePreferences rp) throws PreferencesException {
        preserveCellExtent(userResourceCellExtent, resPlan.getResourceHeader());
        //      preserveCellExtent(userTimelineCellExtent, resPlan.getTimelineHeader());

        int swingConstant = resPlan.getOrientation().swingConstant();
        if (prefs != null) {
            prefs.putInt("orientation", swingConstant);
        }
        if (rp != null) {
            rp.orientation = swingConstant;
        }
        for (Orientation orientation : Orientation.values()) {
            String vh = orientation.select("H", "V");
            if (prefs != null) {
                prefs.putInt("resourceCellExtent" + vh, orientation.extentFrom(userResourceCellExtent));
                //            prefs.putInt("timelineCellExtent" + vh, orientation.extentFrom(userTimelineCellExtent));
            }
            if (rp != null) {
                rp.resourceCellExtent.put("resourceCellExtent" + vh,
                        orientation.extentFrom(userResourceCellExtent));

                //rp.timelineCellExtent.put("timelineCellExtent" + vh, orientation.extentFrom(userTimelineCellExtent));
                for (TimeGranularity tg : this.getTimeGranularities()) {
                    rp.timelineCellExtent.put(tg.getType().getValue() + "_timelineCellExtent" + "H",
                            tg.getCellExtent(Orientation.HORIZONTAL));
                    rp.timelineCellExtent.put(tg.getType().getValue() + "_timelineCellExtent" + "V",
                            tg.getCellExtent(Orientation.VERTICAL));
                }
            }
        }
    }

    public void restoreViewPreferences(Preferences prefs, RestorePreferences rp) throws PreferencesException {
        if (prefs != null) {
            int orientation = prefs.getInt("orientation", -1);
            userResourceCellExtent = new Dimension(prefs.getInt("resourceCellExtentH", -1),
                    prefs.getInt("resourceCellExtentV", -1));
            //         userTimelineCellExtent = new Dimension(prefs.getInt("timelineCellExtentH", -1), prefs.getInt("timelineCellExtentV", -1));
            if (orientation != -1)
                resPlan.setOrientation(Orientation.fromSwingConstant(orientation));
            setCellExtent(resPlan.getResourceHeader(), userResourceCellExtent);
            //         setCellExtent(resPlan.getTimelineHeader(), userTimelineCellExtent);
        }

        if (rp != null) {
            userResourceCellExtent = new Dimension(
                    LangUtils.defaultIfNull(rp.resourceCellExtent.get("resourceCellExtentH"), -1),
                    LangUtils.defaultIfNull(rp.resourceCellExtent.get("resourceCellExtentV"), -1));
            //         userTimelineCellExtent = new Dimension(LangUtils.defaultIfNull(rp.timelineCellExtent.get("timelineCellExtentH"), -1),
            //            LangUtils.defaultIfNull(rp.timelineCellExtent.get("timelineCellExtentV"), -1));
            if (rp.orientation != -1)
                resPlan.setOrientation(Orientation.fromSwingConstant(rp.orientation));
            setCellExtent(resPlan.getResourceHeader(), userResourceCellExtent);

            //setCellExtent(resPlan.getTimelineHeader(), userTimelineCellExtent);
            for (TimeGranularity tg : this.getTimeGranularities()) {
                tg.setCellExtent(Orientation.HORIZONTAL,
                        rp.timelineCellExtent.get(tg.getType().getValue() + "_timelineCellExtent" + "H"));
                tg.setCellExtent(Orientation.VERTICAL,
                        rp.timelineCellExtent.get(tg.getType().getValue() + "_timelineCellExtent" + "V"));
                if (getTimeGranularity() == tg.getType()) {
                    setCellExtent(resPlan.getTimelineHeader(), tg.getCellExtent(resPlan.getOrientation()));
                }
            }
        }
    }

    private static void preserveCellExtent(Dimension dim, JHeaderGrid<?> header) {
        header.getOrientation().updateExtent(dim, header.getCellExtent());
    }

    private static void setCellExtent(JHeaderGrid<?> header, Dimension dim) {
        header.setCellExtent(header.getOrientation().extentFrom(dim));
    }

    private static void setCellExtent(JHeaderGrid<?> header, int extent) {
        header.setCellExtent(extent);
    }

    /**
     * Determines the current top-left area (resource/time) and creates a Runnable
     * which can be used to scroll the view to that area.
     */
    private Runnable createScrollToCurrentAreaRunnable() {
        Point pt = resPlan.getVisibleRect().getLocation();
        pt.translate(5, 5);
        final Collectable resource = resPlan.getResourceAt(pt);
        final Interval<Date> timeInterval = resPlan.getTimeIntervalAt(pt);
        return new Runnable() {
            @Override
            public void run() {
                try {
                    resPlan.scrollRectToArea(resource, timeInterval, true);
                } catch (Exception e) {
                    LOG.error("createScrollToCurrentAreaRunnable failed: " + e, e);
                }
            }
        };
    }

    private Action switchOrientationAction = new AbstractAction(
            SpringLocaleDelegate.getInstance().getText("nuclos.resplan.action.switchOrientation"),
            Icons.getInstance().getIconRelate()) {
        @Override
        public void actionPerformed(ActionEvent e) {
            Runnable runnable = createScrollToCurrentAreaRunnable();
            preserveCellExtent(userResourceCellExtent, resPlan.getResourceHeader());
            //         preserveCellExtent(userTimelineCellExtent, resPlan.getTimelineHeader());
            Orientation orientation = resPlan.getOrientation();
            resPlan.setOrientation(orientation.opposite());
            setCellExtent(resPlan.getResourceHeader(), userResourceCellExtent);
            //         setCellExtent(resPlan.getTimelineHeader(), userTimelineCellExtent);
            if (timeGranularityModel.getSelectedItem() != null
                    && timeGranularityModel.getSelectedItem().getCellExtent(resPlan.getOrientation()) > 0) {
                setCellExtent(resPlan.getTimelineHeader(),
                        timeGranularityModel.getSelectedItem().getCellExtent(resPlan.getOrientation()));
            }
            resPlan.invalidate();
            SwingUtilities.invokeLater(runnable);
        }
    };

    private Action removeAction = new AbstractAction(
            SpringLocaleDelegate.getInstance().getText("nuclos.resplan.action.remove")) {
        @Override
        public void actionPerformed(ActionEvent e) {
            final SpringLocaleDelegate localeDelegate = SpringLocaleDelegate.getInstance();
            ResPlanModel<Collectable, Date, Collectable, Collectable> model = resPlan.getModel();
            final List<Collectable> selectedEntries = resPlan.getSelectedEntries();
            final List<Collectable> selectedRelations = resPlan.getSelectedRelations();
            String title, message;
            if (selectedEntries.isEmpty() && selectedRelations.isEmpty()) {
                return;
            } else if (selectedEntries.size() == 1 && selectedRelations.isEmpty()) {
                title = localeDelegate.getMessage("ResultController.8", null);
                message = localeDelegate.getMessage("ResultController.12", null,
                        selectedEntries.get(0).getIdentifierLabel());
            }
            if (selectedRelations.size() == 1 && selectedEntries.isEmpty()) {
                title = localeDelegate.getMessage("ResultController.8", null);
                message = localeDelegate.getMessage("ResultController.12", null,
                        selectedRelations.get(0).getIdentifierLabel());
            } else { // selectedEntries.size() > 1
                title = localeDelegate.getMessage("ResultController.7", null);
                message = localeDelegate.getMessage("ResultController.13", null,
                        selectedEntries.size() + selectedRelations.size());
            }
            int opt = JOptionPane.showConfirmDialog(ResPlanPanel.this, message, title, JOptionPane.YES_NO_OPTION);
            if (opt == JOptionPane.YES_OPTION) {
                for (Collectable clct : selectedRelations) {
                    if (model.isRemoveRelationAllowed(clct)) {
                        model.removeRelation(clct);
                    }
                }
                for (Collectable clct : selectedEntries) {
                    if (model.isRemoveEntryAllowed(clct)) {
                        model.removeEntry(clct);
                    }
                }
            }
        }
    };

    private Action detailsAction = new AbstractAction(
            SpringLocaleDelegate.getInstance().getText("nuclos.resplan.action.showDetails")) {
        @Override
        public void actionPerformed(ActionEvent e) {
            final List<Collectable> selectedEntries = resPlan.getSelectedEntries();
            for (Collectable clct : selectedEntries) {
                runDetailsCollectable(resPlanModel.getEntryEntity().getEntityName(), clct);
            }
            final List<Collectable> selectedRelations = resPlan.getSelectedRelations();
            for (Collectable clct : selectedRelations) {
                runDetailsCollectable(resPlanModel.getRelationEntity().getEntityName(), clct);
            }
        }
    };

    private Action relateBeginAction = new AbstractAction(
            SpringLocaleDelegate.getInstance().getText("nuclos.resplan.action.relateBegin")) {
        @Override
        public void actionPerformed(ActionEvent e) {
            final List<Collectable> selectedEntries = resPlan.getSelectedEntries();
            if (selectedEntries.size() == 1) {
                resPlan.setRelateBegin(selectedEntries.get(0));
            }
        }
    };

    private Action relateFinishAction = new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            if (resPlan.getRelateBegin() != null) {
                final List<Collectable> selectedEntries = resPlan.getSelectedEntries();
                if (selectedEntries.size() == 1) {
                    Collectable to = selectedEntries.get(0);
                    if (to != resPlan.getRelateBegin()) {
                        resPlanModel.createRelation(resPlan.getRelateBegin(), selectedEntries.get(0));
                        resPlan.setRelateBegin(null);
                    }
                }
            }
        }

        @Override
        public Object getValue(String key) {
            if (Action.NAME.equals(key)) {
                String resourceLabel = "";
                if (resPlan.getRelateBegin() != null) {
                    resourceLabel = resPlan.getEntryAsText(resPlan.getRelateBegin());
                    if (resourceLabel != null && resourceLabel.length() > 130) {
                        resourceLabel = resourceLabel.substring(0, 128) + "...";
                    }
                }
                return String.format(
                        SpringLocaleDelegate.getInstance().getText("nuclos.resplan.action.relateFinish"),
                        resourceLabel);
            }
            return super.getValue(key);
        }
    };

    private Action infoAction = new AbstractAction(null, Icons.getInstance().getIconAbout16()) {
        @Override
        public void actionPerformed(ActionEvent e) {
            showInfoMessages();
        }
    };

    private Action findAction = new FindAction();

    class FindAction extends AbstractAction implements AncestorListener {

        private final JXFindPanel findPanel;

        public FindAction() {
            super("find");
            findPanel = SearchFactory.getInstance().getSharedFindPanel();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (!Arrays.asList(findPanel.getAncestorListeners()).contains(this)) {
                findPanel.addAncestorListener(this);
            }
            SearchFactory.getInstance().showFindDialog(ResPlanPanel.this, resPlan.new ResPlanSearchable());
        }

        @Override
        public void ancestorAdded(AncestorEvent event) {
        }

        @Override
        public void ancestorRemoved(AncestorEvent event) {
            // Remove this listener
            findPanel.removeAncestorListener(this);
            // Clear resource selection
            resPlan.getResourceHeader().setSelectedValue(null);
        }

        @Override
        public void ancestorMoved(AncestorEvent event) {
        }
    }

    private Action exportAction = new ExportAction();

    class ExportAction extends AbstractAction {

        ExportAction() {
            super(SpringLocaleDelegate.getInstance().getText("nuclos.resplan.action.export"),
                    Icons.getInstance().getIconExport16());
            // setEnabled(resPlanModel.isCreateAllowed());
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            // controller.getFrame().add(new ResPlanExportPanel());
            UIUtils.showWaitCursorForFrame(getController().getTab(), true);
            final SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

                @Override
                protected Void doInBackground() throws Exception {
                    try {
                        final ResPlanExportDialog2 d = new ResPlanExportDialog2(ResPlanPanel.this,
                                controller.getTab());
                    } catch (Exception ex) {
                        Errors.getInstance().showExceptionDialog(ResPlanPanel.this, ex);
                    }
                    return null;
                }

                @Override
                protected void done() {
                    UIUtils.showWaitCursorForFrame(getController().getTab(), false);
                }

            };
            worker.execute();
        }
    }

    class RemoveAction extends AbstractAction {

        public RemoveAction() {
            super(SpringLocaleDelegate.getInstance().getText("nuclos.resplan.action.remove"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            final SpringLocaleDelegate localeDelegate = SpringLocaleDelegate.getInstance();
            ResPlanModel<Collectable, Date, Collectable, Collectable> model = resPlan.getModel();
            final List<Collectable> selectedEntries = resPlan.getSelectedEntries();
            String title, message;
            if (selectedEntries.isEmpty()) {
                return;
            } else if (selectedEntries.size() == 1) {
                title = localeDelegate.getMessage("ResultController.8", null);
                message = localeDelegate.getMessage("ResultController.12", null,
                        selectedEntries.get(0).getIdentifierLabel());
            } else { // selectedEntries.size() > 1
                title = localeDelegate.getMessage("ResultController.7", null);
                message = localeDelegate.getMessage("ResultController.13", null, selectedEntries.size());
            }
            int opt = JOptionPane.showConfirmDialog(ResPlanPanel.this, message, title, JOptionPane.YES_NO_OPTION);
            if (opt == JOptionPane.YES_OPTION) {
                for (Collectable clct : selectedEntries) {
                    if (model.isRemoveEntryAllowed(clct)) {
                        model.removeEntry(clct);
                    }
                }
            }
        }
    }

    class AddAction extends AbstractAction {

        private Collectable resource;
        private Interval<Date> interval;

        public AddAction(Collectable resource, Interval<Date> interval) {
            super(SpringLocaleDelegate.getInstance().getText("nuclos.resplan.action.add"));
            setEnabled(resPlanModel.isCreateEntryAllowed());
            this.resource = resource;
            this.interval = interval;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            UIUtils.runCommand(resPlan, new CommonRunnable() {
                @Override
                public void run() throws CommonBusinessException {
                    final MainFrameTab tabIfAny = new MainFrameTab();
                    final NuclosCollectController cntrl = NuclosCollectControllerFactory.getInstance()
                            .newCollectController(resPlanModel.getEntryEntity().getCollectableEntity().getName(),
                                    tabIfAny, ClientParameterProvider.getInstance()
                                            .getValue(ParameterProvider.KEY_LAYOUT_CUSTOM_KEY));
                    Main.getInstance().getMainController().initMainFrameTab(controller, tabIfAny);
                    controller.getTab().add(tabIfAny);

                    cntrl.addCollectableEventListener(new CollectControllerEventHandler(cntrl));
                    Collectable template = null;
                    if (interval != null && resource != null) {
                        template = resPlanModel.createCollectableEntry(resource, interval);
                    }
                    cntrl.runNewWith(template);
                }
            });
        }
    }

    private void runDetailsCollectable(final String entityName, final Collectable clct) {
        if (clct == null)
            return;

        UIUtils.runCommand(Main.getInstance().getMainFrame(), new CommonRunnable() {
            @Override
            public void run() throws CommonBusinessException {
                final MainFrameTab tabIfAny = new MainFrameTab();
                final NuclosCollectController<?> cntrl = NuclosCollectControllerFactory.getInstance()
                        .newCollectController(entityName, tabIfAny, ClientParameterProvider.getInstance()
                                .getValue(ParameterProvider.KEY_LAYOUT_CUSTOM_KEY));

                Main.getInstance().getMainController().initMainFrameTab(cntrl, tabIfAny);
                controller.getTab().add(tabIfAny);

                cntrl.addCollectableEventListener(new CollectControllerEventHandler(cntrl));
                cntrl.runViewSingleCollectableWithId(clct.getId());
            }
        });
    }

    private final class CollectControllerEventHandler implements CollectableEventListener {

        private final NuclosCollectController<?> collectController;

        public CollectControllerEventHandler(NuclosCollectController<?> clctCntrl) {
            this.collectController = clctCntrl;
        }

        @Override
        public void handleCollectableEvent(Collectable collectable, MessageType messageType) {
            switch (messageType) {
            case EDIT_DONE:
            case NEW_DONE:
            case DELETE_DONE:
                try {
                    collectController.getTab().dispose();
                } finally {
                    controller.refresh();
                }
            }
        }
    }

    public static class NewCustomSearchFilter extends EntitySearchFilter {

        public NewCustomSearchFilter() {
            setName(SpringLocaleDelegate.getInstance().getText("nuclos.resplan.action.newSearch"));
        }

        @Override
        public Integer getId() {
            return -1;
        }
    }

    public static class CustomSearchFilter extends EntitySearchFilter {

        static int ID_COUNTER = -3;

        private final int id;
        private boolean initialized;

        public CustomSearchFilter() {
            setName(SpringLocaleDelegate.getInstance().getText("nuclos.resplan.action.customSearch"));
            id = ID_COUNTER--;
        }

        public CustomSearchFilter(CollectableSearchCondition cond) {
            this();
            setSearchCondition(cond);
        }

        @Override
        public void setSearchCondition(CollectableSearchCondition searchcond) {
            super.setSearchCondition(searchcond);
            this.initialized = searchcond != null;
        }

        public boolean isInitialized() {
            return initialized;
        }

        @Override
        public Integer getId() {
            return id;
        }
    }

    public List<TimeGranularity> getTimeGranularities() {
        List<TimeGranularity> result = new ArrayList<TimeGranularity>();
        for (int i = 0; i < timeGranularityModel.getSize(); i++) {
            result.add(timeGranularityModel.getElementAt(i));
        }
        return result;
    }

    public void setRelationPresentation(int relationPresentation) {
        resPlan.setRelationPresentation(relationPresentation);
    }

    public void setRelationFromPresentation(int relationFromPresentation) {
        resPlan.setRelationFromPresentation(relationFromPresentation);
    }

    public void setRelationToPresentation(int relationToPresentation) {
        resPlan.setRelationToPresentation(relationToPresentation);
    }
}