org.eclipse.wb.android.internal.model.layouts.table.TableLayoutSupport.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.wb.android.internal.model.layouts.table.TableLayoutSupport.java

Source

/*******************************************************************************
 * Copyright (c) 2011 Alexander Mitin (Alexander.Mitin@gmail.com)
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Alexander Mitin (Alexander.Mitin@gmail.com) - initial API and implementation
 *******************************************************************************/
package org.eclipse.wb.android.internal.model.layouts.table;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import org.eclipse.wb.android.internal.model.widgets.ViewInfo;
import org.eclipse.wb.draw2d.geometry.Interval;
import org.eclipse.wb.draw2d.geometry.Rectangle;
import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.NotImplementedException;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Android TableLayout model support.
 * 
 * @author mitin_aa
 * @coverage android.model
 */
public final class TableLayoutSupport {
    private final TableLayoutInfo m_layout;
    private CellInfo[][] m_cells;
    private final Interval[] m_rowIntervals;
    private final Interval[] m_columnIntervals;
    private int m_rows;
    private int m_columns;
    private final Map<ViewInfo, Rectangle> m_viewRectangleCells = Maps.newHashMap();
    private final Map<ViewInfo, CellInfo> m_viewCells = Maps.newHashMap();

    public TableLayoutSupport(TableLayoutInfo layout) throws Exception {
        m_layout = layout;
        // row/column count
        List<TableRowInfo> tableRows = layout.getChildrenRows();
        int[] maxWidths = (int[]) ReflectionUtils.getFieldObject(layout.getObject(), "mMaxWidths");
        if (maxWidths == null) {
            // empty
            maxWidths = new int[0];
        }
        m_rows = tableRows.size();
        m_columns = maxWidths.length;
        // intervals
        m_rowIntervals = new Interval[m_rows];
        m_columnIntervals = new Interval[m_columns];
        // prepare cells
        m_cells = new CellInfo[m_rows][m_columns];
        // setup row intervals & cell data
        int offsetHeight = 0;
        for (int row = 0; row < m_rows; ++row) {
            TableRowInfo rowInfo = tableRows.get(row);
            int rowHeight = rowInfo.getBounds().height;
            m_rowIntervals[row] = new Interval(offsetHeight, rowHeight);
            List<ViewInfo> views = rowInfo.getChildrenViews();
            if (views.isEmpty()) {
                // just fill with empty cells 
                for (int column = 0; column < m_columns; ++column) {
                    m_cells[row][column] = new CellInfo(row, column);
                }
            } else {
                for (int viewIndex = 0, column = 0; column < m_columns; ++viewIndex) {
                    // the rest of cells is empty, append empty cells 
                    if (viewIndex >= views.size()) {
                        m_cells[row][column] = new CellInfo(row, column);
                        column++;
                        continue;
                    }
                    // get view
                    ViewInfo view = views.get(viewIndex);
                    int explicitColumn = TableLayoutUtils.getExplicitColumn(view);
                    if (explicitColumn != -1) {
                        // add empty cells before (if any)
                        for (int fillColumn = column; fillColumn < explicitColumn; ++fillColumn) {
                            m_cells[row][fillColumn] = new CellInfo(row, fillColumn);
                        }
                        column = explicitColumn;
                    }
                    CellInfo cell = new CellInfo(row, column);
                    m_cells[row][column] = cell;
                    cell.view = view;
                    m_viewCells.put(view, cell);
                    int span = TableLayoutUtils.getSpanValue(rowInfo, view);
                    cell.span = span;
                    // add empty cells for spanning
                    for (int spannedColumn = column + 1; spannedColumn < column + span; ++spannedColumn) {
                        CellInfo spannedSpace = new CellInfo(row, spannedColumn);
                        spannedSpace.spannedViewCell = cell;
                        m_cells[row][spannedColumn] = spannedSpace;
                    }
                    // store cell occupation
                    Rectangle cellRect = new Rectangle(column, row, span, 1);
                    m_viewRectangleCells.put(view, cellRect);
                    column += span;
                }
            }
            offsetHeight += rowHeight;
        }
        // setup column intervals 
        int offsetWidth = 0;
        for (int column = 0; column < m_columns; column++) {
            m_columnIntervals[column] = new Interval(offsetWidth, maxWidths[column]);
            offsetWidth += maxWidths[column];
        }
    }

