Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: aac305360dde158e7bfac7ad045fa20aadc02281 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
/*******************************************************************************
 * Copyright (c) 2004, 2016 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.ui.intro.config;

import org.eclipse.core.runtime.IAdapterFactory;
import org.eclipse.core.runtime.IRegistryChangeEvent;
import org.eclipse.core.runtime.IRegistryChangeListener;
import org.eclipse.core.runtime.PerformanceStats;
import org.eclipse.core.runtime.Platform;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IWorkbenchPreferenceConstants;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.intro.impl.IIntroConstants;
import org.eclipse.ui.internal.intro.impl.IntroPlugin;
import org.eclipse.ui.internal.intro.impl.Messages;
import org.eclipse.ui.internal.intro.impl.model.IntroModelRoot;
import org.eclipse.ui.internal.intro.impl.model.IntroPartPresentation;
import org.eclipse.ui.internal.intro.impl.model.loader.ContentProviderManager;
import org.eclipse.ui.internal.intro.impl.model.loader.ExtensionPointManager;
import org.eclipse.ui.internal.intro.impl.model.loader.ModelLoaderUtil;
import org.eclipse.ui.internal.intro.impl.parts.StandbyPart;
import org.eclipse.ui.internal.intro.impl.presentations.BrowserIntroPartImplementation;
import org.eclipse.ui.internal.intro.impl.util.DialogUtil;
import org.eclipse.ui.internal.intro.impl.util.Log;
import org.eclipse.ui.internal.intro.impl.util.ReopenUtil;
import org.eclipse.ui.intro.IIntroSite;
import org.eclipse.ui.part.IntroPart;

/**
 * A re-usable intro part that the Eclipse platform uses for its Out of the Box
 * Experience. It is a customizable intro part where both its presentation, and
 * its content can be customized based on a configuration. Both are contributed
 * using the org.eclipse.ui.intro.config extension point. There are two
 * presentations: an SWT browser based presentation, and a UI forms
 * presentation. Based on the configuration, one is chosen on startup. If a
 * Browser based presentation is selected, and the intro is being loaded on a
 * platform that does not support the SWT Browser control, the default behavior
 * is to degrade to UI forms presentation. Content displayed in this intro part
 * can be static or dynamic. Static is html files, dynamic is markup in content
 * files. Again, both of which can be specified using the above extension point.
 * <p>
 * Memento Support: This intro part tries to restore its previous state when
 * possible. The state of the intro page is remembered, along with which standby
 * content content part was opened. IStandbyContent parts are passed the Intro's
 * memento shortly after construction, and are expected to restore there own
 * state based on the memento. The customizable intro part handles there initial
 * creation on load, and leaves restoring state to content part. Same with
 * saving state. The memento is passed shortly before shutdown to enable storing
 * of part specific data.
 *
 * Note: This class was made public for re-use, as-is, as a valid class for the
 * <code>org.eclipse.ui.intro</code> extension point. It is not intended to be
 * subclassed or used otherwise.
 * </p>
 *
 * @since 3.0
 */

/*
 * Internal docs: This class wraps a presentation part and a standby part. The
 * standby part is only shown when an IntroURL asks to show standby or when we
 * are restarting an Intro with an old memento. An internal data object
 * "showStandbyPart" is set on the control by the intro URL to signal standby
 * part needed. This data is nulled when the close button on the standby part is
 * clicked, signaling that the standby part is no longer needed.
 */
