tmf: Move plugins to their own sub-directory
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / internal / tmf / ui / views / TmfAlignmentSynchronizer.java
1 /*******************************************************************************
2 * Copyright (c) 2015 Ericsson
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Marc-Andre Laperle - Initial API and implementation
11 *******************************************************************************/
12
13 package org.eclipse.tracecompass.internal.tmf.ui.views;
14
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.List;
18 import java.util.Timer;
19 import java.util.TimerTask;
20
21 import org.eclipse.core.runtime.preferences.InstanceScope;
22 import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
23 import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
24 import org.eclipse.swt.graphics.Point;
25 import org.eclipse.swt.widgets.Composite;
26 import org.eclipse.swt.widgets.Display;
27 import org.eclipse.swt.widgets.Shell;
28 import org.eclipse.tracecompass.internal.tmf.ui.Activator;
29 import org.eclipse.tracecompass.internal.tmf.ui.ITmfUIPreferences;
30 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
31 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
32 import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo;
33 import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal;
34 import org.eclipse.tracecompass.tmf.ui.views.ITmfTimeAligned;
35 import org.eclipse.tracecompass.tmf.ui.views.TmfView;
36 import org.eclipse.ui.IViewPart;
37 import org.eclipse.ui.IViewReference;
38 import org.eclipse.ui.IWorkbenchPage;
39 import org.eclipse.ui.IWorkbenchWindow;
40 import org.eclipse.ui.PlatformUI;
41
42 /**
43 * Receives various notifications for realignment and
44 * performs the alignment on the appropriate views.
45 *
46 * @since 1.0
47 */
48 public class TmfAlignmentSynchronizer {
49
50 private static final long THROTTLE_DELAY = 500;
51 private static final int NEAR_THRESHOLD = 10;
52 private final Timer fTimer;
53 private final List<AlignmentOperation> fPendingOperations = Collections.synchronizedList(new ArrayList<AlignmentOperation>());
54
55 private TimerTask fCurrentTask;
56
57 /**
58 * Constructor
59 */
60 public TmfAlignmentSynchronizer() {
61 TmfSignalManager.register(this);
62 fTimer = new Timer();
63 createPreferenceListener();
64 fCurrentTask = new TimerTask() {
65 @Override
66 public void run() {
67 /* Do nothing */
68 }
69 };
70 }
71
72 private IPreferenceChangeListener createPreferenceListener() {
73 IPreferenceChangeListener listener = new IPreferenceChangeListener() {
74
75 @Override
76 public void preferenceChange(PreferenceChangeEvent event) {
77 if (event.getKey().equals(ITmfUIPreferences.PREF_ALIGN_VIEWS)) {
78 Object oldValue = event.getOldValue();
79 Object newValue = event.getNewValue();
80 if (Boolean.toString(false).equals(oldValue) && Boolean.toString(true).equals(newValue)) {
81 realignViews();
82 } else if (Boolean.toString(true).equals(oldValue) && Boolean.toString(false).equals(newValue)) {
83 restoreViews();
84 }
85 }
86 }
87 };
88 InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID).addPreferenceChangeListener(listener);
89 return listener;
90 }
91
92 private class AlignmentOperation {
93 final TmfView fView;
94 final TmfTimeViewAlignmentInfo fAlignmentInfo;
95
96 public AlignmentOperation(TmfView view, TmfTimeViewAlignmentInfo timeViewAlignmentInfo) {
97 fView = view;
98 fAlignmentInfo = timeViewAlignmentInfo;
99 }
100 }
101
102 private class AlignTask extends TimerTask {
103
104 @Override
105 public void run() {
106 final List<AlignmentOperation> fCopy;
107 synchronized (fPendingOperations) {
108 fCopy = new ArrayList<>(fPendingOperations);
109 fPendingOperations.clear();
110 }
111 Display.getDefault().syncExec(new Runnable() {
112 @Override
113 public void run() {
114 performAllAlignments(fCopy);
115 }
116 });
117 }
118 }
119
120 /**
121 * Handle a view that was just resized.
122 *
123 * @param view
124 * the view that was resized
125 */
126 public void handleViewResized(TmfView view) {
127 TmfTimeViewAlignmentInfo alignmentInfo = new TmfTimeViewAlignmentInfo(view.getParentComposite().getShell(), getViewLocation(view), 0);
128
129 // Don't use a view that was just resized as a reference view.
130 // Otherwise, a view that was just
131 // created might use itself as a reference but we want to
132 // keep the existing alignment from the other views.
133 ITmfTimeAligned referenceView = getReferenceView(alignmentInfo, view);
134 if (referenceView != null) {
135 queueAlignment(referenceView.getTimeViewAlignmentInfo(), false);
136 }
137 }
138
139 /**
140 * Handle a view that was just closed.
141 *
142 * @param view
143 * the view that was closed
144 */
145 public void handleViewClosed(TmfView view) {
146 // Realign views so that they can use the maximum available width in the
147 // event that a narrow view was just closed
148 realignViews(view.getSite().getPage());
149 }
150
151 /**
152 * Process signal for alignment.
153 *
154 * @param signal the alignment signal
155 */
156 @TmfSignalHandler
157 public void timeViewAlignmentUpdated(TmfTimeViewAlignmentSignal signal) {
158 queueAlignment(signal.getTimeViewAlignmentInfo(), signal.IsSynchronous());
159 }
160
161 /**
162 * Perform all alignment operations for the specified alignment
163 * informations.
164 *
165 * <pre>
166 * - The alignment algorithm chooses the narrowest width to accommodate all views.
167 * - View positions are recomputed for extra accuracy since the views could have been moved or resized.
168 * - Based on the up-to-date view positions, only views that are near and aligned with each other
169 * </pre>
170 */
171 private static void performAllAlignments(final List<AlignmentOperation> alignments) {
172 for (final AlignmentOperation info : alignments) {
173 performAlignment(info);
174 }
175 }
176
177 private static void performAlignment(AlignmentOperation info) {
178
179 TmfView referenceView = info.fView;
180 if (isDisposedView(referenceView)) {
181 return;
182 }
183
184 TmfTimeViewAlignmentInfo alignmentInfo = info.fAlignmentInfo;
185 // The location of the view might have changed (resize, etc). Update the alignment info.
186 alignmentInfo = new TmfTimeViewAlignmentInfo(alignmentInfo.getShell(), getViewLocation(referenceView), getClampedTimeAxisOffset(alignmentInfo));
187
188 TmfView narrowestView = getNarrowestView(alignmentInfo);
189 if (narrowestView == null) {
190 // No valid view found for this alignment. This could mean that the views for this alignment are now too narrow (width == 0) or that shell is not a workbench window.
191 return;
192 }
193
194 int narrowestWidth = ((ITmfTimeAligned) narrowestView).getAvailableWidth(getClampedTimeAxisOffset(alignmentInfo));
195 narrowestWidth = getClampedTimeAxisWidth(alignmentInfo, narrowestWidth);
196 IViewReference[] viewReferences = referenceView.getSite().getPage().getViewReferences();
197 for (IViewReference ref : viewReferences) {
198 IViewPart view = ref.getView(false);
199 if (isTimeAlignedView(view)) {
200 TmfView tmfView = (TmfView) view;
201 ITmfTimeAligned alignedView = (ITmfTimeAligned) view;
202 if (!isDisposedView(tmfView) && isViewLocationNear(getViewLocation(tmfView), alignmentInfo.getViewLocation())) {
203 alignedView.performAlign(getClampedTimeAxisOffset(alignmentInfo), narrowestWidth);
204 }
205 }
206 }
207 }
208
209 /**
210 * Realign all views
211 */
212 private void realignViews() {
213 for (IWorkbenchWindow window : PlatformUI.getWorkbench().getWorkbenchWindows()) {
214 for (IWorkbenchPage page : window.getPages()) {
215 realignViews(page);
216 }
217 }
218 }
219
220 /**
221 * Realign views inside a given page
222 *
223 * @param page
224 * the workbench page
225 */
226 private void realignViews(IWorkbenchPage page) {
227 IViewReference[] viewReferences = page.getViewReferences();
228 for (IViewReference ref : viewReferences) {
229 IViewPart view = ref.getView(false);
230 if (isTimeAlignedView(view)) {
231 queueAlignment(((ITmfTimeAligned) view).getTimeViewAlignmentInfo(), false);
232 }
233 }
234 }
235
236 /**
237 * Restore the views to their respective maximum widths
238 */
239 private static void restoreViews() {
240 // We set the width to Integer.MAX_VALUE so that the
241 // views remove any "filler" space they might have.
242 for (IWorkbenchWindow window : PlatformUI.getWorkbench().getWorkbenchWindows()) {
243 for (IWorkbenchPage page : window.getPages()) {
244 for (IViewReference ref : page.getViewReferences()) {
245 restoreView(ref);
246 }
247 }
248 }
249 }
250
251 private static void restoreView(IViewReference ref) {
252 IViewPart view = ref.getView(false);
253 if (isTimeAlignedView(view)) {
254 ITmfTimeAligned alignedView = (ITmfTimeAligned) view;
255 alignedView.performAlign(getClampedTimeAxisOffset(alignedView.getTimeViewAlignmentInfo()), Integer.MAX_VALUE);
256 }
257 }
258
259 private static boolean isTimeAlignedView(IViewPart view) {
260 if (view instanceof TmfView && view instanceof ITmfTimeAligned) {
261 Composite parentComposite = ((TmfView) view).getParentComposite();
262 if (parentComposite != null && !parentComposite.isDisposed()) {
263 return true;
264 }
265 }
266 return view instanceof TmfView && view instanceof ITmfTimeAligned;
267 }
268
269 private static boolean isDisposedView(TmfView view) {
270 Composite parentComposite = (view).getParentComposite();
271 return parentComposite != null && parentComposite.isDisposed();
272 }
273
274 /**
275 * Queue the operation for processing. If an operation is considered the
276 * same alignment (shell, location) as a previously queued one, it will
277 * replace the old one. This way, only one up-to-date alignment operation is
278 * kept per set of time-axis aligned views. The processing of the operation
279 * is also throttled (TimerTask).
280 *
281 * @param operation
282 * the operation to queue
283 */
284 private void queue(AlignmentOperation operation) {
285 synchronized(fPendingOperations) {
286 fCurrentTask.cancel();
287 for (AlignmentOperation pendingOperation : fPendingOperations) {
288 if (isSameAlignment(operation, pendingOperation)) {
289 fPendingOperations.remove(pendingOperation);
290 break;
291 }
292 }
293 fPendingOperations.add(operation);
294 fCurrentTask = new AlignTask();
295 fTimer.schedule(fCurrentTask, THROTTLE_DELAY);
296 }
297 }
298
299 /**
300 * Two operations are considered to be for the same set of time-axis aligned
301 * views if they are on the same Shell and near the same location.
302 */
303 private static boolean isSameAlignment(AlignmentOperation operation1, AlignmentOperation operation2) {
304 if (operation1.fView == operation2.fView) {
305 return true;
306 }
307
308 if (operation1.fAlignmentInfo.getShell() != operation2.fAlignmentInfo.getShell()) {
309 return false;
310 }
311
312 if (isViewLocationNear(getViewLocation(operation1.fView), getViewLocation(operation2.fView))) {
313 return true;
314 }
315
316 return false;
317 }
318
319 private static boolean isViewLocationNear(Point location1, Point location2) {
320 return Math.abs(location1.x - location2.x) < NEAR_THRESHOLD;
321 }
322
323 private static Point getViewLocation(TmfView view) {
324 return view.getParentComposite().toDisplay(0, 0);
325 }
326
327 private void queueAlignment(TmfTimeViewAlignmentInfo timeViewAlignmentInfo, boolean synchronous) {
328 if (isAlignViewsPreferenceEnabled()) {
329 IWorkbenchWindow workbenchWindow = getWorkbenchWindow(timeViewAlignmentInfo.getShell());
330 if (workbenchWindow == null || workbenchWindow.getActivePage() == null) {
331 // Only time aligned views that are part of a workbench window are supported
332 return;
333 }
334
335 // We need a view so that we can compute position right as we are
336 // about to realign the views. The view could have been resized,
337 // moved, etc.
338 TmfView view = (TmfView) getReferenceView(timeViewAlignmentInfo, null);
339 if (view == null) {
340 // No valid view found for this alignment
341 return;
342 }
343
344 AlignmentOperation operation = new AlignmentOperation(view, timeViewAlignmentInfo);
345 if (synchronous) {
346 performAlignment(operation);
347 } else {
348 queue(operation);
349 }
350 }
351 }
352
353 private static boolean isAlignViewsPreferenceEnabled() {
354 return InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID).getBoolean(ITmfUIPreferences.PREF_ALIGN_VIEWS, true);
355 }
356
357 /**
358 * Get a view that corresponds to the alignment information. The view is
359 * meant to be used as a "reference" for other views to align on. Heuristics
360 * are applied to choose the best view. For example, the view has to be
361 * visible. It also will prioritize the view with lowest time axis offset
362 * because most of the interesting data should be in the time widget.
363 *
364 * @param alignmentInfo
365 * alignment information
366 * @param blackListedView
367 * an optional black listed view that will not be used as
368 * reference (useful for a view that just got created)
369 * @return the reference view
370 */
371 private static ITmfTimeAligned getReferenceView(TmfTimeViewAlignmentInfo alignmentInfo, TmfView blackListedView) {
372 IWorkbenchWindow workbenchWindow = getWorkbenchWindow(alignmentInfo.getShell());
373 if (workbenchWindow == null || workbenchWindow.getActivePage() == null) {
374 // Only time aligned views that are part of a workbench window are supported
375 return null;
376 }
377 IWorkbenchPage page = workbenchWindow.getActivePage();
378
379 int lowestTimeAxisOffset = Integer.MAX_VALUE;
380 ITmfTimeAligned referenceView = null;
381 for (IViewReference ref : page.getViewReferences()) {
382 IViewPart view = ref.getView(false);
383 if (view != blackListedView && isTimeAlignedView(view)) {
384 if (isCandidateForReferenceView((TmfView) view, alignmentInfo, lowestTimeAxisOffset)) {
385 referenceView = (ITmfTimeAligned) view;
386 lowestTimeAxisOffset = getClampedTimeAxisOffset(referenceView.getTimeViewAlignmentInfo());
387 break;
388 }
389 }
390 }
391 return referenceView;
392 }
393
394 private static boolean isCandidateForReferenceView(TmfView tmfView, TmfTimeViewAlignmentInfo alignmentInfo, int lowestTimeAxisOffset) {
395 ITmfTimeAligned alignedView = (ITmfTimeAligned) tmfView;
396 TmfTimeViewAlignmentInfo timeViewAlignmentInfo = alignedView.getTimeViewAlignmentInfo();
397 if (timeViewAlignmentInfo == null) {
398 return false;
399 }
400
401 if (isDisposedView(tmfView)) {
402 return false;
403 }
404
405 Composite parentComposite = tmfView.getParentComposite();
406 boolean isVisible = parentComposite != null && parentComposite.isVisible();
407 if (isVisible) {
408 boolean isViewLocationNear = isViewLocationNear(alignmentInfo.getViewLocation(), getViewLocation(tmfView));
409 boolean isLowestTimeAxisOffset = getClampedTimeAxisOffset(timeViewAlignmentInfo) < lowestTimeAxisOffset;
410 if (isViewLocationNear && isLowestTimeAxisOffset) {
411 int availableWidth = alignedView.getAvailableWidth(getClampedTimeAxisOffset(timeViewAlignmentInfo));
412 availableWidth = getClampedTimeAxisWidth(timeViewAlignmentInfo, availableWidth);
413 if (availableWidth > 0) {
414 return true;
415 }
416 }
417 }
418
419 return false;
420 }
421
422 /**
423 * Get the narrowest view that corresponds to the given alignment information.
424 */
425 private static TmfView getNarrowestView(TmfTimeViewAlignmentInfo alignmentInfo) {
426 IWorkbenchWindow workbenchWindow = getWorkbenchWindow(alignmentInfo.getShell());
427 if (workbenchWindow == null || workbenchWindow.getActivePage() == null) {
428 // Only time aligned views that are part of a workbench window are supported
429 return null;
430 }
431 IWorkbenchPage page = workbenchWindow.getActivePage();
432
433 int narrowestWidth = Integer.MAX_VALUE;
434 TmfView narrowestView = null;
435 for (IViewReference ref : page.getViewReferences()) {
436 IViewPart view = ref.getView(false);
437 if (isTimeAlignedView(view)) {
438 TmfView tmfView = (TmfView) view;
439 if (isCandidateForNarrowestView(tmfView, alignmentInfo, narrowestWidth)) {
440 narrowestWidth = ((ITmfTimeAligned) tmfView).getAvailableWidth(getClampedTimeAxisOffset(alignmentInfo));
441 narrowestWidth = getClampedTimeAxisWidth(alignmentInfo, narrowestWidth);
442 narrowestView = tmfView;
443 }
444 }
445 }
446
447 return narrowestView;
448 }
449
450 private static int getClampedTimeAxisWidth(TmfTimeViewAlignmentInfo alignmentInfo, int width) {
451 int max = getMaxInt(alignmentInfo.getShell());
452 if (validateInt(width, max)) {
453 Activator.getDefault().logError("Time-axis width out of range (" + width + ")", new Throwable()); //$NON-NLS-1$//$NON-NLS-2$
454 }
455 return Math.min(max, Math.max(0, width));
456 }
457
458 private static int getClampedTimeAxisOffset(TmfTimeViewAlignmentInfo alignmentInfo) {
459 int timeAxisOffset = alignmentInfo.getTimeAxisOffset();
460 int max = getMaxInt(alignmentInfo.getShell());
461 if (validateInt(timeAxisOffset, max)) {
462 Activator.getDefault().logError("Time-axis offset out of range (" + timeAxisOffset + ")", new Throwable()); //$NON-NLS-1$//$NON-NLS-2$
463 }
464 return Math.min(max, Math.max(0, timeAxisOffset));
465 }
466
467 private static boolean validateInt(int value, int max) {
468 return value < 0 || value > max;
469 }
470
471 private static int getMaxInt(Shell shell) {
472 // Consider an integer to be buggy if it's bigger than 10 times the
473 // width of *all* monitors combined.
474 final int DISPLAY_WIDTH_FACTOR = 10;
475 return shell.getDisplay().getBounds().width * DISPLAY_WIDTH_FACTOR;
476 }
477
478 private static boolean isCandidateForNarrowestView(TmfView tmfView, TmfTimeViewAlignmentInfo alignmentInfo, int narrowestWidth) {
479 ITmfTimeAligned alignedView = (ITmfTimeAligned) tmfView;
480 TmfTimeViewAlignmentInfo timeViewAlignmentInfo = alignedView.getTimeViewAlignmentInfo();
481 if (timeViewAlignmentInfo == null) {
482 return false;
483 }
484
485 if (isDisposedView(tmfView)) {
486 return false;
487 }
488
489 Composite parentComposite = tmfView.getParentComposite();
490 boolean isVisible = parentComposite != null && parentComposite.isVisible();
491 if (isVisible) {
492 if (isViewLocationNear(getViewLocation(tmfView), alignmentInfo.getViewLocation())) {
493 int availableWidth = alignedView.getAvailableWidth(getClampedTimeAxisOffset(alignmentInfo));
494 availableWidth = getClampedTimeAxisWidth(alignmentInfo, availableWidth);
495 boolean isNarrower = availableWidth < narrowestWidth && availableWidth > 0;
496 return isNarrower;
497 }
498 }
499
500 return false;
501 }
502
503 private static IWorkbenchWindow getWorkbenchWindow(Shell shell) {
504 for (IWorkbenchWindow window : PlatformUI.getWorkbench().getWorkbenchWindows()) {
505 if (window.getShell().equals(shell)) {
506 return window;
507 }
508 }
509
510 return null;
511 }
512 }
This page took 0.056846 seconds and 5 git commands to generate.