    public ViewInfo getViewAt(int row, int column) {
        if (row < m_rows && column < m_columns) {
            CellInfo cell = m_cells[row][column];
            if (cell.view != null) {
                return cell.view;
            } else if (cell.spannedViewCell != null) {
                return cell.spannedViewCell.view;
            }
        }
        return null;
    }

    public int getViewRow(ViewInfo view) {
        throw new NotImplementedException();
    }

    public int getViewColumn(ViewInfo view) {
        throw new NotImplementedException();
    }

    public int getRowCount() {
        return m_rows;
    }

    public int getColumnCount() {
        return m_columns;
    }

    public ViewInfo getNextViewAt(int row, int column) {
        CellInfo[] rowCells = m_cells[row];
        for (int columnIndex = column; columnIndex < rowCells.length; columnIndex++) {
            CellInfo cell = rowCells[columnIndex];
            if (cell.view != null) {
                return cell.view;
            }
        }
        return null;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Visual Data
    //
    ////////////////////////////////////////////////////////////////////////////
    public Interval[] getRowIntervals() {
        return m_rowIntervals;
    }

    public Interval[] getColumnIntervals() {
        return m_columnIntervals;
    }

    public Rectangle getViewCells(ViewInfo view) {
        return m_viewRectangleCells.get(view);
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Add/Insert
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Adds information about new row. Note: it doesn't update visual information.
     */
    public void insertRow(int rowIndex) throws Exception {
        CellInfo newRowCells[] = new CellInfo[m_columns];
        for (int column = 0; column < m_columns; ++column) {
            newRowCells[column] = new CellInfo(rowIndex, column);
        }
        m_cells = (CellInfo[][]) ArrayUtils.add(m_cells, rowIndex, newRowCells);
        // update cells row
        for (int row = rowIndex + 1; row < m_rows + 1; ++row) {
            for (int column = 0; column < m_columns; ++column) {
                m_cells[row][column].row++;
            }
        }
        m_rows++;
    }

    /**
     * Adds information about new column. Note: it doesn't update visual information.
     */
    public void insertColumn(int columnIndex) throws Exception {
        for (int row = 0; row < m_rows; ++row) {
            CellInfo newCell = new CellInfo(row, columnIndex);
            CellInfo cell = null;
            if (columnIndex < m_columns) {
                cell = m_cells[row][columnIndex];
            }
            // insert a cell into row cells
            m_cells[row] = (CellInfo[]) ArrayUtils.add(m_cells[row], columnIndex, newCell);
            // null cell means appending to the end
            if (cell != null) {
                // extend span
                if (cell.isSpanSpace()) {
                    cell.spannedViewCell.span++;
                    newCell.spannedViewCell = cell.spannedViewCell;
                    TableLayoutUtils.setSpanValue(cell.spannedViewCell.view, cell.spannedViewCell.span);
                } else {
                    // find a next cell with view to right to adjust the column number
                    boolean prevEmpty = true;
                    for (int column = columnIndex + 1; column < m_columns + 1; ++column) {
                        CellInfo rightCell = m_cells[row][column];
                        if (!rightCell.isEmpty()) {
                            if (prevEmpty) {
                                // set explicit column
                                TableLayoutUtils.setExplicitColumn(rightCell.view, column);
                            }
                            prevEmpty = false;
                        } else {
                            prevEmpty = true;
                        }
                    }
                }
            }
        }
        m_columns++;
        // fix column number in cell
        for (int row = 0; row < m_rows; ++row) {
            for (int column = columnIndex + 1; column < m_columns; ++column) {
                m_cells[row][column].column++;
            }
        }
    }

    /**
     * Adds a view into given cell. Target cell must be existent and empty.
     */
    public void addView(ViewInfo newView, int row, int column) throws Exception {
        addView0(newView, row, column);
        optimize();
    }

    private void addView0(ViewInfo newView, int row, int column) throws Exception {
        CellInfo rCell = m_cells[row][column];
        rCell.view = newView;
        TableLayoutUtils.setExplicitColumn(rCell.view, column);
        m_viewCells.put(newView, rCell);
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Remove
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Removes a view from grid.
     * 
     * @param view
     *          A view to remove.
     */
    public void deleteView(ViewInfo view) throws Exception {
        deleteView0(view);
        optimize();
    }

    private void deleteView0(ViewInfo view) throws Exception {
        CellInfo cell = m_viewCells.remove(view);
        int row = cell.row;
        // get next cell at this cell's span end to preserve column if it is not empty.
        int endColumn = cell.column + cell.span;
        preserveColumn(row, endColumn);
        // clear cells
        for (int column = cell.column; column < endColumn; ++column) {
            m_cells[row][column].clear();
        }
    }

    /**
     * Removes single column.
     * 
     * @param column
     *          a column number to remove.
     */
    public void deleteColumn(int column) throws Exception {
        // remove views
        for (int row = 0; row < m_rows; ++row) {
            CellInfo cell = m_cells[row][column];
            ViewInfo view = cell.view;
            if (view != null) {
                m_layout.deleteView0(view);
            }
        }
        // optimize will remove column from model
        optimize();
    }

    /**
     * Removes single column.
     * 
     * @param column
     *          a column number to remove.
     */
    private void removeColumn(int column) throws Exception {
        for (int row = 0; row < m_rows; ++row) {
            CellInfo cell = m_cells[row][column];
            // remove a cell from row cells
            m_cells[row] = (CellInfo[]) ArrayUtils.remove(m_cells[row], column);
            // shrink span, as TableLayout cannot keep span thru empty column 
            if (cell.isSpanSpace()) {
                cell.spannedViewCell.span--;
                TableLayoutUtils.setSpanValue(cell.spannedViewCell.view, cell.spannedViewCell.span);
            } else {
                // find a next cell with view to right to adjust the column number
                boolean prevEmpty = true;
                for (int c = column; c < m_columns - 1; ++c) {
                    CellInfo rightCell = m_cells[row][c];
                    if (!rightCell.isEmpty()) {
                        if (prevEmpty) {
                            // set explicit column
                            TableLayoutUtils.setExplicitColumn(rightCell.view, c);
                        }
                        prevEmpty = false;
                    } else {
                        prevEmpty = true;
                    }
                }
            }
        }
        m_columns--;
    }

    /**
     * Removes single row.
     * 
     * @param row
     *          a row number to remove.
     */
    public void deleteRow(int row) throws Exception {
        removeRow(row);
        optimize();
    }

    /**
     * Removes single row.
     * 
     * @param row
     *          a row number to remove.
     */
    private void removeRow(int row) throws Exception {
        m_cells = (CellInfo[][]) ArrayUtils.remove(m_cells, row);
        m_layout.deleteRow0(row);
        m_rows--;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Span
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Managing span for cell.
     * 
     * @param view
     *          A view to manage the span of.
     * @param startColumn
     *          A column at which the span start of.
     * @param spanLength
     *          A span length in cells.
     */
    public void setSpan(ViewInfo view, int startColumn, int spanLength) throws Exception {
        CellInfo cell = m_viewCells.get(view);
        int row = cell.row;
        deleteView0(view);
        // set span
        cell = m_cells[row][startColumn];
        cell.view = view;
        cell.span = spanLength;
        for (int column = cell.column + 1; column < cell.column + cell.span; ++column) {
            m_cells[row][column].spannedViewCell = cell;
        }
        // store cell
        m_viewCells.put(view, cell);
        // explicitly set the column & span
        TableLayoutUtils.setExplicitColumn(view, startColumn);
        TableLayoutUtils.setSpanValue(view, spanLength);
        // do optimize
        optimize();
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Move
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Moves a view into target cell. Attempts to keep span if possible.
     */
    public void move(ViewInfo view, int row, int column) throws Exception {
        CellInfo cell = m_viewCells.get(view);
        int currentSpan = cell.span;
        deleteView0(view);
        addView0(view, row, column);
        // attempt to keep span
        cell = m_viewCells.get(view);
        for (int c = cell.column + 1; c < cell.column + currentSpan; ++c) {
            if (c < m_columns) {
                CellInfo checkCell = m_cells[cell.row][c];
                if (checkCell.view == null) {
                    cell.span++;
                    checkCell.spannedViewCell = cell;
                } else {
                    break;
                }
            } else {
                break;
            }
        }
        TableLayoutUtils.setSpanValue(view, cell.span);
        // optimize
        optimize();
    }

    /**
     * Moves a column from sourceIndex to targetIndex.
     * 
     * @param sourceIndex
     * @param targetIndex
     */
    public void moveColumn(int sourceIndex, int targetIndex) throws Exception {
        // add new column
        insertColumn(targetIndex);
        // gather cells with views
        List<CellInfo> cells = Lists.newArrayList();
        for (int row = 0; row < m_rows; ++row) {
            CellInfo cell = m_cells[row][sourceIndex];
            if (cell.view != null) {
                cells.add(cell);
            }
        }
        // delete column
        removeColumn(sourceIndex);
        // add views into new column
        targetIndex--;
        for (int i = 0; i < cells.size(); ++i) {
            CellInfo cell = cells.get(i);
            addView0(cell.view, cell.row, targetIndex);
        }
        optimize();
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Optimizations
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Does some optimization:
     * 
     * <pre>
     * 1. Removes unneeded explicitly defined column values;
     * 2. Removes empty rows/columns;
     * 3. Eliminates span space and/or corrects columns numbers which don't affect the layout.</pre>
     */
    private void optimize() throws Exception {
        // step 1: remove empty rows/columns
        Set<Integer> nonEmptyRows = Sets.newHashSet();
        Set<Integer> nonEmptyColumns = Sets.newHashSet();
        // go to traverse all cells, maybe create more efficient algorithm later :-)
        for (int row = 0; row < m_rows; ++row) {
            for (int column = 0; column < m_columns; ++column) {
                if (m_cells[row][column].view != null) {
                    nonEmptyRows.add(row);
                    nonEmptyColumns.add(column);
                }
            }
        }
        // proceed with remove
        for (int row = m_rows - 1; row >= 0; --row) {
            if (!nonEmptyRows.contains(row)) {
                removeRow(row);
            }
        }
        for (int column = m_columns - 1; column >= 0; --column) {
            if (!nonEmptyColumns.contains(column)) {
                removeColumn(column);
            }
        }
        // at this point there should not be empty columns, so 
        // step 2: remove unneeded explicit column numbers
        for (int row = 0; row < m_rows; ++row) {
            boolean prevEmpty = false;
            for (int column = 0; column < m_columns; ++column) {
                //  fix cell numbers
                CellInfo cell = m_cells[row][column];
                cell.row = row;
                cell.column = column;
                // check
                if (cell.view != null && !prevEmpty) {
                    TableLayoutUtils.removeExplicitColumn(cell.view);
                }
                // prepare next iteration
                if (cell.isSpanSpace() || cell.view != null) {
                    prevEmpty = false;
                } else {
                    prevEmpty = true;
                }
            }
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Utils
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Sets the column explicitly if the cell has a view.
     */
    private void preserveColumn(int row, int column) throws Exception {
        if (column < m_columns) {
            CellInfo rCell = m_cells[row][column];
            if (rCell.view != null) {
                TableLayoutUtils.setExplicitColumn(rCell.view, column);
            }
        }
    }
}