public final class CustomizableIntroPart extends IntroPart implements
         IRegistryChangeListener {

    private IntroPartPresentation presentation;
    private StandbyPart standbyPart;
    private Composite container;
    private IMemento memento;
    IntroModelRoot model;
    // this flag is used to recreate a cached standby part. It is used once and
    // the set to false when the standby part is first created.
    private boolean restoreStandby;


    // Adapter factory to abstract out the StandbyPart implementation from APIs.
    IAdapterFactory factory = new IAdapterFactory() {

		@Override
		public Class<?>[] getAdapterList() {
            return new Class[] { StandbyPart.class, IntroPartPresentation.class };
        }

		@Override
		public <T> T getAdapter(Object adaptableObject, Class<T> adapterType) {
            if (!(adaptableObject instanceof CustomizableIntroPart))
                return null;

            if (adapterType.equals(StandbyPart.class)) {
				return adapterType.cast(getStandbyPart());
            } else if (adapterType.equals(IntroPartPresentation.class)) {
				return adapterType.cast(getPresentation());
            } else
                return null;
        }
    };

    public CustomizableIntroPart() {
        // register adapter to hide standbypart.
        Platform.getAdapterManager().registerAdapters(factory,
            CustomizableIntroPart.class);
        // model can not be loaded here because the configElement of this part
        // is still not loaded here.

        // if we are logging performance, start the UI creation start time.
        // Clock stops at the end of the standbyStateChanged event.
        if (Log.logPerformance) {
            if (PerformanceStats.ENABLED)
                PerformanceStats.getStats(
                    IIntroConstants.PERF_VIEW_CREATION_TIME,
                    IIntroConstants.INTRO).startRun();
            else
                // capture start time to be used when only Intro performance
                // trace
                // is turned on.
                IntroPlugin.getDefault().setUICreationStartTime(
                    System.currentTimeMillis());
        }
    }

    @Override
	public void init(IIntroSite site, IMemento memento)
            throws PartInitException {
        super.init(site, memento);
        IntroPlugin.getDefault().closeLaunchBar();
        // load the correct model based in the current Intro Part id. Set the
        // IntroPartId in the manager class.
        String introId = getConfigurationElement().getAttribute("id"); //$NON-NLS-1$
        ExtensionPointManager extensionPointManager = IntroPlugin.getDefault()
            .getExtensionPointManager();
        extensionPointManager.setIntroId(introId);
        model = extensionPointManager.getCurrentModel();

        if (model != null && model.hasValidConfig()) {

            boolean startAtHomePage = ReopenUtil.isReopenPreference();
			if (startAtHomePage) {
    			PlatformUI.getPreferenceStore().setValue(
    	        		IWorkbenchPreferenceConstants.SHOW_INTRO, true);
    			memento = null;
    		}
            // we have a valid config contribution, get presentation. Make sure
            // you pass correct memento.
            presentation = model.getPresentation();
            if (presentation != null)
                presentation.init(this, getMemento(memento,
                    IIntroConstants.MEMENTO_PRESENTATION_TAG));

            // standby part is not created here for performance.

            // cache memento, and detemine if we have a cached standby part.
            this.memento = memento;
            restoreStandby = needToRestoreStandby(memento);

            // add the registry listerner for dynamic awarness.
            Platform.getExtensionRegistry().addRegistryChangeListener(this,
                IIntroConstants.PLUGIN_ID);
        }

        if (model == null || !model.hasValidConfig())
            DialogUtil.displayErrorMessage(site.getShell(),
                Messages.CustomizableIntroPart_configNotFound,
                new Object[] { ModelLoaderUtil.getLogString(
                    getConfigurationElement(), null) }, null);

    }

    /**
     * Creates the UI based on how the InroPart has been configured.
     *
     * @see org.eclipse.ui.IWorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
     */
    @Override
	public void createPartControl(Composite parent) {
        container = new Composite(parent, SWT.NULL);
        StackLayout layout = new StackLayout();
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        container.setLayout(layout);

        if (model != null && model.hasValidConfig()) {
            presentation.createPartControl(container);
            // do not create the standby part here for performance.
        }

        if (Log.logPerformance) {
            PerformanceStats stats = PerformanceStats.getStats(
                IIntroConstants.PERF_UI_ZOOM, IIntroConstants.INTRO);
            stats.startRun();
        }

    }




    /**
     * Determine if we need to recreate a standby part. Return true if we have a
     * standby part memento that is not for the empty part AND stangby memento
     * has been tagged for restore, ie: it was open when workbench closed.
     *
     * @param memento
     * @return <code>true</code> if we need to recreate a standby part
     */
    private boolean needToRestoreStandby(IMemento memento) {
        // If we have a standby memento, it means we closed with standby open,
        // and so recreate it.
        IMemento standbyMemento = getMemento(memento, IIntroConstants.MEMENTO_STANDBY_PART_TAG);
        if (standbyMemento == null)
            return false;
        String restore = standbyMemento.getString(IIntroConstants.MEMENTO_RESTORE_ATT);
        if (restore == null)
            return false;
        String cachedStandbyPart = standbyMemento
            .getString(IIntroConstants.MEMENTO_STANDBY_CONTENT_PART_ID_ATT);
        if (cachedStandbyPart != null
                && cachedStandbyPart.equals(IIntroConstants.EMPTY_STANDBY_CONTENT_PART))
            return false;

        return cachedStandbyPart != null ? true : false;
    }

    /*
     * Handled state changes. Recreates the standby part if workbench was shut
     * down with one.
     *
     * @see org.eclipse.ui.IIntroPart#standbyStateChanged(boolean)
     */
    @Override
	public void standbyStateChanged(boolean standby) {

        // do this only if there is a valid config.
        if (model == null || !model.hasValidConfig())
            return;

        if (!standby)
            // we started of not in standby, no need to restore standby.
            restoreStandby = false;

        boolean isStandbyPartNeeded = isStandbyPartNeeded();
        isStandbyPartNeeded = isStandbyPartNeeded | restoreStandby;

        try {
			if (standbyPart == null && standby && isStandbyPartNeeded)
			    // if standby part is not created yet, create it only if in
			    // standby, and we need to.
			    createStandbyPart();

			handleSetFocus(isStandbyPartNeeded);
			setTopControl(isStandbyPartNeeded ? getStandbyControl()
			        : getPresentationControl());
			// triger state change in presentation to enable/disable toobar
			// actions. For this, we need to disable actions as long as we are in
			// standby, or we need to show standby part.
			presentation.standbyStateChanged(standby, isStandbyPartNeeded);
		} catch (RuntimeException e) {
			Log.error("Exception thrown in intro", e); //$NON-NLS-1$
		}
    }

    /**
     * Returns true if we need to show the standby part. False in all other
     * cases. This basically overrides the workbench behavior of Standby/normal
     * states. The design here is that if the showStandbyPart flag is set, then
     * we always need to show the standby part.
     *
     * @return <code>true</code> if we need to show the standby part
     */
    private boolean isStandbyPartNeeded() {
        return container.getData(IIntroConstants.SHOW_STANDBY_PART) == null ? false : true;
    }

    /*
     * Create standby part. Called only when really needed. We reset the restore
     * falg, but we need to tag the intro part as needing standby.
     */
    private void createStandbyPart() {
        standbyPart = new StandbyPart(model);
        standbyPart.init(this, getMemento(memento, IIntroConstants.MEMENTO_STANDBY_PART_TAG));
        standbyPart.createPartControl((Composite) getControl());
        restoreStandby = false;
        container.setData(IIntroConstants.SHOW_STANDBY_PART, "true"); //$NON-NLS-1$
    }

    private void handleSetFocus(boolean standby) {
        if (standby) {
            // standby part is null when Intro has not gone into standby state
            // yet.
            if (standbyPart != null)
                standbyPart.setFocus();
        } else
            presentation.setFocus();
    }

    @Override
	public void setFocus() {
        handleSetFocus(IntroPlugin.isIntroStandby());
    }

    private void setTopControl(Control c) {
        // container has stack layout. safe to cast.
        StackLayout layout = (StackLayout) container.getLayout();
        layout.topControl = c;
        container.layout();
    }

    private Control getPresentationControl() {
        return container.getChildren()[0];
    }

    private Control getStandbyControl() {
        // the Container top control may have only one child if the standby
        // part is not created yet. This happens if the intro never goes into
        // standby. Doing this for performance.
        if (standbyPart != null)
            return container.getChildren()[1];
        return null;
    }

    IntroPartPresentation getPresentation() {
        return presentation;
    }

    @Override
	public void dispose() {
        super.dispose();
        // call dispose on both parts.
        if (presentation != null)
            presentation.dispose();
        if (standbyPart != null)
            standbyPart.dispose();
        // clear all loaded models since we are disposing of the Intro Part.
        IntroPlugin.getDefault().getExtensionPointManager().clear();
        ContentProviderManager.getInst().clear();
        // clean platform adapter.
        Platform.getAdapterManager().unregisterAdapters(factory,
            CustomizableIntroPart.class);
        if (model != null && model.hasValidConfig())
            Platform.getExtensionRegistry().removeRegistryChangeListener(this);

    }

    /**
     * @return Returns the standbyPart.
     */
    StandbyPart getStandbyPart() {
        return standbyPart;
    }

    /**
     * Returns the primary control associated with this Intro part.
     *
     * @return the SWT control which displays this Intro part's content, or
     *         <code>null</code> if this standby part's controls have not yet
     *         been created.
     */
    public Control getControl() {
        return container;
    }

    @Override
	public void saveState(IMemento memento) {
        // give presentation and standby part there own children to create a
        // name space for each. But save either the presentation or the standby
        // part as needing to be restored. This way if we close with a standby
        // mode, we dont get the cached standby part.

        // Find out if presentation or standby is at the top and restore
        // them. Container has stack layout. safe to cast.
        boolean restorePresentation = false;
        StackLayout layout = (StackLayout) container.getLayout();
        if (getPresentationControl().equals(layout.topControl))
            restorePresentation = true;

        IMemento presentationMemento = memento
            .createChild(IIntroConstants.MEMENTO_PRESENTATION_TAG);
        IMemento standbyPartMemento = memento
            .createChild(IIntroConstants.MEMENTO_STANDBY_PART_TAG);
        if (restorePresentation)
            presentationMemento.putString(IIntroConstants.MEMENTO_RESTORE_ATT, "true"); //$NON-NLS-1$
        else
            standbyPartMemento.putString(IIntroConstants.MEMENTO_RESTORE_ATT, "true"); //$NON-NLS-1$
        if (presentation != null)
            presentation.saveState(presentationMemento);
        if (standbyPart != null)
            standbyPart.saveState(standbyPartMemento);
    }

    private IMemento getMemento(IMemento memento, String key) {
        if (memento == null)
            return null;
        return memento.getChild(key);
    }

    /**
     * Support dynamic awarness.
     *
     * @see org.eclipse.core.runtime.IRegistryChangeListener#registryChanged(org.eclipse.core.runtime.IRegistryChangeEvent)
     */
    @Override
	public void registryChanged(final IRegistryChangeEvent event) {
        // Clear cached models first, then update UI by delegating to
        // implementation. wrap in synchExec because notification is
        // asynchronous. The design here is that the notification is centralized
        // here, then this event propagates and each receiving class reacts
        // accordingly.
        Display.getDefault().syncExec(() -> {
		    String currentPageId = model.getCurrentPageId();
		    // clear model, including content providers.
		    ExtensionPointManager.getInst().clear();
		    ContentProviderManager.getInst().clear();
		    // refresh to new model.
		    model = ExtensionPointManager.getInst().getCurrentModel();
		    // reuse existing presentation, since we just nulled it.
		    model.setPresentation(getPresentation());
		    // keep same page on refresh. No need for notification here.
		    model.setCurrentPageId(currentPageId, false);
		    if (getPresentation() != null)
		        getPresentation().registryChanged(event);

		});

    }

    /*
     * Internal test hook (Non-API).
     */
    public boolean internal_isFinishedLoading() {
    	BrowserIntroPartImplementation impl = (BrowserIntroPartImplementation)presentation.getIntroPartImplementation();
    	return impl.isFinishedLoading();
    }
}

Back to the top