/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* Licensed 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 org.jkiss.dbeaver.ui.controls.resultset.spreadsheet;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.Accessible;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.accessibility.AccessibleListener;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.IWorkbenchPartSite;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBeaverPreferences;
import org.jkiss.dbeaver.core.DBeaverUI;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.ui.controls.lightgrid.*;
/**
* ResultSetControl
*/
public class Spreadsheet extends LightGrid implements Listener {
//static final Log log = Log.getLog(Spreadsheet.class);
public enum DoubleClickBehavior {
NONE,
EDITOR,
INLINE_EDITOR
}
public static final int MAX_DEF_COLUMN_WIDTH = 300;
public static final int MAX_INLINE_EDIT_WITH = 300;
@NotNull
private final SpreadsheetCellEditor tableEditor;
@NotNull
private final IWorkbenchPartSite site;
@NotNull
private final SpreadsheetPresentation presentation;
@NotNull
private final IGridContentProvider contentProvider;
@NotNull
private final IGridLabelProvider labelProvider;
private Clipboard clipboard;
public Spreadsheet(
@NotNull final Composite parent,
final int style,
@NotNull final IWorkbenchPartSite site,
@NotNull final SpreadsheetPresentation presentation,
@NotNull final IGridContentProvider contentProvider,
@NotNull final IGridLabelProvider labelProvider)
{
super(parent, style);
GridLayout layout = new GridLayout(1, true);
layout.numColumns = 1;
layout.makeColumnsEqualWidth = false;
layout.marginWidth = 0;
layout.marginHeight = 0;
this.setLayout(layout);
this.site = site;
this.presentation = presentation;
this.contentProvider = contentProvider;
this.labelProvider = labelProvider;
this.clipboard = new Clipboard(getDisplay());
super.setRowHeaderVisible(true);
super.setLinesVisible(true);
super.setHeaderVisible(true);
super.setMaxColumnDefWidth(MAX_DEF_COLUMN_WIDTH);
super.addListener(SWT.MouseDoubleClick, this);
super.addListener(SWT.MouseDown, this);
super.addListener(SWT.KeyDown, this);
super.addListener(SWT.KeyUp, this);
super.addListener(LightGrid.Event_ChangeSort, this);
super.addListener(LightGrid.Event_NavigateLink, this);
tableEditor = new SpreadsheetCellEditor(this);
tableEditor.horizontalAlignment = SWT.LEFT;
tableEditor.verticalAlignment = SWT.TOP;
tableEditor.grabHorizontal = true;
tableEditor.grabVertical = true;
tableEditor.minimumWidth = 50;
hookContextMenu();
hookAccessibility();
{
super.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
if (clipboard != null && !clipboard.isDisposed()) {
clipboard.dispose();
}
}
});
}
}
@NotNull
public SpreadsheetPresentation getPresentation() {
return presentation;
}
public Clipboard getClipboard()
{
return clipboard;
}
/**
* Returns current cursor position
* Note: returned object is not immutable and will be changed if user will change focus cell
* @return cursor position.
*/
public GridPos getCursorPosition()
{
if (super.isDisposed()) {
return new GridPos(-1, -1);
}
return super.getFocusPos();
}
@Nullable
public GridCell getCursorCell()
{
if (super.isDisposed()) {
return null;
}
return super.getFocusCell();
}
public boolean shiftCursor(int xOffset, int yOffset, boolean keepSelection)
{
if (xOffset == 0 && yOffset == 0) {
return false;
}
GridPos curPos = getCursorPosition();
if (curPos == null) {
return false;
}
GridPos newPos = new GridPos(curPos.col, curPos.row);
// Move row
if (yOffset != 0) {
int newRow = curPos.row + yOffset;
if (newRow < 0) {
newRow = 0;
}
if (newRow >= getItemCount()) {
newRow = getItemCount() - 1;
}
newPos.row = newRow;
}
// Move column
if (xOffset != 0) {
int newCol = curPos.col + xOffset;
if (newCol < 0) {
newCol = 0;
}
if (newCol >= getColumnCount()) {
newCol = getColumnCount() - 1;
}
newPos.col = newCol;
}
GridCell newCell = posToCell(newPos);
if (newCell != null) {
setCursor(newCell, keepSelection);
}
return true;
}
public void setCursor(@NotNull GridCell cell, boolean keepSelection)
{
Event selectionEvent = new Event();
// Move row
selectionEvent.data = cell;
GridPos pos = cellToPos(cell);
if (pos.row >= 0) {
super.setFocusItem(pos.row);
super.showItem(pos.row);
}
// Move column
if (pos.col >= 0) {
super.setFocusColumn(pos.col);
super.showColumn(pos.col);
}
if (!keepSelection) {
super.deselectAll();
}
super.selectCell(pos);
// Change selection event
selectionEvent.data = cell;
notifyListeners(SWT.Selection, selectionEvent);
}
public void addCursorChangeListener(Listener listener)
{
super.addListener(SWT.Selection, listener);
}
@Override
public void handleEvent(final Event event)
{
switch (event.type) {
// case SWT.KeyUp:
case SWT.KeyDown:
boolean ctrlPressed = ((event.stateMask & SWT.CTRL) != 0);
if (!ctrlPressed &&
(event.keyCode == SWT.CR ||
(event.keyCode >= SWT.KEYPAD_0 && event.keyCode <= SWT.KEYPAD_9) ||
(event.keyCode >= 'a' && event.keyCode <= 'z') ||
(event.keyCode >= '0' && event.keyCode <= '9')))
{
Control editorControl = tableEditor.getEditor();
if (editorControl == null || editorControl.isDisposed()) {
editorControl = presentation.openValueEditor(true);
}
final SpreadsheetPresentation presentation = getPresentation();
final DBDAttributeBinding attribute = presentation.getCurrentAttribute();
if (editorControl != null && attribute != null && !presentation.getController().isAttributeReadOnly(attribute) && event.keyCode != SWT.CR) {
if (!editorControl.isDisposed()) {
// We used to forward key even to control but it worked poorly.
// So let's just insert first letter (it will remove old value which must be selected for inline controls)
String strValue = String.valueOf((char)event.keyCode);
if ((event.stateMask & SWT.SHIFT) != 0) {
strValue = strValue.toUpperCase();
}
if (editorControl instanceof Text) {
((Text) editorControl).insert(strValue);
} else if (editorControl instanceof StyledText) {
((StyledText) editorControl).insert(strValue);
}
/*
// Set editor value
// Forward the same key event to just created control
final Event kdEvent = new Event();
kdEvent.type = event.type;
kdEvent.character = event.character;
kdEvent.keyCode = event.keyCode;
editorControl.setFocus();
editorControl.getDisplay().post(kdEvent);
*/
}
}
}
break;
case SWT.MouseDoubleClick:
if (event.button != 1) {
return;
}
GridPos pos = super.getCell(new Point(event.x, event.y));
GridPos focusPos = super.getFocusPos();
if (pos != null && focusPos != null && pos.equals(super.getFocusPos())) {
DoubleClickBehavior doubleClickBehavior = DoubleClickBehavior.valueOf(
presentation.getPreferenceStore().getString(DBeaverPreferences.RESULT_SET_DOUBLE_CLICK));
switch (doubleClickBehavior) {
case NONE:
return;
case EDITOR:
presentation.openValueEditor(false);
break;
case INLINE_EDITOR:
presentation.openValueEditor(true);
break;
}
}
break;
case SWT.MouseDown:
if (event.button == 2) {
// presentation.openValueEditor(true);
}
break;
case LightGrid.Event_ChangeSort:
presentation.changeSorting(event.data, event.stateMask);
break;
case LightGrid.Event_NavigateLink:
// Perform navigation async because it may change grid content and
// we don't want to mess current grid state
DBeaverUI.asyncExec(new Runnable() {
@Override
public void run() {
presentation.navigateLink((GridCell) event.data, event.stateMask);
}
});
break;
}
}
@Override
public void refreshData(boolean refreshColumns, boolean keepState) {
cancelInlineEditor();
super.refreshData(refreshColumns, keepState);
super.redraw();
}
private void hookContextMenu()
{
MenuManager menuMgr = new MenuManager();
Menu menu = menuMgr.createContextMenu(this);
menuMgr.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager manager)
{
// Let controller to provide it's own menu items
GridPos focusPos = getFocusPos();
presentation.fillContextMenu(
manager, focusPos.col >= 0 && focusPos.col < columnElements.length ? columnElements[focusPos.col] : null,
focusPos.row >= 0 && focusPos.row < rowElements.length ? rowElements[focusPos.row] : null
);
}
});
menuMgr.setRemoveAllWhenShown(true);
super.setMenu(menu);
site.registerContextMenu(menuMgr, null);
}
public void cancelInlineEditor()
{
Control oldEditor = tableEditor.getEditor();
if (oldEditor != null) {
oldEditor.dispose();
tableEditor.setEditor(null);
this.setFocus();
}
}
@NotNull
@Override
public IGridContentProvider getContentProvider()
{
return contentProvider;
}
@NotNull
@Override
public IGridLabelProvider getLabelProvider()
{
return labelProvider;
}
public void redrawGrid()
{
Rectangle bounds = super.getBounds();
super.redraw(bounds.x, bounds.y, bounds.width, bounds.height, true);
}
public boolean isRowVisible(int rowNum)
{
return rowNum >= super.getTopIndex() && rowNum <= super.getBottomIndex();
}
public void showCellEditor(Composite editor)
{
int minHeight, minWidth;
Point editorSize = editor.computeSize(SWT.DEFAULT, SWT.DEFAULT);
minHeight = editorSize.y;
minWidth = editorSize.x;
if (minWidth > MAX_INLINE_EDIT_WITH) {
minWidth = MAX_INLINE_EDIT_WITH;
}
tableEditor.minimumHeight = minHeight;// + placeholder.getBorderWidth() * 2;//placeholder.getBounds().height;
tableEditor.minimumWidth = minWidth;
/*
if (pos.row == 0) {
tableEditor.verticalAlignment = SWT.TOP;
} else {
tableEditor.verticalAlignment = SWT.CENTER;
}
*/
GridPos pos = getFocusPos();
tableEditor.setEditor(editor, pos.col, pos.row);
}
////////////////////////////////////////////////////////////
// Accessibility support
private void hookAccessibility() {
final Accessible accessible = getAccessible();
accessible.addAccessibleListener(new GridAccessibleListener());
addCursorChangeListener(new Listener() {
@Override
public void handleEvent(Event event) {
accessible.selectionChanged();
}
});
}
private static class GridAccessibleListener implements AccessibleListener {
@Override
public void getName(AccessibleEvent e) {
e.result = "Results grid";
}
@Override
public void getHelp(AccessibleEvent e) {
}
@Override
public void getKeyboardShortcut(AccessibleEvent e) {
}
@Override
public void getDescription(AccessibleEvent e) {
e.result = "Results grid";
}
}
}