/*
* 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.editors.entity.properties;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.*;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.core.CoreMessages;
import org.jkiss.dbeaver.core.DBeaverUI;
import org.jkiss.dbeaver.model.DBIcon;
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
import org.jkiss.dbeaver.model.navigator.DBNDatabaseFolder;
import org.jkiss.dbeaver.model.navigator.DBNDatabaseNode;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.navigator.meta.DBXTreeItem;
import org.jkiss.dbeaver.model.navigator.meta.DBXTreeNode;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.DBRRunnableWithProgress;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.dbeaver.registry.editor.EntityEditorDescriptor;
import org.jkiss.dbeaver.registry.editor.EntityEditorsRegistry;
import org.jkiss.dbeaver.runtime.properties.PropertiesContributor;
import org.jkiss.dbeaver.ui.IProgressControlProvider;
import org.jkiss.dbeaver.ui.IRefreshablePart;
import org.jkiss.dbeaver.ui.ISearchContextProvider;
import org.jkiss.dbeaver.ui.UIUtils;
import org.jkiss.dbeaver.ui.controls.ObjectEditorPageControl;
import org.jkiss.dbeaver.ui.controls.ProgressPageControl;
import org.jkiss.dbeaver.ui.controls.folders.*;
import org.jkiss.dbeaver.ui.editors.AbstractDatabaseObjectEditor;
import org.jkiss.dbeaver.ui.editors.IDatabaseEditor;
import org.jkiss.dbeaver.ui.editors.IDatabaseEditorContributorUser;
import org.jkiss.dbeaver.ui.editors.entity.GlobalContributorManager;
import org.jkiss.dbeaver.ui.navigator.INavigatorModelView;
import org.jkiss.dbeaver.ui.navigator.NavigatorUtils;
import org.jkiss.utils.CommonUtils;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* ObjectPropertiesEditor
*/
public class ObjectPropertiesEditor extends AbstractDatabaseObjectEditor<DBSObject>
implements IRefreshablePart, IProgressControlProvider, ITabbedFolderContainer, ISearchContextProvider, INavigatorModelView
{
private static final Log log = Log.getLog(ObjectPropertiesEditor.class);
private TabbedFolderComposite folderComposite;
private ObjectEditorPageControl pageControl;
private final List<ITabbedFolderListener> folderListeners = new ArrayList<>();
private String curFolderId;
private final List<ISaveablePart> nestedSaveable = new ArrayList<>();
private final Map<ITabbedFolder, IEditorActionBarContributor> pageContributors = new HashMap<>();
private SashForm sashForm;
private boolean activated = false;
private Composite propsPlaceholder;
private TabbedFolderPageProperties propertiesPanel;
public ObjectPropertiesEditor()
{
}
@Override
public void createPartControl(Composite parent)
{
// Add lazy props listener
//PropertiesContributor.getInstance().addLazyListener(this);
pageControl = new ObjectEditorPageControl(parent, SWT.SHEET, this);
pageControl.setShowDivider(true);
Composite container = new Composite(pageControl, SWT.NONE);
GridLayout gl = new GridLayout(1, false);
gl.verticalSpacing = 5;
gl.horizontalSpacing = 0;
gl.marginHeight = 0;
gl.marginWidth = 0;
container.setLayout(gl);
container.setLayoutData(new GridData(GridData.FILL_BOTH));
pageControl.createProgressPanel();
createPropertyBrowser(container);
}
private void createPropertyBrowser(Composite container)
{
TabbedFolderInfo[] folders = collectFolders(this);
if (folders.length == 0) {
createPropertiesPanel(container);
} else {
sashForm = UIUtils.createPartDivider(getSite().getPart(), container, SWT.VERTICAL);
sashForm.setLayoutData(new GridData(GridData.FILL_BOTH));
createPropertiesPanel(sashForm);
createFoldersPanel(sashForm, folders);
}
}
private void createPropertiesPanel(Composite container) {
// Main panel
propsPlaceholder = UIUtils.createPlaceholder(container, 2, 0);
propsPlaceholder.setLayoutData(new GridData(GridData.FILL_BOTH));
}
private void createFoldersPanel(Composite parent, TabbedFolderInfo[] folders) {
// Properties
Composite foldersPlaceholder = UIUtils.createPlaceholder(parent, 1, 0);
foldersPlaceholder.setLayoutData(new GridData(GridData.FILL_BOTH));
boolean single = folders.length < 4;
if (single) {
for (TabbedFolderInfo fi : folders) {
if (!fi.isEmbeddable()) {
single = false;
}
}
}
folderComposite = new TabbedFolderComposite(foldersPlaceholder, SWT.LEFT | (single ? SWT.SINGLE : SWT.MULTI));
folderComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
// Load properties
folderComposite.setFolders(folders);
// Collect section contributors
GlobalContributorManager contributorManager = GlobalContributorManager.getInstance();
for (TabbedFolderInfo folder : folders) {
ITabbedFolder page = folder.getContents();
if (page instanceof IDatabaseEditorContributorUser) {
IEditorActionBarContributor contributor = ((IDatabaseEditorContributorUser) page).getContributor(contributorManager);
if (contributor != null) {
contributorManager.addContributor(contributor, this);
pageContributors.put(page, contributor);
}
}
if (page instanceof ISaveablePart) {
nestedSaveable.add((ISaveablePart) page);
}
}
final String folderId = getEditorInput().getDefaultFolderId();
folderComposite.switchFolder(folderId);
folderComposite.addFolderListener(new ITabbedFolderListener() {
@Override
public void folderSelected(String folderId) {
if (CommonUtils.equalObjects(curFolderId, folderId)) {
return;
}
synchronized (folderListeners) {
curFolderId = folderId;
for (ITabbedFolderListener listener : folderListeners) {
listener.folderSelected(folderId);
}
}
}
});
}
private void updateSashWidths() {
if (sashForm.isDisposed()) {
return;
}
Control[] children = sashForm.getChildren();
if (children.length < 2) {
// Unexpected
return;
}
Point propsSize = children[0].computeSize(SWT.DEFAULT, SWT.DEFAULT);
//Point foldersSize = children[1].computeSize(SWT.DEFAULT, SWT.DEFAULT);
Point sashSize = sashForm.getSize();
if (propsSize.y < sashSize.y / 2) {
int[] weights = new int[] {
propsSize.y,
(sashSize.y - propsSize.y)};
sashForm.setWeights(weights);
} else {
sashForm.setWeights(new int[] { 400, 600 });
}
sashForm.layout();
}
@Override
public void activatePart()
{
if (activated) {
return;
}
activated = true;
propertiesPanel = new TabbedFolderPageProperties(this, getEditorInput());
propertiesPanel.createControl(propsPlaceholder);
pageControl.layout(true);
propsPlaceholder.layout(true);
if (sashForm != null) {
Runnable sashUpdater = new Runnable() {
@Override
public void run() {
updateSashWidths();
}
};
if (sashForm.getSize().y > 0) {
sashUpdater.run();
} else {
DBeaverUI.asyncExec(sashUpdater);
}
}
}
@Override
public void dispose()
{
// Remove contributors
GlobalContributorManager contributorManager = GlobalContributorManager.getInstance();
for (IEditorActionBarContributor contributor : pageContributors.values()) {
contributorManager.removeContributor(contributor, this);
}
pageContributors.clear();
//PropertiesContributor.getInstance().removeLazyListener(this);
super.dispose();
}
@Override
public void setFocus()
{
// do not force focus in active editor. We can't do it properly because folderComposite detects
// active folder by focus (which it doesn't have)
if (folderComposite != null) {
ITabbedFolder selectedPage = folderComposite.getActiveFolder();
if (selectedPage != null) {
selectedPage.setFocus();
// IEditorActionBarContributor contributor = pageContributors.get(selectedPage);
}
} else if (pageControl != null) {
pageControl.setFocus();
}
}
@Override
public void doSave(IProgressMonitor monitor)
{
for (ISaveablePart sp : nestedSaveable) {
sp.doSave(monitor);
}
}
@Override
public void doSaveAs()
{
Object activeFolder = getActiveFolder();
if (activeFolder instanceof ISaveablePart) {
((ISaveablePart) activeFolder).doSaveAs();
}
}
@Override
public void init(IEditorSite site, IEditorInput input)
throws PartInitException
{
setSite(site);
setInput(input);
}
@Override
public boolean isDirty()
{
for (ISaveablePart sp : nestedSaveable) {
if (sp.isDirty()) {
return true;
}
}
return false;
}
@Override
public boolean isSaveAsAllowed()
{
return false;
}
@Nullable
@Override
public ProgressPageControl getProgressControl()
{
return pageControl;
}
@Nullable
@Override
public ITabbedFolder getActiveFolder()
{
return folderComposite == null ? null : folderComposite.getActiveFolder();
}
@Override
public void switchFolder(String folderId)
{
if (folderComposite != null) {
folderComposite.switchFolder(folderId);
}
}
@Override
public void addFolderListener(ITabbedFolderListener listener)
{
synchronized (folderListeners) {
folderListeners.add(listener);
}
}
@Override
public void removeFolderListener(ITabbedFolderListener listener)
{
synchronized (folderListeners) {
folderListeners.remove(listener);
}
}
@Nullable
private ISearchContextProvider getFolderSearch()
{
Object activeFolder = getActiveFolder();
if (activeFolder instanceof ISearchContextProvider) {
return (ISearchContextProvider)activeFolder;
}
return null;
}
@Override
public boolean isSearchPossible()
{
return true;
}
@Override
public boolean isSearchEnabled()
{
ISearchContextProvider provider = getFolderSearch();
return provider != null && provider.isSearchEnabled();
}
@Override
public boolean performSearch(SearchType searchType)
{
ISearchContextProvider folderSearch = getFolderSearch();
if (folderSearch != null) {
return folderSearch.performSearch(searchType);
} else {
return false;
}
}
@Override
public void refreshPart(Object source, boolean force) {
if (propertiesPanel != null) {
propertiesPanel.refreshPart(source, force);
}
if (folderComposite != null && folderComposite.getFolders() != null) {
for (TabbedFolderInfo folder : folderComposite.getFolders()) {
if (folder.getContents() instanceof IRefreshablePart) {
((IRefreshablePart) folder.getContents()).refreshPart(source, force);
}
}
}
}
@Override
public <T> T getAdapter(Class<T> adapter)
{
Object result = null;
final Object activeFolder = getActiveFolder();
if (activeFolder != null) {
if (activeFolder instanceof IAdaptable) {
result = ((IAdaptable) activeFolder).getAdapter(adapter);
} else if (adapter.isAssignableFrom(activeFolder.getClass())) {
result = activeFolder;
}
}
if (result != null) {
return adapter.cast(result);
}
return null;//super.getAdapter(adapter);
}
public TabbedFolderInfo[] collectFolders(IWorkbenchPart part)
{
List<TabbedFolderInfo> tabList = new ArrayList<>();
//makeStandardPropertiesTabs(tabList);
if (part instanceof IDatabaseEditor) {
makeDatabaseEditorTabs((IDatabaseEditor)part, tabList);
}
return tabList.toArray(new TabbedFolderInfo[tabList.size()]);
}
private void makeStandardPropertiesTabs(List<TabbedFolderInfo> tabList)
{
tabList.add(new TabbedFolderInfo(
//PropertiesContributor.CATEGORY_INFO,
PropertiesContributor.TAB_STANDARD,
CoreMessages.ui_properties_category_information,
DBIcon.TREE_INFO,
"General information",
false,
new TabbedFolderPageProperties(this, getEditorInput())));
}
private void makeDatabaseEditorTabs(final IDatabaseEditor part, final List<TabbedFolderInfo> tabList)
{
final DBNDatabaseNode node = part.getEditorInput().getNavigatorNode();
if (node == null) {
return;
}
final DBSObject object = node.getObject();
if (!node.getMeta().isStandaloneNode()) {
// Collect tabs from navigator tree model
DBRRunnableWithProgress tabsCollector = new DBRRunnableWithProgress() {
@Override
public void run(DBRProgressMonitor monitor) {
collectNavigatorTabs(monitor, part, node, tabList);
}
};
try {
if (node.needsInitialization()) {
DBeaverUI.runInProgressService(tabsCollector);
} else {
tabsCollector.run(new VoidProgressMonitor());
}
} catch (InvocationTargetException e) {
log.error(e.getTargetException());
} catch (InterruptedException e) {
// just go further
}
}
// Query for entity editors
List<EntityEditorDescriptor> editors = EntityEditorsRegistry.getInstance().getEntityEditors(object, null);
if (!CommonUtils.isEmpty(editors)) {
for (EntityEditorDescriptor descriptor : editors) {
if (descriptor.getType() == EntityEditorDescriptor.Type.folder) {
tabList.add(new TabbedFolderInfo(
descriptor.getId(),
descriptor.getName(),
descriptor.getIcon(),
descriptor.getDescription(),
descriptor.isEmbeddable(),
new TabbedFolderPageEditor(this, descriptor)));
}
}
}
}
private static void collectNavigatorTabs(DBRProgressMonitor monitor, IDatabaseEditor part, DBNNode node, List<TabbedFolderInfo> tabList)
{
// Add all nested folders as tabs
if (node instanceof DBNDataSource && !((DBNDataSource)node).getDataSourceContainer().isConnected()) {
// Do not add children tabs
} else if (node != null) {
try {
DBNNode[] children = NavigatorUtils.getNodeChildrenFiltered(monitor, node, false);
if (children != null) {
for (DBNNode child : children) {
if (child instanceof DBNDatabaseFolder) {
DBNDatabaseFolder folder = (DBNDatabaseFolder)child;
monitor.subTask(CoreMessages.ui_properties_task_add_folder + child.getNodeName() + "'"); //$NON-NLS-2$
tabList.add(
new TabbedFolderInfo(
folder.getNodeName(),
folder.getNodeName(),
folder.getNodeIconDefault(),
child.getNodeDescription(),
false,//folder.getMeta().isInline(),
new TabbedFolderPageNode(part, folder, null)
));
}
}
}
} catch (DBException e) {
log.error("Error initializing property tabs", e); //$NON-NLS-1$
}
// Add itself as tab (if it has child items)
if (node instanceof DBNDatabaseNode) {
DBNDatabaseNode databaseNode = (DBNDatabaseNode)node;
List<DBXTreeNode> subNodes = databaseNode.getMeta().getChildren(databaseNode);
if (subNodes != null) {
for (DBXTreeNode child : subNodes) {
if (child instanceof DBXTreeItem) {
try {
if (!((DBXTreeItem)child).isOptional() || databaseNode.hasChildren(monitor, child)) {
monitor.subTask(CoreMessages.ui_properties_task_add_node + node.getNodeName() + "'"); //$NON-NLS-2$
String nodeName = child.getChildrenType(databaseNode.getObject().getDataSource());
tabList.add(
new TabbedFolderInfo(
nodeName,
nodeName,
node.getNodeIconDefault(),
node.getNodeDescription(),
false,
new TabbedFolderPageNode(part, node, child)));
}
} catch (DBException e) {
log.debug("Can't add child items tab", e); //$NON-NLS-1$
}
}
}
}
}
}
}
@Override
public DBNNode getRootNode() {
ITabbedFolder activeFolder = folderComposite.getActiveFolder();
if (activeFolder instanceof INavigatorModelView) {
return ((INavigatorModelView) activeFolder).getRootNode();
}
return null;
}
@Nullable
@Override
public Viewer getNavigatorViewer() {
ITabbedFolder activeFolder = folderComposite.getActiveFolder();
if (activeFolder instanceof INavigatorModelView) {
return ((INavigatorModelView) activeFolder).getNavigatorViewer();
}
return null;
}
}