diff options
author | Eike Stepper | 2022-02-01 13:53:59 +0000 |
---|---|---|
committer | Eike Stepper | 2022-02-02 07:47:11 +0000 |
commit | 7ab597b9d57d2e4bfbb886a9500a3047e7513689 (patch) | |
tree | 779c46f4892e27df218e2c20703d515378a74f40 /plugins/org.eclipse.emf.cdo.ui/src/org | |
parent | c23959f69d7c13f97c9a2100dba5f75f992c207d (diff) | |
download | cdo-7ab597b9d57d2e4bfbb886a9500a3047e7513689.tar.gz cdo-7ab597b9d57d2e4bfbb886a9500a3047e7513689.tar.xz cdo-7ab597b9d57d2e4bfbb886a9500a3047e7513689.zip |
[578512] [UI] Provide an exemplary view for CDORemoteTopics
https://bugs.eclipse.org/bugs/show_bug.cgi?id=578512
Diffstat (limited to 'plugins/org.eclipse.emf.cdo.ui/src/org')
6 files changed, 1562 insertions, 3 deletions
diff --git a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/bundle/OM.java b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/bundle/OM.java index 57256e95e3..6e841ac14e 100644 --- a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/bundle/OM.java +++ b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/bundle/OM.java @@ -11,6 +11,7 @@ */ package org.eclipse.emf.cdo.internal.ui.bundle; +import org.eclipse.emf.cdo.internal.ui.views.UserInfo; import org.eclipse.emf.cdo.ui.CDOEditorOpener; import org.eclipse.emf.cdo.ui.CDOLabelDecorator; import org.eclipse.emf.cdo.ui.OverlayImage; @@ -72,6 +73,18 @@ public abstract class OM public static final OMPreference<Long> PREF_LOCK_TIMEOUT = // PREFS.init("PREF_LOCK_TIMEOUT", 10000L); //$NON-NLS-1$ + public static final OMPreference<Boolean> PREF_TOPICS_LINK_WITH_EDITOR = // + PREFS.init("PREF_TOPICS_LINK_WITH_EDITOR", false); //$NON-NLS-1$ + + public static final OMPreference<String> PREF_USER_FIRST_NAME = // + PREFS.init("PREF_USER_FIRST_NAME", ""); //$NON-NLS-1$ + + public static final OMPreference<String> PREF_USER_LAST_NAME = // + PREFS.init("PREF_USER_LAST_NAME", ""); //$NON-NLS-1$ + + public static final OMPreference<String> PREF_USER_DISPLAY_NAME = // + PREFS.init("PREF_USER_DISPLAY_NAME", System.getProperty("user.name")); //$NON-NLS-1$ + private static Boolean historySupportAvailable; private static Boolean compareSupportAvailable; @@ -148,11 +161,13 @@ public abstract class OM protected void doStart() throws Exception { CDOEditorOpener.Registry.INSTANCE.activate(); + UserInfo.Manager.INSTANCE.activate(); } @Override protected void doStop() throws Exception { + UserInfo.Manager.INSTANCE.deactivate(); CDOEditorOpener.Registry.INSTANCE.deactivate(); } } diff --git a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/editor/CDOEditor.java b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/editor/CDOEditor.java index 2519dd2a29..f5950fce67 100644 --- a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/editor/CDOEditor.java +++ b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/editor/CDOEditor.java @@ -14,12 +14,15 @@ package org.eclipse.emf.cdo.internal.ui.editor; import org.eclipse.emf.cdo.CDOObject; import org.eclipse.emf.cdo.CDOState; +import org.eclipse.emf.cdo.common.branch.CDOBranch; +import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.common.id.CDOIDUtil; import org.eclipse.emf.cdo.common.model.CDOPackageInfo; import org.eclipse.emf.cdo.common.model.CDOPackageRegistry; import org.eclipse.emf.cdo.common.model.CDOPackageUnit; import org.eclipse.emf.cdo.common.model.EMFUtil; +import org.eclipse.emf.cdo.common.util.CDOCommonUtil; import org.eclipse.emf.cdo.eresource.CDOResource; import org.eclipse.emf.cdo.internal.ui.CDOContentProvider; import org.eclipse.emf.cdo.internal.ui.RunnableViewerRefresh; @@ -37,6 +40,7 @@ import org.eclipse.emf.cdo.ui.CDOEditorInput3; import org.eclipse.emf.cdo.ui.CDOEventHandler; import org.eclipse.emf.cdo.ui.CDOInvalidRootAgent; import org.eclipse.emf.cdo.ui.CDOLabelProvider; +import org.eclipse.emf.cdo.ui.CDOTopicProvider; import org.eclipse.emf.cdo.ui.CDOTreeExpansionAgent; import org.eclipse.emf.cdo.ui.shared.SharedIcons; import org.eclipse.emf.cdo.util.CDOURIUtil; @@ -50,6 +54,7 @@ import org.eclipse.emf.internal.cdo.view.CDOStateMachine; import org.eclipse.net4j.util.AdapterUtil; import org.eclipse.net4j.util.ReflectUtil; import org.eclipse.net4j.util.StringUtil; +import org.eclipse.net4j.util.collection.ConcurrentArray; import org.eclipse.net4j.util.lifecycle.LifecycleUtil; import org.eclipse.net4j.util.om.OMPlatform; import org.eclipse.net4j.util.om.trace.ContextTracer; @@ -193,13 +198,14 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; /** * @author Eike Stepper - * @generated + * @generated not */ -public class CDOEditor extends MultiPageEditorPart implements IEditingDomainProvider, ISelectionProvider, IMenuListener, IViewerProvider +public class CDOEditor extends MultiPageEditorPart implements IEditingDomainProvider, ISelectionProvider, IMenuListener, IViewerProvider, CDOTopicProvider { /** * The filters for file extensions supported by the editor. @@ -529,6 +535,17 @@ public class CDOEditor extends MultiPageEditorPart implements IEditingDomainProv protected final AtomicBoolean pagesCreated = new AtomicBoolean(); + protected final ConcurrentArray<Listener> topicListeners = new ConcurrentArray<>() + { + @Override + protected Listener[] newArray(int length) + { + return new Listener[length]; + } + }; + + protected Topic currentTopic; + /** * Handles activation of the editor or it's associated views. * <!-- begin-user-doc --> <!-- end-user-doc --> @@ -1381,6 +1398,12 @@ public class CDOEditor extends MultiPageEditorPart implements IEditingDomainProv eventHandler = new CDOEventHandler(view, selectionViewer) { @Override + protected void viewTargetChanged(CDOBranchPoint branchPoint) + { + setCurrentTopic(createTopic()); + } + + @Override protected void objectInvalidated(InternalCDOObject cdoObject) { if (CDOUtil.isLegacyObject(cdoObject)) @@ -1467,6 +1490,8 @@ public class CDOEditor extends MultiPageEditorPart implements IEditingDomainProv fireDirtyPropertyChange(); } }; + + setCurrentTopic(createTopic()); } catch (RuntimeException ex) { @@ -2261,6 +2286,80 @@ public class CDOEditor extends MultiPageEditorPart implements IEditingDomainProv setInputWithNotify(editorInput); } + private void setCurrentTopic(Topic topic) + { + if (!Objects.equals(currentTopic, topic)) + { + if (currentTopic != null) + { + for (Listener listener : topicListeners.get()) + { + listener.topicRemoved(this, currentTopic); + } + } + + currentTopic = topic; + + if (currentTopic != null) + { + for (Listener listener : topicListeners.get()) + { + listener.topicAdded(this, currentTopic); + } + } + } + } + + private Topic createTopic() + { + if (view != null) + { + ResourceSet resourceSet = view.getResourceSet(); + Resource resource = resourceSet.getResources().get(0); + + if (resource instanceof CDOResource) + { + CDOResource cdoResource = (CDOResource)resource; + String path = cdoResource.getPath(); + + CDOBranch branch = view.getBranch(); + long timeStamp = view.getTimeStamp(); + + String id = "org.eclipse.emf.cdo.resource" + path + "/" + branch.getID() + "/" + timeStamp; + Image image = getTitleImage(); + String text = path + " [" + branch.getName() + "/" + CDOCommonUtil.formatTimeStamp(timeStamp) + "]"; + String description = text; + + return new Topic(view.getSession(), id, image, text, description); + } + } + + return null; + } + + @Override + public Topic[] getTopics() + { + if (currentTopic != null) + { + return new Topic[] { currentTopic }; + } + + return new Topic[0]; + } + + @Override + public void addTopicListener(Listener listener) + { + topicListeners.add(listener); + } + + @Override + public void removeTopicListener(Listener listener) + { + topicListeners.remove(listener); + } + /** * <!-- begin-user-doc --> <!-- end-user-doc --> * @generated diff --git a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/views/CDORemoteTopicsView.java b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/views/CDORemoteTopicsView.java new file mode 100644 index 0000000000..2161b119de --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/views/CDORemoteTopicsView.java @@ -0,0 +1,758 @@ +/* + * Copyright (c) 2007-2013, 2015, 2016, 2019, 2020 Eike Stepper (Loehne, Germany) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Eike Stepper - initial API and implementation + */ +package org.eclipse.emf.cdo.internal.ui.views; + +import org.eclipse.emf.cdo.internal.ui.bundle.OM; +import org.eclipse.emf.cdo.internal.ui.views.UserInfo.Manager.UserChangedEvent; +import org.eclipse.emf.cdo.session.CDOSession; +import org.eclipse.emf.cdo.session.remote.CDORemoteSession; +import org.eclipse.emf.cdo.session.remote.CDORemoteSessionManager; +import org.eclipse.emf.cdo.session.remote.CDORemoteTopic; +import org.eclipse.emf.cdo.ui.CDOTopicProvider; +import org.eclipse.emf.cdo.ui.CDOTopicProvider.Listener; +import org.eclipse.emf.cdo.ui.CDOTopicProvider.Topic; +import org.eclipse.emf.cdo.ui.shared.SharedIcons; + +import org.eclipse.net4j.ui.shared.CollapseAllAction; +import org.eclipse.net4j.ui.shared.ExpandAllAction; +import org.eclipse.net4j.ui.shared.LinkWithEditorAction; +import org.eclipse.net4j.util.AdapterUtil; +import org.eclipse.net4j.util.container.IContainerEvent; +import org.eclipse.net4j.util.container.IContainerEventVisitor; +import org.eclipse.net4j.util.event.EventUtil; +import org.eclipse.net4j.util.event.IEvent; +import org.eclipse.net4j.util.event.IListener; +import org.eclipse.net4j.util.lifecycle.ILifecycleEvent; +import org.eclipse.net4j.util.lifecycle.ILifecycleEvent.Kind; +import org.eclipse.net4j.util.om.OMPlatform; +import org.eclipse.net4j.util.ui.GlobalPartAdapter; +import org.eclipse.net4j.util.ui.UIUtil; +import org.eclipse.net4j.util.ui.views.ContainerItemProvider; + +import org.eclipse.emf.common.notify.AdapterFactory; +import org.eclipse.emf.common.notify.Notification; +import org.eclipse.emf.edit.provider.ComposedAdapterFactory; +import org.eclipse.emf.edit.provider.ItemProvider; +import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider; +import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.dialogs.InputDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchPartReference; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.part.ISetSelectionTarget; +import org.eclipse.ui.part.ViewPart; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * @author Eike Stepper + */ +public class CDORemoteTopicsView extends ViewPart implements ISelectionProvider, ISetSelectionTarget +{ + public static final String ID = "org.eclipse.emf.cdo.ui.CDORemoteTopicsView"; //$NON-NLS-1$ + + private static final boolean LOCAL_USER_INFO_HIDE = // + OMPlatform.INSTANCE.isProperty("org.eclipse.emf.cdo.ui.CDORemoteTopicsView.LOCAL_USER_INFO_HIDE"); //$NON-NLS-1$ + + private static final boolean LOCAL_USER_INFO_DISABLE = // + OMPlatform.INSTANCE.isProperty("org.eclipse.emf.cdo.ui.CDORemoteTopicsView.LOCAL_USER_INFO_DISABLE"); //$NON-NLS-1$ + + private static final boolean EXPAND_SELECTION = // + OMPlatform.INSTANCE.isProperty("org.eclipse.emf.cdo.ui.CDORemoteTopicsView.EXPAND_SELECTION"); //$NON-NLS-1$ + + private AutoCloseable userInfoListener; + + private final Map<Topic, CDORemoteTopic> remoteTopics = new HashMap<>(); + + private final Map<CDORemoteTopic, RemoteTopicItem> remoteTopicItems = new HashMap<>(); + + private GlobalPartAdapter workbenchListener = new GlobalPartAdapter(false) + { + @Override + public void partOpened(IWorkbenchPartReference partRef) + { + CDOTopicProvider provider = getTopicProvider(partRef); + if (provider != null) + { + addTopics(provider.getTopics()); + provider.addTopicListener(topicListener); + } + } + + @Override + public void partClosed(IWorkbenchPartReference partRef) + { + CDOTopicProvider provider = getTopicProvider(partRef); + if (provider != null) + { + provider.removeTopicListener(topicListener); + removeTopics(provider.getTopics()); + } + } + + @Override + public void partActivated(IWorkbenchPartReference partRef) + { + if (linkWithEditor) + { + CDOTopicProvider provider = getTopicProvider(partRef); + if (provider != null) + { + selectTopics(provider.getTopics()); + } + } + } + }; + + private final Listener topicListener = new Listener() + { + @Override + public void topicAdded(CDOTopicProvider provider, Topic topic) + { + addTopics(topic); + } + + @Override + public void topicRemoved(CDOTopicProvider provider, Topic topic) + { + removeTopics(topic); + } + }; + + private final AdapterFactory adapterFactory = new ComposedAdapterFactory(); + + private final ItemProvider root = new ItemProvider(adapterFactory); + + private Shell shell; + + private TreeViewer viewer; + + private boolean linkWithEditor = OM.PREF_TOPICS_LINK_WITH_EDITOR.getValue(); + + private boolean inEditorActivation; + + private ISelectionChangedListener selectionListener = event -> { + if (!inEditorActivation) + { + ITreeSelection selection = (ITreeSelection)event.getSelection(); + IActionBars bars = getViewSite().getActionBars(); + selectionChanged(bars, selection); + } + }; + + public CDORemoteTopicsView() + { + } + + public Shell getShell() + { + return shell; + } + + public TreeViewer getViewer() + { + return viewer; + } + + @Override + public void setFocus() + { + viewer.getControl().setFocus(); + } + + @Override + public void dispose() + { + workbenchListener.dispose(); + + try + { + userInfoListener.close(); + } + catch (Exception ex) + { + OM.LOG.error(ex); + } + + super.dispose(); + } + + @Override + public ISelection getSelection() + { + if (viewer != null) + { + return viewer.getSelection(); + } + + return StructuredSelection.EMPTY; + } + + @Override + public void setSelection(ISelection selection) + { + if (viewer != null) + { + viewer.setSelection(selection); + } + } + + @Override + public void addSelectionChangedListener(ISelectionChangedListener listener) + { + if (viewer != null) + { + viewer.addSelectionChangedListener(listener); + } + } + + @Override + public void removeSelectionChangedListener(ISelectionChangedListener listener) + { + if (viewer != null) + { + viewer.removeSelectionChangedListener(listener); + } + } + + @Override + public void selectReveal(ISelection selection) + { + if (viewer != null) + { + viewer.setSelection(selection, true); + } + } + + @Override + public final void createPartControl(Composite parent) + { + shell = parent.getShell(); + Composite composite = UIUtil.createGridComposite(parent, 1); + + Control control = createUI(composite); + control.setLayoutData(UIUtil.createGridData()); + + hookContextMenu(); + hookDoubleClick(); + contributeToActionBars(); + + workbenchListener.register(true); + getSite().setSelectionProvider(this); + } + + protected Control createUI(Composite parent) + { + AdapterFactoryLabelProvider labelProvider = new AdapterFactoryLabelProvider(adapterFactory); + labelProvider.setFireLabelUpdateNotifications(true); + + viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + viewer.setContentProvider(new AdapterFactoryContentProvider(adapterFactory)); + viewer.setLabelProvider(labelProvider); + viewer.setComparator(new ViewerComparator()); + viewer.setInput(root); + viewer.addSelectionChangedListener(selectionListener); + + userInfoListener = EventUtil.addListener(UserInfo.Manager.INSTANCE, UserChangedEvent.class, e -> { + CDORemoteSession remoteSession = e.getRemoteSession(); + if (remoteSession != null) + { + for (Object topic : root.getChildren()) + { + for (Object child : ((RemoteTopicItem)topic).getChildren()) + { + RemoteMemberItem member = (RemoteMemberItem)child; + if (member.getRemoteSession() == remoteSession) + { + UIUtil.asyncExec(() -> member.updateText()); + } + } + } + } + }); + + return viewer.getControl(); + } + + protected void hookDoubleClick() + { + viewer.addDoubleClickListener(e -> doubleClicked(((ITreeSelection)viewer.getSelection()).getFirstElement())); + } + + protected void hookContextMenu() + { + MenuManager manager = new MenuManager("#PopupMenu"); //$NON-NLS-1$ + manager.setRemoveAllWhenShown(true); + manager.addMenuListener(m -> fillContextMenu(m, (ITreeSelection)viewer.getSelection())); + + Menu menu = manager.createContextMenu(viewer.getControl()); + viewer.getControl().setMenu(menu); + getViewSite().registerContextMenu(manager, viewer); + } + + protected void contributeToActionBars() + { + IActionBars bars = getViewSite().getActionBars(); + fillLocalPullDown(bars.getMenuManager()); + fillLocalToolBar(bars.getToolBarManager()); + } + + protected void fillLocalPullDown(IMenuManager manager) + { + } + + protected void fillLocalToolBar(IToolBarManager manager) + { + if (!LOCAL_USER_INFO_HIDE) + { + UserInfo localUser = UserInfo.Manager.INSTANCE.getLocalUser(); + + manager.add(new Action(localUser.getDisplayName(), IAction.AS_PUSH_BUTTON) + { + { + setToolTipText("Change Local Name"); + setEnabled(!LOCAL_USER_INFO_DISABLE); + } + + @Override + public void run() + { + String displayName = localUser.getDisplayName(); + InputDialog dialog = new InputDialog(getShell(), getToolTipText(), "Local name:", displayName, null); + if (dialog.open() == InputDialog.OK) + { + String newDisplayName = dialog.getValue(); + UserInfo.Manager.INSTANCE.changeLocalUser(localUser.getFirstName(), localUser.getLastName(), newDisplayName); + + newDisplayName = localUser.getDisplayName(); // Could be different if it was originally empty. + if (!Objects.equals(newDisplayName, displayName)) + { + setText(newDisplayName); + + if (manager instanceof ToolBarManager) + { + ToolBar toolBar = ((ToolBarManager)manager).getControl(); + toolBar.getParent().pack(true); + } + } + } + } + }); + } + + manager.add(new ExpandAllAction(viewer)); + manager.add(new CollapseAllAction(viewer)); + + LinkWithEditorAction linkAction = new LinkWithEditorAction() + { + @Override + protected void linkWithEditorChanged(boolean linkWithEditor) + { + CDORemoteTopicsView.this.linkWithEditor = linkWithEditor; + OM.PREF_TOPICS_LINK_WITH_EDITOR.setValue(linkWithEditor); + } + }; + + linkAction.setChecked(linkWithEditor); + manager.add(linkAction); + } + + protected void fillContextMenu(IMenuManager manager, ITreeSelection selection) + { + } + + protected void selectionChanged(IActionBars bars, ITreeSelection selection) + { + if (linkWithEditor) + { + Object element = selection.getFirstElement(); + if (element instanceof RemoteMemberItem) + { + element = ((RemoteMemberItem)element).getParent(); + } + + if (element instanceof RemoteTopicItem) + { + RemoteTopicItem remoteTopicItem = (RemoteTopicItem)element; + Topic topic = remoteTopicItem.getTopic(); + + IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + if (!activatePart(topic, activePage.getEditorReferences())) + { + activatePart(topic, activePage.getViewReferences()); + } + } + } + } + + protected void doubleClicked(Object object) + { + if (object instanceof ContainerItemProvider.ErrorElement) + { + try + { + UIUtil.getActiveWorkbenchPage().showView(UIUtil.ERROR_LOG_ID); + } + catch (PartInitException ex) + { + OM.LOG.error(ex); + } + } + else if (object != null && viewer.isExpandable(object)) + { + if (viewer.getExpandedState(object)) + { + viewer.collapseToLevel(object, TreeViewer.ALL_LEVELS); + } + else + { + viewer.expandToLevel(object, 1); + } + } + } + + /** + * @since 3.1 + */ + protected void refreshPressed() + { + UIUtil.refreshViewer(viewer); + } + + /** + * @since 3.3 + */ + protected void collapseAllPressed() + { + getViewer().collapseAll(); + } + + protected void closeView() + { + UIUtil.syncExec(getDisplay(), () -> { + getSite().getPage().hideView(CDORemoteTopicsView.this); + dispose(); + }); + } + + protected Display getDisplay() + { + Display display = viewer.getControl().getDisplay(); + if (display == null) + { + display = UIUtil.getDisplay(); + } + + return display; + } + + public Topic getTopic(CDORemoteTopic remoteTopic) + { + synchronized (remoteTopics) + { + RemoteTopicItem item = remoteTopicItems.get(remoteTopic); + return item == null ? null : item.getTopic(); + } + } + + protected void addTopics(Topic... topics) + { + synchronized (remoteTopics) + { + for (Topic topic : topics) + { + CDORemoteTopic remoteTopic = remoteTopics.computeIfAbsent(topic, k -> { + CDOSession session = topic.getSession(); + String id = topic.getId(); + + CDORemoteSessionManager remoteSessionManager = session.getRemoteSessionManager(); + return remoteSessionManager.subscribeTopic(id); + }); + + RemoteTopicItem item = remoteTopicItems.computeIfAbsent(remoteTopic, k -> { + RemoteTopicItem newItem = new RemoteTopicItem(topic, remoteTopic); + root.getChildren().add(newItem); + return newItem; + }); + + item.reference(); + } + } + } + + protected void removeTopics(Topic... topics) + { + synchronized (remoteTopics) + { + for (Topic topic : topics) + { + CDORemoteTopic remoteTopic = remoteTopics.get(topic); + if (remoteTopic != null) + { + RemoteTopicItem item = remoteTopicItems.get(remoteTopic); + if (item.dereference() == 0) + { + remoteTopic.unsubscribe(); + + remoteTopics.remove(topic); + remoteTopicItems.remove(remoteTopic); + root.getChildren().remove(item); + } + } + } + } + } + + protected void selectTopics(Topic... topics) + { + List<RemoteTopicItem> items = new ArrayList<>(); + + synchronized (remoteTopics) + { + for (Topic topic : topics) + { + CDORemoteTopic remoteTopic = remoteTopics.get(topic); + if (remoteTopic != null) + { + RemoteTopicItem item = remoteTopicItems.get(remoteTopic); + if (item != null) + { + items.add(item); + } + } + } + } + + try + { + inEditorActivation = true; + + IStructuredSelection selection = new StructuredSelection(items); + viewer.setSelection(selection); + + if (EXPAND_SELECTION) + { + viewer.setExpandedElements(items.toArray()); + } + } + finally + { + inEditorActivation = false; + } + } + + private static CDOTopicProvider getTopicProvider(IWorkbenchPartReference partRef) + { + IWorkbenchPart part = partRef.getPart(true); + return AdapterUtil.adapt(part, CDOTopicProvider.class); + } + + private static boolean activatePart(Topic selectedTopic, IWorkbenchPartReference[] partRefs) + { + for (IWorkbenchPartReference partRef : partRefs) + { + CDOTopicProvider topicProvider = getTopicProvider(partRef); + if (topicProvider != null) + { + for (Topic topic : topicProvider.getTopics()) + { + if (Objects.equals(topic, selectedTopic)) + { + partRef.getPage().activate(partRef.getPart(true)); + return true; + } + } + } + } + + return false; + } + + /** + * @author Eike Stepper + */ + private class Item extends ItemProvider + { + protected Item() + { + super(CDORemoteTopicsView.this.adapterFactory); + } + + /** + * Work around EMF bug 578508. + */ + @Override + public void setText(Object object, String text) + { + this.text = text; + + fireNotifyChanged(new ItemProviderNotification(Notification.SET, null, text, Notification.NO_INDEX, false, false, true) + { + @Override + public boolean isLabelUpdate() + { + return isLabelUpdate; + } + }); + } + } + + /** + * @author Eike Stepper + */ + private final class RemoteTopicItem extends Item implements IListener + { + private final Topic topic; + + private final CDORemoteTopic remoteTopic; + + private int refCount; + + public RemoteTopicItem(Topic topic, CDORemoteTopic remoteTopic) + { + this.topic = topic; + this.remoteTopic = remoteTopic; + + setImage(topic.getImage()); + setText(topic.getText()); + + List<RemoteMemberItem> children = new ArrayList<>(); + for (CDORemoteSession remoteSession : remoteTopic.getRemoteSessions()) + { + RemoteMemberItem item = new RemoteMemberItem(remoteSession); + children.add(item); + } + + getChildren().addAll(children); + remoteTopic.addListener(this); + } + + public Topic getTopic() + { + return topic; + } + + @Override + public void notifyEvent(IEvent event) + { + if (event instanceof ILifecycleEvent) + { + ILifecycleEvent e = (ILifecycleEvent)event; + if (e.getKind() == Kind.DEACTIVATED) + { + dispose(); + } + } + else if (event instanceof IContainerEvent) + { + @SuppressWarnings("unchecked") + IContainerEvent<CDORemoteSession> e = (IContainerEvent<CDORemoteSession>)event; + + UIUtil.asyncExec(() -> e.accept(new IContainerEventVisitor<>() + { + @Override + public void added(CDORemoteSession remoteSession) + { + RemoteMemberItem item = new RemoteMemberItem(remoteSession); + getChildren().add(item); + } + + @Override + public void removed(CDORemoteSession remoteSession) + { + for (Iterator<Object> it = getChildren().iterator(); it.hasNext();) + { + RemoteMemberItem item = (RemoteMemberItem)it.next(); + if (item.getRemoteSession() == remoteSession) + { + it.remove(); + break; + } + } + } + })); + } + } + + @Override + public void dispose() + { + remoteTopic.removeListener(this); + } + + private void reference() + { + ++refCount; + } + + private int dereference() + { + return --refCount; + } + } + + /** + * @author Eike Stepper + */ + private final class RemoteMemberItem extends Item + { + private final CDORemoteSession remoteSession; + + public RemoteMemberItem(CDORemoteSession remoteSession) + { + this.remoteSession = remoteSession; + + setImage(SharedIcons.getImage(SharedIcons.OBJ_PERSON)); + updateText(); + } + + public void updateText() + { + UserInfo userInfo = UserInfo.Manager.INSTANCE.getRemoteUser(remoteSession); + setText(userInfo.getDisplayName()); + } + + public CDORemoteSession getRemoteSession() + { + return remoteSession; + } + } +} diff --git a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/views/UserInfo.java b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/views/UserInfo.java new file mode 100644 index 0000000000..0d39f15f32 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/views/UserInfo.java @@ -0,0 +1,539 @@ +/* + * Copyright (c) 2021 Eike Stepper (Loehne, Germany) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Eike Stepper - initial API and implementation + */ +package org.eclipse.emf.cdo.internal.ui.views; + +import org.eclipse.emf.cdo.internal.ui.bundle.OM; +import org.eclipse.emf.cdo.session.remote.CDORemoteSession; +import org.eclipse.emf.cdo.session.remote.CDORemoteSessionRequest; +import org.eclipse.emf.cdo.session.remote.CDORemoteSessionRequest.GlobalRequestHandler; + +import org.eclipse.net4j.util.StringUtil; +import org.eclipse.net4j.util.container.IManagedContainer; +import org.eclipse.net4j.util.container.IPluginContainer; +import org.eclipse.net4j.util.event.Event; +import org.eclipse.net4j.util.factory.ProductCreationException; +import org.eclipse.net4j.util.io.IOUtil; +import org.eclipse.net4j.util.lifecycle.Lifecycle; +import org.eclipse.net4j.util.om.OMPlatform; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +/** + * @author Eike Stepper + */ +public final class UserInfo +{ + private static final String UNKNOWN = "Unknown"; + + private String firstName; + + private String lastName; + + private String displayName; + + UserInfo() + { + this(null, null, null); + } + + public UserInfo(String firstName, String lastName, String displayName) + { + change(firstName, lastName, displayName); + } + + public String getFirstName() + { + return firstName; + } + + public String getLastName() + { + return lastName; + } + + public String getDisplayName() + { + return displayName; + } + + @Override + public String toString() + { + return "UserInfo[" + displayName + "]"; + } + + void change(String firstName, String lastName, String displayName) + { + this.firstName = StringUtil.isEmpty(firstName) ? UNKNOWN : firstName; + this.lastName = StringUtil.isEmpty(lastName) ? UNKNOWN : lastName; + this.displayName = StringUtil.isEmpty(displayName) ? UNKNOWN : displayName; + } + + void change(UserInfo userInfo) + { + change(userInfo.getFirstName(), userInfo.getLastName(), userInfo.getDisplayName()); + } + + byte[] serialize() + { + String string = firstName + StringUtil.NL + lastName + StringUtil.NL + displayName; + return string.getBytes(StandardCharsets.UTF_8); + } + + void deserialize(byte[] data) + { + String string = new String(data, StandardCharsets.UTF_8); + StringTokenizer tokenizer = new StringTokenizer(string, StringUtil.NL); + change(tokenizer.nextToken(), tokenizer.nextToken(), tokenizer.nextToken()); + } + + /** + * @author Eike Stepper + */ + public static final class Manager extends Lifecycle + { + public static final UserInfo.Manager INSTANCE = new Manager(); + + private final Map<CDORemoteSession, UserInfo> remoteUsers = new HashMap<>(); + + private UserInfoStorage localUserInfoStorage; + + private UserInfo localUser; + + private GlobalRequestHandler userInfoRequestHandler; + + private GlobalRequestHandler userInfoNotificationHandler; + + private Manager() + { + } + + public synchronized UserInfo getLocalUser() + { + return localUser; + } + + public void changeLocalUser(String firstName, String lastName, String displayName) + { + UserInfo userInfo; + CDORemoteSession[] remoteSessions; + + synchronized (this) + { + if (localUser != null) + { + localUser.change(firstName, lastName, displayName); + } + + userInfo = localUser; + remoteSessions = remoteUsers.keySet().toArray(new CDORemoteSession[remoteUsers.size()]); + } + + if (userInfo != null) + { + if (localUserInfoStorage instanceof UserInfoStorage.Writable) + { + UserInfoStorage.Writable writable = (UserInfoStorage.Writable)localUserInfoStorage; + + try + { + writable.saveUserInfo(userInfo); + } + catch (Exception ex) + { + OM.LOG.error(ex); + } + } + + fireEvent(new UserChangedEvent(null, userInfo)); + + byte[] data = userInfo.serialize(); + for (CDORemoteSession remoteSession : remoteSessions) + { + new CDORemoteSessionRequest(UserInfoNotificationHandler.TYPE, data) // + .send(remoteSession); + } + } + } + + public synchronized UserInfo getRemoteUser(CDORemoteSession remoteSession) + { + return remoteUsers.computeIfAbsent(remoteSession, k -> { + UserInfo userInfo = new UserInfo(); + + new CDORemoteSessionRequest(UserInfoRequestHandler.TYPE) // + .onResponse(data -> changeRemoteUser(remoteSession, data)) // + .send(remoteSession); + + return userInfo; + }); + } + + void changeRemoteUser(CDORemoteSession remoteSession, byte[] data) + { + UserInfo userInfo; + synchronized (this) + { + userInfo = remoteUsers.get(remoteSession); + if (userInfo != null) + { + userInfo.deserialize(data); + } + } + + if (userInfo != null) + { + fireEvent(new UserChangedEvent(remoteSession, userInfo)); + } + } + + @Override + protected void doActivate() throws Exception + { + super.doActivate(); + + localUserInfoStorage = UserInfoStorage.Factory.get(IPluginContainer.INSTANCE); + localUser = localUserInfoStorage == null ? null : localUserInfoStorage.loadUserInfo(); + + if (localUser == null) + { + localUser = new UserInfo(); + } + + userInfoRequestHandler = new UserInfoRequestHandler(); + userInfoNotificationHandler = new UserInfoNotificationHandler(); + } + + @Override + protected void doDeactivate() throws Exception + { + userInfoNotificationHandler.deactivate(); + userInfoNotificationHandler = null; + + userInfoRequestHandler.deactivate(); + userInfoRequestHandler = null; + + localUser = null; + super.doDeactivate(); + } + + /** + * @author Eike Stepper + */ + public final class UserChangedEvent extends Event + { + private static final long serialVersionUID = 1L; + + private final CDORemoteSession remoteSession; + + private final UserInfo userInfo; + + protected UserChangedEvent(CDORemoteSession remoteSession, UserInfo userInfo) + { + super(Manager.this); + this.remoteSession = remoteSession; + this.userInfo = userInfo; + } + + @Override + public UserInfo.Manager getSource() + { + return (UserInfo.Manager)super.getSource(); + } + + public CDORemoteSession getRemoteSession() + { + return remoteSession; + } + + public UserInfo getUserInfo() + { + return userInfo; + } + } + + /** + * @author Eike Stepper + */ + private static final class UserInfoRequestHandler extends GlobalRequestHandler + { + private static final String TYPE = "org.eclipse.emf.cdo.ui.UserInfo"; + + public UserInfoRequestHandler() + { + super(TYPE); + } + + @Override + protected byte[] createResponse(CDORemoteSession sender, byte[] request) + { + UserInfo userInfo = INSTANCE.getLocalUser(); + return userInfo.serialize(); + } + } + + /** + * @author Eike Stepper + */ + private static final class UserInfoNotificationHandler extends GlobalRequestHandler + { + private static final String TYPE = "org.eclipse.emf.cdo.ui.UserInfoChanged"; + + public UserInfoNotificationHandler() + { + super(TYPE); + } + + @Override + protected byte[] createResponse(CDORemoteSession sender, byte[] request) + { + INSTANCE.changeRemoteUser(sender, request); + return null; + } + } + + /** + * @author Eike Stepper + */ + public interface UserInfoStorage + { + public UserInfo loadUserInfo() throws IOException; + + /** + * @author Eike Stepper + */ + public interface Writable extends UserInfoStorage + { + public void saveUserInfo(UserInfo userInfo) throws IOException; + } + + /** + * @author Eike Stepper + */ + public static abstract class Factory extends org.eclipse.net4j.util.factory.Factory + { + public static final String PRODUCT_GROUP = "org.eclipse.emf.cdo.ui.userInfoStorages"; + + private static final String DEFAULT_TYPE = OMPlatform.INSTANCE.getProperty("org.eclipse.emf.cdo.ui.UserInfoStorageFactory.DEFAULT_TYPE", + PreferencesUserInfoStorage.Factory.TYPE); + + private static final String DEFAULT_DESCRIPTION = OMPlatform.INSTANCE.getProperty("org.eclipse.emf.cdo.ui.UserInfoStorageFactory.DEFAULT_DESCRIPTION"); + + public Factory(String type) + { + super(PRODUCT_GROUP, type); + } + + @Override + public abstract UserInfoStorage create(String description) throws ProductCreationException; + + public static UserInfoStorage get(IManagedContainer container) + { + return get(container, null); + } + + public static UserInfoStorage get(IManagedContainer container, String type) + { + return get(container, type, null); + } + + public static UserInfoStorage get(IManagedContainer container, String type, String description) + { + if (type == null) + { + type = DEFAULT_TYPE; + } + + if (description == null) + { + description = DEFAULT_DESCRIPTION; + } + + return container.getElementOrNull(PRODUCT_GROUP, type, description); + } + } + } + + /** + * @author Eike Stepper + */ + public static final class PreferencesUserInfoStorage implements UserInfoStorage.Writable + { + public PreferencesUserInfoStorage() + { + } + + @Override + public UserInfo loadUserInfo() throws IOException + { + String firstName = OM.PREF_USER_FIRST_NAME.getValue(); + String lastName = OM.PREF_USER_LAST_NAME.getValue(); + String displayName = OM.PREF_USER_DISPLAY_NAME.getValue(); + return new UserInfo(firstName, lastName, displayName); + } + + @Override + public void saveUserInfo(UserInfo userInfo) throws IOException + { + OM.PREF_USER_FIRST_NAME.setValue(userInfo.getFirstName()); + OM.PREF_USER_LAST_NAME.setValue(userInfo.getLastName()); + OM.PREF_USER_DISPLAY_NAME.setValue(userInfo.getDisplayName()); + } + + /** + * @author Eike Stepper + */ + public static final class Factory extends UserInfoStorage.Factory + { + public static final String TYPE = "preferences"; + + public Factory() + { + super(TYPE); + } + + @Override + public UserInfoStorage create(String description) throws ProductCreationException + { + return new PreferencesUserInfoStorage(); + } + } + } + + /** + * @author Eike Stepper + */ + public static final class HomeUserInfoStorage implements UserInfoStorage.Writable + { + private static final String PROP_FIRST_NAME = "firstName"; + + private static final String PROP_LAST_NAME = "lastName"; + + private static final String PROP_DISPLAY_NAME = "displayName"; + + private static final File FILE = new File(OM.BUNDLE.getUserLocation(), "user.properties"); + + public HomeUserInfoStorage() + { + FILE.getParentFile().mkdirs(); + } + + @Override + public UserInfo loadUserInfo() throws IOException + { + if (FILE.isFile()) + { + FileInputStream in = null; + + try + { + in = new FileInputStream(FILE); + + Properties properties = new Properties(); + properties.load(in); + + String firstName = properties.getProperty(PROP_FIRST_NAME); + String lastName = properties.getProperty(PROP_LAST_NAME); + String displayName = properties.getProperty(PROP_DISPLAY_NAME); + + return new UserInfo(firstName, lastName, displayName); + } + catch (Exception ex) + { + OM.LOG.error(ex); + } + finally + { + IOUtil.close(in); + } + } + + return null; + } + + @Override + public void saveUserInfo(UserInfo userInfo) throws IOException + { + OutputStream out = null; + + try + { + out = new FileOutputStream(FILE); + + Properties properties = new Properties(); + properties.setProperty(PROP_FIRST_NAME, userInfo.getFirstName()); + properties.setProperty(PROP_LAST_NAME, userInfo.getLastName()); + properties.setProperty(PROP_DISPLAY_NAME, userInfo.getDisplayName()); + properties.store(out, "Local user information"); + } + catch (IOException ex) + { + OM.LOG.error(ex); + } + finally + { + IOUtil.close(out); + } + } + + public static void saveProperties(File folder, String fileName, Properties properties, String comment) + { + OutputStream out = null; + + try + { + folder.mkdirs(); + + File file = new File(folder, fileName); + out = new FileOutputStream(file); + + properties.store(out, comment); + } + catch (IOException ex) + { + OM.LOG.error(ex); + } + finally + { + IOUtil.close(out); + } + } + + /** + * @author Eike Stepper + */ + public static final class Factory extends UserInfoStorage.Factory + { + public static final String TYPE = "home"; + + public Factory() + { + super(TYPE); + } + + @Override + public UserInfoStorage create(String description) throws ProductCreationException + { + return new HomeUserInfoStorage(); + } + } + } + } +} diff --git a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/ui/CDOEventHandler.java b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/ui/CDOEventHandler.java index d997a6ae8c..440c97a433 100644 --- a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/ui/CDOEventHandler.java +++ b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/ui/CDOEventHandler.java @@ -14,6 +14,7 @@ package org.eclipse.emf.cdo.ui; import org.eclipse.emf.cdo.CDOObject; import org.eclipse.emf.cdo.CDOState; +import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; import org.eclipse.emf.cdo.internal.ui.ItemsProcessor; import org.eclipse.emf.cdo.internal.ui.bundle.OM; import org.eclipse.emf.cdo.session.CDOSession; @@ -24,6 +25,7 @@ import org.eclipse.emf.cdo.view.CDOObjectHandler; import org.eclipse.emf.cdo.view.CDOView; import org.eclipse.emf.cdo.view.CDOViewInvalidationEvent; import org.eclipse.emf.cdo.view.CDOViewLocksChangedEvent; +import org.eclipse.emf.cdo.view.CDOViewTargetChangedEvent; import org.eclipse.net4j.util.container.IContainerDelta; import org.eclipse.net4j.util.container.IContainerEvent; @@ -88,7 +90,12 @@ public class CDOEventHandler @Override public void notifyEvent(IEvent event) { - if (event instanceof CDOViewInvalidationEvent) + if (event instanceof CDOViewTargetChangedEvent) + { + CDOViewTargetChangedEvent e = (CDOViewTargetChangedEvent)event; + viewTargetChanged(e.getBranchPoint()); + } + else if (event instanceof CDOViewInvalidationEvent) { CDOViewInvalidationEvent e = (CDOViewInvalidationEvent)event; // Remove detached object from selection, could incur into unwanted exceptions @@ -375,6 +382,13 @@ public class CDOEventHandler { } + /** + * @since 4.12 + */ + protected void viewTargetChanged(CDOBranchPoint branchPoint) + { + } + protected void viewClosed() { } diff --git a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/ui/CDOTopicProvider.java b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/ui/CDOTopicProvider.java new file mode 100644 index 0000000000..3b6d26603a --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/ui/CDOTopicProvider.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2022 Eike Stepper (Loehne, Germany) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Eike Stepper - initial API and implementation + */ +package org.eclipse.emf.cdo.ui; + +import org.eclipse.emf.cdo.session.CDOSession; + +import org.eclipse.net4j.util.StringUtil; + +import org.eclipse.swt.graphics.Image; + +import java.util.Objects; + +/** + * @author Eike Stepper + * @since 4.12 + */ +public interface CDOTopicProvider +{ + public Topic[] getTopics(); + + public void addTopicListener(Listener listener); + + public void removeTopicListener(Listener listener); + + /** + * @author Eike Stepper + */ + public static final class Topic + { + private final CDOSession session; + + private final String id; + + private final Image image; + + private final String text; + + private final String description; + + public Topic(CDOSession session, String id, Image image, String text, String description) + { + this.session = session; + this.id = id; + this.image = image; + this.text = text; + this.description = description; + } + + public CDOSession getSession() + { + return session; + } + + public String getId() + { + return id; + } + + public Image getImage() + { + return image; + } + + public String getText() + { + return text; + } + + public String getDescription() + { + return description; + } + + @Override + public int hashCode() + { + return Objects.hash(session, id); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + Topic other = (Topic)obj; + return Objects.equals(session, other.session) && Objects.equals(id, other.id); + } + + @Override + public String toString() + { + String string = session.getRepositoryInfo().getName() + "/" + id; + + String userID = session.getUserID(); + if (!StringUtil.isEmpty(userID)) + { + string = userID + "@" + string; + } + + return string; + } + } + + /** + * @author Eike Stepper + */ + public interface Listener + { + public void topicAdded(CDOTopicProvider provider, Topic topic); + + public void topicRemoved(CDOTopicProvider provider, Topic topic); + } +} |