blob: be30048d5f9b5032eae16590fac116067fbcb993 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Tom Hofmann, Perspectix AG - https://bugs.eclipse.org/bugs/show_bug.cgi?id=291750
* Asma Smaoui - CEA LIST - https://bugs.eclipse.org/bugs/show_bug.cgi?id=517379
* Christoph Läubrich - Bug 552773 - Simplify logging in platform code base
*******************************************************************************/
package org.eclipse.ui.internal.cheatsheets.views;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.help.ui.internal.views.HelpTray;
import org.eclipse.help.ui.internal.views.IHelpPartPage;
import org.eclipse.help.ui.internal.views.ReusableHelpPart;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.TrayDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
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.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.cheatsheets.CheatSheetListener;
import org.eclipse.ui.cheatsheets.ICheatSheetEvent;
import org.eclipse.ui.cheatsheets.ICheatSheetViewer;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ImageHyperlink;
import org.eclipse.ui.internal.cheatsheets.CheatSheetPlugin;
import org.eclipse.ui.internal.cheatsheets.CheatSheetStopWatch;
import org.eclipse.ui.internal.cheatsheets.ICheatSheetResource;
import org.eclipse.ui.internal.cheatsheets.Messages;
import org.eclipse.ui.internal.cheatsheets.actions.IMenuContributor;
import org.eclipse.ui.internal.cheatsheets.composite.model.CompositeCheatSheetModel;
import org.eclipse.ui.internal.cheatsheets.composite.views.CompositeCheatSheetPage;
import org.eclipse.ui.internal.cheatsheets.data.CheatSheet;
import org.eclipse.ui.internal.cheatsheets.data.CheatSheetParser;
import org.eclipse.ui.internal.cheatsheets.data.CheatSheetSaveHelper;
import org.eclipse.ui.internal.cheatsheets.data.ICheatSheet;
import org.eclipse.ui.internal.cheatsheets.data.IParserTags;
import org.eclipse.ui.internal.cheatsheets.data.ParserInput;
import org.eclipse.ui.internal.cheatsheets.registry.CheatSheetElement;
import org.eclipse.ui.internal.cheatsheets.registry.CheatSheetRegistryReader;
import org.eclipse.ui.internal.cheatsheets.state.DefaultStateManager;
import org.eclipse.ui.internal.cheatsheets.state.ICheatSheetStateManager;
import org.eclipse.ui.internal.cheatsheets.state.NoSaveStateManager;
import org.eclipse.ui.internal.cheatsheets.state.TrayStateManager;
import org.osgi.framework.Bundle;
public class CheatSheetViewer implements ICheatSheetViewer, IMenuContributor {
//CS Elements
private CheatSheetElement contentElement;
private ParserInput parserInput;
private String currentID;
private int currentItemNum;
// Used to indicate if an invalid cheat sheet id was specified via setInput.
private boolean invalidCheatSheetId = false;
// Used to indicate if a null cheat sheet id was specified via setInput.
private boolean nullCheatSheetId = false;
private CheatSheetParser parser;
private ICheatSheet model;
private CheatSheetManager manager;
private CheatSheetSaveHelper saveHelper;
private CheatSheetExpandRestoreAction expandRestoreAction;
private Action copyAction;
//ITEMS
private ViewItem currentItem;
//Lists
private ArrayList<String> expandRestoreList = new ArrayList<>();
private ArrayList<ViewItem> viewItemList = new ArrayList<>();
//Composites
protected Composite control;
private Cursor busyCursor;
// The page currently displayed, may be a CheatSheetPage, CompositeCheatSheetPage
// or ErrorPage
private Page currentPage;
private Label howToBegin;
private boolean inDialog;
private Listener listener;
private ICheatSheetStateManager stateManager; // The state manager to use when saving
private ICheatSheetStateManager preTrayManager; // The state manager in use before a tray was opened
private String restorePath;
private int dialogReturnCode;
private boolean isRestricted;
/**
* The constructor.
*
* @param inDialog whether or not this viewer will be placed in a modal dialog
*/
public CheatSheetViewer(boolean inDialog) {
currentItemNum = -1;
this.inDialog = inDialog;
saveHelper = new CheatSheetSaveHelper();
}
public void advanceIntroItem() {
if (getViewItemAtIndex(0) == null) {
return; // Cheat Sheet has no items or was not opened correctly
}
resetItemState();
/* LP-item event */
// fireManagerItemEvent(ICheatSheetItemEvent.ITEM_DEACTIVATED, introItem);
currentItemNum = 1;
ViewItem nextItem = getViewItemAtIndex(currentItemNum);
if (nextItem.item.isDynamic()) {
nextItem.handleButtons();
}
nextItem.setAsCurrentActiveItem();
/* LP-item event */
// fireManagerItemEvent(ICheatSheetItemEvent.ITEM_ACTIVATED, nextItem);
collapseAllButCurrent(false);
saveCurrentSheet();
}
/**
* Reset the state of all the items in this cheatsheet
*/
private void resetItemState() {
IntroItem introItem = (IntroItem) getViewItemAtIndex(0);
boolean isStarted = introItem.isCompleted();
expandRestoreList = new ArrayList<>();
if(expandRestoreAction != null)
expandRestoreAction.setCollapsed(false);
clearBackgrounds();
clearIcons();
collapseAllButtons();
if(isStarted)
initManager();
for (ViewItem item : viewItemList) {
if (item instanceof CoreItem) {
CoreItem c = (CoreItem) item;
ArrayList<SubItemCompositeHolder> l = c.getListOfSubItemCompositeHolders();
if (l != null)
for (int j = 0; j < l.size(); j++) {
l.get(j).setSkipped(false);
l.get(j).setCompleted(false);
}
}
}
if (isStarted)
getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_RESTARTED);
else
getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_STARTED);
isStarted = true;
introItem.setAsNormalCollapsed();
introItem.setComplete();
introItem.setRestartImage();
}
/*package*/
/*
* This function can do one of three things
* 1. If this item has a completion message which has not been displayed, display it
* 2. Otherwise if this is the final item return to the introduction
* 3. If neither condition 1 or 2 is satisfied move to the next item
*/
public void advanceItem(ImageHyperlink link, boolean markAsCompleted) {
currentItem = (ViewItem) link.getData();
int indexNextItem = getIndexOfItem(currentItem) +1;
boolean isFinalItem = indexNextItem >= viewItemList.size();
if (markAsCompleted
&& currentItem.hasCompletionMessage()
&& !currentItem.isCompletionMessageExpanded()) {
currentItem.setCompletionMessageExpanded(isFinalItem);
currentItem.setComplete();
if (isFinalItem) {
getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_COMPLETED);
}
saveCurrentSheet();
return;
}
if (indexNextItem < currentItemNum) {
ViewItem vi = getViewItemAtIndex(currentItemNum);
vi.setAsNormalNonCollapsed();
}
if (currentItem != null) {
//set that item to it's original color.
currentItem.setAsNormalCollapsed();
//set that item as complete.
if (markAsCompleted) {
if (!currentItem.isCompleted()) {
currentItem.setComplete();
}
/* LP-item event */
// fireManagerItemEvent(ICheatSheetItemEvent.ITEM_COMPLETED, currentItem);
// fireManagerItemEvent(ICheatSheetItemEvent.ITEM_DEACTIVATED, currentItem);
} else {
currentItem.setSkipped();
/* LP-item event */
// fireManagerItemEvent(ICheatSheetItemEvent.ITEM_SKIPPED, currentItem);
// fireManagerItemEvent(ICheatSheetItemEvent.ITEM_DEACTIVATED, currentItem);
}
}
if (!isFinalItem) {
ViewItem nextItem = getViewItemAtIndex(indexNextItem);
currentItemNum = indexNextItem;
if (nextItem != null) {
//Handle lazy button instantiation here.
if (nextItem.item.isDynamic()) {
((CoreItem) nextItem).handleButtons();
}
nextItem.setAsCurrentActiveItem();
/* LP-item event */
// fireManagerItemEvent(ICheatSheetItemEvent.ITEM_ACTIVATED, nextItem);
currentItem = nextItem;
}
FormToolkit.ensureVisible(currentItem.getMainItemComposite());
} else if (indexNextItem == viewItemList.size()) {
if (!currentItem.isCompletionMessageExpanded()) { // The event will already have been fired
getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_COMPLETED);
}
showIntroItem();
}
saveCurrentSheet();
}
private void showIntroItem() {
ViewItem item = getViewItemAtIndex(0);
item.setAsCurrentActiveItem();
}
public void advanceSubItem(ImageHyperlink link, boolean markAsCompleted, int subItemIndex) {
Label l = null;
ArrayList<SubItemCompositeHolder> list = null;
SubItemCompositeHolder sich = null;
CoreItem ciws = null;
currentItem = (ViewItem) link.getData();
if (currentItem instanceof CoreItem)
ciws = (CoreItem) currentItem;
if (ciws != null) {
list = ciws.getListOfSubItemCompositeHolders();
sich = list.get(subItemIndex);
l = sich.getCheckDoneLabel();
}
if (l != null) {
if (markAsCompleted) {
sich.setCompleted(true);
sich.setSkipped(false);
/* LP-subitem event */
// fireManagerSubItemEvent(ICheatSheetItemEvent.ITEM_COMPLETED, ciws, subItemID);
} else {
sich.setSkipped(true);
sich.setCompleted(false);
/* LP-subitem event */
// fireManagerSubItemEvent(ICheatSheetItemEvent.ITEM_SKIPPED, ciws, subItemID);
}
ciws.refreshItem();
}
boolean allAttempted = checkAllAttempted(list);
boolean anySkipped = checkContainsSkipped(list);
if (allAttempted && !anySkipped) {
advanceItem(link, true);
return;
} else if (allAttempted && anySkipped) {
advanceItem(link, false);
return;
}
setFocus();
saveCurrentSheet();
}
private boolean checkAllAttempted(ArrayList<SubItemCompositeHolder> list) {
for (int i = 0; i < list.size(); i++) {
SubItemCompositeHolder s = list.get(i);
if (s.isCompleted() || s.isSkipped()) {
continue;
}
return false;
}
return true;
}
private boolean checkContainsSkipped(ArrayList<SubItemCompositeHolder> list) {
for (int i = 0; i < list.size(); i++) {
SubItemCompositeHolder s = list.get(i);
if (s.isSkipped()) {
return true;
}
}
return false;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private boolean loadState() {
try {
Properties props = stateManager.getProperties();
manager = stateManager.getCheatSheetManager();
// There is a bug which causes the background of the buttons to
// remain white, even though the color is set. So instead of calling
// clearBackgrounds() only the following line should be needed. D'oh!
// ((ViewItem) viewItemList.get(0)).setOriginalColor();
clearBackgrounds();
if (props == null) {
getViewItemAtIndex(0).setAsCurrentActiveItem();
/* LP-item event */
// fireManagerItemEvent(ICheatSheetItemEvent.ITEM_ACTIVATED, items[0]);
return true;
}
boolean buttonIsDown = (Integer.parseInt((String) props.get(IParserTags.BUTTON)) == 0) ? false : true;
int itemNum = Integer.parseInt((String) props.get(IParserTags.CURRENT));
ArrayList completedStatesList = (ArrayList) props.get(IParserTags.COMPLETED);
ArrayList expandedStatesList = (ArrayList) props.get(IParserTags.EXPANDED);
expandRestoreList = (ArrayList<String>) props.get(IParserTags.EXPANDRESTORE);
String cid = (String) props.get(IParserTags.ID);
Hashtable completedSubItems = (Hashtable) props.get(IParserTags.SUBITEMCOMPLETED);
Hashtable<String, String> skippedSubItems = (Hashtable<String, String>) props
.get(IParserTags.SUBITEMSKIPPED);
ArrayList completedSubItemsItemList = new ArrayList<>();
ArrayList skippedSubItemsItemList = new ArrayList<>();
Enumeration e = completedSubItems.keys();
while (e.hasMoreElements())
completedSubItemsItemList.add(e.nextElement());
Enumeration e2 = skippedSubItems.keys();
while (e2.hasMoreElements())
skippedSubItemsItemList.add(e2.nextElement());
if (cid != null)
currentID = cid;
if (itemNum >= 0) {
currentItemNum = itemNum;
currentItem = getViewItemAtIndex(itemNum);
CheatSheetStopWatch.startStopWatch("CheatSheetViewer.checkSavedState()"); //$NON-NLS-1$
for (int i = 0; i < viewItemList.size(); i++) {
ViewItem item = getViewItemAtIndex(i);
if (i > 0 && item.item.isDynamic() && i <= currentItemNum) {
item.handleButtons();
item.setOriginalColor();
}
if (completedStatesList.contains(Integer.toString(i))) {
item.setComplete();
item.setRestartImage();
} else {
if (i < currentItemNum) {
item.setSkipped();
}
}
if (expandedStatesList.contains(Integer.toString(i))) {
item.setExpanded();
} else {
item.setCollapsed();
}
if (i > currentItemNum) {
item.setButtonsVisible(false);
item.setCompletionMessageCollapsed();
} else {
item.setButtonsVisible(true);
if (i >currentItemNum || item.isCompleted()) {
item.setCompletionMessageExpanded(i + 1 >= viewItemList.size());
} else {
item.setCompletionMessageCollapsed();
}
}
if (expandRestoreList.contains(Integer.toString(i))) {
item.setCollapsed();
}
if (completedSubItemsItemList.contains(Integer.toString(i))) {
String subItemNumbers = (String) completedSubItems.get(Integer.toString(i));
StringTokenizer st = new StringTokenizer(subItemNumbers, ","); //$NON-NLS-1$
if (item instanceof CoreItem) {
CoreItem coreitemws = (CoreItem) item;
ArrayList<SubItemCompositeHolder> subItemCompositeHolders = coreitemws
.getListOfSubItemCompositeHolders();
if (subItemCompositeHolders != null) {
while (st.hasMoreTokens()) {
String token = st.nextToken();
subItemCompositeHolders.get(Integer.parseInt(token)).setCompleted(true);
ArrayList<SubItemCompositeHolder> l = subItemCompositeHolders;
SubItemCompositeHolder s = l.get(Integer.parseInt(token));
if (s != null && s.getStartButton() != null) {
s.getStartButton().setImage(CheatSheetPlugin.getPlugin().getImage(ICheatSheetResource.CHEATSHEET_ITEM_BUTTON_RESTART));
s.getStartButton().setToolTipText(Messages.RESTART_TASK_TOOLTIP);
}
}
}
}
}
if (skippedSubItemsItemList.contains(Integer.toString(i))) {
String subItemNumbers = skippedSubItems.get(Integer.toString(i));
StringTokenizer st = new StringTokenizer(subItemNumbers, ","); //$NON-NLS-1$
if (item instanceof CoreItem) {
CoreItem coreitemws = (CoreItem) item;
while (st.hasMoreTokens()) {
String token = st.nextToken();
coreitemws.getListOfSubItemCompositeHolders().get(Integer.parseInt(token))
.setSkipped(true);
}
}
}
CheatSheetStopWatch.printLapTime("CheatSheetViewer.checkSavedState()", "Time in CheatSheetViewer.checkSavedState() after loop #"+i+": "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
CheatSheetStopWatch.printLapTime("CheatSheetViewer.checkSavedState()", "Time in CheatSheetViewer.checkSavedState() after loop: "); //$NON-NLS-1$ //$NON-NLS-2$
if (buttonIsDown) {
if(expandRestoreAction != null)
expandRestoreAction.setCollapsed(true);
}
// If the last item is the current one and it is complete then
// we should collapse the last item and set the focus on intro.
// For all other cases, set the current item as the active item.
if(viewItemList.size()-1 == itemNum && currentItem.isCompleted()) {
currentItem.setCollapsed();
getViewItemAtIndex(0).getMainItemComposite().setFocus();
// The cheat sheet has been restored but is also completed so fire both events
getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_RESTORED);
getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_COMPLETED);
} else {
currentItem.setAsCurrentActiveItem();
// If the intro item is completed, than the cheat sheet has been restored.
if(getViewItemAtIndex(0).isCompleted())
getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_RESTORED);
}
/* LP-item event */
// fireManagerItemEvent(ICheatSheetItemEvent.ITEM_ACTIVATED, currentItem);
} else {
getViewItemAtIndex(0).setAsCurrentActiveItem();
/* LP-item event */
// fireManagerItemEvent(ICheatSheetItemEvent.ITEM_ACTIVATED, items[0]);
}
return true;
} catch(Exception e) {
// An exception while restoring the saved state data usually only occurs if
// the cheat sheet has been modified since this previous execution. This most
// often occurs during development of cheat sheets and as such an end user is
// not as likely to encounter this.
boolean reset = MessageDialog.openConfirm(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
Messages.CHEATSHEET_STATE_RESTORE_FAIL_TITLE,
Messages.CHEATSHEET_STATE_RESET_CONFIRM);
if (reset) {
restart();
return true;
}
// Log the exception
String stateFile = saveHelper.getStateFile(currentID).toOSString();
String message = NLS.bind(Messages.ERROR_APPLYING_STATE_DATA_LOG, (new Object[] {stateFile, currentID}));
CheatSheetPlugin.getPlugin().getLog().error(message, e);
// Set the currentID to null so it is not saved during internalDispose()
currentID = null;
internalDispose();
// Reinitialize a few variables because there is no currentItem or currentPage now
parserInput = null;
currentItem = null;
currentItemNum = -1;
currentPage = null;
expandRestoreList = new ArrayList<>();
viewItemList = new ArrayList<>();
// Create the errorpage to show the user
createErrorPage(Messages.ERROR_APPLYING_STATE_DATA);
return false;
}
}
private void clearBackgrounds() {
for (ViewItem item : viewItemList) {
item.setOriginalColor();
}
}
private void clearIcons() {
for (ViewItem item : viewItemList) {
item.setOriginalColor();
if (item.isCompleted() || item.isExpanded() || item.isSkipped())
item.setIncomplete();
}
}
private void collapseAllButCurrent(boolean fromAction) {
expandRestoreList = new ArrayList<>();
try {
ViewItem current = getViewItemAtIndex(currentItemNum);
for (ListIterator<ViewItem> iter = viewItemList.listIterator(viewItemList.size()); iter.hasPrevious();) {
ViewItem item = iter.previous();
if (item != current && item.isExpanded()) {
item.setCollapsed();
if (fromAction)
expandRestoreList.add(Integer.toString(getIndexOfItem(item)));
}
}
} catch (Exception e) {
}
}
private void collapseAllButtons() {
for (Iterator<ViewItem> iter = viewItemList.listIterator(1); iter.hasNext();) {
ViewItem item = iter.next();
item.setButtonsVisible(false);
item.setCompletionMessageCollapsed();
}
}
private void createErrorPage(String message) {
setCollapseExpandButtonEnabled(false);
if(message != null) {
currentPage = new ErrorPage(message);
} else {
currentPage = new ErrorPage();
}
currentPage.createPart(control);
control.layout(true);
}
private void showStartPage() {
setCollapseExpandButtonEnabled(false);
internalDispose();
howToBegin = new Label(control, SWT.WRAP);
howToBegin.setText(Messages.INITIAL_VIEW_DIRECTIONS);
howToBegin.setLayoutData(new GridData(GridData.FILL_BOTH));
currentPage = null;
control.layout(true);
}
private void createErrorPage(IStatus status) {
setCollapseExpandButtonEnabled(false);
currentPage = new ErrorPage(status);
currentPage.createPart(control);
control.layout(true);
}
/**
* Creates the SWT controls for this workbench part.
* <p>
* Clients should not call this method (the workbench calls this method at
* appropriate times).
* </p>
* <p>
* For implementors this is a multi-step process:
* </p>
* <ol>
* <li>Create one or more controls within the parent.</li>
* <li>Set the parent layout as needed.</li>
* <li>Register any global actions with the <code>IActionService</code>.</li>
* <li>Register any popup menus with the <code>IActionService</code>.</li>
* <li>Register a selection provider with the <code>ISelectionService</code>
* (optional). </li>
* </ol>
*
* @param parent the parent control
*/
@Override
public void createPartControl(Composite parent) {
control = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.verticalSpacing = 0;
layout.horizontalSpacing = 0;
layout.numColumns = 1;
control.setLayout(layout);
control.addDisposeListener(e -> dispose());
showStartPage();
Display display = parent.getDisplay();
busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT);
if(contentElement != null) {
initCheatSheetView();
}
}
/**
* Called when any TrayDialog is opened. The viewer must react by disabling
* itself and moving the cheat sheet to the dialog's tray if the current item
* was flagged as one that opens a modal dialog.
*
* @param dialog the dialog that was opened
*/
private void dialogOpened(final TrayDialog dialog) {
if (isActive()) {
HelpTray tray = (HelpTray)dialog.getTray();
if (tray == null) {
tray = new HelpTray();
dialog.openTray(tray);
}
ReusableHelpPart helpPart = tray.getHelpPart();
IHelpPartPage page = helpPart.createPage(CheatSheetHelpPart.ID, null, null);
page.setVerticalSpacing(0);
page.setHorizontalMargin(0);
ICheatSheetStateManager trayManager = new TrayStateManager();
preTrayManager = stateManager;
stateManager = trayManager;
saveCurrentSheet(); // Save the state into the tray manager
helpPart.addPart(CheatSheetHelpPart.ID, new CheatSheetHelpPart(helpPart.getForm().getForm().getBody(), helpPart.getForm().getToolkit(), page.getToolBarManager(), contentElement, trayManager));
page.addPart(CheatSheetHelpPart.ID, true);
helpPart.addPage(page);
helpPart.showPage(CheatSheetHelpPart.ID);
/*
* Disable the viewer until the tray is closed, then show it again.
*/
control.setVisible(false);
Display.getCurrent().removeFilter(SWT.Show, listener);
helpPart.getControl().addListener(SWT.Dispose, event -> {
control.setVisible(true);
Display.getCurrent().addFilter(SWT.Show, listener);
if (preTrayManager != null) {
loadState(); // Load from the tray manager
stateManager = preTrayManager;
preTrayManager = null;
}
dialogReturnCode = dialog.getReturnCode();
});
}
}
/**
* Disposes of this cheat sheet viewer.
*/
private void dispose() {
internalDispose();
}
/*
* Returns the cheat sheet being viewed.
*/
public ICheatSheet getCheatSheet() {
return model;
}
@Override
public String getCheatSheetID() {
if(getContent() != null) {
return getContent().getID();
}
return null;
}
/**
* Returns the current content.
*
* @return CheatSheetElement
*/
/*package*/ CheatSheetElement getContent() {
return contentElement;
}
@Override
public Control getControl() {
return control;
}
private int getIndexOfItem(ViewItem item) {
int index = viewItemList.indexOf(item);
if(index != -1) {
return index;
}
return 0;
}
/*package*/ CheatSheetManager getManager() {
if (manager == null) {
getNewManager();
}
return manager;
}
private CheatSheetManager getNewManager(){
manager = new CheatSheetManager(contentElement);
return manager;
}
private CheatSheetManager initManager(){
CheatSheetManager csManager = getManager();
csManager.setData(new Hashtable<>());
return csManager;
}
private ViewItem getViewItemAtIndex(int index) {
if (viewItemList != null && !viewItemList.isEmpty()) {
return viewItemList.get(index);
}
return null;
}
/**
* Returns whether or not this viewer contains the given Control, which
* is currently in focus.
*
* @param control the Control currently in focus
* @return whether this viewer contains the given Control or not
*/
public boolean hasFocusControl(Control control) {
return (control == this.control) || (currentPage.getControl() == control);
}
/**
* If in a dialog-opening step, will add the appropriate listener for
* the cheatsheet to jump into the dialog's tray once opened.
*
* Should be called before executing any action.
*/
private void hookDialogListener() {
/*
* org.eclipse.help.ui is an optional dependency; only perform this
* step is this plugin is present.
*/
if (!inDialog && isInDialogItem() && (Platform.getBundle("org.eclipse.help.ui") != null)) { //$NON-NLS-1$
listener = event -> {
if (isTrayDialog(event.widget)) {
dialogOpened((TrayDialog) ((Shell) event.widget).getData());
}
};
Display.getCurrent().addFilter(SWT.Show, listener);
}
}
/**
* Removes the dialog-opening listener, if it was added.
*
* Should be called after executing any action.
*/
private void unhookDialogListener() {
if (listener != null) {
Display.getCurrent().removeFilter(SWT.Show, listener);
}
}
/*
* return true if a cheat sheet was opened successfully
*/
private boolean initCheatSheetView() {
CheatSheetStopWatch.startStopWatch("CheatSheetViewer.initCheatSheetView()"); //$NON-NLS-1$
//Re-initialize list to store items collapsed by expand/restore action on c.s. toolbar.
expandRestoreList = new ArrayList<>();
// re set that action to turned off.
if(expandRestoreAction != null)
expandRestoreAction.setCollapsed(false);
//reset current item to be null; next item too.
currentItem = null;
currentItemNum = 0;
viewItemList = new ArrayList<>();
// Reset the page variable
currentPage = null;
if(howToBegin != null) {
howToBegin.dispose();
howToBegin = null;
}
// If a null cheat sheet id was specified, return leaving the cheat sheet empty.
if(nullCheatSheetId) {
return false;
}
if(invalidCheatSheetId) {
createErrorPage(Messages.ERROR_CHEATSHEET_DOESNOT_EXIST);
return false;
}
// read our contents, if there are problems reading the file an error page should be created.
CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() before readFile() call: "); //$NON-NLS-1$ //$NON-NLS-2$
IStatus parseStatus = readFile();
CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() after readFile() call: "); //$NON-NLS-1$ //$NON-NLS-2$
if (!parseStatus.isOK()) {
CheatSheetPlugin.getPlugin().getLog().log(parseStatus);
}
if(parseStatus.getSeverity() == Status.ERROR){
// Error during parsing.
// Something is wrong with the Cheat sheet content file at the xml level.
createErrorPage(parseStatus);
return false;
}
control.setRedraw(false);
if (model instanceof CheatSheet) {
CheatSheet cheatSheetModel = (CheatSheet)model;
if (isRestricted && cheatSheetModel.isContainsCommandOrAction()) {
boolean isOK = MessageDialog.openConfirm(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
Messages.CHEATSHEET_FROM_URL_WITH_EXEC_TITLE,
Messages.CHEATSHEET_FROM_URL_WITH_EXEC);
if (!isOK) {
control.setRedraw(true);
showStartPage();
return true;
}
}
currentPage = new CheatSheetPage(cheatSheetModel, viewItemList, this);
setCollapseExpandButtonEnabled(true);
} else if (model instanceof CompositeCheatSheetModel) {
CompositeCheatSheetModel compositeCheatSheetModel = ((CompositeCheatSheetModel)model);
compositeCheatSheetModel.setId(currentID);
currentPage = new CompositeCheatSheetPage(compositeCheatSheetModel, stateManager);
compositeCheatSheetModel.setCheatSheetManager(initManager());
setCollapseExpandButtonEnabled(false);
}
CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() after CheatSheetPage() call: "); //$NON-NLS-1$ //$NON-NLS-2$
currentPage.createPart(control);
CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() after CheatSheetPage.createPart() call: "); //$NON-NLS-1$ //$NON-NLS-2$
if (model instanceof CheatSheet) {
CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() after fireEvent() call: "); //$NON-NLS-1$ //$NON-NLS-2$
if(!loadState()) {
// An error occurred when apply the saved state data.
control.setRedraw(true);
control.layout();
return true;
}
getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_OPENED);
}
CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() after checkSavedState() call: "); //$NON-NLS-1$ //$NON-NLS-2$
currentPage.initialized();
control.setRedraw(true);
control.layout();
CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() after layout() call: "); //$NON-NLS-1$ //$NON-NLS-2$
if (currentItem != null && !currentItem.isCompleted())
currentItem.setFocus();
CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() at end of method: "); //$NON-NLS-1$ //$NON-NLS-2$
return true;
}
private void internalDispose() {
if(manager != null)
manager.fireEvent(ICheatSheetEvent.CHEATSHEET_CLOSED);
saveCurrentSheet();
for (Iterator<ViewItem> iter = viewItemList.iterator(); iter.hasNext();) {
ViewItem item = iter.next();
item.dispose();
}
if(currentPage != null) {
currentPage.dispose();
}
manager = null;
}
/**
* Returns whether or not the cheat sheet viewer is currently active. This
* means it is visible to the user and enabled.
*
* @return whether or not this viewer is active
*/
private boolean isActive() {
Control control = getControl();
if (control != null && !control.isDisposed()) {
Control parent = control.getParent();
return (parent != null && !parent.isDisposed() && parent.isVisible() && parent.isEnabled());
}
return false;
}
/*
* Show the collapse/expand button if we have access to the toolbar
*/
private void setCollapseExpandButtonEnabled(boolean enable) {
if (expandRestoreAction != null) {
expandRestoreAction.setEnabled(enable);
}
}
/**
* Returns whether or not the currently active item requires opening a
* modal dialog.
*
* @return whether the current item opens a modal dialog
*/
private boolean isInDialogItem() {
if (currentItem != null) {
return currentItem.getItem().isDialog();
}
return false;
}
/**
* Returns whether or not this cheat sheet viewer is inside a modal
* dialog.
*
* @return whether this viewer is inside a modal dialog
*/
public boolean isInDialogMode() {
return inDialog;
}
/**
* Returns whether the given widget is a TrayDialog.
*
* @param widget the widget to check
* @return whether or not the widget is a TrayDialog
*/
private boolean isTrayDialog(Widget widget) {
return (widget instanceof Shell && ((Shell)widget).getData() instanceof TrayDialog);
}
/**
* Read the contents of the cheat sheet file
* @return true if the file was read and parsed without error
*/
private IStatus readFile() {
if(parser == null)
parser = new CheatSheetParser();
// If the cheat sheet was registered then
// search for a specific type - composite or simple
int cheatSheetKind = CheatSheetParser.ANY;
if (contentElement.isRegistered()) {
if (contentElement.isComposite()) {
cheatSheetKind = CheatSheetParser.COMPOSITE_ONLY;
} else {
cheatSheetKind = CheatSheetParser.SIMPLE_ONLY;
}
}
model = parser.parse(parserInput, cheatSheetKind);
return parser.getStatus();
}
private void restoreExpandStates() {
try {
for (int i = 0; i < expandRestoreList.size(); i++) {
int index = Integer.parseInt((expandRestoreList.get(i)));
ViewItem item = getViewItemAtIndex(index);
if (!item.isExpanded()) {
item.setExpanded();
}
}
expandRestoreList = null;
} catch (Exception e) {
}
}
/*package*/ void runPerformExecutable(ImageHyperlink link) {
link.setCursor(busyCursor);
currentItem = (ViewItem) link.getData();
CoreItem coreItem = (CoreItem) currentItem;
Page page= currentPage;
if (coreItem != null) {
try {
hookDialogListener();
dialogReturnCode = -1;
IStatus status = coreItem.runExecutable(getManager());
if ( status.getSeverity() == IStatus.ERROR) {
CheatSheetPlugin.getPlugin().getLog().log(status);
org.eclipse.jface.dialogs.ErrorDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), null, null, status);
}
if (page != currentPage) {
// action closed the cheatsheet view or changed the cheatsheet
return;
}
if (status.isOK() && dialogReturnCode != Window.CANCEL) {
coreItem.setRestartImage();
if (!coreItem.hasConfirm()) {
//set that item as complete.
advanceItem(link, true);
saveCurrentSheet();
}
}
}
finally {
unhookDialogListener();
}
}
link.setCursor(null);
}
/*package*/ void runSubItemPerformExecutable(ImageHyperlink link, int subItemIndex) {
CoreItem coreItem = null;
link.setCursor(busyCursor);
currentItem = (ViewItem) link.getData();
coreItem = (CoreItem) currentItem;
try {
if (coreItem != null) {
hookDialogListener();
if (coreItem.runSubItemExecutable(getManager(), subItemIndex) == ViewItem.VIEWITEM_ADVANCE && !coreItem.hasConfirm(subItemIndex)) {
ArrayList<SubItemCompositeHolder> l = coreItem.getListOfSubItemCompositeHolders();
SubItemCompositeHolder s = l.get(subItemIndex);
s.getStartButton().setImage(CheatSheetPlugin.getPlugin().getImage(ICheatSheetResource.CHEATSHEET_ITEM_BUTTON_RESTART));
s.getStartButton().setToolTipText(Messages.RESTART_TASK_TOOLTIP);
advanceSubItem(link, true, subItemIndex);
saveCurrentSheet();
}
}
} catch (RuntimeException e) {
IStatus status = new Status(IStatus.ERROR, ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID, IStatus.OK, Messages.ERROR_RUNNING_ACTION, e);
CheatSheetPlugin.getPlugin().getLog().log(status);
org.eclipse.jface.dialogs.ErrorDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), null, null, status);
} finally {
unhookDialogListener();
link.setCursor(null);
}
}
public void saveCurrentSheet() {
if(currentID != null) {
if (currentPage instanceof CheatSheetPage) {
Properties properties = saveHelper.createProperties(currentItemNum, viewItemList, getExpandRestoreActionState(), expandRestoreList, currentID, restorePath);
IStatus status = stateManager.saveState(properties, getManager());
if (!status.isOK()) {
CheatSheetPlugin.getPlugin().getLog().log(status);
}
} else if (currentPage instanceof CompositeCheatSheetPage) {
((CompositeCheatSheetPage)currentPage).saveState();
}
}
}
private boolean getExpandRestoreActionState() {
boolean expandRestoreActionState = false;
if(expandRestoreAction != null)
expandRestoreActionState = expandRestoreAction.isCollapsed();
return expandRestoreActionState;
}
/*package*/ void setContent(CheatSheetElement element, ICheatSheetStateManager inputStateManager) {
CheatSheetStopWatch.startStopWatch("CheatSheetViewer.setContent(CheatSheetElement element)"); //$NON-NLS-1$
// Cleanup previous contents
internalDispose();
// Set the current content to new content
contentElement = element;
stateManager = inputStateManager;
stateManager.setElement(element);
currentID = null;
parserInput = null;
if (element != null) {
initInputFields(element);
}
CheatSheetStopWatch.printLapTime("CheatSheetViewer.setContent(CheatSheetElement element)", "Time in CheatSheetViewer.setContent() before initCheatSheetView() call: "); //$NON-NLS-1$ //$NON-NLS-2$
// Initialize the view with the new contents
boolean cheatSheetOpened = false;
if (control != null) {
cheatSheetOpened = initCheatSheetView();
}
if (!cheatSheetOpened) {
contentElement = null;
stateManager = null;
}
// If the cheat sheet failed to open clear the content element so we don't see an
CheatSheetStopWatch.printLapTime("CheatSheetViewer.setContent(CheatSheetElement element)", "Time in CheatSheetViewer.setContent() after initCheatSheetView() call: "); //$NON-NLS-1$ //$NON-NLS-2$
}
private void initInputFields(CheatSheetElement element) {
currentID = element.getID();
String contentXml = element.getContentXml();
URL contentURL = null;
restorePath = element.getRestorePath();
String errorMessage = null;
if (contentXml != null) {
parserInput = new ParserInput(contentXml, element.getHref());
return;
}
// The input was not an XML string, find the content URL
Bundle bundle = null;
if(element != null && element.getConfigurationElement() != null)
try{
String pluginId = element.getConfigurationElement().getContributor().getName();
bundle = Platform.getBundle(pluginId);
} catch (Exception e) {
// do nothing
}
if (bundle != null) {
contentURL = FileLocator.find(bundle, new Path(element.getContentFile()), null);
if (contentURL == null && element.getContentFile() != null) {
errorMessage = NLS.bind(Messages.ERROR_OPENING_FILE_IN_PARSER, (new Object[] {element.getContentFile()}));
}
}
if (contentURL == null) {
try {
contentURL = new URL(element.getHref());
} catch (MalformedURLException mue) {
}
if (contentURL == null && element.getHref() != null) {
errorMessage = NLS.bind(Messages.ERROR_OPENING_FILE_IN_PARSER, (new Object[] {element.getHref()}));
}
}
String pluginId = bundle != null ? bundle.getSymbolicName() : null;
parserInput = new ParserInput(contentURL, pluginId, errorMessage);
}
/*package*/ void setExpandRestoreAction(CheatSheetExpandRestoreAction action) {
expandRestoreAction = action;
}
/**
* Passing the focus request to the viewer's control.
*/
@Override
public void setFocus() {
//need this to have current item selected. (Assumes that when you reactivate the view you will work with current item.)
if (currentItem != null) {
currentItem.setFocus();
} else {
getControl().setFocus();
}
}
@Override
public void setInput(String id) {
setInput(id, new DefaultStateManager());
}
public void setInput(String id, ICheatSheetStateManager inputStateManager) {
CheatSheetStopWatch.startStopWatch("CheatSheetViewer.setInput(String id)"); //$NON-NLS-1$
CheatSheetElement element = null;
if(id == null) {
nullCheatSheetId = true;
} else {
nullCheatSheetId = false;
element = CheatSheetRegistryReader.getInstance().findCheatSheet(id);
if(element == null) {
String message = NLS.bind(Messages.ERROR_INVALID_CHEATSHEET_ID, (new Object[] {id}));
IStatus status = new Status(IStatus.ERROR, ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID, IStatus.OK, message, null);
CheatSheetPlugin.getPlugin().getLog().log(status);
invalidCheatSheetId = true;
} else {
invalidCheatSheetId = false;
this.isRestricted = false;
}
}
CheatSheetStopWatch.printLapTime("CheatSheetViewer.setInput(String id)", "Time in CheatSheetViewer.setInput(String id) before setContent() call: "); //$NON-NLS-1$ //$NON-NLS-2$
setContent(element, inputStateManager);
CheatSheetStopWatch.printLapTime("CheatSheetViewer.setInput(String id)", "Time in CheatSheetViewer.setInput(String id) after setContent() call: "); //$NON-NLS-1$ //$NON-NLS-2$
// Update most recently used cheat sheets list.
CheatSheetPlugin.getPlugin().getCheatSheetHistory().add(element);
CheatSheetStopWatch.printLapTime("CheatSheetViewer.setInput(String id)", "Time in CheatSheetViewer.setInput(String id) after getCheatSheetHistory() call: "); //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public void setInput(String id, String name, URL url) {
setInput(id, name, url, new DefaultStateManager(), false);
}
public void setInputFromXml(String id, String name, String xml, String basePath) {
if (id == null || name == null || xml == null) {
throw new IllegalArgumentException();
}
CheatSheetElement element = new CheatSheetElement(name);
element.setID(id);
element.setContentXml(xml);
element.setHref(basePath);
nullCheatSheetId = false;
invalidCheatSheetId = false;
isRestricted = false;
setContent(element, new NoSaveStateManager());
}
public void setInput(String id, String name, URL url,
ICheatSheetStateManager inputStateManager, boolean isRestricted) {
if (id == null || name == null || url == null) {
throw new IllegalArgumentException();
}
CheatSheetElement element = new CheatSheetElement(name);
element.setID(id);
element.setHref(url.toString());
nullCheatSheetId = false;
invalidCheatSheetId = false;
this.isRestricted = isRestricted;
setContent(element, inputStateManager);
}
/*package*/ void toggleExpandRestore() {
if(expandRestoreAction == null)
return;
if (expandRestoreAction.isCollapsed()) {
restoreExpandStates();
expandRestoreAction.setCollapsed(false);
} else {
collapseAllButCurrent(true);
expandRestoreAction.setCollapsed(true);
}
}
public Action getCopyAction() {
return copyAction;
}
public void setCopyAction(Action copyAction) {
this.copyAction = copyAction;
}
public void copy() {
if (currentItem!=null)
currentItem.copy();
}
public void addListener(CheatSheetListener listener) {
if (contentElement != null ) {
getManager().addListener(listener);
}
}
@Override
public int contributeToViewMenu(Menu menu, int index) {
if (currentPage instanceof IMenuContributor) {
return ((IMenuContributor)currentPage).contributeToViewMenu(menu, index);
}
return index;
}
public void restart() {
resetItemState();
currentItemNum = 0;
collapseAllButCurrent(false);
IntroItem introItem = (IntroItem) getViewItemAtIndex(0);
introItem.setIncomplete();
showIntroItem();
}
public void saveState(IMemento memento) {
if (currentPage instanceof CheatSheetPage) {
Properties properties = saveHelper.createProperties(currentItemNum, viewItemList, getExpandRestoreActionState(), expandRestoreList, currentID, restorePath);
saveHelper.saveToMemento(properties, getManager(), memento);
}
}
@Override
public void reset(Map<String, String> cheatSheetData) {
if (currentPage instanceof CheatSheetPage) {
restart();
getManager().setData(cheatSheetData);
} else if (currentPage instanceof CompositeCheatSheetPage) {
((CompositeCheatSheetPage)currentPage).restart(cheatSheetData);
}
}
public void showError(String message) {
internalDispose();
if(howToBegin != null) {
howToBegin.dispose();
howToBegin = null;
}
createErrorPage(message);
}
}