| /******************************************************************************* |
| * Copyright (c) 2016 Lars Vogel and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Lars Vogel <Lars.Vogel@vogella.com> - initial API and implementation |
| * Axel Richard <axel.richard@obeo.fr> - Bug 486644 |
| * Mikael Barbero <mikael@eclipse.org> - Bug 486644 |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.ide.addons; |
| |
| import java.util.concurrent.TimeUnit; |
| |
| import javax.annotation.PreDestroy; |
| import javax.inject.Inject; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.e4.core.di.annotations.Optional; |
| import org.eclipse.e4.core.di.extensions.Preference; |
| import org.eclipse.e4.core.services.events.IEventBroker; |
| import org.eclipse.e4.ui.workbench.UIEvents; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.SWTException; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.IEditorReference; |
| import org.eclipse.ui.IWorkbench; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.internal.IPreferenceConstants; |
| import org.eclipse.ui.progress.WorkbenchJob; |
| import org.osgi.service.event.Event; |
| import org.osgi.service.event.EventHandler; |
| |
| /** |
| * Model add-on for automatic save of dirty editors. |
| * |
| * @since 3.12 |
| */ |
| public class SaveAllDirtyPartsAddon { |
| |
| private final class DirtyEventHandler implements EventHandler { |
| @Override |
| public void handleEvent(Event event) { |
| if (isAutoSaveActive) { |
| Object isDirty = event.getProperty(UIEvents.EventTags.NEW_VALUE); |
| if (isDirty instanceof Boolean && (Boolean) isDirty) { |
| autoSaveJob.schedule(autoSaveInterval); |
| addIdleListenerToWorkbenchDisplay(); |
| } else if (noDirtyEditor(PlatformUI.getWorkbench())) { |
| removeIdleListenerFromWorkbenchDisplay(); |
| autoSaveJob.cancel(); |
| } |
| } |
| } |
| |
| private boolean noDirtyEditor(IWorkbench workbench) { |
| IWorkbenchWindow[] windows = workbench.getWorkbenchWindows(); |
| for (IWorkbenchWindow window : windows) { |
| IWorkbenchPage p = window.getActivePage(); |
| if (p != null) { |
| for (IEditorReference editorRef : p.getEditorReferences()) { |
| if (editorRef.isDirty()) { |
| return false; |
| } |
| } |
| } |
| } |
| return true; |
| } |
| } |
| |
| private final class IdleListener implements Listener { |
| @Override |
| public void handleEvent(org.eclipse.swt.widgets.Event event) { |
| // the user has pressed a key or has clicked somewhere |
| // (see #addIdleListenerToWorkbenchDisplay for exact list of |
| // listened events), re-schedule the job if it the previous |
| // delay has not expired yet. |
| if (autoSaveJob.getState() == Job.SLEEPING) { |
| autoSaveJob.cancel(); |
| autoSaveJob.schedule(autoSaveInterval); |
| } |
| } |
| } |
| |
| private final class AutoSaveJob extends WorkbenchJob { |
| |
| private AutoSaveJob(String name) { |
| super(name); |
| } |
| |
| @Override |
| public IStatus runInUIThread(IProgressMonitor monitor) { |
| if (isAutoSaveActive) { |
| IWorkbench workbench = PlatformUI.getWorkbench(); |
| if (workbench != null) { |
| IWorkbenchWindow[] windows = workbench.getWorkbenchWindows(); |
| for (IWorkbenchWindow window : windows) { |
| IWorkbenchPage p = window.getActivePage(); |
| // We do not want to save dirty editors when a sub |
| // shell is visible or active (e.g. content assist, |
| // javadoc hover...) |
| if (p != null && !hasVisibleSubShell(getWorkbenchDisplay())) { |
| p.saveAllEditors(false); |
| } else { |
| // reschedule the job. No need to wait for the full |
| // interval time as we already have waited for this |
| // amount of time. |
| this.schedule(autoSaveInterval / 2); |
| } |
| } |
| } |
| } |
| return Status.OK_STATUS; |
| } |
| } |
| |
| @Inject |
| IEventBroker eventBroker; |
| |
| private final WorkbenchJob autoSaveJob; |
| |
| private final EventHandler dirtyHandler; |
| |
| private final Listener idleListener; |
| |
| private boolean isAutoSaveActive; |
| |
| private long autoSaveInterval; |
| |
| /** |
| * @param autoSave |
| */ |
| @Inject |
| @Optional |
| public void setAutoSave( |
| @SuppressWarnings("restriction") @Preference(value = IPreferenceConstants.SAVE_AUTOMATICALLY, nodePath = "org.eclipse.ui.workbench") boolean autoSave) { |
| isAutoSaveActive = autoSave; |
| if (isAutoSaveActive) { |
| eventBroker.subscribe(UIEvents.Dirtyable.TOPIC_DIRTY, dirtyHandler); |
| } else { |
| eventBroker.unsubscribe(dirtyHandler); |
| } |
| } |
| |
| /** |
| * @param newInterval |
| */ |
| @Inject |
| @Optional |
| public void autoSaveIntervalChanged( |
| @SuppressWarnings("restriction") @Preference(value = IPreferenceConstants.SAVE_AUTOMATICALLY_INTERVAL, nodePath = "org.eclipse.ui.workbench") int newInterval) { |
| autoSaveInterval = TimeUnit.SECONDS.toMillis(newInterval); |
| } |
| |
| /** |
| * Default constructor |
| */ |
| public SaveAllDirtyPartsAddon() { |
| autoSaveJob = new AutoSaveJob("Auto save all editors"); //$NON-NLS-1$ |
| // auto-save job should not be displayed in the progress view. |
| autoSaveJob.setSystem(true); |
| idleListener = new IdleListener(); |
| dirtyHandler = new DirtyEventHandler(); |
| } |
| |
| private void addIdleListenerToWorkbenchDisplay() { |
| Display display = getWorkbenchDisplay(); |
| if (display != null && !display.isDisposed()) { |
| display.addFilter(SWT.KeyUp, idleListener); |
| display.addFilter(SWT.MouseUp, idleListener); |
| } |
| } |
| |
| private void removeIdleListenerFromWorkbenchDisplay() { |
| Display display = getWorkbenchDisplay(); |
| if (display != null && !display.isDisposed()) { |
| display.removeFilter(SWT.MouseUp, idleListener); |
| display.removeFilter(SWT.KeyUp, idleListener); |
| } |
| } |
| |
| @PreDestroy |
| private void shutdown() { |
| eventBroker.unsubscribe(dirtyHandler); |
| autoSaveJob.cancel(); |
| |
| final Display display = getWorkbenchDisplay(); |
| if (display != null && !display.isDisposed()) { |
| try { |
| display.asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| removeIdleListenerFromWorkbenchDisplay(); |
| // save jov could have been rescheduled by idleListener |
| // before it has been removed |
| autoSaveJob.cancel(); |
| } |
| }); |
| } catch (SWTException ex) { |
| // ignore |
| } |
| } |
| } |
| |
| /** |
| * Checks whether the current active shell has at least one visible sub |
| * shell. This is especially the case when the content assist popup is |
| * visible or the javadoc on mouse hover is enabled. |
| * |
| * @param display |
| * the display from which the active shell should be retrieved |
| * @return true if the active shell has at least one sub shell visible, |
| * false otherwise. |
| */ |
| private static boolean hasVisibleSubShell(final Display display) { |
| if (display != null && !display.isDisposed()) { |
| Shell shell = display.getActiveShell(); |
| if (shell != null) { |
| for (Shell subShell : shell.getShells()) { |
| if (subShell.isVisible()) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the current workbench display, null otherwise. |
| * |
| * @return the current workbench display, null otherwise. |
| */ |
| private static Display getWorkbenchDisplay() { |
| final IWorkbench workbench = PlatformUI.getWorkbench(); |
| if (workbench != null) { |
| return workbench.getDisplay(); |
| } |
| return null; |
| } |
| } |