blob: d6764b78f73461a12aad4df53190502748d7e2f7 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2006, 2020 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.nico.ui.views;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IDebugEventSetListener;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.TableDragSourceEffect;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.menus.CommandContributionItem;
import org.eclipse.ui.menus.CommandContributionItemParameter;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.ts.core.Tool;
import org.eclipse.statet.jcommons.ts.core.ToolRunnable;
import org.eclipse.statet.ecommons.ts.ui.workbench.WorkbenchToolRegistry;
import org.eclipse.statet.ecommons.ts.ui.workbench.WorkbenchToolRegistryListener;
import org.eclipse.statet.ecommons.ts.ui.workbench.WorkbenchToolSessionData;
import org.eclipse.statet.ecommons.ui.util.UIAccess;
import org.eclipse.statet.internal.nico.ui.LocalTaskTransfer;
import org.eclipse.statet.internal.nico.ui.Messages;
import org.eclipse.statet.nico.core.runtime.Queue;
import org.eclipse.statet.nico.core.runtime.ToolProcess;
import org.eclipse.statet.nico.ui.NicoUI;
import org.eclipse.statet.nico.ui.NicoUITools;
import org.eclipse.statet.nico.ui.util.ToolProgressGroup;
/**
* A view for the queue of a tool process.
*
* Usage: This class is not intended to be subclassed.
*/
public class QueueView extends ViewPart {
private class ViewContentProvider implements IStructuredContentProvider, IDebugEventSetListener {
private volatile boolean expectInfoEvent= false;
private ImList<ToolRunnable> refreshData;
@Override
public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) {
if (oldInput != null && newInput == null) {
unregister();
}
if (newInput != null) {
final ToolProcess newProcess= (ToolProcess) newInput;
final DebugPlugin manager= DebugPlugin.getDefault();
if (manager != null) {
manager.addDebugEventListener(this);
}
}
}
@Override
public Object[] getElements(final Object inputElement) {
ToolRunnable[] elements;
if (this.refreshData != null) {
elements= this.refreshData.toArray(new ToolRunnable[this.refreshData.size()]);
this.refreshData= null;
}
else {
elements= new ToolRunnable[0];
final Queue queue= getQueue();
if (queue != null) {
this.expectInfoEvent= true;
queue.sendElements();
}
}
return elements;
}
private void unregister() {
final DebugPlugin manager= DebugPlugin.getDefault();
if (manager != null) {
manager.removeDebugEventListener(this);
}
}
@Override
public void dispose() {
unregister();
}
private void setElements(final ImList<ToolRunnable> elements) {
UIAccess.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
if (!UIAccess.isOkToUse(QueueView.this.tableViewer)) {
return;
}
ViewContentProvider.this.refreshData= elements;
QueueView.this.tableViewer.refresh();
}
});
}
@Override
public void handleDebugEvents(final DebugEvent[] events) {
final ToolProcess process= QueueView.this.process;
if (process == null) {
return;
}
boolean updateProgress= false;
final Queue queue= process.getQueue();
EVENT: for (int i= 0; i < events.length; i++) {
final DebugEvent event= events[i];
final Object source= event.getSource();
if (source == queue) {
switch (event.getKind()) {
case DebugEvent.CHANGE:
if (event.getDetail() != DebugEvent.CONTENT) {
continue EVENT;
}
final Queue.TaskDelta taskDelta= (Queue.TaskDelta) event.getData();
switch (taskDelta.type) {
case ToolRunnable.ADDING_TO:
case ToolRunnable.MOVING_TO:
if (!this.expectInfoEvent) {
if (events.length > i + 1 && taskDelta.data.size() == 1) {
// Added and removed in same set
final DebugEvent next= events[i + 1];
if (next.getSource() == queue
&& next.getKind() == DebugEvent.CHANGE
&& next.getDetail() == DebugEvent.CONTENT) {
final Queue.TaskDelta nextDelta= (Queue.TaskDelta) next.getData();
if (nextDelta.type == ToolRunnable.STARTING
&& taskDelta.data.get(0) == nextDelta.data.get(0)) {
updateProgress= true;
i++;
continue EVENT;
}
}
}
UIAccess.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
if (!UIAccess.isOkToUse(QueueView.this.tableViewer)) {
return;
}
if (taskDelta.position >= 0) {
for (int j= 0; j < taskDelta.data.size(); j++) {
QueueView.this.tableViewer.insert(taskDelta.data.get(j), taskDelta.position + j);
}
}
else {
QueueView.this.tableViewer.add(taskDelta.data.toArray());
}
}
});
}
continue EVENT;
case ToolRunnable.STARTING:
updateProgress= true;
//$FALL-THROUGH$ continue with delete
case ToolRunnable.REMOVING_FROM:
case ToolRunnable.MOVING_FROM:
if (!this.expectInfoEvent) {
UIAccess.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
if (!UIAccess.isOkToUse(QueueView.this.tableViewer)) {
return;
}
QueueView.this.tableViewer.remove(taskDelta.data.toArray());
}
});
}
continue EVENT;
// case Queue.QUEUE_CHANGE:
// if (!fExpectInfoEvent) {
// setElements((IToolRunnable[]) event.getData());
// }
// continue EVENT;
}
continue EVENT;
case DebugEvent.MODEL_SPECIFIC:
if (event.getDetail() == Queue.QUEUE_INFO && this.expectInfoEvent) {
this.expectInfoEvent= false;
setElements((ImList<ToolRunnable>) event.getData());
}
continue EVENT;
case DebugEvent.TERMINATE:
disconnect(process);
continue EVENT;
default:
continue EVENT;
}
}
}
if (updateProgress && QueueView.this.showProgress) {
final ToolProgressGroup progress= QueueView.this.progressControl;
if (progress != null) {
progress.refresh(false);
}
}
}
}
private class TableLabelProvider extends LabelProvider implements ITableLabelProvider {
@Override
public Image getColumnImage(final Object element, final int columnIndex) {
if (columnIndex == 0) {
return getImage(element);
}
return null;
}
@Override
public Image getImage(final Object element) {
return NicoUITools.getImage((ToolRunnable) element);
}
@Override
public String getColumnText(final Object element, final int columnIndex) {
if (columnIndex == 0) {
return getText(element);
}
return ""; //$NON-NLS-1$
}
@Override
public String getText(final Object element) {
final ToolRunnable runnable= (ToolRunnable) element;
return runnable.getLabel();
}
}
private class ShowDescriptionAction extends Action {
public ShowDescriptionAction() {
setText(Messages.ShowToolDescription_name);
setToolTipText(Messages.ShowToolDescription_tooltip);
setChecked(QueueView.this.showDescription);
}
@Override
public void run() {
QueueView.this.showDescription= isChecked();
updateContentDescription(QueueView.this.process);
}
}
private class ShowProgressAction extends Action {
public ShowProgressAction() {
setText(Messages.ShowProgress_name);
setToolTipText(Messages.ShowProgress_tooltip);
setChecked(QueueView.this.showProgress);
}
@Override
public void run() {
QueueView.this.showProgress= isChecked();
if (QueueView.this.showProgress) {
createProgressControl();
QueueView.this.progressControl.setTool(QueueView.this.process, true);
QueueView.this.progressControl.getControl().moveAbove(QueueView.this.tableViewer.getControl());
}
else {
if (QueueView.this.progressControl != null) {
QueueView.this.progressControl.getControl().dispose();
QueueView.this.progressControl= null;
}
}
QueueView.this.composite.layout(true);
}
}
private Composite composite;
private ToolProgressGroup progressControl;
private TableViewer tableViewer;
private ToolProcess process;
private WorkbenchToolRegistryListener toolRegistryListener;
private static final String M_SHOW_DESCRIPTION= "QueueView.ShowDescription"; //$NON-NLS-1$
private boolean showDescription;
private Action showDescriptionAction;
private static final String M_SHOW_PROGRESS= "QueueView.ShowProgress"; //$NON-NLS-1$
private boolean showProgress;
private Action showProgressAction;
private Action selectAllAction;
private Action deleteAction;
public QueueView() {
}
@Override
public void init(final IViewSite site, final IMemento memento) throws PartInitException {
super.init(site, memento);
final String showDescription= (memento != null) ? memento.getString(M_SHOW_DESCRIPTION) : null;
if (showDescription == null || showDescription.equals("off")) { // default //$NON-NLS-1$
this.showDescription= false;
} else {
this.showDescription= true;
}
final String showProgress= (memento != null) ? memento.getString(M_SHOW_PROGRESS) : null;
if (showProgress== null || showProgress.equals("on")) { // default //$NON-NLS-1$
this.showProgress= true;
} else {
this.showProgress= false;
}
}
@Override
public void saveState(final IMemento memento) {
super.saveState(memento);
memento.putString(M_SHOW_DESCRIPTION, (this.showDescription) ? "on" : "off"); //$NON-NLS-1$ //$NON-NLS-2$
memento.putString(M_SHOW_PROGRESS, (this.showProgress) ? "on" : "off"); //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public void createPartControl(final Composite parent) {
updateContentDescription(null);
this.composite= parent;
final GridLayout layout= new GridLayout();
layout.marginHeight= 0;
layout.marginWidth= 0;
layout.verticalSpacing= 0;
parent.setLayout(layout);
if (this.showProgress) {
createProgressControl();
}
this.tableViewer= new TableViewer(parent, SWT.MULTI | SWT.V_SCROLL);
this.tableViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
this.tableViewer.getTable().setLinesVisible(false);
this.tableViewer.getTable().setHeaderVisible(false);
new TableColumn(this.tableViewer.getTable(), SWT.DEFAULT);
this.tableViewer.getTable().addControlListener(new ControlAdapter() {
@Override
public void controlResized(final ControlEvent e) {
// adapt the column width to the width of the table
final Table table= QueueView.this.tableViewer.getTable();
final Rectangle area= table.getClientArea();
final TableColumn column= table.getColumn(0);
column.setWidth(area.width-3); // it looks better with a small gap
}
});
this.tableViewer.setContentProvider(new ViewContentProvider());
this.tableViewer.setLabelProvider(new TableLabelProvider());
createActions();
contributeToActionBars();
hookDND();
// listen on console changes
final WorkbenchToolRegistry toolRegistry= NicoUI.getToolRegistry();
connect(toolRegistry.getActiveToolSession(getViewSite().getPage()).getTool());
this.toolRegistryListener= new WorkbenchToolRegistryListener() {
@Override
public void toolSessionActivated(final WorkbenchToolSessionData sessionData) {
final Tool tool= sessionData.getTool();
UIAccess.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
connect(tool);
}
});
}
@Override
public void toolTerminated(final WorkbenchToolSessionData sessionData) {
// handled by debug events
}
};
toolRegistry.addListener(this.toolRegistryListener, getViewSite().getPage());
}
private void createProgressControl() {
this.progressControl= new ToolProgressGroup(this.composite);
this.progressControl.getControl().setLayoutData(
new GridData(SWT.FILL, SWT.FILL, true, false));
}
protected void updateContentDescription(final ToolProcess process) {
if (this.showDescription) {
setContentDescription(process != null ? process.getLabel(0) : " "); //$NON-NLS-1$
}
else {
setContentDescription(""); //$NON-NLS-1$
}
}
private void createActions() {
this.showDescriptionAction= new ShowDescriptionAction();
this.showProgressAction= new ShowProgressAction();
this.selectAllAction= new Action() {
@Override
public void run() {
QueueView.this.tableViewer.getTable().selectAll();
}
};
this.deleteAction= new Action() {
@Override
public void run() {
final Queue queue= getQueue();
if (queue != null) {
final IStructuredSelection selection= (IStructuredSelection) QueueView.this.tableViewer.getSelection();
queue.remove(ImCollections.toList(selection.toList()));
}
}
};
}
private void contributeToActionBars() {
final IActionBars bars= getViewSite().getActionBars();
bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), this.selectAllAction);
bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), this.deleteAction);
fillLocalPullDown(bars.getMenuManager());
fillLocalToolBar(bars.getToolBarManager());
}
private void fillLocalPullDown(final IMenuManager manager) {
manager.add(this.showDescriptionAction);
manager.add(this.showProgressAction);
}
private void fillLocalToolBar(final IToolBarManager manager) {
manager.add(new CommandContributionItem(new CommandContributionItemParameter(
getSite(), null, NicoUI.PAUSE_COMMAND_ID, null,
null, null, null,
null, null, null,
CommandContributionItem.STYLE_CHECK, null, false)));
}
private void hookDND() {
this.tableViewer.addDragSupport(DND.DROP_MOVE,
new Transfer[] { LocalTaskTransfer.getTransfer() },
new TableDragSourceEffect(this.tableViewer.getTable()) {
@Override
public void dragStart(final DragSourceEvent event) {
if (QueueView.this.tableViewer.getTable().getSelectionCount() > 0) {
event.doit= true;
} else {
event.doit= false;
}
LocalTaskTransfer.getTransfer().init(QueueView.this.process);
super.dragStart(event);
}
@Override
public void dragSetData(final DragSourceEvent event) {
super.dragSetData(event);
final LocalTaskTransfer.Data data= LocalTaskTransfer.getTransfer().createData();
if (data.process != QueueView.this.process) {
event.doit= false;
return;
}
data.runnables= ImCollections.toList(
((IStructuredSelection) QueueView.this.tableViewer.getSelection()).toList() );
event.data= data;
}
@Override
public void dragFinished(final DragSourceEvent event) {
super.dragFinished(event);
LocalTaskTransfer.getTransfer().finished();
}
});
}
private void disconnect(final ToolProcess process) {
UIAccess.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
if (QueueView.this.process != null && QueueView.this.process == process) {
connect(null);
}
}
});
}
/** May only be called in UI thread */
private void connect(final Tool tool) {
final ToolProcess process= (tool instanceof ToolProcess) ? (ToolProcess)tool : null;
final Runnable runnable= new Runnable() {
@Override
public void run() {
if (!UIAccess.isOkToUse(QueueView.this.tableViewer)) {
return;
}
QueueView.this.process= process;
updateContentDescription(process);
if (QueueView.this.progressControl != null) {
QueueView.this.progressControl.setTool(process, true);
}
QueueView.this.tableViewer.setInput(process);
}
};
BusyIndicator.showWhile(UIAccess.getDisplay(), runnable);
}
/**
* Returns the tool process, which this view is connected to.
*
* @return a tool process or <code>null</code>, if no process is connected.
*/
public ToolProcess getProcess() {
return this.process;
}
public Queue getQueue() {
if (this.process != null) {
return this.process.getQueue();
}
return null;
}
@Override
public void setFocus() {
// Passing the focus request to the viewer's control.
this.tableViewer.getControl().setFocus();
}
@Override
public void dispose() {
super.dispose();
}
}