1 /*******************************************************************************
2  * Copyright (c) 2003, 2018 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Lars Vogel <Lars.Vogel@gmail.com> - Bug 430694
14  *     Christian Georgi (SAP)            - Bug 432480
15  *     Patrik Suzzi <psuzzi@gmail.com>   - Bug 490700, 502050
16  *     Vasili Gulevich                   - Bug 501404
17  *******************************************************************************/
18 package org.eclipse.ui.internal.ide.application;
19 
20 import java.lang.reflect.InvocationTargetException;
21 import java.net.URL;
22 import java.text.Collator;
23 import java.util.ArrayList;
24 import java.util.Map;
25 import java.util.TreeMap;
26 
27 import org.eclipse.core.internal.resources.Workspace;
28 import org.eclipse.core.net.proxy.IProxyService;
29 import org.eclipse.core.resources.IContainer;
30 import org.eclipse.core.resources.IResource;
31 import org.eclipse.core.resources.IWorkspace;
32 import org.eclipse.core.resources.ResourcesPlugin;
33 import org.eclipse.core.resources.WorkspaceJob;
34 import org.eclipse.core.runtime.CoreException;
35 import org.eclipse.core.runtime.FileLocator;
36 import org.eclipse.core.runtime.IAdaptable;
37 import org.eclipse.core.runtime.IBundleGroup;
38 import org.eclipse.core.runtime.IBundleGroupProvider;
39 import org.eclipse.core.runtime.IPath;
40 import org.eclipse.core.runtime.IProgressMonitor;
41 import org.eclipse.core.runtime.IStatus;
42 import org.eclipse.core.runtime.MultiStatus;
43 import org.eclipse.core.runtime.Path;
44 import org.eclipse.core.runtime.Platform;
45 import org.eclipse.core.runtime.ProgressMonitorWrapper;
46 import org.eclipse.core.runtime.Status;
47 import org.eclipse.core.runtime.jobs.ISchedulingRule;
48 import org.eclipse.core.runtime.jobs.Job;
49 import org.eclipse.e4.core.contexts.IEclipseContext;
50 import org.eclipse.e4.ui.internal.workbench.E4Workbench;
51 import org.eclipse.jface.dialogs.ErrorDialog;
52 import org.eclipse.jface.dialogs.IDialogSettings;
53 import org.eclipse.jface.dialogs.MessageDialog;
54 import org.eclipse.jface.dialogs.TrayDialog;
55 import org.eclipse.jface.operation.IRunnableWithProgress;
56 import org.eclipse.jface.preference.IPreferenceStore;
57 import org.eclipse.jface.resource.ImageDescriptor;
58 import org.eclipse.jface.util.Policy;
59 import org.eclipse.jface.window.Window;
60 import org.eclipse.swt.SWT;
61 import org.eclipse.swt.events.SelectionAdapter;
62 import org.eclipse.swt.events.SelectionEvent;
63 import org.eclipse.swt.widgets.Composite;
64 import org.eclipse.swt.widgets.Display;
65 import org.eclipse.swt.widgets.Listener;
66 import org.eclipse.swt.widgets.Shell;
67 import org.eclipse.ui.PlatformUI;
68 import org.eclipse.ui.application.IWorkbenchConfigurer;
69 import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
70 import org.eclipse.ui.application.WorkbenchAdvisor;
71 import org.eclipse.ui.application.WorkbenchWindowAdvisor;
72 import org.eclipse.ui.ide.IDE;
73 import org.eclipse.ui.internal.ISelectionConversionService;
74 import org.eclipse.ui.internal.PluginActionBuilder;
75 import org.eclipse.ui.internal.Workbench;
76 import org.eclipse.ui.internal.ide.AboutInfo;
77 import org.eclipse.ui.internal.ide.IDEInternalPreferences;
78 import org.eclipse.ui.internal.ide.IDEInternalWorkbenchImages;
79 import org.eclipse.ui.internal.ide.IDESelectionConversionService;
80 import org.eclipse.ui.internal.ide.IDEWorkbenchActivityHelper;
81 import org.eclipse.ui.internal.ide.IDEWorkbenchErrorHandler;
82 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
83 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
84 import org.eclipse.ui.internal.ide.undo.WorkspaceUndoMonitor;
85 import org.eclipse.ui.internal.progress.ProgressMonitorJobsDialog;
86 import org.eclipse.ui.progress.IProgressService;
87 import org.eclipse.ui.statushandlers.AbstractStatusHandler;
88 import org.eclipse.urischeme.AutoRegisterSchemeHandlersJob;
89 import org.osgi.framework.Bundle;
90 import org.osgi.framework.ServiceReference;
91 import org.osgi.framework.Version;
92 
93 /**
94  * IDE-specified workbench advisor which configures the workbench for use as an
95  * IDE.
96  * <p>
97  * Note: This class replaces <code>org.eclipse.ui.internal.Workbench</code>.
98  * </p>
99  *
100  * @since 3.0
101  */
102 public class IDEWorkbenchAdvisor extends WorkbenchAdvisor {
103 
104 	private static final String WORKBENCH_PREFERENCE_CATEGORY_ID = "org.eclipse.ui.preferencePages.Workbench"; //$NON-NLS-1$
105 
106 	/**
107 	 * The dialog setting key to access the known installed features since the
108 	 * last time the workbench was run.
109 	 */
110 	private static final String INSTALLED_FEATURES = "installedFeatures"; //$NON-NLS-1$
111 
112 	private static IDEWorkbenchAdvisor workbenchAdvisor = null;
113 
114 	/**
115 	 * Ordered map of versioned feature ids -gt; info that are new for this
116 	 * session; <code>null</code> if uninitialized. Key type:
117 	 * <code>String</code>, Value type: <code>AboutInfo</code>.
118 	 */
119 	private Map<String, AboutInfo> newlyAddedBundleGroups;
120 
121 	/**
122 	 * Array of <code>AboutInfo</code> for all new installed features that
123 	 * specify a welcome perspective.
124 	 */
125 	private AboutInfo[] welcomePerspectiveInfos = null;
126 
127 	/**
128 	 * Helper for managing activites in response to workspace changes.
129 	 */
130 	private IDEWorkbenchActivityHelper activityHelper = null;
131 
132 	private Listener settingsChangeListener;
133 
134 	/**
135 	 * Support class for monitoring workspace changes and periodically
136 	 * validating the undo history
137 	 */
138 	private WorkspaceUndoMonitor workspaceUndoMonitor;
139 
140 	/**
141 	 * The IDE workbench error handler.
142 	 */
143 	private AbstractStatusHandler ideWorkbenchErrorHandler;
144 
145 	/**
146 	 * Helper class used to process delayed events.
147 	 */
148 	private final DelayedEventsProcessor delayedEventsProcessor;
149 
150 	private static boolean jfaceComparatorIsSet = false;
151 
152 	/**
153 	 * Base wait time between workspace lock attempts
154 	 */
155 	private final int workspaceWaitDelay;
156 
157 	private final Listener closeListener = event -> {
158 		boolean doExit = IDEWorkbenchWindowAdvisor.promptOnExit(null);
159 		event.doit = doExit;
160 		if (!doExit)
161 			event.type = SWT.None;
162 	};
163 
164 	/**
165 	 * Creates a new workbench advisor instance.
166 	 */
IDEWorkbenchAdvisor()167 	public IDEWorkbenchAdvisor() {
168 		this(1000, null);
169 	}
170 
IDEWorkbenchAdvisor(int workspaceWaitDelay, DelayedEventsProcessor processor)171 	IDEWorkbenchAdvisor(int workspaceWaitDelay, DelayedEventsProcessor processor) {
172 		super();
173 		this.workspaceWaitDelay = workspaceWaitDelay;
174 		if (workbenchAdvisor != null) {
175 			throw new IllegalStateException();
176 		}
177 		workbenchAdvisor = this;
178 
179 		this.delayedEventsProcessor = processor;
180 		Display.getDefault().addListener(SWT.Close, closeListener);
181 	}
182 
183 	/**
184 	 * Creates a new workbench advisor instance supporting delayed file open.
185 	 * @param processor helper class used to process delayed events
186 	 */
IDEWorkbenchAdvisor(DelayedEventsProcessor processor)187 	public IDEWorkbenchAdvisor(DelayedEventsProcessor processor) {
188 		this(1000, processor);
189 	}
190 
191 	@Override
initialize(IWorkbenchConfigurer configurer)192 	public void initialize(IWorkbenchConfigurer configurer) {
193 
194 		PluginActionBuilder.setAllowIdeLogging(true);
195 
196 		// make sure we always save and restore workspace state
197 		configurer.setSaveAndRestore(true);
198 
199 		// register workspace adapters
200 		IDE.registerAdapters();
201 
202 		// register shared images
203 		declareWorkbenchImages();
204 
205 		// initialize the activity helper
206 		activityHelper = IDEWorkbenchActivityHelper.getInstance();
207 
208 		// initialize the workspace undo monitor
209 		workspaceUndoMonitor = WorkspaceUndoMonitor.getInstance();
210 
211 		// show Help button in JFace dialogs
212 		TrayDialog.setDialogHelpAvailable(true);
213 
214 		// Set the default value of the preference controlling the workspace
215 		// name displayed in the window title.
216 		setWorkspaceNameDefault();
217 
218 		if (!jfaceComparatorIsSet) {
219 			// Policy.setComparator can only be called once in Jface lifetime
220 			Policy.setComparator(Collator.getInstance());
221 			jfaceComparatorIsSet = true;
222 		}
223 
224 		new AutoRegisterSchemeHandlersJob().schedule();
225 	}
226 
227 	@Override
preStartup()228 	public void preStartup() {
229 		// Register the build actions
230 		IProgressService service = PlatformUI.getWorkbench()
231 				.getProgressService();
232 		ImageDescriptor newImage = IDEInternalWorkbenchImages
233 				.getImageDescriptor(IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC);
234 		service.registerIconForFamily(newImage,
235 				ResourcesPlugin.FAMILY_MANUAL_BUILD);
236 		service.registerIconForFamily(newImage,
237 				ResourcesPlugin.FAMILY_AUTO_BUILD);
238 	}
239 
240 	@Override
postStartup()241 	public void postStartup() {
242 		try {
243 			refreshFromLocal();
244 			activateProxyService();
245 			((Workbench) PlatformUI.getWorkbench()).registerService(
246 					ISelectionConversionService.class,
247 					new IDESelectionConversionService());
248 
249 			initializeSettingsChangeListener();
250 			Display.getCurrent().addListener(SWT.Settings,
251 					settingsChangeListener);
252 		} finally {
253 			// Resume the job manager to allow background jobs to run.
254 			// The job manager was suspended by the IDEApplication.start method.
255 			Job.getJobManager().resume();
256 		}
257 	}
258 
259 	/**
260 	 * Activate the proxy service by obtaining it.
261 	 */
activateProxyService()262 	private void activateProxyService() {
263 		Bundle bundle = Platform.getBundle("org.eclipse.ui.ide"); //$NON-NLS-1$
264 		Object proxyService = null;
265 		if (bundle != null) {
266 			ServiceReference<IProxyService> ref = bundle.getBundleContext().getServiceReference(IProxyService.class);
267 			if (ref != null)
268 				proxyService = bundle.getBundleContext().getService(ref);
269 		}
270 		if (proxyService == null) {
271 			IDEWorkbenchPlugin.log("Proxy service could not be found."); //$NON-NLS-1$
272 		}
273 	}
274 
275 	/**
276 	 * Initialize the listener for settings changes.
277 	 */
initializeSettingsChangeListener()278 	private void initializeSettingsChangeListener() {
279 		settingsChangeListener = new Listener() {
280 
281 			boolean currentHighContrast = Display.getCurrent()
282 					.getHighContrast();
283 
284 			@Override
285 			public void handleEvent(org.eclipse.swt.widgets.Event event) {
286 				if (Display.getCurrent().getHighContrast() == currentHighContrast)
287 					return;
288 
289 				currentHighContrast = !currentHighContrast;
290 
291 				// make sure they really want to do this
292 				if (new MessageDialog(null, IDEWorkbenchMessages.SystemSettingsChange_title, null,
293 						IDEWorkbenchMessages.SystemSettingsChange_message, MessageDialog.QUESTION, 1,
294 						IDEWorkbenchMessages.SystemSettingsChange_yes, IDEWorkbenchMessages.SystemSettingsChange_no)
295 								.open() == Window.OK) {
296 					PlatformUI.getWorkbench().restart();
297 				}
298 			}
299 		};
300 
301 	}
302 
303 	@Override
postShutdown()304 	public void postShutdown() {
305 		Display.getDefault().removeListener(SWT.Close, closeListener);
306 		if (activityHelper != null) {
307 			activityHelper.shutdown();
308 			activityHelper = null;
309 		}
310 
311 		if (workspaceUndoMonitor != null) {
312 			workspaceUndoMonitor.shutdown();
313 			workspaceUndoMonitor = null;
314 		}
315 
316 		IWorkspace workspace = IDEWorkbenchPlugin.getPluginWorkspace();
317 
318 		final Runnable disconnectFromWorkspace = new Runnable() {
319 
320 			int attempts;
321 
322 			@Override
323 			public void run() {
324 				if (isWorkspaceLocked(workspace)) {
325 					if (attempts < 3) {
326 						attempts++;
327 						IDEWorkbenchPlugin.log(null, createErrorStatus("Workspace is locked, waiting...")); //$NON-NLS-1$
328 						Display.getCurrent().timerExec(workspaceWaitDelay * attempts, this);
329 					} else {
330 						IDEWorkbenchPlugin.log(null, createErrorStatus("Workspace is locked and can't be saved.")); //$NON-NLS-1$
331 					}
332 					return;
333 				}
334 				disconnectFromWorkspace();
335 			}
336 
337 			IStatus createErrorStatus(String exceptionMessage) {
338 				return new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, 1,
339 						IDEWorkbenchMessages.ProblemsSavingWorkspace, new IllegalStateException(exceptionMessage));
340 			}
341 		};
342 
343 		// postShutdown may be called while workspace is locked, for example -
344 		// during file save operation, see
345 		// https://bugs.eclipse.org/bugs/show_bug.cgi?id=501404.
346 		// Disconnect is postponed until workspace is unlocked to prevent
347 		// deadlock between background thread launched by
348 		// disconnectFromWorkspace() and current thread
349 		// WARNING: this condition makes code that relies on synchronous
350 		// disconnect very hard to discover and test
351 		if (workspace != null) {
352 			if (isWorkspaceLocked(workspace)) {
353 				Display.getCurrent().asyncExec(disconnectFromWorkspace);
354 			} else {
355 				disconnectFromWorkspace.run();
356 			}
357 		}
358 		// This completes workbench lifecycle.
359 		// Another advisor can now be created for a new workbench instance.
360 		workbenchAdvisor = null;
361 	}
362 
isWorkspaceLocked(IWorkspace workspace)363 	private boolean isWorkspaceLocked(IWorkspace workspace) {
364 		ISchedulingRule currentRule = Job.getJobManager().currentRule();
365 		return currentRule != null && currentRule.isConflicting(workspace.getRoot());
366 	}
367 
368 	@Override
preShutdown()369 	public boolean preShutdown() {
370 		Display.getCurrent().removeListener(SWT.Settings,
371 				settingsChangeListener);
372 		return super.preShutdown();
373 	}
374 
375 	@Override
createWorkbenchWindowAdvisor( IWorkbenchWindowConfigurer configurer)376 	public WorkbenchWindowAdvisor createWorkbenchWindowAdvisor(
377 			IWorkbenchWindowConfigurer configurer) {
378 		return new IDEWorkbenchWindowAdvisor(this, configurer);
379 	}
380 
381 	/**
382 	 * Return true if the intro plugin is present and false otherwise.
383 	 *
384 	 * @return boolean
385 	 */
hasIntro()386 	public boolean hasIntro() {
387 		return getWorkbenchConfigurer().getWorkbench().getIntroManager()
388 				.hasIntro();
389 	}
390 
refreshFromLocal()391 	private void refreshFromLocal() {
392 		String[] commandLineArgs = Platform.getCommandLineArgs();
393 		IPreferenceStore store = IDEWorkbenchPlugin.getDefault()
394 				.getPreferenceStore();
395 		boolean refresh = store
396 				.getBoolean(IDEInternalPreferences.REFRESH_WORKSPACE_ON_STARTUP);
397 		if (!refresh) {
398 			return;
399 		}
400 
401 		// Do not refresh if it was already done by core on startup.
402 		for (String commandLineArg : commandLineArgs) {
403 			if (commandLineArg.equalsIgnoreCase("-refresh")) { //$NON-NLS-1$
404 				return;
405 			}
406 		}
407 
408 		final IContainer root = ResourcesPlugin.getWorkspace().getRoot();
409 		Job job = new WorkspaceJob(IDEWorkbenchMessages.Workspace_refreshing) {
410 			@Override
411 			public IStatus runInWorkspace(IProgressMonitor monitor)
412 					throws CoreException {
413 				root.refreshLocal(IResource.DEPTH_INFINITE, monitor);
414 				return Status.OK_STATUS;
415 			}
416 		};
417 		job.setRule(root);
418 		job.schedule();
419 	}
420 
421 	private static class CancelableProgressMonitorWrapper extends
422 			ProgressMonitorWrapper {
423 		private double total = 0;
424 		private ProgressMonitorJobsDialog dialog;
425 
CancelableProgressMonitorWrapper(IProgressMonitor monitor, ProgressMonitorJobsDialog dialog)426 		CancelableProgressMonitorWrapper(IProgressMonitor monitor,
427 				ProgressMonitorJobsDialog dialog) {
428 			super(monitor);
429 			this.dialog = dialog;
430 		}
431 
432 		@Override
internalWorked(double work)433 		public void internalWorked(double work) {
434 			super.internalWorked(work);
435 			total += work;
436 			updateProgressDetails();
437 		}
438 
439 		@Override
worked(int work)440 		public void worked(int work) {
441 			super.worked(work);
442 			total += work;
443 			updateProgressDetails();
444 		}
445 
446 		@Override
beginTask(String name, int totalWork)447 		public void beginTask(String name, int totalWork) {
448 			super.beginTask(name, totalWork);
449 			subTask(IDEWorkbenchMessages.IDEWorkbenchAdvisor_preHistoryCompaction);
450 		}
451 
updateProgressDetails()452 		private void updateProgressDetails() {
453 			if (!isCanceled() && Math.abs(total - 4.0) < 0.0001 /* right before history compacting */) {
454 				subTask(IDEWorkbenchMessages.IDEWorkbenchAdvisor_cancelHistoryPruning);
455 				dialog.setCancelable(true);
456 			}
457 			if (Math.abs(total - 5.0) < 0.0001 /* history compacting finished */) {
458 				subTask(IDEWorkbenchMessages.IDEWorkbenchAdvisor_postHistoryCompaction);
459 				dialog.setCancelable(false);
460 			}
461 		}
462 	}
463 
464 	private static class CancelableProgressMonitorJobsDialog extends
465 			ProgressMonitorJobsDialog {
466 
CancelableProgressMonitorJobsDialog(Shell parent)467 		public CancelableProgressMonitorJobsDialog(Shell parent) {
468 			super(parent);
469 		}
470 
471 		@Override
createButtonsForButtonBar(Composite parent)472 		protected void createButtonsForButtonBar(Composite parent) {
473 			super.createButtonsForButtonBar(parent);
474 			registerCancelButtonListener();
475 		}
476 
registerCancelButtonListener()477 		public void registerCancelButtonListener() {
478 			cancel.addSelectionListener(new SelectionAdapter() {
479 				@Override
480 				public void widgetSelected(SelectionEvent e) {
481 					subTaskLabel.setText(""); //$NON-NLS-1$
482 				}
483 			});
484 		}
485 	}
486 
487 	/**
488 	 * Disconnect from the core workspace.
489 	 *
490 	 * Locks workspace in a background thread, should not be called while
491 	 * holding any workspace locks.
492 	 */
disconnectFromWorkspace()493 	private void disconnectFromWorkspace() {
494 		// save the workspace
495 		final MultiStatus status = new MultiStatus(IDEWorkbenchPlugin.IDE_WORKBENCH, 1,
496 				IDEWorkbenchMessages.ProblemSavingWorkbench);
497 		try {
498 			final ProgressMonitorJobsDialog p = new CancelableProgressMonitorJobsDialog(
499 					null);
500 
501 			final boolean applyPolicy = ResourcesPlugin.getWorkspace()
502 					.getDescription().isApplyFileStatePolicy();
503 
504 			IRunnableWithProgress runnable = monitor -> {
505 				try {
506 					if (applyPolicy)
507 						monitor = new CancelableProgressMonitorWrapper(monitor, p);
508 
509 					status.merge(((Workspace) ResourcesPlugin.getWorkspace()).save(true, true, monitor));
510 				} catch (CoreException e) {
511 					status.merge(e.getStatus());
512 				}
513 			};
514 
515 			p.run(true, false, runnable);
516 		} catch (InvocationTargetException e) {
517 			status
518 					.merge(new Status(IStatus.ERROR,
519 							IDEWorkbenchPlugin.IDE_WORKBENCH, 1,
520 							IDEWorkbenchMessages.InternalError, e
521 									.getTargetException()));
522 		} catch (InterruptedException e) {
523 			status.merge(new Status(IStatus.ERROR,
524 					IDEWorkbenchPlugin.IDE_WORKBENCH, 1,
525 					IDEWorkbenchMessages.InternalError, e));
526 		}
527 		ErrorDialog.openError(null,
528 				IDEWorkbenchMessages.ProblemsSavingWorkspace, null, status,
529 				IStatus.ERROR | IStatus.WARNING);
530 		if (!status.isOK()) {
531 			IDEWorkbenchPlugin.log(
532 					IDEWorkbenchMessages.ProblemsSavingWorkspace, status);
533 		}
534 	}
535 
536 	@Override
getDefaultPageInput()537 	public IAdaptable getDefaultPageInput() {
538 		return ResourcesPlugin.getWorkspace().getRoot();
539 	}
540 
541 	@Override
getInitialWindowPerspectiveId()542 	public String getInitialWindowPerspectiveId() {
543 		int index = PlatformUI.getWorkbench().getWorkbenchWindowCount() - 1;
544 
545 		String perspectiveId = null;
546 		AboutInfo[] welcomeInfos = getWelcomePerspectiveInfos();
547 		if (index >= 0 && welcomeInfos != null && index < welcomeInfos.length) {
548 			perspectiveId = welcomeInfos[index].getWelcomePerspectiveId();
549 		}
550 		if (perspectiveId == null) {
551 			perspectiveId = IDE.RESOURCE_PERSPECTIVE_ID;
552 		}
553 		return perspectiveId;
554 	}
555 
556 	/**
557 	 * Returns the map of versioned feature ids -&gt; info object for all installed
558 	 * features. The format of the versioned feature id (the key of the map) is
559 	 * featureId + ":" + versionId.
560 	 *
561 	 * @return map of versioned feature ids -&gt; info object (key type:
562 	 *         <code>String</code>, value type: <code>AboutInfo</code>)
563 	 * @since 3.0
564 	 */
computeBundleGroupMap()565 	private Map<String, AboutInfo> computeBundleGroupMap() {
566 		// use tree map to get predicable order
567 		Map<String, AboutInfo> ids = new TreeMap<>();
568 
569 		IBundleGroupProvider[] providers = Platform.getBundleGroupProviders();
570 		for (IBundleGroupProvider provider : providers) {
571 			IBundleGroup[] groups = provider.getBundleGroups();
572 			for (IBundleGroup group : groups) {
573 				AboutInfo info = new AboutInfo(group);
574 
575 				String version = info.getVersionId();
576 				version = version == null ? "0.0.0" //$NON-NLS-1$
577 						: new Version(version).toString();
578 				String versionedFeature = group.getIdentifier() + ":" + version; //$NON-NLS-1$
579 
580 				ids.put(versionedFeature, info);
581 			}
582 		}
583 
584 		return ids;
585 	}
586 
587 	/**
588 	 * Returns the ordered map of versioned feature ids -&gt; AboutInfo that are
589 	 * new for this session.
590 	 *
591 	 * @return ordered map of versioned feature ids (key type:
592 	 *         <code>String</code>) -&gt; infos (value type:
593 	 *         <code>AboutInfo</code>).
594 	 */
getNewlyAddedBundleGroups()595 	public Map<String, AboutInfo> getNewlyAddedBundleGroups() {
596 		if (newlyAddedBundleGroups == null) {
597 			newlyAddedBundleGroups = createNewBundleGroupsMap();
598 		}
599 		return newlyAddedBundleGroups;
600 	}
601 
602 	/**
603 	 * Updates the old features setting and returns a map of new features.
604 	 */
createNewBundleGroupsMap()605 	private Map<String, AboutInfo> createNewBundleGroupsMap() {
606 		// retrieve list of installed bundle groups from last session
607 		IDialogSettings settings = IDEWorkbenchPlugin.getDefault()
608 				.getDialogSettings();
609 		String[] previousFeaturesArray = settings.getArray(INSTALLED_FEATURES);
610 
611 		// get a map of currently installed bundle groups and store it for next
612 		// session
613 		Map<String, AboutInfo> bundleGroups = computeBundleGroupMap();
614 		String[] currentFeaturesArray = new String[bundleGroups.size()];
615 		bundleGroups.keySet().toArray(currentFeaturesArray);
616 		settings.put(INSTALLED_FEATURES, currentFeaturesArray);
617 
618 		// remove the previously known from the current set
619 		if (previousFeaturesArray != null) {
620 			for (String previousFeature : previousFeaturesArray) {
621 				bundleGroups.remove(previousFeature);
622 			}
623 		}
624 
625 		return bundleGroups;
626 	}
627 
628 	/**
629 	 * Sets the default value of the preference controlling the workspace name
630 	 * displayed in the window title to the name of the workspace directory.
631 	 * This preference cannot be set in the preference initializer because the
632 	 * workspace directory may not be known when the preference initializer is
633 	 * called.
634 	 */
setWorkspaceNameDefault()635 	private static void setWorkspaceNameDefault() {
636 		IPreferenceStore preferences = IDEWorkbenchPlugin.getDefault().getPreferenceStore();
637 		String workspaceNameDefault = preferences.getDefaultString(IDEInternalPreferences.WORKSPACE_NAME);
638 		if (workspaceNameDefault != null && !workspaceNameDefault.isEmpty())
639 			return; // Default is set in a plugin customization file - don't change it.
640 		IPath workspaceDir = Platform.getLocation();
641 		if (workspaceDir == null)
642 			return;
643 		String workspaceName = workspaceDir.lastSegment();
644 		if (workspaceName == null)
645 			return;
646 		preferences.setDefault(IDEInternalPreferences.WORKSPACE_NAME, workspaceName);
647 	}
648 
649 	/**
650 	 * Declares all IDE-specific workbench images. This includes both "shared"
651 	 * images (named in {@link org.eclipse.ui.ide.IDE.SharedImages}) and internal images (named in
652 	 * {@link org.eclipse.ui.internal.ide.IDEInternalWorkbenchImages}).
653 	 *
654 	 * @see IWorkbenchConfigurer#declareImage
655 	 */
declareWorkbenchImages()656 	private void declareWorkbenchImages() {
657 
658 		final String ICONS_PATH = "$nl$/icons/full/";//$NON-NLS-1$
659 		final String PATH_ELOCALTOOL = ICONS_PATH + "elcl16/"; // Enabled //$NON-NLS-1$
660 
661 		// toolbar
662 		// icons.
663 		final String PATH_DLOCALTOOL = ICONS_PATH + "dlcl16/"; // Disabled //$NON-NLS-1$
664 		// //$NON-NLS-1$
665 		// toolbar
666 		// icons.
667 		final String PATH_ETOOL = ICONS_PATH + "etool16/"; // Enabled toolbar //$NON-NLS-1$
668 		// //$NON-NLS-1$
669 		// icons.
670 		final String PATH_DTOOL = ICONS_PATH + "dtool16/"; // Disabled toolbar //$NON-NLS-1$
671 		// //$NON-NLS-1$
672 		// icons.
673 		final String PATH_OBJECT = ICONS_PATH + "obj16/"; // Model object //$NON-NLS-1$
674 		// //$NON-NLS-1$
675 		// icons
676 		final String PATH_WIZBAN = ICONS_PATH + "wizban/"; // Wizard //$NON-NLS-1$
677 		// //$NON-NLS-1$
678 		// icons
679 
680 		// View icons
681 		final String PATH_EVIEW= ICONS_PATH + "eview16/"; //$NON-NLS-1$
682 
683 
684 		Bundle ideBundle = Platform.getBundle(IDEWorkbenchPlugin.IDE_WORKBENCH);
685 
686 		declareWorkbenchImage(ideBundle,
687 				IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC, PATH_ETOOL
688 						+ "build_exec.png", false); //$NON-NLS-1$
689 		declareWorkbenchImage(ideBundle,
690 				IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC_HOVER,
691 				PATH_ETOOL + "build_exec.png", false); //$NON-NLS-1$
692 		declareWorkbenchImage(ideBundle,
693 				IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC_DISABLED,
694 				PATH_DTOOL + "build_exec.png", false); //$NON-NLS-1$
695 
696 		declareWorkbenchImage(ideBundle,
697 				IDEInternalWorkbenchImages.IMG_ETOOL_SEARCH_SRC, PATH_ETOOL
698 						+ "search_src.png", false); //$NON-NLS-1$
699 		declareWorkbenchImage(ideBundle,
700 				IDEInternalWorkbenchImages.IMG_ETOOL_SEARCH_SRC_HOVER,
701 				PATH_ETOOL + "search_src.png", false); //$NON-NLS-1$
702 		declareWorkbenchImage(ideBundle,
703 				IDEInternalWorkbenchImages.IMG_ETOOL_SEARCH_SRC_DISABLED,
704 				PATH_DTOOL + "search_src.png", false); //$NON-NLS-1$
705 
706 		declareWorkbenchImage(ideBundle,
707 				IDEInternalWorkbenchImages.IMG_ETOOL_NEXT_NAV, PATH_ETOOL
708 						+ "next_nav.png", false); //$NON-NLS-1$
709 
710 		declareWorkbenchImage(ideBundle,
711 				IDEInternalWorkbenchImages.IMG_ETOOL_PREVIOUS_NAV, PATH_ETOOL
712 						+ "prev_nav.png", false); //$NON-NLS-1$
713 
714 		declareWorkbenchImage(ideBundle,
715 				IDEInternalWorkbenchImages.IMG_WIZBAN_NEWPRJ_WIZ, PATH_WIZBAN
716 						+ "newprj_wiz.png", false); //$NON-NLS-1$
717 		declareWorkbenchImage(ideBundle,
718 				IDEInternalWorkbenchImages.IMG_WIZBAN_NEWFOLDER_WIZ,
719 				PATH_WIZBAN + "newfolder_wiz.png", false); //$NON-NLS-1$
720 		declareWorkbenchImage(ideBundle,
721 				IDEInternalWorkbenchImages.IMG_WIZBAN_NEWFILE_WIZ, PATH_WIZBAN
722 						+ "newfile_wiz.png", false); //$NON-NLS-1$
723 
724 		declareWorkbenchImage(ideBundle,
725 				IDEInternalWorkbenchImages.IMG_WIZBAN_IMPORTDIR_WIZ,
726 				PATH_WIZBAN + "importdir_wiz.png", false); //$NON-NLS-1$
727 		declareWorkbenchImage(ideBundle,
728 				IDEInternalWorkbenchImages.IMG_WIZBAN_IMPORTZIP_WIZ,
729 				PATH_WIZBAN + "importzip_wiz.png", false); //$NON-NLS-1$
730 
731 		declareWorkbenchImage(ideBundle,
732 				IDEInternalWorkbenchImages.IMG_WIZBAN_EXPORTDIR_WIZ,
733 				PATH_WIZBAN + "exportdir_wiz.png", false); //$NON-NLS-1$
734 		declareWorkbenchImage(ideBundle,
735 				IDEInternalWorkbenchImages.IMG_WIZBAN_EXPORTZIP_WIZ,
736 				PATH_WIZBAN + "exportzip_wiz.png", false); //$NON-NLS-1$
737 
738 		declareWorkbenchImage(ideBundle,
739 				IDEInternalWorkbenchImages.IMG_WIZBAN_RESOURCEWORKINGSET_WIZ,
740 				PATH_WIZBAN + "workset_wiz.png", false); //$NON-NLS-1$
741 
742 		declareWorkbenchImage(ideBundle,
743 				IDEInternalWorkbenchImages.IMG_DLGBAN_SAVEAS_DLG, PATH_WIZBAN
744 						+ "saveas_wiz.png", false); //$NON-NLS-1$
745 
746 		declareWorkbenchImage(ideBundle,
747 				IDEInternalWorkbenchImages.IMG_DLGBAN_QUICKFIX_DLG, PATH_WIZBAN
748 						+ "quick_fix.png", false); //$NON-NLS-1$
749 
750 		declareWorkbenchImage(ideBundle, IDE.SharedImages.IMG_OBJ_PROJECT,
751 				PATH_OBJECT + "prj_obj.png", true); //$NON-NLS-1$
752 		declareWorkbenchImage(ideBundle,
753 				IDE.SharedImages.IMG_OBJ_PROJECT_CLOSED, PATH_OBJECT
754 						+ "cprj_obj.png", true); //$NON-NLS-1$
755 		declareWorkbenchImage(ideBundle, IDE.SharedImages.IMG_OPEN_MARKER,
756 				PATH_ELOCALTOOL + "gotoobj_tsk.png", true); //$NON-NLS-1$
757 
758 
759 		// Quick fix icons
760 		declareWorkbenchImage(ideBundle,
761 				IDEInternalWorkbenchImages.IMG_ELCL_QUICK_FIX_ENABLED,
762 				PATH_ELOCALTOOL + "smartmode_co.png", true); //$NON-NLS-1$
763 
764 		declareWorkbenchImage(ideBundle,
765 				IDEInternalWorkbenchImages.IMG_DLCL_QUICK_FIX_DISABLED,
766 				PATH_DLOCALTOOL + "smartmode_co.png", true); //$NON-NLS-1$
767 
768 		declareWorkbenchImage(ideBundle,
769 				IDEInternalWorkbenchImages.IMG_OBJS_FIXABLE_WARNING,
770 				PATH_OBJECT + "quickfix_warning_obj.png", true); //$NON-NLS-1$
771 		declareWorkbenchImage(ideBundle,
772 				IDEInternalWorkbenchImages.IMG_OBJS_FIXABLE_ERROR,
773 				PATH_OBJECT + "quickfix_error_obj.png", true); //$NON-NLS-1$
774 		declareWorkbenchImage(ideBundle,
775 				IDEInternalWorkbenchImages.IMG_OBJS_FIXABLE_INFO,
776 				PATH_OBJECT + "quickfix_info_obj.png", true); //$NON-NLS-1$
777 
778 
779 		// task objects
780 		// declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_HPRIO_TSK,
781 		// PATH_OBJECT+"hprio_tsk.png");
782 		// declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_MPRIO_TSK,
783 		// PATH_OBJECT+"mprio_tsk.png");
784 		// declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_LPRIO_TSK,
785 		// PATH_OBJECT+"lprio_tsk.png");
786 
787 		declareWorkbenchImage(ideBundle, IDE.SharedImages.IMG_OBJS_TASK_TSK,
788 				PATH_OBJECT + "taskmrk_tsk.png", true); //$NON-NLS-1$
789 		declareWorkbenchImage(ideBundle, IDE.SharedImages.IMG_OBJS_BKMRK_TSK,
790 				PATH_OBJECT + "bkmrk_tsk.png", true); //$NON-NLS-1$
791 
792 		declareWorkbenchImage(ideBundle,
793 				IDEInternalWorkbenchImages.IMG_OBJS_COMPLETE_TSK, PATH_OBJECT
794 						+ "complete_tsk.png", true); //$NON-NLS-1$
795 		declareWorkbenchImage(ideBundle,
796 				IDEInternalWorkbenchImages.IMG_OBJS_INCOMPLETE_TSK, PATH_OBJECT
797 						+ "incomplete_tsk.png", true); //$NON-NLS-1$
798 		declareWorkbenchImage(ideBundle,
799 				IDEInternalWorkbenchImages.IMG_OBJS_WELCOME_ITEM, PATH_OBJECT
800 						+ "welcome_item.png", true); //$NON-NLS-1$
801 		declareWorkbenchImage(ideBundle,
802 				IDEInternalWorkbenchImages.IMG_OBJS_WELCOME_BANNER, PATH_OBJECT
803 						+ "welcome_banner.png", true); //$NON-NLS-1$
804 		declareWorkbenchImage(ideBundle,
805 				IDEInternalWorkbenchImages.IMG_OBJS_ERROR_PATH, PATH_OBJECT
806 						+ "error_tsk.png", true); //$NON-NLS-1$
807 		declareWorkbenchImage(ideBundle,
808 				IDEInternalWorkbenchImages.IMG_OBJS_WARNING_PATH, PATH_OBJECT
809 						+ "warn_tsk.png", true); //$NON-NLS-1$
810 		declareWorkbenchImage(ideBundle,
811 				IDEInternalWorkbenchImages.IMG_OBJS_INFO_PATH, PATH_OBJECT
812 						+ "info_tsk.png", true); //$NON-NLS-1$
813 
814 		declareWorkbenchImage(ideBundle,
815 				IDEInternalWorkbenchImages.IMG_LCL_FLAT_LAYOUT, PATH_ELOCALTOOL
816 						+ "flatLayout.png", true); //$NON-NLS-1$
817 		declareWorkbenchImage(ideBundle,
818 				IDEInternalWorkbenchImages.IMG_LCL_HIERARCHICAL_LAYOUT,
819 				PATH_ELOCALTOOL + "hierarchicalLayout.png", true); //$NON-NLS-1$
820 		declareWorkbenchImage(ideBundle,
821 				IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEM_CATEGORY,
822 				PATH_ETOOL + "problem_category.png", true); //$NON-NLS-1$
823 
824 		declareWorkbenchImage(ideBundle,
825 				IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEMS_VIEW,
826 				PATH_EVIEW + "problems_view.png", true); //$NON-NLS-1$
827 		declareWorkbenchImage(ideBundle,
828 				IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEMS_VIEW_ERROR,
829 				PATH_EVIEW + "problems_view_error.png", true); //$NON-NLS-1$
830 		declareWorkbenchImage(ideBundle,
831 				IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEMS_VIEW_WARNING,
832 				PATH_EVIEW + "problems_view_warning.png", true); //$NON-NLS-1$
833 		declareWorkbenchImage(ideBundle,
834 				IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEMS_VIEW_INFO,
835 				PATH_EVIEW + "problems_view_info.png", true); //$NON-NLS-1$
836 
837 		// synchronization indicator objects
838 		// declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_WBET_STAT,
839 		// PATH_OVERLAY+"wbet_stat.png");
840 		// declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_SBET_STAT,
841 		// PATH_OVERLAY+"sbet_stat.png");
842 		// declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_CONFLICT_STAT,
843 		// PATH_OVERLAY+"conflict_stat.png");
844 
845 		// content locality indicator objects
846 		// declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_NOTLOCAL_STAT,
847 		// PATH_STAT+"notlocal_stat.png");
848 		// declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_LOCAL_STAT,
849 		// PATH_STAT+"local_stat.png");
850 		// declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_FILLLOCAL_STAT,
851 		// PATH_STAT+"filllocal_stat.png");
852 	}
853 
854 	/**
855 	 * Declares an IDE-specific workbench image.
856 	 *
857 	 * @param symbolicName
858 	 *            the symbolic name of the image
859 	 * @param path
860 	 *            the path of the image file; this path is relative to the base
861 	 *            of the IDE plug-in
862 	 * @param shared
863 	 *            <code>true</code> if this is a shared image, and
864 	 *            <code>false</code> if this is not a shared image
865 	 * @see IWorkbenchConfigurer#declareImage
866 	 */
declareWorkbenchImage(Bundle ideBundle, String symbolicName, String path, boolean shared)867 	private void declareWorkbenchImage(Bundle ideBundle, String symbolicName,
868 			String path, boolean shared) {
869 		URL url = FileLocator.find(ideBundle, new Path(path), null);
870 		ImageDescriptor desc = ImageDescriptor.createFromURL(url);
871 		getWorkbenchConfigurer().declareImage(symbolicName, desc, shared);
872 	}
873 
874 	@Override
getMainPreferencePageId()875 	public String getMainPreferencePageId() {
876 		// indicate that we want the Workench preference page to be prominent
877 		return WORKBENCH_PREFERENCE_CATEGORY_ID;
878 	}
879 
880 	/**
881 	 * Returns the location specified in command line when -showlocation is
882 	 * defined. Otherwise returns null
883 	 *
884 	 * @return
885 	 */
getCommandLineLocation()886 	public String getCommandLineLocation() {
887 		IEclipseContext context = getWorkbenchConfigurer().getWorkbench().getService(IEclipseContext.class);
888 		return context != null ? (String) context.get(E4Workbench.FORCED_SHOW_LOCATION) : null;
889 	}
890 
891 	/**
892 	 * Returns the location to show in the window title, depending on a
893 	 * {@link IDEInternalPreferences#SHOW_LOCATION} user preference. Note that
894 	 * this may be overridden by the '-showlocation' command line argument.
895 	 *
896 	 * @return the location string, or <code>null</code> if the location is not
897 	 *         being shown
898 	 */
getWorkspaceLocation()899 	public String getWorkspaceLocation() {
900 		String location = getCommandLineLocation();
901 		// read command line, which has priority
902 		if (location != null) {
903 			return location;
904 		}
905 		// read the preferences
906 		if (IDEWorkbenchPlugin.getDefault().getPreferenceStore().getBoolean(IDEInternalPreferences.SHOW_LOCATION)) {
907 			// show the full location
908 			return Platform.getLocation().toOSString();
909 		}
910 		return null;
911 	}
912 
913 	/**
914 	 * @return the welcome perspective infos, or <code>null</code> if none or
915 	 *         if they should be ignored due to the new intro being present
916 	 */
getWelcomePerspectiveInfos()917 	public AboutInfo[] getWelcomePerspectiveInfos() {
918 		if (welcomePerspectiveInfos == null) {
919 			// support old welcome perspectives if intro plugin is not present
920 			if (!hasIntro()) {
921 				Map<String, AboutInfo> m = getNewlyAddedBundleGroups();
922 				ArrayList<AboutInfo> list = new ArrayList<>(m.size());
923 				for (AboutInfo info : m.values()) {
924 					if (info != null && info.getWelcomePerspectiveId() != null
925 							&& info.getWelcomePageURL() != null) {
926 						list.add(info);
927 					}
928 				}
929 				welcomePerspectiveInfos = new AboutInfo[list.size()];
930 				list.toArray(welcomePerspectiveInfos);
931 			}
932 		}
933 		return welcomePerspectiveInfos;
934 	}
935 
936 	@Override
getWorkbenchErrorHandler()937 	public synchronized AbstractStatusHandler getWorkbenchErrorHandler() {
938 		if (ideWorkbenchErrorHandler == null) {
939 			ideWorkbenchErrorHandler = new IDEWorkbenchErrorHandler(
940 					getWorkbenchConfigurer());
941 		}
942 		return ideWorkbenchErrorHandler;
943 	}
944 
945 	@Override
eventLoopIdle(Display display)946 	public void eventLoopIdle(Display display) {
947 		if (delayedEventsProcessor != null)
948 			delayedEventsProcessor.catchUp(display);
949 		super.eventLoopIdle(display);
950 	}
951 }
952