/* * 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.itemlist; import org.eclipse.jface.viewers.*; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.ui.IWorkbenchSite; import org.eclipse.ui.services.IServiceLocator; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.core.DBeaverCore; import org.jkiss.dbeaver.core.DBeaverUI; import org.jkiss.dbeaver.model.DBPDataSourceContainer; import org.jkiss.dbeaver.model.DBPImage; import org.jkiss.dbeaver.model.DBPObject; import org.jkiss.dbeaver.model.IDataSourceContainerProvider; import org.jkiss.dbeaver.model.edit.DBECommandContext; import org.jkiss.dbeaver.model.edit.DBEObjectEditor; import org.jkiss.dbeaver.model.navigator.DBNDatabaseNode; import org.jkiss.dbeaver.model.navigator.DBNEvent; import org.jkiss.dbeaver.model.navigator.DBNNode; import org.jkiss.dbeaver.model.navigator.INavigatorListener; import org.jkiss.dbeaver.model.navigator.meta.DBXTreeFolder; import org.jkiss.dbeaver.model.navigator.meta.DBXTreeNode; import org.jkiss.dbeaver.model.navigator.meta.DBXTreeNodeHandler; import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor; import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor; import org.jkiss.dbeaver.model.struct.DBSObject; import org.jkiss.dbeaver.model.struct.DBSWrapper; import org.jkiss.dbeaver.registry.editor.EntityEditorsRegistry; import org.jkiss.dbeaver.runtime.properties.PropertySourceAbstract; import org.jkiss.dbeaver.runtime.properties.PropertySourceEditable; import org.jkiss.dbeaver.ui.actions.navigator.NavigatorHandlerObjectOpen; import org.jkiss.dbeaver.ui.controls.ListContentProvider; import org.jkiss.dbeaver.ui.controls.ObjectViewerRenderer; import org.jkiss.dbeaver.ui.controls.TreeContentProvider; import org.jkiss.dbeaver.ui.editors.IDatabaseEditor; import org.jkiss.dbeaver.ui.navigator.INavigatorModelView; import org.jkiss.dbeaver.ui.navigator.NavigatorUtils; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; /** * NodeListControl */ public abstract class NodeListControl extends ObjectListControl<DBNNode> implements IDataSourceContainerProvider, INavigatorModelView, INavigatorListener { static final Log log = Log.getLog(NodeListControl.class); private final IWorkbenchSite workbenchSite; private DBNNode rootNode; private DBXTreeNode nodeMeta; private final NodeSelectionProvider selectionProvider; protected NodeListControl(Composite parent, int style, final IWorkbenchSite workbenchSite, DBNNode rootNode, IContentProvider contentProvider) { super(parent, style, contentProvider); this.workbenchSite = workbenchSite; this.rootNode = rootNode; this.selectionProvider = new NodeSelectionProvider(super.getSelectionProvider()); // Add context menu NavigatorUtils.addContextMenu(workbenchSite, getItemsViewer()); setDoubleClickHandler(new IDoubleClickListener() { @Override public void doubleClick(DoubleClickEvent event) { // Run default node action DBNNode node = NavigatorUtils.getSelectedNode(getItemsViewer()); if (node == null || !node.allowsOpen()) { return; } openNodeEditor(node); } }); // Add drag and drop support NavigatorUtils.addDragAndDropSupport(getItemsViewer()); DBeaverCore.getInstance().getNavigatorModel().addListener(this); } protected void openNodeEditor(DBNNode node) { IServiceLocator serviceLocator = workbenchSite != null ? workbenchSite : DBeaverUI.getActiveWorkbenchWindow(); NavigatorUtils.executeNodeAction(DBXTreeNodeHandler.Action.open, node, serviceLocator); } public NodeListControl( Composite parent, int style, final IWorkbenchSite workbenchSite, DBNNode rootNode, DBXTreeNode nodeMeta) { this(parent, style, workbenchSite, rootNode, createContentProvider(rootNode, nodeMeta)); this.nodeMeta = nodeMeta; } public IWorkbenchSite getWorkbenchSite() { return workbenchSite; } @Override public DBPDataSourceContainer getDataSourceContainer() { if (rootNode instanceof DBNDatabaseNode) { return ((DBNDatabaseNode) rootNode).getDataSourceContainer(); } return null; } @Override public void disposeControl() { selectionProvider.dispose(); DBeaverCore.getInstance().getNavigatorModel().removeListener(this); super.disposeControl(); } @Override public ISelectionProvider getSelectionProvider() { return selectionProvider; } private static IContentProvider createContentProvider(DBNNode node, DBXTreeNode metaNode) { if (node instanceof DBNDatabaseNode) { final DBNDatabaseNode dbNode = (DBNDatabaseNode) node; if (metaNode == null) { metaNode = dbNode.getMeta(); } final List<DBXTreeNode> inlineMetas = collectInlineMetas(dbNode, metaNode); if (!inlineMetas.isEmpty()) { return new TreeContentProvider() { @Override public boolean hasChildren(Object parentElement) { return parentElement instanceof DBNDatabaseNode && ((DBNDatabaseNode) parentElement).hasChildren(false); } @Override public Object[] getChildren(Object parentElement) { if (parentElement instanceof DBNDatabaseNode) { try { // Read children with void progress monitor because inline children SHOULD be already cached DBNNode[] children = NavigatorUtils.getNodeChildrenFiltered(new VoidProgressMonitor(), (DBNDatabaseNode)parentElement, false); if (ArrayUtils.isEmpty(children)) { return null; } else { return children; } } catch (DBException e) { log.error(e); } } return null; } }; } } return new ListContentProvider(); } private static List<DBXTreeNode> collectInlineMetas(DBNDatabaseNode node, DBXTreeNode meta) { final List<DBXTreeNode> inlineMetas = new ArrayList<>(); if (meta instanceof DBXTreeFolder) { // If this is a folder - iterate through all its children for (DBXTreeNode metaChild : meta.getChildren(node)) { collectInlineChildren(metaChild, inlineMetas); } } else { // Just check child metas collectInlineChildren(meta, inlineMetas); } return inlineMetas; } private static void collectInlineChildren(DBXTreeNode meta, List<DBXTreeNode> inlineMetas) { final List<DBXTreeNode> metaChildren = meta.getChildren(null); if (!CommonUtils.isEmpty(metaChildren)) { for (DBXTreeNode child : metaChildren) { if (child.isInline()) { inlineMetas.add(child); } } } } @Nullable @Override protected Class<?>[] getListBaseTypes(Collection<DBNNode> items) { // Collect base types for root node if (getRootNode() instanceof DBNDatabaseNode) { DBNDatabaseNode dbNode = (DBNDatabaseNode) getRootNode(); List<Class<?>> baseTypes = dbNode.getChildrenTypes(nodeMeta); // Collect base types for inline children return CommonUtils.isEmpty(baseTypes) ? null : baseTypes.toArray(new Class<?>[baseTypes.size()]); } else { return null; } } @Nullable @Override public Viewer getNavigatorViewer() { return getItemsViewer(); } @Override public DBNNode getRootNode() { return rootNode; } public void setRootNode(DBNNode rootNode) { this.rootNode = rootNode; } protected DBXTreeNode getNodeMeta() { return nodeMeta; } @Override protected Object getObjectValue(DBNNode item) { return item instanceof DBSWrapper ? ((DBSWrapper)item).getObject() : item; } @Override protected DBPImage getObjectImage(DBNNode item) { return item.getNodeIconDefault(); } @Override protected boolean isNewObject(DBNNode objectValue) { return !objectValue.isPersisted(); } @NotNull @Override protected String getListConfigId(List<Class<?>> classList) { StringBuilder sb = new StringBuilder("NodeList"); for (Class theClass : classList) { sb.append("/").append(theClass.getSimpleName()); } return sb.toString(); } @Override protected PropertySourceAbstract createListPropertySource() { if (workbenchSite instanceof IWorkbenchPartSite && ((IWorkbenchPartSite) workbenchSite).getPart() instanceof IDatabaseEditor) { return new NodeListPropertySource(((IDatabaseEditor) ((IWorkbenchPartSite) workbenchSite).getPart()).getEditorInput().getCommandContext()); } else { return super.createListPropertySource(); } } @Override public void nodeChanged(final DBNEvent event) { if (isDisposed()) { return; } if (event.getNode().isChildOf(getRootNode())) { if (event.getAction() != DBNEvent.Action.UPDATE) { // Add or remove - just reload list content loadData(false); } else { DBeaverUI.asyncExec(new Runnable() { @Override public void run() { getItemsViewer().update(event.getNode(), null); } }); } } } @Override protected ObjectViewerRenderer createRenderer() { return new NodeRenderer(); } private class NodeRenderer extends ViewerRenderer { @Override public boolean isHyperlink(Object cellValue) { Object ownerObject = null; if (rootNode instanceof DBNDatabaseNode) { ownerObject = ((DBNDatabaseNode) rootNode).getValueObject(); } return cellValue instanceof DBSObject && cellValue != ownerObject; } @Override public void navigateHyperlink(Object cellValue) { if (cellValue instanceof DBSObject) { NavigatorHandlerObjectOpen.openEntityEditor((DBSObject) cellValue); } } } private class NodeListPropertySource extends PropertySourceEditable { private NodeListPropertySource(DBECommandContext commandContext) { super(commandContext, NodeListControl.this, NodeListControl.this); } @Override public DBNNode getSourceObject() { return getCurrentListObject(); } @Override public Object getEditableValue() { return getObjectValue(getCurrentListObject()); } @Override public boolean isEditable(Object editableValue) { if (editableValue == null) { return false; } final DBNNode rootNode = getRootNode(); if (!(rootNode instanceof DBNDatabaseNode)) { return false; } final Class<?> curClass = editableValue.getClass(); final Object valueObject = ((DBNDatabaseNode) rootNode).getValueObject(); if (valueObject == null) { return false; } DBEObjectEditor objectEditor = EntityEditorsRegistry.getInstance().getObjectManager(curClass, DBEObjectEditor.class); return objectEditor != null && editableValue instanceof DBPObject && objectEditor.canEditObject((DBPObject) editableValue); } @Override public DBPPropertyDescriptor[] getPropertyDescriptors2() { Set<DBPPropertyDescriptor> props = getAllProperties(); return props.toArray(new DBPPropertyDescriptor[props.size()]); } } private class NodeSelectionProvider implements ISelectionProvider, ISelectionChangedListener { private final ISelectionProvider original; private final List<ISelectionChangedListener> listeners = new ArrayList<>(); private final StructuredSelection defaultSelection; public NodeSelectionProvider(ISelectionProvider original) { this.original = original; this.defaultSelection = new StructuredSelection(rootNode); this.original.addSelectionChangedListener(this); } @Override public void addSelectionChangedListener(ISelectionChangedListener listener) { synchronized (listeners) { listeners.add(listener); } } @Override public ISelection getSelection() { final ISelection selection = original.getSelection(); if (selection == null || selection.isEmpty()) { return defaultSelection; } else { return selection; } } @Override public void removeSelectionChangedListener(ISelectionChangedListener listener) { synchronized (listeners) { listeners.remove(listener); } } @Override public void setSelection(ISelection selection) { if (selection == defaultSelection) { original.setSelection(new StructuredSelection()); } else { original.setSelection(selection); } selectionChanged(new SelectionChangedEvent(this, selection)); } @Override public void selectionChanged(SelectionChangedEvent event) { synchronized (listeners) { event = new SelectionChangedEvent(this, getSelection()); for (ISelectionChangedListener listener : listeners) { listener.selectionChanged(event); } } } void dispose() { this.original.removeSelectionChangedListener(this); } } }