/*
* 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.panel;
import org.eclipse.jface.action.*;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.*;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPImage;
import org.jkiss.dbeaver.model.DBValueFormatting;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.data.aggregate.IAggregateFunction;
import org.jkiss.dbeaver.registry.functions.AggregateFunctionDescriptor;
import org.jkiss.dbeaver.registry.functions.FunctionsRegistry;
import org.jkiss.dbeaver.ui.DBeaverIcons;
import org.jkiss.dbeaver.ui.UIIcon;
import org.jkiss.dbeaver.ui.UIUtils;
import org.jkiss.dbeaver.ui.controls.resultset.*;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.*;
import java.util.List;
/**
* RSV value view panel
*/
public class AggregateColumnsPanel implements IResultSetPanel {
private static final Log log = Log.getLog(AggregateColumnsPanel.class);
public static final String PANEL_ID = "column-aggregate";
public static final String SETTINGS_SECTION_AGGREGATE = "panel-" + PANEL_ID;
public static final String PARAM_GROUP_BY_COLUMNS = "groupByColumns";
private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("###,###,###,###,###,##0.000");
private static final DecimalFormat INTEGER_FORMAT = new DecimalFormat("###,###,###,###,###,##0");
private IResultSetPresentation presentation;
private Tree aggregateTable;
private boolean groupByColumns;
//private boolean runServerQueries;
private IDialogSettings panelSettings;
private final List<AggregateFunctionDescriptor> enabledFunctions = new ArrayList<>();
public AggregateColumnsPanel() {
}
@Override
public String getPanelTitle() {
return "Aggregate";
}
@Override
public DBPImage getPanelImage() {
return UIIcon.PANEL_AGGREGATE;
}
@Override
public String getPanelDescription() {
return "Aggregate columns";
}
@Override
public Control createContents(final IResultSetPresentation presentation, Composite parent) {
this.presentation = presentation;
this.panelSettings = ResultSetUtils.getViewerSettings(SETTINGS_SECTION_AGGREGATE);
loadSettings();
this.aggregateTable = new Tree(parent, SWT.SINGLE | SWT.FULL_SELECTION);
this.aggregateTable.setHeaderVisible(true);
this.aggregateTable.setLinesVisible(true);
new TreeColumn(this.aggregateTable, SWT.LEFT).setText("Function");
new TreeColumn(this.aggregateTable, SWT.RIGHT).setText("Value");
if (this.presentation instanceof ISelectionProvider) {
((ISelectionProvider) this.presentation).addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
if (presentation.getController().getVisiblePanel() == AggregateColumnsPanel.this) {
refresh(false);
}
}
});
}
MenuManager menuMgr = new MenuManager();
menuMgr.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager manager)
{
manager.add(new CopyAction());
manager.add(new CopyAllAction());
manager.add(new Separator());
fillToolBar(manager);
}
});
menuMgr.setRemoveAllWhenShown(true);
this.aggregateTable.setMenu(menuMgr.createContextMenu(this.aggregateTable));
aggregateTable.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
presentation.getController().updatePanelActions();
}
});
return this.aggregateTable;
}
private void loadSettings() {
groupByColumns = panelSettings.getBoolean(PARAM_GROUP_BY_COLUMNS);
IDialogSettings functionsSection = panelSettings.getSection("functions");
if (functionsSection != null) {
final Map<AggregateFunctionDescriptor, Integer> funcIndexes = new HashMap<>();
for (IDialogSettings funcSection : functionsSection.getSections()) {
String funcId = funcSection.getName();
if (!funcSection.getBoolean("enabled")) {
continue;
}
AggregateFunctionDescriptor func = FunctionsRegistry.getInstance().getFunction(funcId);
if (func == null) {
log.debug("Function '" + funcId + "' not found");
} else {
funcIndexes.put(func, funcSection.getInt("index"));
enabledFunctions.add(func);
}
}
Collections.sort(enabledFunctions, new Comparator<AggregateFunctionDescriptor>() {
@Override
public int compare(AggregateFunctionDescriptor o1, AggregateFunctionDescriptor o2) {
return funcIndexes.get(o1) - funcIndexes.get(o2);
}
});
}
if (enabledFunctions.isEmpty()) {
loadDefaultFunctions();
}
}
private void loadDefaultFunctions() {
for (AggregateFunctionDescriptor func : FunctionsRegistry.getInstance().getFunctions()) {
if (func.isDefault()) {
enabledFunctions.add(func);
}
}
Collections.sort(enabledFunctions, new Comparator<AggregateFunctionDescriptor>() {
@Override
public int compare(AggregateFunctionDescriptor o1, AggregateFunctionDescriptor o2) {
return o1.getLabel().compareTo(o2.getLabel());
}
});
}
private void saveSettings() {
panelSettings.put(PARAM_GROUP_BY_COLUMNS, groupByColumns);
IDialogSettings functionsSection = UIUtils.getSettingsSection(panelSettings, "functions");
for (AggregateFunctionDescriptor func : FunctionsRegistry.getInstance().getFunctions()) {
IDialogSettings funcSection = UIUtils.getSettingsSection(functionsSection, func.getId());
boolean enabled = enabledFunctions.contains(func);
funcSection.put("enabled", enabled);
if (enabled) {
funcSection.put("index", enabledFunctions.indexOf(func));
} else {
funcSection.put("index", -1);
}
}
}
@Override
public void activatePanel() {
refresh(false);
}
@Override
public void deactivatePanel() {
}
@Override
public void refresh(boolean force) {
aggregateTable.setRedraw(false);
try {
aggregateTable.removeAll();
if (this.presentation instanceof ISelectionProvider) {
ISelection selection = ((ISelectionProvider) presentation).getSelection();
if (selection instanceof IResultSetSelection) {
aggregateSelection((IResultSetSelection)selection);
}
}
UIUtils.packColumns(aggregateTable, true, null);
} finally {
aggregateTable.setRedraw(true);
}
saveSettings();
}
@Override
public void contributeActions(ToolBarManager manager) {
fillToolBar(manager);
}
private void aggregateSelection(IResultSetSelection selection) {
ResultSetModel model = presentation.getController().getModel();
if (groupByColumns) {
Map<DBDAttributeBinding, List<Object>> attrValues = new LinkedHashMap<>();
for (Object element : selection.toList()) {
DBDAttributeBinding attr = selection.getElementAttribute(element);
ResultSetRow row = selection.getElementRow(element);
Object cellValue = model.getCellValue(attr, row);
List<Object> values = attrValues.get(attr);
if (values == null) {
values = new ArrayList<>();
attrValues.put(attr, values);
}
values.add(cellValue);
}
for (Map.Entry<DBDAttributeBinding, List<Object>> entry : attrValues.entrySet()) {
TreeItem attrItem = new TreeItem(aggregateTable, SWT.NONE);
attrItem.setText(entry.getKey().getName());
attrItem.setImage(DBeaverIcons.getImage(DBValueFormatting.getObjectImage(entry.getKey())));
aggregateValues(attrItem, entry.getValue());
attrItem.setExpanded(true);
}
} else {
List<Object> allValues = new ArrayList<>(selection.size());
for (Object element : selection.toList()) {
DBDAttributeBinding attr = selection.getElementAttribute(element);
ResultSetRow row = selection.getElementRow(element);
Object cellValue = model.getCellValue(attr, row);
allValues.add(cellValue);
}
aggregateValues(null, allValues);
}
}
private void aggregateValues(TreeItem parentItem, Collection<Object> values) {
List<AggregateFunctionDescriptor> functions = enabledFunctions;
Map<IAggregateFunction, TreeItem> funcMap = new IdentityHashMap<>();
for (AggregateFunctionDescriptor funcDesc : functions) {
TreeItem funcItem = (parentItem == null) ?
new TreeItem(aggregateTable, SWT.NONE) :
new TreeItem(parentItem, SWT.NONE);
funcItem.setData(funcDesc);
funcItem.setText(0, funcDesc.getLabel());
DBPImage icon = funcDesc.getIcon();
if (icon != null) {
funcItem.setImage(0, DBeaverIcons.getImage(icon));
}
try {
IAggregateFunction func = funcDesc.createFunction();
funcMap.put(func, funcItem);
} catch (DBException e) {
log.error(e);
}
}
IAggregateFunction[] funcs = funcMap.keySet().toArray(new IAggregateFunction[funcMap.size()]);
int[] funcCount = new int[funcs.length];
for (Object element : values) {
for (int i = 0; i < funcs.length; i++) {
if (funcs[i].accumulate(element)) {
funcCount[i]++;
}
}
}
for (int i = 0; i < funcs.length; i++) {
if (funcCount[i] <= 0) {
continue;
}
IAggregateFunction func = funcs[i];
Object result = func.getResult(funcCount[i]);
if (result != null) {
TreeItem treeItem = funcMap.get(func);
String strValue;
if (result instanceof Double || result instanceof Float || result instanceof BigDecimal) {
strValue = DOUBLE_FORMAT.format(result);
} else if (result instanceof Integer || result instanceof Long || result instanceof Short) {
strValue = INTEGER_FORMAT.format(result);
} else {
strValue = result.toString();
}
treeItem.setText(1, strValue);
}
}
}
public void clearValue()
{
aggregateTable.removeAll();
}
private void fillToolBar(IContributionManager contributionManager)
{
contributionManager.add(new AddFunctionAction());
contributionManager.add(new RemoveFunctionAction());
contributionManager.add(new ResetFunctionsAction());
contributionManager.add(new Separator());
contributionManager.add(new GroupByColumnsAction());
}
private class GroupByColumnsAction extends Action {
public GroupByColumnsAction() {
super("Group by columns", IAction.AS_CHECK_BOX);
setImageDescriptor(DBeaverIcons.getImageDescriptor(UIIcon.GROUP_BY_ATTR));
setChecked(groupByColumns);
}
@Override
public void run() {
groupByColumns = !groupByColumns;
setChecked(groupByColumns);
refresh(false);
}
}
private class AddFunctionAction extends Action {
public AddFunctionAction() {
super("Add function", DBeaverIcons.getImageDescriptor(UIIcon.OBJ_ADD));
}
@Override
public void run() {
List<AggregateFunctionDescriptor> missingFunctions = new ArrayList<>();
for (AggregateFunctionDescriptor func : FunctionsRegistry.getInstance().getFunctions()) {
if (!enabledFunctions.contains(func)) {
missingFunctions.add(func);
}
}
if (!missingFunctions.isEmpty()) {
Point location = aggregateTable.getDisplay().map(aggregateTable, null, new Point(10, 10));
MenuManager menuManager = new MenuManager();
for (final AggregateFunctionDescriptor func : missingFunctions) {
menuManager.add(new AddFunctionItemAction(func));
}
final Menu contextMenu = menuManager.createContextMenu(aggregateTable);
contextMenu.setLocation(location);
contextMenu.setVisible(true);
}
}
}
private class AddFunctionItemAction extends Action {
private final AggregateFunctionDescriptor func;
public AddFunctionItemAction(AggregateFunctionDescriptor func) {
super(func.getLabel(), DBeaverIcons.getImageDescriptor(func.getIcon()));
this.func = func;
}
@Override
public void run() {
enabledFunctions.add(func);
refresh(false);
}
}
private class RemoveFunctionAction extends Action {
public RemoveFunctionAction() {
super("Remove function", DBeaverIcons.getImageDescriptor(UIIcon.OBJ_REMOVE));
}
@Override
public boolean isEnabled() {
return aggregateTable.getSelectionCount() > 0;
}
@Override
public void run() {
for (TreeItem item : aggregateTable.getSelection()) {
AggregateFunctionDescriptor func = (AggregateFunctionDescriptor) item.getData();
enabledFunctions.remove(func);
}
refresh(false);
}
}
private class ResetFunctionsAction extends Action {
public ResetFunctionsAction() {
super("Reset", DBeaverIcons.getImageDescriptor(UIIcon.OBJ_REFRESH));
}
@Override
public void run() {
enabledFunctions.clear();
loadDefaultFunctions();
refresh(false);
}
}
private class CopyAction extends Action {
public CopyAction() {
super("Copy Value");
}
@Override
public void run() {
StringBuilder result = new StringBuilder();
for (TreeItem item : aggregateTable.getSelection()) {
if (result.length() > 0) result.append("\n");
if (item.getData() instanceof AggregateFunctionDescriptor) {
result.append(item.getText(1));
} else {
result.append(item.getText(0));
}
}
UIUtils.setClipboardContents(aggregateTable.getDisplay(), TextTransfer.getInstance(), result.toString());
}
}
private class CopyAllAction extends Action {
public CopyAllAction() {
super("Copy All");
}
@Override
public void run() {
StringBuilder result = new StringBuilder();
if (!groupByColumns) {
for (TreeItem item : aggregateTable.getItems()) {
if (result.length() > 0) result.append("\n");
result.append(item.getText(0)).append("=").append(item.getText(1));
}
} else {
for (TreeItem item : aggregateTable.getItems()) {
if (result.length() > 0) result.append("\n");
result.append(item.getText(0));
for (TreeItem funcItem : item.getItems()) {
result.append("\n\t");
result.append(funcItem.getText(0)).append("=").append(funcItem.getText(1));
}
}
}
UIUtils.setClipboardContents(aggregateTable.getDisplay(), TextTransfer.getInstance(), result.toString());
}
}
}