/*
* 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.plaintext;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.printing.PrintDialog;
import org.eclipse.swt.printing.Printer;
import org.eclipse.swt.printing.PrinterData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.themes.IThemeManager;
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.model.data.DBDDisplayFormat;
import org.jkiss.dbeaver.ui.TextUtils;
import org.jkiss.dbeaver.ui.UIUtils;
import org.jkiss.dbeaver.ui.controls.StyledTextFindReplaceTarget;
import org.jkiss.dbeaver.ui.controls.resultset.*;
import org.jkiss.utils.CommonUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* Empty presentation.
* Used when RSV has no results (initially).
*/
public class PlainTextPresentation extends AbstractPresentation implements IAdaptable {
public static final int FIRST_ROW_LINE = 2;
private StyledText text;
private DBDAttributeBinding curAttribute;
private StyledTextFindReplaceTarget findReplaceTarget;
public boolean activated;
private Color curLineColor;
private int[] colWidths;
private StyleRange curLineRange;
private int totalRows = 0;
private String curSelection;
@Override
public void createPresentation(@NotNull final IResultSetController controller, @NotNull Composite parent) {
super.createPresentation(controller, parent);
UIUtils.createHorizontalLine(parent);
text = new StyledText(parent, SWT.READ_ONLY | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
text.setBlockSelection(true);
text.setCursor(parent.getDisplay().getSystemCursor(SWT.CURSOR_IBEAM));
text.setMargins(4, 4, 4, 4);
text.setFont(JFaceResources.getFont(JFaceResources.TEXT_FONT));
text.setLayoutData(new GridData(GridData.FILL_BOTH));
text.addCaretListener(new CaretListener() {
@Override
public void caretMoved(CaretEvent event) {
onCursorChange(event.caretOffset);
}
});
text.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
curSelection = text.getSelectionText();
fireSelectionChanged(new PlainTextSelectionImpl());
}
});
final ScrollBar verticalBar = text.getVerticalBar();
verticalBar.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (verticalBar.getSelection() + verticalBar.getPageIncrement() >= verticalBar.getMaximum()) {
if (controller.getPreferenceStore().getBoolean(DBeaverPreferences.RESULT_SET_AUTO_FETCH_NEXT_SEGMENT) &&
!controller.isRecordMode() &&
controller.isHasMoreData()) {
controller.readNextSegment();
}
}
}
});
findReplaceTarget = new StyledTextFindReplaceTarget(text);
UIUtils.enableHostEditorKeyBindingsSupport(controller.getSite(), text);
applyThemeSettings();
registerContextMenu();
trackPresentationControl();
}
private void applyThemeSettings() {
IThemeManager themeManager = controller.getSite().getWorkbenchWindow().getWorkbench().getThemeManager();
curLineColor = themeManager.getCurrentTheme().getColorRegistry().get(ThemeConstants.COLOR_SQL_RESULT_CELL_ODD_BACK);
}
private void onCursorChange(int offset) {
ResultSetModel model = controller.getModel();
int lineNum = text.getLineAtOffset(offset);
int lineOffset = text.getOffsetAtLine(lineNum);
int horizontalOffset = offset - lineOffset;
int lineCount = text.getLineCount();
int rowNum = lineNum - FIRST_ROW_LINE; //First 2 lines is header
if (controller.isRecordMode()) {
if (rowNum < 0) {
rowNum = 0;
}
if (rowNum >= 0 && rowNum < model.getVisibleAttributeCount()) {
curAttribute = model.getVisibleAttribute(rowNum);
}
} else {
int colNum = 0;
int horOffsetBegin = 0, horOffsetEnd = 0;
for (int i = 0; i < colWidths.length; i++) {
horOffsetBegin = horOffsetEnd;
horOffsetEnd += colWidths[i] + 1;
if (horizontalOffset < horOffsetEnd) {
colNum = i;
break;
}
}
if (rowNum < 0 && model.getRowCount() > 0) {
rowNum = 0;
}
if (rowNum >= 0 && rowNum < model.getRowCount() && colNum >= 0 && colNum < model.getVisibleAttributeCount()) {
controller.setCurrentRow(model.getRow(rowNum));
curAttribute = model.getVisibleAttribute(colNum);
}
controller.updateEditControls();
{
// Highlight row
if (curLineRange == null || curLineRange.start != lineOffset + horOffsetBegin) {
curLineRange = new StyleRange(
lineOffset + horOffsetBegin,
horOffsetEnd - horOffsetBegin - 1,
null,
curLineColor);
DBeaverUI.asyncExec(new Runnable() {
@Override
public void run() {
text.setStyleRanges(new StyleRange[]{curLineRange});
}
});
}
}
if (lineNum == lineCount - 1 &&
controller.isHasMoreData() &&
controller.getPreferenceStore().getBoolean(DBeaverPreferences.RESULT_SET_AUTO_FETCH_NEXT_SEGMENT)) {
controller.readNextSegment();
}
}
fireSelectionChanged(new PlainTextSelectionImpl());
}
@Override
public Control getControl() {
return text;
}
@Override
public void refreshData(boolean refreshMetadata, boolean append, boolean keepState) {
colWidths = null;
if (controller.isRecordMode()) {
printRecord();
} else {
printGrid(append);
}
}
private void printGrid(boolean append) {
int maxColumnSize = getController().getPreferenceStore().getInt(DBeaverPreferences.RESULT_TEXT_MAX_COLUMN_SIZE);
DBDDisplayFormat displayFormat = DBDDisplayFormat.safeValueOf(getController().getPreferenceStore().getString(DBeaverPreferences.RESULT_TEXT_VALUE_FORMAT));
StringBuilder grid = new StringBuilder(512);
ResultSetModel model = controller.getModel();
List<DBDAttributeBinding> attrs = model.getVisibleAttributes();
List<ResultSetRow> allRows = model.getAllRows();
if (colWidths == null) {
// Calculate column widths
colWidths = new int[attrs.size()];
for (int i = 0; i < attrs.size(); i++) {
DBDAttributeBinding attr = attrs.get(i);
colWidths[i] = getAttributeName(attr).length();
for (ResultSetRow row : allRows) {
String displayString = getCellString(model, attr, row, displayFormat);
colWidths[i] = Math.max(colWidths[i], displayString.length());
}
}
for (int i = 0; i < colWidths.length; i++) {
colWidths[i]++;
if (colWidths[i] > maxColumnSize) {
colWidths[i] = maxColumnSize;
}
}
}
if (!append) {
// Print header
for (int i = 0; i < attrs.size(); i++) {
DBDAttributeBinding attr = attrs.get(i);
String attrName = getAttributeName(attr);
grid.append(attrName);
for (int k = colWidths[i] - attrName.length(); k > 0; k--) {
grid.append(" ");
}
grid.append("|");
}
grid.append("\n");
// Print divider
// Print header
for (int i = 0; i < attrs.size(); i++) {
for (int k = colWidths[i]; k > 0; k--) {
grid.append("-");
}
grid.append("|");
}
grid.append("\n");
}
// Print rows
int firstRow = append ? totalRows : 0;
if (append) {
grid.append("\n");
}
for (int i = firstRow; i < allRows.size(); i++) {
ResultSetRow row = allRows.get(i);
for (int k = 0; k < attrs.size(); k++) {
DBDAttributeBinding attr = attrs.get(k);
String displayString = getCellString(model, attr, row, displayFormat);
if (displayString.length() >= colWidths[k] - 1) {
displayString = CommonUtils.truncateString(displayString, colWidths[k] - 1);
}
grid.append(displayString);
for (int j = colWidths[k] - displayString.length(); j > 0; j--) {
grid.append(" ");
}
grid.append("|");
}
grid.append("\n");
}
grid.setLength(grid.length() - 1); // cut last line feed
if (append) {
text.append(grid.toString());
} else {
text.setText(grid.toString());
}
totalRows = allRows.size();
}
private static String getAttributeName(DBDAttributeBinding attr) {
if (CommonUtils.isEmpty(attr.getLabel())) {
return attr.getName();
} else {
return attr.getLabel();
}
}
private String getCellString(ResultSetModel model, DBDAttributeBinding attr, ResultSetRow row, DBDDisplayFormat displayFormat) {
String displayString = attr.getValueHandler().getValueDisplayString(attr, model.getCellValue(attr, row), displayFormat);
return TextUtils.getSingleLineString(displayString);
}
private void printRecord() {
DBDDisplayFormat displayFormat = DBDDisplayFormat.safeValueOf(getController().getPreferenceStore().getString(DBeaverPreferences.RESULT_TEXT_VALUE_FORMAT));
StringBuilder grid = new StringBuilder(512);
ResultSetModel model = controller.getModel();
List<DBDAttributeBinding> attrs = model.getVisibleAttributes();
String[] values = new String[attrs.size()];
ResultSetRow currentRow = controller.getCurrentRow();
// Calculate column widths
int nameWidth = 4, valueWidth = 5;
for (int i = 0; i < attrs.size(); i++) {
DBDAttributeBinding attr = attrs.get(i);
nameWidth = Math.max(nameWidth, getAttributeName(attr).length());
values[i] = attr.getValueHandler().getValueDisplayString(attr, model.getCellValue(attr, currentRow), displayFormat);
valueWidth = Math.max(valueWidth, values[i].length());
}
// Header
grid.append("Name");
for (int j = nameWidth - 4; j > 0; j--) {
grid.append(" ");
}
grid.append("|Value\n");
for (int j = 0; j < nameWidth; j++) grid.append("-");
grid.append("|");
for (int j = 0; j < valueWidth; j++) grid.append("-");
grid.append("\n");
// Values
for (int i = 0; i < attrs.size(); i++) {
DBDAttributeBinding attr = attrs.get(i);
String name = getAttributeName(attr);
grid.append(name);
for (int j = nameWidth - name.length(); j > 0; j--) {
grid.append(" ");
}
grid.append("|");
grid.append(values[i]);
grid.append("\n");
}
grid.setLength(grid.length() - 1); // cut last line feed
text.setText(grid.toString());
}
@Override
public void formatData(boolean refreshData) {
//controller.refreshData(null);
}
@Override
public void clearMetaData() {
colWidths = null;
curLineRange = null;
totalRows = 0;
}
@Override
public void updateValueView() {
}
@Override
public void fillMenu(@NotNull IMenuManager menu) {
}
@Override
public void changeMode(boolean recordMode) {
}
@Override
public void scrollToRow(@NotNull RowPosition position) {
if (controller.isRecordMode()) {
super.scrollToRow(position);
} else {
int caretOffset = text.getCaretOffset();
if (caretOffset < 0) caretOffset = 0;
int lineNum = text.getLineAtOffset(caretOffset);
if (lineNum < FIRST_ROW_LINE) {
lineNum = FIRST_ROW_LINE;
}
int lineOffset = text.getOffsetAtLine(lineNum);
int xOffset = caretOffset - lineOffset;
int totalLines = text.getLineCount();
switch (position) {
case FIRST:
lineNum = FIRST_ROW_LINE;
break;
case PREVIOUS:
lineNum--;
break;
case NEXT:
lineNum++;
break;
case LAST:
lineNum = totalLines - 1;
break;
case CURRENT:
lineNum = controller.getCurrentRow().getVisualNumber() + FIRST_ROW_LINE;
break;
}
if (lineNum < FIRST_ROW_LINE || lineNum >= totalLines) {
return;
}
int newOffset = text.getOffsetAtLine(lineNum);
newOffset += xOffset;
text.setCaretOffset(newOffset);
//text.setSelection(newOffset, 0);
text.showSelection();
}
}
@Nullable
@Override
public DBDAttributeBinding getCurrentAttribute() {
return curAttribute;
}
@Nullable
@Override
public String copySelectionToString(ResultSetCopySettings settings) {
return text.getSelectionText();
}
private static PrinterData fgPrinterData= null;
@Override
public void printResultSet() {
final Shell shell = getControl().getShell();
StyledTextPrintOptions options = new StyledTextPrintOptions();
options.printTextFontStyle = true;
options.printTextForeground = true;
if (Printer.getPrinterList().length == 0) {
UIUtils.showMessageBox(shell, "No printers", "Printers not found", SWT.ICON_ERROR);
return;
}
final PrintDialog dialog = new PrintDialog(shell, SWT.PRIMARY_MODAL);
dialog.setPrinterData(fgPrinterData);
final PrinterData data = dialog.open();
if (data != null) {
final Printer printer = new Printer(data);
final Runnable styledTextPrinter = text.print(printer, options);
new Thread("Printing") { //$NON-NLS-1$
public void run() {
styledTextPrinter.run();
printer.dispose();
}
}.start();
/*
* FIXME:
* Should copy the printer data to avoid threading issues,
* but this is currently not possible, see http://bugs.eclipse.org/297957
*/
fgPrinterData = data;
fgPrinterData.startPage = 1;
fgPrinterData.endPage = 1;
fgPrinterData.scope = PrinterData.ALL_PAGES;
fgPrinterData.copyCount = 1;
}
}
@Override
public <T> T getAdapter(Class<T> adapter) {
if (adapter == IFindReplaceTarget.class) {
return adapter.cast(findReplaceTarget);
}
return null;
}
@Override
public ISelection getSelection() {
return new PlainTextSelectionImpl();
}
private class PlainTextSelectionImpl implements IResultSetSelection {
@Nullable
@Override
public Object getFirstElement()
{
return curSelection;
}
@Override
public Iterator<String> iterator()
{
return toList().iterator();
}
@Override
public int size()
{
return curSelection == null ? 0 : 1;
}
@Override
public Object[] toArray()
{
return curSelection == null ?
new Object[0] :
new Object[] { curSelection };
}
@Override
public List<String> toList()
{
return curSelection == null ?
Collections.<String>emptyList() :
Collections.singletonList(curSelection);
}
@Override
public boolean isEmpty()
{
return false;
}
@NotNull
@Override
public IResultSetController getController()
{
return controller;
}
@NotNull
@Override
public Collection<DBDAttributeBinding> getSelectedAttributes() {
if (curAttribute == null) {
return Collections.emptyList();
}
return Collections.singleton(curAttribute);
}
@NotNull
@Override
public Collection<ResultSetRow> getSelectedRows()
{
ResultSetRow currentRow = controller.getCurrentRow();
if (currentRow == null) {
return Collections.emptyList();
}
return Collections.singletonList(currentRow);
}
@Override
public DBDAttributeBinding getElementAttribute(Object element) {
return curAttribute;
}
@Override
public ResultSetRow getElementRow(Object element) {
return getController().getCurrentRow();
}
}
}