Package pivot.wtk.content

Source Code of pivot.wtk.content.TableViewRowEditor

/*
* 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 pivot.wtk.content;

import java.awt.Color;
import java.awt.Graphics2D;

import pivot.beans.BeanDictionary;
import pivot.collections.Dictionary;
import pivot.collections.HashMap;
import pivot.collections.List;
import pivot.util.Vote;
import pivot.wtk.Bounds;
import pivot.wtk.CardPane;
import pivot.wtk.Component;
import pivot.wtk.ComponentKeyListener;
import pivot.wtk.ComponentListener;
import pivot.wtk.Container;
import pivot.wtk.ContainerMouseListener;
import pivot.wtk.Display;
import pivot.wtk.ImageView;
import pivot.wtk.Keyboard;
import pivot.wtk.Mouse;
import pivot.wtk.ScrollPane;
import pivot.wtk.TablePane;
import pivot.wtk.TableView;
import pivot.wtk.TableViewListener;
import pivot.wtk.TableViewRowListener;
import pivot.wtk.TextInput;
import pivot.wtk.Viewport;
import pivot.wtk.ViewportListener;
import pivot.wtk.Window;
import pivot.wtk.WindowStateListener;
import pivot.wtk.effects.FlipTransition;
import pivot.wtk.effects.Transition;
import pivot.wtk.effects.TransitionListener;
import pivot.wtk.media.Image;

/**
* Default table view row editor.
*
* @author tvolkert
*/
public class TableViewRowEditor implements TableView.RowEditor {
    /**
     * Paints the row being edited.
     *
     * @author tvolkert
     */
    private Image tableRowImage = new Image() {
        public int getWidth() {
            return (tableView == null) ? 0 : tableView.getRowBounds(rowIndex).width;
        }

        public int getHeight() {
            return (tableView == null) ? 0 : tableView.getRowBounds(rowIndex).height;
        }

        public void paint(Graphics2D graphics) {
            Bounds rowBounds = tableView.getRowBounds(rowIndex);
            int width = rowBounds.width;
            int height = rowBounds.height;

            TableView.ColumnSequence columns = tableView.getColumns();
            Component.StyleDictionary styles = tableView.getStyles();

            boolean rowSelected = tableView.isRowSelected(rowIndex);
            boolean rowDisabled = tableView.isRowDisabled(rowIndex);

            // Paint the background
            Color backgroundColor = (Color)styles.get("backgroundColor");

            if (rowSelected) {
                backgroundColor = tableView.isFocused() ?
                    (Color)styles.get("selectionBackgroundColor") :
                    (Color)styles.get("inactiveSelectionBackgroundColor");
            } else {
                Color alternateRowColor = (Color)styles.get("alternateRowColor");

                if (alternateRowColor != null && rowIndex % 2 > 0) {
                    backgroundColor = alternateRowColor;
                }
            }

            if (backgroundColor != null) {
                graphics.setPaint(backgroundColor);
                graphics.fillRect(0, 0, width, height);
            }

            // Paint the cells
            Object tableRow = tableView.getTableData().get(rowIndex);
            int cellX = 0;

            for (int i = 0, n = columns.getLength(); i < n; i++) {
                TableView.Column column = columns.get(i);
                TableView.CellRenderer cellRenderer = column.getCellRenderer();

                int columnWidth = tableView.getColumnBounds(i).width;

                Graphics2D rendererGraphics = (Graphics2D)graphics.create(cellX, 0, columnWidth, height);

                cellRenderer.render(tableRow, tableView, column, rowSelected, false, rowDisabled);
                cellRenderer.setSize(columnWidth, height - 1);
                cellRenderer.paint(rendererGraphics);

                rendererGraphics.dispose();

                cellX += columnWidth + 1;
            }
        }
    };

