1 /*******************************************************************************
2 * Copyright (c) 2013, 2014 Ericsson
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
10 * Marc-Andre Laperle - Initial API and implementation
11 *******************************************************************************/
13 package org
.eclipse
.tracecompass
.internal
.tmf
.ui
.project
.wizards
.tracepkg
;
15 import java
.io
.ByteArrayOutputStream
;
17 import java
.io
.IOException
;
18 import java
.io
.PrintStream
;
19 import java
.util
.ArrayList
;
20 import java
.util
.Arrays
;
21 import java
.util
.List
;
23 import org
.eclipse
.core
.runtime
.CoreException
;
24 import org
.eclipse
.core
.runtime
.IStatus
;
25 import org
.eclipse
.core
.runtime
.Status
;
26 import org
.eclipse
.jface
.dialogs
.ErrorDialog
;
27 import org
.eclipse
.jface
.dialogs
.IDialogSettings
;
28 import org
.eclipse
.jface
.resource
.ImageDescriptor
;
29 import org
.eclipse
.jface
.viewers
.CheckStateChangedEvent
;
30 import org
.eclipse
.jface
.viewers
.CheckboxTreeViewer
;
31 import org
.eclipse
.jface
.viewers
.ICheckStateListener
;
32 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
33 import org
.eclipse
.jface
.wizard
.WizardPage
;
34 import org
.eclipse
.swt
.SWT
;
35 import org
.eclipse
.swt
.events
.SelectionAdapter
;
36 import org
.eclipse
.swt
.events
.SelectionEvent
;
37 import org
.eclipse
.swt
.events
.SelectionListener
;
38 import org
.eclipse
.swt
.layout
.GridData
;
39 import org
.eclipse
.swt
.layout
.GridLayout
;
40 import org
.eclipse
.swt
.widgets
.Button
;
41 import org
.eclipse
.swt
.widgets
.Combo
;
42 import org
.eclipse
.swt
.widgets
.Composite
;
43 import org
.eclipse
.swt
.widgets
.Event
;
44 import org
.eclipse
.swt
.widgets
.FileDialog
;
45 import org
.eclipse
.swt
.widgets
.Label
;
46 import org
.eclipse
.swt
.widgets
.Listener
;
47 import org
.eclipse
.swt
.widgets
.TreeItem
;
48 import org
.eclipse
.tracecompass
.internal
.tmf
.ui
.Activator
;
49 import org
.eclipse
.tracecompass
.tmf
.ui
.dialog
.TmfFileDialogFactory
;
52 * An abstract wizard page containing common code useful for both import and
53 * export trace package wizard pages
55 * @author Marc-Andre Laperle
57 public abstract class AbstractTracePackageWizardPage
extends WizardPage
{
59 private static final int COMBO_HISTORY_LENGTH
= 5;
60 private static final String STORE_FILE_PATHS_ID
= ".STORE_FILEPATHS_ID"; //$NON-NLS-1$
62 private final String fStoreFilePathId
;
63 private final IStructuredSelection fSelection
;
65 private CheckboxTreeViewer fElementViewer
;
66 private Button fSelectAllButton
;
67 private Button fDeselectAllButton
;
68 private Combo fFilePathCombo
;
69 private Button fBrowseButton
;
72 * Create the trace package wizard page
75 * the name of the page
77 * the title for this wizard page, or null if none
79 * the image descriptor for the title of this wizard page, or
82 * the current object selection
84 protected AbstractTracePackageWizardPage(String pageName
, String title
, ImageDescriptor titleImage
, IStructuredSelection selection
) {
85 super(pageName
, title
, titleImage
);
86 fStoreFilePathId
= getName() + STORE_FILE_PATHS_ID
;
87 fSelection
= selection
;
91 * Create the element viewer
93 * @param compositeParent
94 * the parent composite
96 protected void createElementViewer(Composite compositeParent
) {
97 fElementViewer
= new CheckboxTreeViewer(compositeParent
, SWT
.SINGLE
| SWT
.H_SCROLL
| SWT
.V_SCROLL
| SWT
.BORDER
| SWT
.CHECK
);
99 fElementViewer
.addCheckStateListener(new ICheckStateListener() {
101 public void checkStateChanged(CheckStateChangedEvent event
) {
102 TracePackageElement element
= (TracePackageElement
) event
.getElement();
103 if (!element
.isEnabled()) {
104 fElementViewer
.setChecked(element
, element
.isChecked());
106 setSubtreeChecked(fElementViewer
, element
, true, event
.getChecked());
108 maintainCheckIntegrity(element
);
110 if (element
.getParent() != null) {
111 // Uncheck everything in this trace if Trace files are unchecked
112 if (element
instanceof TracePackageFilesElement
) {
113 if (!element
.isChecked()) {
114 setSubtreeChecked(fElementViewer
, element
.getParent(), false, false);
116 // Check Trace files if anything else is selected
117 } else if (element
.isChecked()) {
118 TracePackageElement parent
= element
.getParent();
119 while (parent
!= null) {
120 for (TracePackageElement e
: parent
.getChildren()) {
121 if (e
instanceof TracePackageFilesElement
) {
122 setSubtreeChecked(fElementViewer
, e
, false, true);
126 parent
= parent
.getParent();
132 updateApproximateSelectedSize();
133 updatePageCompletion();
136 private void maintainCheckIntegrity(final TracePackageElement element
) {
137 TracePackageElement parentElement
= element
.getParent();
138 boolean allChecked
= true;
139 boolean oneChecked
= false;
140 if (parentElement
!= null) {
141 if (parentElement
.getChildren() != null) {
142 for (TracePackageElement child
: parentElement
.getChildren()) {
143 if (fElementViewer
.getGrayed(child
)) {
148 boolean checked
= fElementViewer
.getChecked(child
);
149 oneChecked
|= checked
;
150 allChecked
&= checked
;
153 if (oneChecked
&& !allChecked
) {
154 fElementViewer
.setGrayChecked(parentElement
, true);
156 fElementViewer
.setGrayed(parentElement
, false);
157 fElementViewer
.setChecked(parentElement
, allChecked
);
159 maintainCheckIntegrity(parentElement
);
163 GridData layoutData
= new GridData(GridData
.FILL_BOTH
);
164 fElementViewer
.getTree().setLayoutData(layoutData
);
165 fElementViewer
.setContentProvider(new TracePackageContentProvider());
166 fElementViewer
.setLabelProvider(new TracePackageLabelProvider());
170 * Create the input for the element viewer
172 * @return the input for the element viewer
174 protected abstract Object
createElementViewerInput();
177 * Create the file path group that allows the user to type or browse for a
181 * the parent composite
183 * the label to describe the file path (i.e. import/export)
184 * @param fileDialogStyle
185 * SWT.OPEN or SWT.SAVE
187 protected void createFilePathGroup(Composite parent
, String label
, final int fileDialogStyle
) {
189 Composite filePathSelectionGroup
= new Composite(parent
, SWT
.NONE
);
190 GridLayout layout
= new GridLayout();
191 layout
.numColumns
= 3;
192 filePathSelectionGroup
.setLayout(layout
);
193 filePathSelectionGroup
.setLayoutData(new GridData(
194 GridData
.HORIZONTAL_ALIGN_FILL
| GridData
.VERTICAL_ALIGN_FILL
));
196 Label destinationLabel
= new Label(filePathSelectionGroup
, SWT
.NONE
);
197 destinationLabel
.setText(label
);
199 fFilePathCombo
= new Combo(filePathSelectionGroup
, SWT
.SINGLE
201 GridData data
= new GridData(GridData
.HORIZONTAL_ALIGN_FILL
202 | GridData
.GRAB_HORIZONTAL
);
203 data
.grabExcessHorizontalSpace
= true;
204 fFilePathCombo
.setLayoutData(data
);
206 fBrowseButton
= new Button(filePathSelectionGroup
,
208 fBrowseButton
.setText(org
.eclipse
.tracecompass
.internal
.tmf
.ui
.project
.wizards
.tracepkg
.Messages
.TracePackage_Browse
);
209 fBrowseButton
.addListener(SWT
.Selection
, new Listener() {
211 public void handleEvent(Event event
) {
212 handleFilePathBrowseButtonPressed(fileDialogStyle
);
215 setButtonLayoutData(fBrowseButton
);
219 * Update the page with the file path the current file path selection
221 protected abstract void updateWithFilePathSelection();
224 * Creates the buttons for selecting all or none of the elements.
228 * @return the button group
230 protected Composite
createButtonsGroup(Composite parent
) {
233 Composite buttonComposite
= new Composite(parent
, SWT
.NONE
);
235 GridLayout layout
= new GridLayout();
236 layout
.numColumns
= 3;
237 buttonComposite
.setLayout(layout
);
238 buttonComposite
.setLayoutData(new GridData(GridData
.VERTICAL_ALIGN_FILL
239 | GridData
.HORIZONTAL_ALIGN_FILL
));
241 fSelectAllButton
= new Button(buttonComposite
, SWT
.PUSH
);
242 fSelectAllButton
.setText(org
.eclipse
.tracecompass
.internal
.tmf
.ui
.project
.wizards
.tracepkg
.Messages
.TracePackage_SelectAll
);
244 SelectionListener listener
= new SelectionAdapter() {
246 public void widgetSelected(SelectionEvent e
) {
247 setAllChecked(fElementViewer
, true, true);
248 updateApproximateSelectedSize();
249 updatePageCompletion();
252 fSelectAllButton
.addSelectionListener(listener
);
254 fDeselectAllButton
= new Button(buttonComposite
, SWT
.PUSH
);
255 fDeselectAllButton
.setText(org
.eclipse
.tracecompass
.internal
.tmf
.ui
.project
.wizards
.tracepkg
.Messages
.TracePackage_DeselectAll
);
257 listener
= new SelectionAdapter() {
259 public void widgetSelected(SelectionEvent e
) {
260 setAllChecked(fElementViewer
, true, false);
261 updateApproximateSelectedSize();
262 updatePageCompletion();
265 fDeselectAllButton
.addSelectionListener(listener
);
267 return buttonComposite
;
271 * Restore widget values to the values that they held last time this wizard
272 * was used to completion.
274 protected void restoreWidgetValues() {
275 IDialogSettings settings
= getDialogSettings();
276 if (settings
!= null) {
277 String
[] directoryNames
= settings
.getArray(fStoreFilePathId
);
278 if (directoryNames
== null || directoryNames
.length
== 0) {
282 for (int i
= 0; i
< directoryNames
.length
; i
++) {
283 fFilePathCombo
.add(directoryNames
[i
]);
289 * Save widget values to Dialog settings
291 protected void saveWidgetValues() {
292 IDialogSettings settings
= getDialogSettings();
293 if (settings
!= null) {
294 // update directory names history
295 String
[] directoryNames
= settings
.getArray(fStoreFilePathId
);
296 if (directoryNames
== null) {
297 directoryNames
= new String
[0];
300 directoryNames
= addToHistory(directoryNames
, getFilePathValue());
301 settings
.put(fStoreFilePathId
, directoryNames
);
306 * Determine if the page is complete and update the page appropriately.
308 protected void updatePageCompletion() {
309 boolean pageComplete
= determinePageCompletion();
310 setPageComplete(pageComplete
);
312 setErrorMessage(null);
317 * Determine if the page is completed or not
319 * @return true if the page is completed, false otherwise
321 protected boolean determinePageCompletion() {
322 return fElementViewer
.getCheckedElements().length
> 0 && !getFilePathValue().isEmpty();
326 * Handle error status
331 protected void handleErrorStatus(IStatus status
) {
333 Throwable exception
= status
.getException();
334 String message
= status
.getMessage().length() > 0 ? status
.getMessage() : org
.eclipse
.tracecompass
.internal
.tmf
.ui
.project
.wizards
.tracepkg
.Messages
.TracePackage_ErrorOperation
;
336 if (!status
.isMultiStatus()) {
337 handleError(message
, exception
);
341 // Build a string with all the children status messages, exception
342 // messages and stack traces
343 StringBuilder sb
= new StringBuilder();
344 for (IStatus childStatus
: status
.getChildren()) {
345 StringBuilder childSb
= new StringBuilder();
346 if (!childStatus
.getMessage().isEmpty()) {
347 childSb
.append(childStatus
.getMessage() + '\n');
350 Throwable childException
= childStatus
.getException();
351 if (childException
!= null) {
352 String reason
= childException
.getMessage();
353 // Some system exceptions have no message
354 if (reason
== null) {
355 reason
= childException
.toString();
358 String stackMessage
= getExceptionStackMessage(childException
);
359 if (stackMessage
== null) {
360 stackMessage
= reason
;
363 childSb
.append(stackMessage
);
366 if (childSb
.length() > 0) {
367 childSb
.insert(0, '\n');
368 sb
.append(childSb
.toString());
372 // ErrorDialog only prints the call stack for a CoreException
373 exception
= new CoreException(new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, IStatus
.ERROR
, sb
.toString(), null));
374 final Status statusWithException
= new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, IStatus
.ERROR
, org
.eclipse
.tracecompass
.internal
.tmf
.ui
.project
.wizards
.tracepkg
.Messages
.TracePackage_ErrorMultipleProblems
, exception
);
376 Activator
.getDefault().logError(message
, exception
);
377 ErrorDialog
.openError(getContainer().getShell(), org
.eclipse
.tracecompass
.internal
.tmf
.ui
.project
.wizards
.tracepkg
.Messages
.TracePackage_InternalErrorTitle
, message
, statusWithException
);
381 * Handle errors occurring in the wizard operations
386 * the exception attached to the message
388 protected void handleError(String message
, Throwable exception
) {
389 Activator
.getDefault().logError(message
, exception
);
390 displayErrorDialog(message
, exception
);
393 private static String
getExceptionStackMessage(Throwable exception
) {
394 String stackMessage
= null;
395 ByteArrayOutputStream baos
= new ByteArrayOutputStream();
396 PrintStream ps
= new PrintStream(baos
);
397 exception
.printStackTrace(ps
);
401 stackMessage
= baos
.toString();
402 } catch (IOException e
) {
408 private void displayErrorDialog(String message
, Throwable exception
) {
409 if (exception
== null) {
410 final Status s
= new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, message
);
411 ErrorDialog
.openError(getContainer().getShell(), org
.eclipse
.tracecompass
.internal
.tmf
.ui
.project
.wizards
.tracepkg
.Messages
.TracePackage_InternalErrorTitle
, null, s
);
415 String reason
= exception
.getMessage();
416 // Some system exceptions have no message
417 if (reason
== null) {
418 reason
= exception
.toString();
421 String stackMessage
= getExceptionStackMessage(exception
);
422 if (stackMessage
== null || stackMessage
.isEmpty()) {
423 stackMessage
= reason
;
426 // ErrorDialog only prints the call stack for a CoreException
427 CoreException coreException
= new CoreException(new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, IStatus
.ERROR
, stackMessage
, exception
));
428 final Status s
= new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, IStatus
.ERROR
, reason
, coreException
);
429 ErrorDialog
.openError(getContainer().getShell(), org
.eclipse
.tracecompass
.internal
.tmf
.ui
.project
.wizards
.tracepkg
.Messages
.TracePackage_InternalErrorTitle
, message
, s
);
433 * A version of setSubtreeChecked that is aware of isEnabled
440 * if only enabled elements should be considered
442 * true if the item should be checked, and false if it should be
445 protected static void setSubtreeChecked(CheckboxTreeViewer viewer
, TracePackageElement element
, boolean enabledOnly
, boolean checked
) {
446 if (!enabledOnly
|| element
.isEnabled()) {
447 viewer
.setChecked(element
, checked
);
449 viewer
.setGrayed(element
, false);
451 element
.setChecked(checked
);
452 if (element
.getChildren() != null) {
453 for (TracePackageElement child
: element
.getChildren()) {
454 setSubtreeChecked(viewer
, child
, enabledOnly
, checked
);
461 * Sets all items in the element viewer to be checked or unchecked
466 * if only enabled elements should be considered
468 * whether or not items should be checked
470 protected static void setAllChecked(CheckboxTreeViewer viewer
, boolean enabledOnly
, boolean checked
) {
471 TreeItem
[] items
= viewer
.getTree().getItems();
472 for (int i
= 0; i
< items
.length
; i
++) {
473 Object element
= items
[i
].getData();
474 setSubtreeChecked(viewer
, (TracePackageElement
) element
, enabledOnly
, checked
);
478 private static void addToHistory(List
<String
> history
, String newEntry
) {
479 history
.remove(newEntry
);
480 history
.add(0, newEntry
);
482 // since only one new item was added, we can be over the limit
483 // by at most one item
484 if (history
.size() > COMBO_HISTORY_LENGTH
) {
485 history
.remove(COMBO_HISTORY_LENGTH
);
489 private static String
[] addToHistory(String
[] history
, String newEntry
) {
490 ArrayList
<String
> l
= new ArrayList
<>(Arrays
.asList(history
));
491 addToHistory(l
, newEntry
);
492 String
[] r
= new String
[l
.size()];
498 * Open an appropriate file dialog so that the user can specify a file to
500 * @param fileDialogStyle
502 private void handleFilePathBrowseButtonPressed(int fileDialogStyle
) {
503 FileDialog dialog
= TmfFileDialogFactory
.create(getContainer().getShell(), fileDialogStyle
| SWT
.SHEET
);
504 dialog
.setFilterExtensions(new String
[] { "*.zip;*.tar.gz;*.tar;*.tgz", "*.*" }); //$NON-NLS-1$ //$NON-NLS-2$
505 dialog
.setText(Messages
.TracePackage_FileDialogTitle
);
506 String currentSourceString
= getFilePathValue();
507 int lastSeparatorIndex
= currentSourceString
.lastIndexOf(File
.separator
);
508 if (lastSeparatorIndex
!= -1) {
509 dialog
.setFilterPath(currentSourceString
.substring(0, lastSeparatorIndex
));
511 String selectedFileName
= dialog
.open();
513 if (selectedFileName
!= null) {
514 setFilePathValue(selectedFileName
);
515 updateWithFilePathSelection();
520 * Get the current file path value
522 * @return the current file path value
524 protected String
getFilePathValue() {
525 return fFilePathCombo
.getText().trim();
529 * Set the file path value
534 protected void setFilePathValue(String value
) {
535 fFilePathCombo
.setText(value
);
536 updatePageCompletion();
540 * Update the approximate size of the selected elements
542 protected void updateApproximateSelectedSize() {
546 * Get the element tree viewer
548 * @return the element tree viewer
550 protected CheckboxTreeViewer
getElementViewer() {
551 return fElementViewer
;
555 * Get the file path combo box
557 * @return the file path combo box
559 protected Combo
getFilePathCombo() {
560 return fFilePathCombo
;
564 * Get the object selection when the wizard was created
566 * @return the object selection
568 protected IStructuredSelection
getSelection() {