org.jkiss.dbeaver.ui.controls.resultset.spreadsheet.SpreadsheetPresentation.java Source code

Java tutorial

Introduction

Here is the source code for org.jkiss.dbeaver.ui.controls.resultset.spreadsheet.SpreadsheetPresentation.java

Source

/*
 * DBeaver - Universal Database Manager
 * Copyright (C) 2010-2016 Serge Rieder (serge@jkiss.org)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (version 2)
 * as published by the Free Software Foundation.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
/*
 * DBeaver - Universal Database Manager
 * Copyright (C) 2010-2016 Serge Rieder (serge@jkiss.org)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (version 2)
 * as published by the Free Software Foundation.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package org.jkiss.dbeaver.ui.controls.resultset.spreadsheet;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.menus.CommandContributionItem;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.themes.IThemeManager;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.IPropertySourceProvider;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.DBeaverPreferences;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.core.CoreMessages;
import org.jkiss.dbeaver.core.DBeaverCore;
import org.jkiss.dbeaver.model.*;
import org.jkiss.dbeaver.model.data.*;
import org.jkiss.dbeaver.model.exec.DBCSession;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.dbeaver.runtime.properties.PropertyCollector;
import org.jkiss.dbeaver.ui.ActionUtils;
import org.jkiss.dbeaver.ui.DBeaverIcons;
import org.jkiss.dbeaver.ui.UIUtils;
import org.jkiss.dbeaver.ui.controls.PropertyPageStandard;
import org.jkiss.dbeaver.ui.controls.lightgrid.GridCell;
import org.jkiss.dbeaver.ui.controls.lightgrid.GridPos;
import org.jkiss.dbeaver.ui.controls.lightgrid.IGridContentProvider;
import org.jkiss.dbeaver.ui.controls.lightgrid.IGridLabelProvider;
import org.jkiss.dbeaver.ui.controls.resultset.*;
import org.jkiss.dbeaver.ui.data.IMultiController;
import org.jkiss.dbeaver.ui.data.IValueController;
import org.jkiss.dbeaver.ui.data.IValueEditor;
import org.jkiss.dbeaver.ui.data.IValueEditorStandalone;
import org.jkiss.dbeaver.ui.data.managers.BaseValueManager;
import org.jkiss.dbeaver.ui.dialogs.ConfirmationDialog;
import org.jkiss.dbeaver.ui.properties.PropertySourceDelegate;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;

import java.util.*;
import java.util.List;

/**
 * Spreadsheet presentation.
 * Visualizes results as grid.
 */