    /**
     * Responsible for "edit initialization" and "edit finalization" tasks when
     * the edit popup is opened and closed, respectively.  Also responsible for
     * running the flip transition to close the popup.
     *
     * @author tvolkert
     */
    private WindowStateListener popupStateHandler = new WindowStateListener.Adapter() {
        private boolean closeTransitionStarted = false;
        private boolean closeTransitionComplete = false;

        @Override
        public void windowOpened(Window window) {
            Display display = window.getDisplay();
            display.getContainerMouseListeners().add(displayMouseHandler);

            tableView.getComponentListeners().add(tableViewComponentHandler);
            tableView.getTableViewListeners().add(tableViewHandler);
            tableView.getTableViewRowListeners().add(tableViewRowHandler);

            // Scroll the editor to match that of the table view
            if (tableViewScrollPane != null) {
                editorScrollPane.setScrollLeft(tableViewScrollPane.getScrollLeft());
            }

            // Start the "flip open" transition
            flipTransition = new FlipTransition(FLIP_DURATION, editorCardPane, 0, Math.PI);

            flipTransition.start(new TransitionListener() {
                public void transitionCompleted(Transition transition) {
                    Component focusComponent = editorTablePane.getCellComponent(0, columnIndex);
                    focusComponent.requestFocus();
                }
            });
        }

        @Override
        public Vote previewWindowClose(Window window) {
            Vote vote = (closeTransitionComplete ? Vote.APPROVE : Vote.DEFER);

            if (!closeTransitionStarted) {
                closeTransitionStarted = true;

                // Restore focus to the table view
                tableView.requestFocus();

                int duration = FLIP_DURATION;
                double beginTheta = Math.PI;

                // If we're still flipping open, then start the reverse flip at
                // the point where we're stopping the current flip
                if (flipTransition.isRunning()) {
                    flipTransition.stop();
                    duration = flipTransition.getElapsedTime();
                    beginTheta = flipTransition.getCurrentTheta();
                }

                flipTransition.setDuration(duration);
                flipTransition.setBeginTheta(beginTheta);
                flipTransition.setEndTheta(0);

                flipTransition.start(new TransitionListener() {
                    public void transitionCompleted(Transition transition) {
                        closeTransitionComplete = true;
                        popup.close();
                    }
                });
            }

            return vote;
        }

        @Override
        public void windowClosed(Window window, Display display) {
            // Clean up
            display.getContainerMouseListeners().remove(displayMouseHandler);

            tableView.getComponentListeners().remove(tableViewComponentHandler);
            tableView.getTableViewListeners().remove(tableViewHandler);
            tableView.getTableViewRowListeners().remove(tableViewRowHandler);

            // Reset flags
            closeTransitionStarted = false;
            closeTransitionComplete = false;

            // Free memory
            tableView = null;
            tableViewScrollPane = null;
            flipTransition = null;

            TablePane.ColumnSequence tablePaneColumns = editorTablePane.getColumns();
            TablePane.Row tablePaneRow = editorTablePane.getRows().get(0);
            tablePaneColumns.remove(0, tablePaneColumns.getLength());
            tablePaneRow.remove(0, tablePaneRow.getLength());
        }
    };

    /**
     * Responsible for saving or cancelling the edit based on the user pressing
     * the <tt>ENTER</tt> key or the <tt>ESCAPE</tt> key, respectively.
     *
     * @author tvolkert
     */
    private ComponentKeyListener popupKeyHandler = new ComponentKeyListener.Adapter() {
        @Override
        public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
            if (keyCode == Keyboard.KeyCode.ENTER) {
                save();
            } else if (keyCode == Keyboard.KeyCode.ESCAPE) {
                cancel();
            }

            return false;
        }
    };

    /**
     * Responsible for closing the popup whenever the user clicks outside the
     * bounds of the popup.
     *
     * @author tvolkert
     */
    private ContainerMouseListener displayMouseHandler = new ContainerMouseListener.Adapter() {
        @Override
        public boolean mouseDown(Container container, Mouse.Button button, int x, int y) {
            boolean consumed = false;

            // If the event occurred outside the popup, close the popup
            Display display = (Display)container;
            Window window = (Window)display.getComponentAt(x, y);

            if (popup != window &&
                (window == null || !popup.isOwner(window))) {
                save();
                consumed = true;
            }

            return consumed;
        }

        @Override
        public boolean mouseWheel(Container container, Mouse.ScrollType scrollType,
            int scrollAmount, int wheelRotation, int x, int y) {
            boolean consumed = false;

            // If the event occurred outside the popup, consume the event
            Display display = (Display)container;
            Window window = (Window)display.getComponentAt(x, y);

            if (popup != window &&
                (window == null || !popup.isOwner(window))) {
                consumed = true;
            }

            return consumed;
        }
    };

    /**
     * Responsible for cancelling the edit if the table view's size changes.
     *
     * @author tvolkert
     */
    private ComponentListener tableViewComponentHandler = new ComponentListener.Adapter() {
        @Override
        public void sizeChanged(Component component, int previousWidth, int previousHeight) {
            cancel();
        }
    };

    /**
     * Responsible for cancelling the edit if any relevant changes are made to
     * the table view while we're editing.
     *
     * @author tvolkert
     */
    private TableViewListener tableViewHandler = new TableViewListener.Adapter() {
        @Override
        public void rowEditorChanged(TableView tableView, TableView.RowEditor previousRowEditor) {
            cancel();
        }

        @Override
        public void tableDataChanged(TableView tableView, List<?> previousTableData) {
            cancel();
        }
    };

    /**
     * Responsible for cancelling the edit if any changes are made to the table
     * data while we're editing.
     *
     * @author tvolkert
     */
    private TableViewRowListener tableViewRowHandler = new TableViewRowListener.Adapter() {
        @Override
        public void rowInserted(TableView tableView, int index) {
            cancel();
        }

        @Override
        public void rowsRemoved(TableView tableView, int index, int count) {
            cancel();
        }

        @Override
        public void rowsSorted(TableView tableView) {
            cancel();
        }

        @Override
        public void rowsCleared(TableView tableView) {
            cancel();
        }

        @Override
        public void rowUpdated(TableView tableView, int index) {
            cancel();
        }
    };

    /**
     * Responsible for keeping the table view scroll pane's scrollLeft value in
     * sync with the editor scroll pane's scrollLeft value.
     *
     * @author tvolkert
     */
    private ViewportListener viewportHandler = new ViewportListener.Adapter() {
        @Override
        public void scrollLeftChanged(Viewport viewport, int previousScrollLeft) {
            if (tableViewScrollPane != null) {
                tableViewScrollPane.setScrollLeft(viewport.getScrollLeft());
            }
        }
    };

    // Transient data (only meaningful during editing)
    private TableView tableView = null;
    private ScrollPane tableViewScrollPane = null;
    private int rowIndex;
    private int columnIndex;

    // Editor components (persistent across edits)
    private Window popup;
    private ScrollPane editorScrollPane;
    private CardPane editorCardPane;
    private TablePane editorTablePane;

    // Cell editors specified by the caller
    private HashMap<String, Component> cellEditors = new HashMap<String, Component>();

    // Transition
    private FlipTransition flipTransition = null;

    // The duration in milliseconds of a full (non-interrupted) transition
    private static final int FLIP_DURATION = 350;

    /**
     * Creates a new <tt>TableViewRowEditor</tt>. This object should only be
     * associated with one table view at a time.
     */
    public TableViewRowEditor() {
        // Create the editor components
        popup = new Window(true);
        editorScrollPane = new ScrollPane(ScrollPane.ScrollBarPolicy.NEVER, ScrollPane.ScrollBarPolicy.FILL);
        editorCardPane = new CardPane();
        editorTablePane = new TablePane();
        editorTablePane.getStyles().put("horizontalSpacing", 1);

        // Set up the editor component hierarchy
        popup.setContent(editorScrollPane);
        editorScrollPane.setView(editorCardPane);
        editorCardPane.add(new ImageView(tableRowImage));
        editorCardPane.add(editorTablePane);
        editorTablePane.getRows().add(new TablePane.Row(1, true));

        // Register listeners
        editorScrollPane.getViewportListeners().add(viewportHandler);
        popup.getWindowStateListeners().add(popupStateHandler);
        popup.getComponentKeyListeners().add(popupKeyHandler);
    }

    /**
     * Gets this row editor's cell editor dictionary. The caller may specify
     * explicit editor components and place them in this dictionary by their
     * table view column names. Any column that does not have an entry in this
     * dictionary will have a <tt>TextInput</tt> implicitly associated with it
     * during editing.
     * <p>
     * This row editor uses data binding to populate the cell editor components
     * and to get the data back out of those components, so it is the caller's
     * responsibility to set up the data binding keys in each component they
     * specify in this dictionary. The data binding key should equal the column
     * name that the cell editor serves.
     *
     * @return
     * The cell editor dictionary.
     */
    public Dictionary<String, Component> getCellEditors() {
        return cellEditors;
    }

    @SuppressWarnings("unchecked")
    public void edit(TableView tableView, int rowIndex, int columnIndex) {
        if (isEditing()) {
            throw new IllegalStateException();
        }

        this.tableView = tableView;
        this.rowIndex = rowIndex;
        this.columnIndex = columnIndex;

        Container tableViewParent = tableView.getParent();
        if (tableViewParent instanceof ScrollPane) {
            tableViewScrollPane = (ScrollPane)tableViewParent;
        }

        // Match the table pane's columns to the table view's
        TableView.ColumnSequence tableViewColumns = tableView.getColumns();
        TablePane.ColumnSequence tablePaneColumns = editorTablePane.getColumns();
        TablePane.Row tablePaneRow = editorTablePane.getRows().get(0);

        for (int i = 0, n = tableViewColumns.getLength(); i < n; i++) {
            // Add a new column to the table pane to match the table view column
            TablePane.Column tablePaneColumn = new TablePane.Column();
            tablePaneColumns.add(tablePaneColumn);

            // Size the table pane column to match that of the table view
            // column. We get the real-time column width from the table view as
            // opposed to the width property of the column, because the latter
            // may represent a relative width, and we need the actual width
            int columnWidth = tableView.getColumnBounds(i).width;
            tablePaneColumn.setWidth(columnWidth, false);

            // Determine which component to use as the editor for this column
            String columnName = tableViewColumns.get(i).getName();
            Component editorComponent = cellEditors.get(columnName);

            // Default to a TextInput editor
            if (editorComponent == null) {
                TextInput editorTextInput = new TextInput();
                editorTextInput.setTextKey(columnName);
                editorComponent = editorTextInput;
            }

            // Add the editor component to the table pane
            tablePaneRow.add(editorComponent);

            if (columnWidth == 0) {
                // Remove non-visible components from focus contention
                editorComponent.setEnabled(false);
            }
        }

        // Get the row data, represented as a Dictionary
        Object tableRow = tableView.getTableData().get(rowIndex);
        Dictionary<String, Object> rowData;
        if (tableRow instanceof Dictionary<?, ?>) {
            rowData = (Dictionary<String, Object>)tableRow;
        } else {
            rowData = new BeanDictionary(tableRow);
        }

        // Load the row data into the editor components
        editorTablePane.load(rowData);

        // Calculate the visible bounds of the row
        Bounds editBounds = tableView.getRowBounds(rowIndex);
        tableView.scrollAreaToVisible(editBounds);
        editBounds = tableView.getVisibleArea(editBounds);

        // Open the popup over the row
        popup.setLocation(editBounds.x, editBounds.y);
        popup.setPreferredSize(editBounds.width, editBounds.height);
        popup.open(tableView.getWindow());
    }

    public boolean isEditing() {
        return (tableView != null);
    }

    @SuppressWarnings("unchecked")
    public void save() {
        if (!isEditing()) {
            throw new IllegalStateException();
        }

        List<Object> tableData = (List<Object>)tableView.getTableData();

        // Get the row data, represented as a Dictionary
        Object tableRow = tableData.get(rowIndex);
        Dictionary<String, Object> rowData;
        if (tableRow instanceof Dictionary<?, ?>) {
            rowData = (Dictionary<String, Object>)tableRow;
        } else {
            rowData = new BeanDictionary(tableRow);
        }

        // Update the row data using data binding
        editorTablePane.store(rowData);

        // Notifying the parent will close the popup
        if (tableData.getComparator() == null) {
            tableData.update(rowIndex, tableRow);
        } else {
            // Save local reference to members variables before they get cleared
            TableView tableView = this.tableView;

            tableData.remove(rowIndex, 1);
            tableData.add(tableRow);

            // Re-select the row, and make sure it's visible
            rowIndex = tableData.indexOf(tableRow);
            tableView.setSelectedIndex(rowIndex);
            tableView.scrollAreaToVisible(tableView.getRowBounds(rowIndex));
        }
    }

    public void cancel() {
        if (!isEditing()) {
            throw new IllegalStateException();
        }

        popup.close();
    }
}
TOP

Related Classes of pivot.wtk.content.TableViewRowEditor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.