public class SpreadsheetPresentation extends AbstractPresentation
        implements IResultSetEditor, ISelectionProvider, IStatefulControl, IAdaptable {

    private static final Log log = Log.getLog(SpreadsheetPresentation.class);

    private static final String VIEW_PANEL_VISIBLE = "viewPanelVisible";
    private static final String VIEW_PANEL_RATIO = "viewPanelRatio";

    private SashForm resultsSash;
    private Spreadsheet spreadsheet;
    private ViewValuePanel previewPane;
    private SpreadsheetValueController panelValueController;

    @Nullable
    private DBDAttributeBinding curAttribute;
    private int columnOrder = SWT.NONE;

    private final Map<SpreadsheetValueController, IValueEditorStandalone> openEditors = new HashMap<>();

    private SpreadsheetFindReplaceTarget findReplaceTarget;

    // UI modifiers
    private IThemeManager themeManager;
    private IPropertyChangeListener themeChangeListener;

    private Color backgroundAdded;
    private Color backgroundDeleted;
    private Color backgroundModified;
    private Color backgroundNormal;
    private Color backgroundOdd;
    private Color backgroundReadOnly;
    private Color foregroundDefault;
    private Color foregroundNull;
    private Font boldFont;

    private boolean showOddRows = true;
    private boolean showCelIcons = true;

    public SpreadsheetPresentation() {
        findReplaceTarget = new SpreadsheetFindReplaceTarget(this);

    }

    public Spreadsheet getSpreadsheet() {
        return spreadsheet;
    }

    @Nullable
    DBPDataSource getDataSource() {
        DBSDataContainer dataContainer = controller.getDataContainer();
        return dataContainer == null ? null : dataContainer.getDataSource();
    }

    @Override
    public void createPresentation(@NotNull IResultSetController controller, @NotNull Composite parent) {
        super.createPresentation(controller, parent);

        this.boldFont = UIUtils.makeBoldFont(parent.getFont());
        this.foregroundNull = parent.getShell().getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);

        {
            resultsSash = UIUtils.createPartDivider(controller.getSite().getPart(), parent,
                    SWT.HORIZONTAL | SWT.SMOOTH);
            resultsSash.setBackgroundMode(SWT.INHERIT_FORCE);
            resultsSash.setLayoutData(new GridData(GridData.FILL_BOTH));
            resultsSash.setSashWidth(5);
            //resultsSash.setBackground(resultsSash.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));

            this.spreadsheet = new Spreadsheet(resultsSash, SWT.MULTI | SWT.VIRTUAL | SWT.H_SCROLL | SWT.V_SCROLL,
                    controller.getSite(), this, new ContentProvider(), new GridLabelProvider());
            this.spreadsheet.setLayoutData(new GridData(GridData.FILL_BOTH));

            this.previewPane = new ViewValuePanel(controller, resultsSash) {
                @Override
                protected void hidePanel() {
                    togglePreview();
                }
            };

            final DBPPreferenceStore preferences = getPreferenceStore();
            int ratio = preferences.getInt(VIEW_PANEL_RATIO);
            boolean viewPanelVisible = preferences.getBoolean(VIEW_PANEL_VISIBLE);
            if (ratio <= 0) {
                ratio = 750;
            }
            resultsSash.setWeights(new int[] { ratio, 1000 - ratio });
            if (!viewPanelVisible) {
                resultsSash.setMaximizedControl(spreadsheet);
            }
            previewPane.addListener(SWT.Resize, new Listener() {
                @Override
                public void handleEvent(Event event) {
                    DBPDataSource dataSource = getDataSource();
                    if (dataSource != null) {
                        if (!resultsSash.isDisposed()) {
                            int[] weights = resultsSash.getWeights();
                            int ratio = weights[0];
                            DBeaverCore.getGlobalPreferenceStore().setValue(VIEW_PANEL_RATIO, ratio);
                        }
                    }
                }
            });
        }

        this.spreadsheet.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                fireSelectionChanged(new SpreadsheetSelectionImpl());
            }
        });
        this.spreadsheet.addCursorChangeListener(new Listener() {
            @Override
            public void handleEvent(Event event) {
                if (event.detail != SWT.DRAG && event.detail != SWT.DROP_DOWN) {
                    updateGridCursor((GridCell) event.data);
                }
            }
        });

        spreadsheet.addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent e) {
                SpreadsheetPresentation.this.controller.updateEditControls();
            }

            @Override
            public void focusLost(FocusEvent e) {
                SpreadsheetPresentation.this.controller.updateEditControls();
            }
        });

        this.themeManager = controller.getSite().getWorkbenchWindow().getWorkbench().getThemeManager();
        this.themeChangeListener = new IPropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent event) {
                if (event.getProperty().startsWith(ThemeConstants.RESULTS_PROP_PREFIX)) {
                    applyThemeSettings();
                }
            }
        };
        this.themeManager.addPropertyChangeListener(themeChangeListener);

        applyThemeSettings();

        this.spreadsheet.addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(DisposeEvent e) {
                dispose();
            }
        });

        trackPresentationControl();
        UIUtils.enableHostEditorKeyBindingsSupport(controller.getSite(), spreadsheet);
    }

    private void dispose() {
        closeEditors();
        clearMetaData();

        themeManager.removePropertyChangeListener(themeChangeListener);

        UIUtils.dispose(this.boldFont);
    }

    public void scrollToRow(@NotNull RowPosition position) {
        boolean recordMode = controller.isRecordMode();
        ResultSetRow curRow = controller.getCurrentRow();
        ResultSetModel model = controller.getModel();

        switch (position) {
        case FIRST:
            if (recordMode) {
                if (model.getRowCount() > 0) {
                    controller.setCurrentRow(model.getRow(0));
                } else {
                    controller.setCurrentRow(null);
                }
            } else {
                spreadsheet.shiftCursor(0, -spreadsheet.getItemCount(), false);
            }
            break;
        case PREVIOUS:
            if (recordMode && curRow != null && curRow.getVisualNumber() > 0) {
                controller.setCurrentRow(model.getRow(curRow.getVisualNumber() - 1));
            } else {
                spreadsheet.shiftCursor(0, -1, false);
            }
            break;
        case NEXT:
            if (recordMode && curRow != null && curRow.getVisualNumber() < model.getRowCount() - 1) {
                controller.setCurrentRow(model.getRow(curRow.getVisualNumber() + 1));
            } else {
                spreadsheet.shiftCursor(0, 1, false);
            }
            break;
        case LAST:
            if (recordMode && model.getRowCount() > 0) {
                controller.setCurrentRow(model.getRow(model.getRowCount() - 1));
            } else {
                spreadsheet.shiftCursor(0, spreadsheet.getItemCount(), false);
            }
            break;
        case CURRENT:
            if (curRow != null) {
                GridPos curPos = spreadsheet.getCursorPosition();
                GridCell newCell = spreadsheet.posToCell(new GridPos(curPos.col, curRow.getVisualNumber()));
                if (newCell != null) {
                    spreadsheet.setCursor(newCell, false);
                }
            }
            break;
        }
        if (controller.isRecordMode()) {
            // Update focus cell
            restoreState(curAttribute);
        }
        // Update controls
        controller.updateEditControls();
        controller.updateStatusMessage();

        if (recordMode) {
            // Refresh meta if we are in record mode
            refreshData(true, false);
        }
    }

    @Nullable
    @Override
    public DBDAttributeBinding getCurrentAttribute() {
        return curAttribute;
    }

    @Override
    public Point getCursorLocation() {
        GridPos focusPos = spreadsheet.getFocusPos();
        Rectangle columnBounds = spreadsheet.getColumnBounds(focusPos.col);
        if (columnBounds != null) {
            columnBounds.y += spreadsheet.getHeaderHeight();
            return new Point(columnBounds.x, columnBounds.y);
        }
        return super.getCursorLocation();
    }

    @Override
    public Object saveState() {
        return curAttribute;
    }

    @Override
    public void restoreState(Object state) {
        this.curAttribute = (DBDAttributeBinding) state;
        ResultSetRow curRow = controller.getCurrentRow();
        if (curRow != null && this.curAttribute != null) {
            GridCell cell = controller.isRecordMode() ? new GridCell(curRow, this.curAttribute)
                    : new GridCell(this.curAttribute, curRow);
            //spreadsheet.selectCell(cell);
            spreadsheet.setCursor(cell, false);
        }
    }

    private void updateGridCursor(GridCell cell) {
        boolean changed;
        Object newCol = cell == null ? null : cell.col;
        Object newRow = cell == null ? null : cell.row;
        ResultSetRow curRow = controller.getCurrentRow();
        if (!controller.isRecordMode()) {
            changed = curRow != newRow || curAttribute != newCol;
            if (newRow instanceof ResultSetRow && newCol instanceof DBDAttributeBinding) {
                curRow = (ResultSetRow) newRow;
                curAttribute = (DBDAttributeBinding) newCol;
            }
            controller.setCurrentRow(curRow);
        } else {
            changed = curAttribute != newRow;
            if (newRow instanceof DBDAttributeBinding) {
                curAttribute = (DBDAttributeBinding) newRow;
            }
        }
        if (changed) {
            ResultSetPropertyTester.firePropertyChange(ResultSetPropertyTester.PROP_CAN_MOVE);
            ResultSetPropertyTester.firePropertyChange(ResultSetPropertyTester.PROP_EDITABLE);
            spreadsheet.redrawGrid();
            previewValue(true);
        }
    }

    @Nullable
    public String copySelectionToString(boolean copyHeader, boolean copyRowNumbers, boolean cut, String delimiter,
            DBDDisplayFormat format) {
        if (delimiter == null) {
            delimiter = "\t";
        }
        String lineSeparator = GeneralUtils.getDefaultLineSeparator();
        List<Object> selectedColumns = spreadsheet.getColumnSelection();
        IGridLabelProvider labelProvider = spreadsheet.getLabelProvider();
        StringBuilder tdt = new StringBuilder();
        if (copyHeader) {
            if (copyRowNumbers) {
                tdt.append("#");
            }
            for (Object column : selectedColumns) {
                if (tdt.length() > 0) {
                    tdt.append(delimiter);
                }
                tdt.append(labelProvider.getText(column));
            }
            tdt.append(lineSeparator);
        }

        List<GridCell> selectedCells = spreadsheet.getCellSelection();

        GridCell prevCell = null;
        for (GridCell cell : selectedCells) {
            if (prevCell == null || cell.row != prevCell.row) {
                // Next row
                if (prevCell != null && prevCell.col != cell.col) {
                    // Fill empty row tail
                    int prevColIndex = selectedColumns.indexOf(prevCell.col);
                    for (int i = prevColIndex; i < selectedColumns.size() - 1; i++) {
                        tdt.append(delimiter);
                    }
                }
                if (prevCell != null) {
                    tdt.append(lineSeparator);
                }
                if (copyRowNumbers) {
                    tdt.append(labelProvider.getText(cell.row)).append(delimiter);
                }
            }
            if (prevCell != null && prevCell.col != cell.col) {
                int prevColIndex = selectedColumns.indexOf(prevCell.col);
                int curColIndex = selectedColumns.indexOf(cell.col);
                for (int i = prevColIndex; i < curColIndex; i++) {
                    tdt.append(delimiter);
                }
            }

            boolean recordMode = controller.isRecordMode();
            DBDAttributeBinding column = (DBDAttributeBinding) (!recordMode ? cell.col : cell.row);
            ResultSetRow row = (ResultSetRow) (!recordMode ? cell.row : cell.col);
            Object value = controller.getModel().getCellValue(column, row);
            String cellText = column.getValueRenderer().getValueDisplayString(column.getAttribute(), value, format);
            tdt.append(cellText);

            if (cut) {
                IValueController valueController = new SpreadsheetValueController(controller, column, row,
                        IValueController.EditType.NONE, null);
                if (!valueController.isReadOnly()) {
                    valueController.updateValue(BaseValueManager.makeNullValue(valueController));
                }
            }

            prevCell = cell;
        }

        return tdt.toString();
    }

    @Override
    public void pasteFromClipboard(boolean extended) {
        try {
            if (extended) {
                DBPDataSource dataSource = getDataSource();
                String strValue;
                Clipboard clipboard = new Clipboard(Display.getCurrent());
                try {
                    strValue = (String) clipboard.getContents(TextTransfer.getInstance());
                } finally {
                    clipboard.dispose();
                }
                if (CommonUtils.isEmpty(strValue)) {
                    return;
                }
                GridPos focusPos = spreadsheet.getFocusPos();
                int rowNum = focusPos.row;
                if (rowNum < 0 || rowNum >= spreadsheet.getItemCount()) {
                    return;
                }
                try (DBCSession session = DBUtils.openUtilSession(VoidProgressMonitor.INSTANCE, dataSource,
                        "Advanced paste")) {
                    for (String line : strValue.split("\n")) {
                        int colNum = focusPos.col;
                        Object rowElement = spreadsheet.getRowElement(rowNum);
                        for (String value : line.split("\t")) {
                            if (colNum >= spreadsheet.getColumnCount()) {
                                break;
                            }
                            Object colElement = spreadsheet.getColumnElement(colNum);
                            final DBDAttributeBinding attr = (DBDAttributeBinding) (controller.isRecordMode()
                                    ? rowElement
                                    : colElement);
                            final ResultSetRow row = (ResultSetRow) (controller.isRecordMode() ? colElement
                                    : rowElement);
                            if (controller.isAttributeReadOnly(attr)) {
                                continue;
                            }
                            Object newValue = attr.getValueHandler().getValueFromObject(session,
                                    attr.getAttribute(), value, true);
                            new SpreadsheetValueController(controller, attr, row, IValueController.EditType.NONE,
                                    null).updateValue(newValue);

                            colNum++;
                        }
                        rowNum++;
                        if (rowNum >= spreadsheet.getItemCount()) {
                            break;
                        }
                    }
                }

            } else {
                DBDAttributeBinding attr = getFocusAttribute();
                ResultSetRow row = controller.getCurrentRow();
                if (attr == null || row == null) {
                    return;
                }
                if (controller.isAttributeReadOnly(attr)) {
                    // No inline editors for readonly columns
                    return;
                }

                Object newValue = ResultSetUtils.getAttributeValueFromClipboard(attr);
                if (newValue == null) {
                    return;
                }
                new SpreadsheetValueController(controller, attr, row, IValueController.EditType.NONE, null)
                        .updateValue(newValue);
            }
        } catch (Exception e) {
            UIUtils.showErrorDialog(spreadsheet.getShell(), "Cannot replace cell value", null, e);
        }
    }

    @Override
    public Control getControl() {
        return spreadsheet;
    }

    @Override
    public void refreshData(boolean refreshMetadata, boolean append) {
        // Cache preferences
        DBPPreferenceStore preferenceStore = getPreferenceStore();
        showOddRows = preferenceStore.getBoolean(DBeaverPreferences.RESULT_SET_SHOW_ODD_ROWS);
        showCelIcons = preferenceStore.getBoolean(DBeaverPreferences.RESULT_SET_SHOW_CELL_ICONS);

        spreadsheet.setRedraw(false);
        try {
            spreadsheet.refreshData(refreshMetadata);
        } finally {
            spreadsheet.setRedraw(true);
        }
    }

    @Override
    public void formatData(boolean refreshData) {
        reorderLocally();
        spreadsheet.refreshData(false);
    }

    @Override
    public void clearMetaData() {
        this.curAttribute = null;
        this.columnOrder = SWT.NONE;
    }

    @Override
    public void updateValueView() {
        spreadsheet.redrawGrid();
        previewValue(false);
    }

    @Override
    public void fillToolbar(@NotNull IToolBarManager toolBar) {
        //        toolBar.insertAfter(PRES_TOOLS_BEGIN, ActionUtils.makeCommandContribution(
        //            controller.getSite(),
        //            SpreadsheetCommandHandler.CMD_TOGGLE_PREVIEW,
        //            CommandContributionItem.STYLE_CHECK));
    }

    @Override
    public void fillMenu(@NotNull IMenuManager menu) {
        menu.add(ActionUtils.makeCommandContribution(controller.getSite(),
                SpreadsheetCommandHandler.CMD_TOGGLE_PREVIEW, CommandContributionItem.STYLE_CHECK));
    }

    @Override
    public void changeMode(boolean recordMode) {
        ResultSetRow oldRow = controller.getCurrentRow();
        DBDAttributeBinding oldAttribute = this.curAttribute;
        int rowCount = controller.getModel().getRowCount();
        if (rowCount > 0) {
            // Fix row number if needed
            if (oldRow == null) {
                oldRow = controller.getModel().getRow(0);
            } else if (oldRow.getVisualNumber() >= rowCount) {
                oldRow = controller.getModel().getRow(rowCount - 1);
            }
        }
        if (oldAttribute == null && controller.getModel().getVisibleAttributeCount() > 0) {
            oldAttribute = controller.getModel().getVisibleAttribute(0);
        }

        this.columnOrder = recordMode ? SWT.DEFAULT : SWT.NONE;
        if (oldRow != null && oldAttribute != null) {
            if (!recordMode) {
                spreadsheet.setCursor(new GridCell(oldAttribute, oldRow), false);
            } else {
                spreadsheet.setCursor(new GridCell(oldRow, oldAttribute), false);
            }
        }
        spreadsheet.layout(true, true);
        previewValue(false);
        //controller.setCurrentRow(oldRow);
    }

    public void fillContextMenu(@NotNull IMenuManager manager, @Nullable Object colObject,
            @Nullable Object rowObject) {
        final DBDAttributeBinding attr = (DBDAttributeBinding) (controller.isRecordMode() ? rowObject : colObject);
        final ResultSetRow row = (ResultSetRow) (controller.isRecordMode() ? colObject : rowObject);
        controller.fillContextMenu(manager, attr, row);

        if (attr != null && row != null) {
            final List<Object> selectedColumns = spreadsheet.getColumnSelection();
            if (!controller.isRecordMode() && !selectedColumns.isEmpty()) {
                String hideTitle;
                if (selectedColumns.size() == 1) {
                    DBDAttributeBinding columnToHide = (DBDAttributeBinding) selectedColumns.get(0);
                    hideTitle = "Hide column '" + columnToHide.getName() + "'";
                } else {
                    hideTitle = "Hide selected columns (" + selectedColumns.size() + ")";
                }
                manager.insertAfter(IResultSetController.MENU_GROUP_EDIT, new Action(hideTitle) {
                    @Override
                    public void run() {
                        ResultSetModel model = controller.getModel();
                        if (selectedColumns.size() >= model.getVisibleAttributeCount()) {
                            UIUtils.showMessageBox(getControl().getShell(), "Hide columns",
                                    "Can't hide all result columns, at least one column must be visible",
                                    SWT.ERROR);
                        } else {
                            for (int i = 0, selectedColumnsSize = selectedColumns
                                    .size(); i < selectedColumnsSize; i++) {
                                model.setAttributeVisibility((DBDAttributeBinding) selectedColumns.get(i), false);
                            }
                            refreshData(true, false);
                        }
                    }
                });
            }
        }
    }

    ////////////////////////////////////////////////////////////
    // Value preview

    public boolean isPreviewVisible() {
        return resultsSash.getMaximizedControl() == null;
    }

    public void togglePreview() {
        spreadsheet.cancelInlineEditor();
        if (resultsSash.getMaximizedControl() == null) {
            resultsSash.setMaximizedControl(spreadsheet);
        } else {
            resultsSash.setMaximizedControl(null);
            previewValue(true);
            spreadsheet.updateScrollbars();

            if (curAttribute != null) {
                spreadsheet.showColumn(curAttribute);
            }
        }
        DBeaverCore.getGlobalPreferenceStore().setValue(VIEW_PANEL_VISIBLE, isPreviewVisible());

        // Refresh elements
        ICommandService commandService = controller.getSite().getService(ICommandService.class);
        if (commandService != null) {
            commandService.refreshElements(SpreadsheetCommandHandler.CMD_TOGGLE_PREVIEW, null);
        }
    }

    void previewValue(boolean savePrevious) {
        DBDAttributeBinding attr = getFocusAttribute();
        ResultSetRow row = getFocusRow();
        if (!isPreviewVisible()) {
            return;
        }
        if (attr == null || row == null) {
            previewPane.clearValue();
            return;
        }
        if (savePrevious) {
            // TODO: do smart save + dirty flag
            //            previewPane.saveValue();
        }
        if (panelValueController == null || panelValueController.getBinding() != attr) {
            panelValueController = new SpreadsheetValueController(controller, attr, row,
                    IValueController.EditType.PANEL, previewPane.getViewPlaceholder());
        } else {
            panelValueController.setCurRow(row);
        }
        previewPane.viewValue(panelValueController);
    }

    /////////////////////////////////////////////////
    // Edit

    private void closeEditors() {
        List<IValueEditorStandalone> editors = new ArrayList<>(openEditors.values());
        for (IValueEditorStandalone editor : editors) {
            if (editor.getControl() != null && !editor.getControl().isDisposed()) {
                editor.closeValueEditor();
            }
        }
        openEditors.clear();
    }

    @Override
    @Nullable
    public Control openValueEditor(final boolean inline) {
        // The control that will be the editor must be a child of the Table
        DBDAttributeBinding attr = getFocusAttribute();
        ResultSetRow row = getFocusRow();
        if (attr == null || row == null) {
            return null;
        }

        if (!inline) {
            for (Iterator<SpreadsheetValueController> iterator = openEditors.keySet().iterator(); iterator
                    .hasNext();) {
                SpreadsheetValueController valueController = iterator.next();
                if (attr == valueController.getBinding() && row == valueController.getCurRow()) {
                    IValueEditorStandalone editor = openEditors.get(valueController);
                    if (editor.getControl() != null && !editor.getControl().isDisposed()) {
                        editor.showValueEditor();
                        return null;
                    } else {
                        // Remove disposed editor from the list
                        iterator.remove();
                    }
                }
            }
        }

        //        if (controller.isAttributeReadOnly(attr) && inline) {
        //            // No inline editors for readonly columns
        //            return null;
        //        }

        Composite placeholder = null;
        if (inline) {
            if (controller.isReadOnly()) {
                return null;
            }
            spreadsheet.cancelInlineEditor();

            placeholder = new Composite(spreadsheet, SWT.NONE);
            placeholder.setFont(spreadsheet.getFont());
            placeholder.setLayout(new FillLayout());

            GridData gd = new GridData(GridData.FILL_BOTH);
            gd.horizontalIndent = 0;
            gd.verticalIndent = 0;
            gd.grabExcessHorizontalSpace = true;
            gd.grabExcessVerticalSpace = true;
            placeholder.setLayoutData(gd);

            controller.lockActionsByControl(placeholder);
        }

        SpreadsheetValueController valueController = new SpreadsheetValueController(controller, attr, row,
                inline ? IValueController.EditType.INLINE : IValueController.EditType.EDITOR, placeholder);

        IValueController.EditType[] supportedEditTypes = valueController.getValueManager().getSupportedEditTypes();
        if (supportedEditTypes.length == 0) {
            if (placeholder != null) {
                placeholder.dispose();
            }
            return null;
        }
        /*
                if (inline &&
        (!ArrayUtils.contains(supportedEditTypes, IValueController.EditType.INLINE) || controller.isAttributeReadOnly(attr)) &&
        ArrayUtils.contains(supportedEditTypes, IValueController.EditType.PANEL))
                {
        // Inline editor isn't supported but panel viewer is
        // Enable panel
        if (!isPreviewVisible()) {
            togglePreview();
        }
        placeholder.dispose();
            
        return null;
                }
        */

        final IValueEditor editor;
        try {
            editor = valueController.getValueManager().createEditor(valueController);
        } catch (Exception e) {
            UIUtils.showErrorDialog(spreadsheet.getShell(), "Cannot edit value", null, e);
            return null;
        }
        if (editor != null) {
            editor.createControl();
        }
        if (editor instanceof IValueEditorStandalone) {
            valueController.registerEditor((IValueEditorStandalone) editor);
            // show dialog in separate job to avoid block
            new UIJob("Open separate editor") {
                @Override
                public IStatus runInUIThread(IProgressMonitor monitor) {
                    ((IValueEditorStandalone) editor).showValueEditor();
                    return Status.OK_STATUS;
                }
            }.schedule();
            //((IValueEditorStandalone)editor).showValueEditor();
        } else {
            // Set editable value
            if (editor != null) {
                try {
                    editor.primeEditorValue(valueController.getValue());
                } catch (DBException e) {
                    log.error(e);
                }
                editor.setDirty(false);
            }
        }
        if (inline) {
            if (editor != null) {
                spreadsheet.showCellEditor(placeholder);
                return editor.getControl();
            } else {
                // No editor was created so just drop placeholder
                placeholder.dispose();
                // Probably we can just show preview panel
                if (ArrayUtils.contains(supportedEditTypes, IValueController.EditType.PANEL)) {
                    // Inline editor isn't supported but panel viewer is
                    // Enable panel
                    if (!isPreviewVisible()) {
                        togglePreview();
                    }
                    return null;
                }
            }
        }
        return null;
    }

    public void resetCellValue(@NotNull Object colElement, @NotNull Object rowElement, boolean delete) {
        boolean recordMode = controller.isRecordMode();
        final DBDAttributeBinding attr = (DBDAttributeBinding) (recordMode ? rowElement : colElement);
        final ResultSetRow row = (ResultSetRow) (recordMode ? colElement : rowElement);
        controller.getModel().resetCellValue(attr, row);
        updateValueView();
    }

    ///////////////////////////////////////////////
    // Links

    public void navigateLink(@NotNull GridCell cell, final int state) {
        boolean recordMode = controller.isRecordMode();
        final DBDAttributeBinding attr = (DBDAttributeBinding) (recordMode ? cell.row : cell.col);
        final ResultSetRow row = (ResultSetRow) (recordMode ? cell.col : cell.row);

        Object value = controller.getModel().getCellValue(attr, row);
        if (DBUtils.isNullValue(value)) {
            log.warn("Can't navigate to NULL value");
            return;
        }

        new AbstractJob("Navigate association") {
            @Override
            protected IStatus run(DBRProgressMonitor monitor) {
                try {
                    boolean ctrlPressed = (state & SWT.CTRL) == SWT.CTRL;
                    controller.navigateAssociation(monitor, attr, row, ctrlPressed);
                } catch (DBException e) {
                    return GeneralUtils.makeExceptionStatus(e);
                }
                return Status.OK_STATUS;
            }
        }.schedule();
    }

    ///////////////////////////////////////////////
    // Themes

    private void applyThemeSettings() {
        ITheme currentTheme = themeManager.getCurrentTheme();
        Font rsFont = currentTheme.getFontRegistry().get(ThemeConstants.FONT_SQL_RESULT_SET);
        if (rsFont != null) {
            this.spreadsheet.setFont(rsFont);
        }
        Color previewBack = currentTheme.getColorRegistry().get(ThemeConstants.COLOR_SQL_RESULT_SET_PREVIEW_BACK);
        if (previewBack != null) {
            //            this.previewPane.getViewPlaceholder().setBackground(previewBack);
            //            for (Control control : this.previewPane.getViewPlaceholder().getChildren()) {
            //                control.setBackground(previewBack);
            //            }
        }
        //this.foregroundDefault = currentTheme.getColorRegistry().get(ThemeConstants.COLOR_SQL_RESULT_CELL_FORE);
        this.backgroundAdded = currentTheme.getColorRegistry().get(ThemeConstants.COLOR_SQL_RESULT_CELL_NEW_BACK);
        this.backgroundDeleted = currentTheme.getColorRegistry()
                .get(ThemeConstants.COLOR_SQL_RESULT_CELL_DELETED_BACK);
        this.backgroundModified = currentTheme.getColorRegistry()
                .get(ThemeConstants.COLOR_SQL_RESULT_CELL_MODIFIED_BACK);
        this.backgroundOdd = currentTheme.getColorRegistry().get(ThemeConstants.COLOR_SQL_RESULT_CELL_ODD_BACK);
        this.backgroundReadOnly = currentTheme.getColorRegistry()
                .get(ThemeConstants.COLOR_SQL_RESULT_CELL_READ_ONLY);

        this.spreadsheet.recalculateSizes();
    }

    ///////////////////////////////////////////////
    // Ordering

    private boolean supportsDataFilter() {
        DBSDataContainer dataContainer = controller.getDataContainer();
        return dataContainer != null && (dataContainer.getSupportedFeatures()
                & DBSDataContainer.DATA_FILTER) == DBSDataContainer.DATA_FILTER;
    }

    private void reorderLocally() {
        controller.rejectChanges();
        controller.getModel().resetOrdering();
        refreshData(false, false);
    }

    public void changeSorting(Object columnElement, final int state) {
        if (columnElement == null) {
            columnOrder = columnOrder == SWT.DEFAULT ? SWT.DOWN : (columnOrder == SWT.DOWN ? SWT.UP : SWT.DEFAULT);
            spreadsheet.refreshData(false);
            spreadsheet.redrawGrid();
            return;
        }
        DBDDataFilter dataFilter = controller.getModel().getDataFilter();
        boolean ctrlPressed = (state & SWT.CTRL) == SWT.CTRL;
        boolean altPressed = (state & SWT.ALT) == SWT.ALT;
        if (ctrlPressed) {
            dataFilter.resetOrderBy();
        }
        DBDAttributeBinding metaColumn = (DBDAttributeBinding) columnElement;
        DBDAttributeConstraint constraint = dataFilter.getConstraint(metaColumn);
        assert constraint != null;
        //int newSort;
        if (constraint.getOrderPosition() == 0) {
            if (ResultSetUtils.isServerSideFiltering(controller) && supportsDataFilter()) {
                if (ConfirmationDialog.showConfirmDialogEx(spreadsheet.getShell(),
                        DBeaverPreferences.CONFIRM_ORDER_RESULTSET, ConfirmationDialog.QUESTION,
                        ConfirmationDialog.WARNING, metaColumn.getName()) != IDialogConstants.YES_ID) {
                    return;
                }
            }
            constraint.setOrderPosition(dataFilter.getMaxOrderingPosition() + 1);
            constraint.setOrderDescending(altPressed);
        } else if (!constraint.isOrderDescending()) {
            constraint.setOrderDescending(true);
        } else {
            for (DBDAttributeConstraint con2 : dataFilter.getConstraints()) {
                if (con2.getOrderPosition() > constraint.getOrderPosition()) {
                    con2.setOrderPosition(con2.getOrderPosition() - 1);
                }
            }
            constraint.setOrderPosition(0);
            constraint.setOrderDescending(false);
        }

        if (!ResultSetUtils.isServerSideFiltering(controller) || !controller.isHasMoreData()) {
            reorderLocally();
        } else {
            controller.refreshData(null);
        }
    }

    ///////////////////////////////////////////////
    // Misc

    public DBPPreferenceStore getPreferenceStore() {
        return controller.getPreferenceStore();
    }

    @Override
    public Object getAdapter(Class adapter) {
        if (adapter == IPropertySheetPage.class) {
            // Show cell properties
            PropertyPageStandard page = new PropertyPageStandard();
            page.setPropertySourceProvider(new IPropertySourceProvider() {
                @Nullable
                @Override
                public IPropertySource getPropertySource(Object object) {
                    if (object instanceof GridCell) {
                        GridCell cell = (GridCell) object;
                        boolean recordMode = controller.isRecordMode();
                        final DBDAttributeBinding attr = (DBDAttributeBinding) (recordMode ? cell.row : cell.col);
                        final ResultSetRow row = (ResultSetRow) (recordMode ? cell.col : cell.row);
                        final SpreadsheetValueController valueController = new SpreadsheetValueController(
                                controller, attr, row, IValueController.EditType.NONE, null);
                        PropertyCollector props = new PropertyCollector(valueController.getBinding().getAttribute(),
                                false);
                        props.collectProperties();
                        valueController.getValueManager().contributeProperties(props, valueController);
                        return new PropertySourceDelegate(props);
                    }
                    return null;
                }
            });
            return page;
        } else if (adapter == IFindReplaceTarget.class) {
            return findReplaceTarget;
        }
        return null;
    }

    @Nullable
    public DBDAttributeBinding getFocusAttribute() {
        return controller.isRecordMode() ? (DBDAttributeBinding) spreadsheet.getFocusRowElement()
                : (DBDAttributeBinding) spreadsheet.getFocusColumnElement();
    }

    @Nullable
    public ResultSetRow getFocusRow() {
        return controller.isRecordMode() ? (ResultSetRow) spreadsheet.getFocusColumnElement()
                : (ResultSetRow) spreadsheet.getFocusRowElement();
    }

    ///////////////////////////////////////////////
    // Selection provider

    @Override
    public IResultSetSelection getSelection() {
        return new SpreadsheetSelectionImpl();
    }

    @Override
    public void setSelection(ISelection selection) {
        if (selection instanceof IResultSetSelection
                && ((IResultSetSelection) selection).getController() == getController()) {
            // It may occur on simple focus change so we won't do anything
            return;
        }
        spreadsheet.deselectAll();
        if (!selection.isEmpty() && selection instanceof IStructuredSelection) {
            List<GridPos> cellSelection = new ArrayList<>();
            for (Iterator iter = ((IStructuredSelection) selection).iterator(); iter.hasNext();) {
                Object cell = iter.next();
                if (cell instanceof GridPos) {
                    cellSelection.add((GridPos) cell);
                } else {
                    log.warn("Bad selection object: " + cell);
                }
            }
            spreadsheet.selectCells(cellSelection);
            spreadsheet.showSelection();
        }
        fireSelectionChanged(selection);
    }

    private class SpreadsheetSelectionImpl implements IResultSetSelection {

        @Nullable
        @Override
        public GridPos getFirstElement() {
            Collection<GridPos> ssSelection = spreadsheet.getSelection();
            if (ssSelection.isEmpty()) {
                return null;
            }
            return ssSelection.iterator().next();
        }

        @Override
        public Iterator<GridPos> iterator() {
            return spreadsheet.getSelection().iterator();
        }

        @Override
        public int size() {
            return spreadsheet.getSelection().size();
        }

        @Override
        public Object[] toArray() {
            return spreadsheet.getSelection().toArray();
        }

        @Override
        public List toList() {
            return new ArrayList<>(spreadsheet.getSelection());
        }

        @Override
        public boolean isEmpty() {
            return spreadsheet.getSelection().isEmpty();
        }

        @NotNull
        @Override
        public IResultSetController getController() {
            return SpreadsheetPresentation.this.getController();
        }

        @NotNull
        @Override
        public Collection<DBDAttributeBinding> getSelectedAttributes() {
            if (controller.isRecordMode()) {
                List<DBDAttributeBinding> attrs = new ArrayList<>();
                for (Integer row : spreadsheet.getRowSelection()) {
                    attrs.add(controller.getModel().getVisibleAttribute(row));
                }
                return attrs;
            } else {
                List<DBDAttributeBinding> attrs = new ArrayList<>();
                for (Object row : spreadsheet.getColumnSelection()) {
                    attrs.add((DBDAttributeBinding) row);
                }
                return attrs;
            }
        }

        @NotNull
        @Override
        public Collection<ResultSetRow> getSelectedRows() {
            if (controller.isRecordMode()) {
                ResultSetRow currentRow = controller.getCurrentRow();
                if (currentRow == null) {
                    return Collections.emptyList();
                }
                return Collections.singletonList(currentRow);
            } else {
                List<ResultSetRow> rows = new ArrayList<>();
                for (Integer row : spreadsheet.getRowSelection()) {
                    rows.add(controller.getModel().getRow(row));
                }
                return rows;
            }
        }
    }

    private class ContentProvider implements IGridContentProvider {

        @NotNull
        @Override
        public Object[] getElements(boolean horizontal) {
            boolean recordMode = controller.isRecordMode();
            ResultSetModel model = controller.getModel();
            if (horizontal) {
                // columns
                if (!recordMode) {
                    return model.getVisibleAttributes().toArray();
                } else {
                    Object curRow = controller.getCurrentRow();
                    return curRow == null ? new Object[0] : new Object[] { curRow };
                }
            } else {
                // rows
                if (!recordMode) {
                    return model.getAllRows().toArray();
                } else {
                    DBDAttributeBinding[] columns = model.getVisibleAttributes()
                            .toArray(new DBDAttributeBinding[model.getVisibleAttributeCount()]);
                    if (columnOrder != SWT.NONE && columnOrder != SWT.DEFAULT) {
                        Arrays.sort(columns, new Comparator<DBDAttributeBinding>() {
                            @Override
                            public int compare(DBDAttributeBinding o1, DBDAttributeBinding o2) {
                                return o1.getName().compareTo(o2.getName()) * (columnOrder == SWT.DOWN ? 1 : -1);
                            }
                        });
                    }
                    return columns;
                }
            }
        }

        @Nullable
        @Override
        public Object[] getChildren(Object element) {
            if (element instanceof DBDAttributeBinding) {
                DBDAttributeBinding binding = (DBDAttributeBinding) element;
                switch (binding.getDataKind()) {
                case ARRAY:
                    if (controller.isRecordMode()) {
                        ResultSetRow curRow = controller.getCurrentRow();
                        if (curRow != null) {
                            Object value = controller.getModel().getCellValue(binding, curRow);
                            if (value instanceof DBDCollection) {
                                return curRow.getCollectionData(binding, ((DBDCollection) value)).getElements();
                            }
                        }
                        return null;
                    }
                case STRUCT:
                case DOCUMENT:
                case ANY:
                    final List<DBDAttributeBinding> children = controller.getModel().getVisibleAttributes(binding);
                    if (children != null) {
                        return children.toArray();
                    }
                    break;
                }
            }

            return null;
        }

        @Override
        public int getSortOrder(@Nullable Object column) {
            if (column instanceof DBDAttributeBinding) {
                DBDAttributeBinding binding = (DBDAttributeBinding) column;
                if (!binding.hasNestedBindings()) {
                    DBDAttributeConstraint co = controller.getModel().getDataFilter().getConstraint(binding);
                    if (co != null && co.getOrderPosition() > 0) {
                        return co.isOrderDescending() ? SWT.UP : SWT.DOWN;
                    }
                    return SWT.DEFAULT;
                }
            } else if (column == null && controller.isRecordMode()) {
                // Columns order in record mode
                return columnOrder;
            }
            return SWT.NONE;
        }

        @Override
        public ElementState getDefaultState(@NotNull Object element) {
            if (element instanceof DBDAttributeBinding) {
                DBDAttributeBinding binding = (DBDAttributeBinding) element;
                switch (binding.getAttribute().getDataKind()) {
                case STRUCT:
                case DOCUMENT:
                    return ElementState.EXPANDED;
                case ARRAY:
                    ResultSetRow curRow = controller.getCurrentRow();
                    if (curRow != null) {
                        Object cellValue = controller.getModel().getCellValue(binding, curRow);
                        if (cellValue instanceof DBDCollection && ((DBDCollection) cellValue).getItemCount() < 3) {
                            return ElementState.EXPANDED;
                        }
                    }
                    return ElementState.COLLAPSED;
                default:
                    break;
                }
            }
            return ElementState.NONE;
        }

        @Override
        public int getCellState(Object colElement, Object rowElement) {
            int state = STATE_NONE;
            boolean recordMode = controller.isRecordMode();
            DBDAttributeBinding attr = (DBDAttributeBinding) (recordMode ? rowElement : colElement);
            ResultSetRow row = (ResultSetRow) (recordMode ? colElement : rowElement);
            Object value = controller.getModel().getCellValue(attr, row);
            if (!CommonUtils.isEmpty(attr.getReferrers()) && !DBUtils.isNullValue(value)) {
                state |= STATE_LINK;
            }
            return state;
        }

        @Override
        public void dispose() {
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        }

        @Nullable
        @Override
        public Object getCellValue(Object colElement, Object rowElement, boolean formatString) {
            DBDAttributeBinding attr = (DBDAttributeBinding) (rowElement instanceof DBDAttributeBinding ? rowElement
                    : colElement);
            ResultSetRow row = (ResultSetRow) (colElement instanceof ResultSetRow ? colElement : rowElement);
            int rowNum = row.getVisualNumber();
            Object value = controller.getModel().getCellValue(attr, row);

            boolean recordMode = controller.isRecordMode();
            if (rowNum > 0 && rowNum == controller.getModel().getRowCount() - 1
                    && controller.getPreferenceStore()
                            .getBoolean(DBeaverPreferences.RESULT_SET_AUTO_FETCH_NEXT_SEGMENT)
                    && (recordMode || spreadsheet.isRowVisible(rowNum)) && controller.isHasMoreData()) {
                controller.readNextSegment();
            }

            if (formatString) {
                if (recordMode) {
                    if (attr.getDataKind() == DBPDataKind.ARRAY && value instanceof DBDCollection) {
                        return "[" + ((DBDCollection) value).getItemCount() + "]";
                    } else if (attr.getDataKind() == DBPDataKind.STRUCT && value instanceof DBDComposite) {
                        return "[" + ((DBDComposite) value).getDataType().getName() + "]";
                    }
                }
                return attr.getValueRenderer().getValueDisplayString(attr.getAttribute(), value,
                        DBDDisplayFormat.UI);
            } else {
                return value;
            }
        }

        @Nullable
        @Override
        public DBPImage getCellImage(Object colElement, Object rowElement) {
            if (!showCelIcons) {
                return null;
            }
            Object cellValue = getCellValue(colElement, rowElement, false);
            if (cellValue instanceof DBDContent || cellValue instanceof DBDReference) {
                DBDAttributeBinding attr = (DBDAttributeBinding) (controller.isRecordMode() ? rowElement
                        : colElement);
                return DBUtils.getTypeImage(attr.getMetaAttribute());
            } else {
                return null;
            }
        }

        @NotNull
        @Override
        public String getCellText(Object colElement, Object rowElement) {
            return String.valueOf(getCellValue(colElement, rowElement, true));
        }

        @Nullable
        @Override
        public Color getCellForeground(Object colElement, Object rowElement) {
            ResultSetRow row = (ResultSetRow) (!controller.isRecordMode() ? rowElement : colElement);
            if (row.foreground != null) {
                return row.foreground;
            }

            Object value = getCellValue(colElement, rowElement, false);
            if (DBUtils.isNullValue(value)) {
                return foregroundNull;
            } else {
                if (foregroundDefault == null) {
                    foregroundDefault = controller.getDefaultForeground();
                }
                return foregroundDefault;
            }
        }

        @Nullable
        @Override
        public Color getCellBackground(Object colElement, Object rowElement) {
            boolean recordMode = controller.isRecordMode();
            ResultSetRow row = (ResultSetRow) (!recordMode ? rowElement : colElement);
            DBDAttributeBinding attribute = (DBDAttributeBinding) (!recordMode ? colElement : rowElement);
            boolean odd = row.getVisualNumber() % 2 == 0;
            if (row.background != null) {
                return row.background;
            }
            if (row.getState() == ResultSetRow.STATE_ADDED) {
                return backgroundAdded;
            }
            if (row.getState() == ResultSetRow.STATE_REMOVED) {
                return backgroundDeleted;
            }
            if (row.changes != null && row.changes.containsKey(attribute)) {
                return backgroundModified;
            }
            if (attribute.getValueHandler() instanceof DBDValueHandlerComposite) {
                return backgroundReadOnly;
            }
            if (!recordMode && odd && showOddRows) {
                return backgroundOdd;
            }

            if (backgroundNormal == null) {
                backgroundNormal = controller.getDefaultBackground();
            }
            return backgroundNormal;
        }

        @Override
        public void resetColors() {
            backgroundNormal = null;
            foregroundDefault = null;
        }
    }

    private class GridLabelProvider implements IGridLabelProvider {
        @Nullable
        @Override
        public Image getImage(Object element) {
            if (element instanceof DBDAttributeBinding/* && (!isRecordMode() || !model.isDynamicMetadata())*/) {
                return DBeaverIcons
                        .getImage(DBUtils.getTypeImage(((DBDAttributeBinding) element).getMetaAttribute()));
            }
            return null;
        }

        @Nullable
        @Override
        public Color getForeground(Object element) {
            if (element == null) {
                if (foregroundDefault == null) {
                    foregroundDefault = controller.getDefaultForeground();
                }
                return foregroundDefault;
            }
            return null;
        }

        @Nullable
        @Override
        public Color getBackground(Object element) {
            if (backgroundNormal == null) {
                backgroundNormal = controller.getDefaultBackground();
            }
            if (element == null) {
                return backgroundNormal;
            }

            /*
                        ResultSetRow row = (ResultSetRow) (!recordMode ?  element : curRow);
                        boolean odd = row != null && row.getVisualNumber() % 2 == 0;
                        if (!recordMode && odd && showOddRows) {
            return backgroundOdd;
                        }
                        return backgroundNormal;
            */
            return null;
        }

        @NotNull
        @Override
        public String getText(Object element) {
            if (element instanceof DBDAttributeBinding) {
                DBDAttributeBinding attributeBinding = (DBDAttributeBinding) element;
                if (CommonUtils.isEmpty(attributeBinding.getLabel())) {
                    return attributeBinding.getName();
                } else {
                    return attributeBinding.getLabel();
                }
            } else {
                if (!controller.isRecordMode()) {
                    return String.valueOf(((ResultSetRow) element).getVisualNumber() + 1);
                } else {
                    return CoreMessages.controls_resultset_viewer_value;
                }
            }
        }

        @Nullable
        @Override
        public Font getFont(Object element) {
            if (element instanceof DBDAttributeBinding) {
                DBDAttributeBinding attributeBinding = (DBDAttributeBinding) element;
                DBDAttributeConstraint constraint = controller.getModel().getDataFilter()
                        .getConstraint(attributeBinding);
                if (constraint != null && constraint.hasFilter()) {
                    return boldFont;
                }
            }
            return null;
        }

        @Nullable
        @Override
        public String getTooltip(Object element) {
            if (element instanceof DBDAttributeBinding) {
                DBDAttributeBinding attributeBinding = (DBDAttributeBinding) element;
                final String name = attributeBinding.getName();
                final String typeName = DBUtils.getFullTypeName(attributeBinding.getAttribute());
                final String description = attributeBinding.getDescription();
                return CommonUtils.isEmpty(description) ? name + ": " + typeName
                        : name + ": " + typeName + "\n" + description;
            }
            return null;
        }
    }

    /////////////////////////////
    // Value controller

    public class SpreadsheetValueController extends ResultSetValueController implements IMultiController {

        public SpreadsheetValueController(@NotNull IResultSetController controller,
                @NotNull DBDAttributeBinding binding, @NotNull ResultSetRow row, @NotNull EditType editType,
                @Nullable Composite inlinePlaceholder) {
            super(controller, binding, row, editType, inlinePlaceholder);
        }

        @Override
        public Object getValue() {
            return spreadsheet.getContentProvider().getCellValue(curRow, binding, false);
        }

        @Nullable
        @Override
        public org.eclipse.jface.action.IContributionManager getEditBar() {
            return isPreviewVisible() ? previewPane.getToolBar() : null;
        }

        @Override
        public void closeInlineEditor() {
            spreadsheet.cancelInlineEditor();
        }

        @Override
        public void nextInlineEditor(boolean next) {
            spreadsheet.cancelInlineEditor();
            int colOffset = next ? 1 : -1;
            int rowOffset = 0;
            //final int rowCount = spreadsheet.getItemCount();
            final int colCount = spreadsheet.getColumnCount();
            final GridPos curPosition = spreadsheet.getCursorPosition();
            if (colOffset > 0 && curPosition.col + colOffset >= colCount) {
                colOffset = -colCount;
                rowOffset = 1;
            } else if (colOffset < 0 && curPosition.col + colOffset < 0) {
                colOffset = colCount;
                rowOffset = -1;
            }
            spreadsheet.shiftCursor(colOffset, rowOffset, false);
            openValueEditor(true);
        }

        public void registerEditor(IValueEditorStandalone editor) {
            openEditors.put(this, editor);
        }

        public void unregisterEditor(IValueEditorStandalone editor) {
            openEditors.remove(this);
        }

    }

}