net.tourbook.export.DialogExportTour.java Source code

Java tutorial

Introduction

Here is the source code for net.tourbook.export.DialogExportTour.java

Source

/*******************************************************************************
 * Copyright (C) 2005, 2016 Wolfgang Schramm and Contributors
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *******************************************************************************/
package net.tourbook.export;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;

import net.tourbook.Messages;
import net.tourbook.application.TourbookPlugin;
import net.tourbook.common.UI;
import net.tourbook.common.time.TimeTools;
import net.tourbook.common.util.StatusUtil;
import net.tourbook.common.util.Util;
import net.tourbook.data.TourData;
import net.tourbook.data.TourMarker;
import net.tourbook.data.TourWayPoint;
import net.tourbook.database.TourDatabase;
import net.tourbook.ext.velocity.VelocityService;
import net.tourbook.extension.export.ExportTourExtension;
import net.tourbook.tour.TourManager;
import net.tourbook.ui.FileCollisionBehavior;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.tools.generic.MathTool;
import org.dinopolis.gpstool.gpsinput.GPSRoute;
import org.dinopolis.gpstool.gpsinput.GPSTrack;
import org.dinopolis.gpstool.gpsinput.GPSTrackpoint;
import org.dinopolis.gpstool.gpsinput.garmin.GarminTrack;
import org.dinopolis.gpstool.gpsinput.garmin.GarminTrackpointAdapter;
import org.dinopolis.gpstool.gpsinput.garmin.GarminTrackpointD304;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Text;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.osgi.framework.Version;

public class DialogExportTour extends TitleAreaDialog {

    private static final String EXPORT_ID_GPX = "net.tourbook.export.gpx"; //$NON-NLS-1$
    private static final String EXPORT_ID_TCX = "net.tourbook.export.tcx"; //$NON-NLS-1$

    private static final String STATE_GPX_IS_ABSOLUTE_DISTANCE = "STATE_GPX_IS_ABSOLUTE_DISTANCE"; //$NON-NLS-1$
    private static final String STATE_GPX_IS_EXPORT_DESCRITION = "STATE_GPX_IS_EXPORT_DESCRITION"; //$NON-NLS-1$
    private static final String STATE_GPX_IS_EXPORT_MARKERS = "STATE_GPX_IS_EXPORT_MARKERS"; //$NON-NLS-1$
    private static final String STATE_GPX_IS_EXPORT_TOUR_DATA = "STATE_GPX_IS_EXPORT_TOUR_DATA"; //$NON-NLS-1$
    private static final String STATE_GPX_IS_WITH_BAROMETER = "STATE_GPX_IS_WITH_BAROMETER"; //$NON-NLS-1$

    private static final String STATE_TCX_IS_COURSES = "STATE_TCX_IS_COURSES"; //$NON-NLS-1$
    private static final String STATE_TCX_IS_EXPORT_DESCRITION = "STATE_TCX_IS_EXPORT_DESCRITION"; //$NON-NLS-1$
    private static final String STATE_TCX_IS_NAME_FROM_TOUR = "STATE_TCX_IS_NAME_FROM_TOUR"; //$NON-NLS-1$
    private static final String STATE_TCX_COURSE_NAME = "STATE_TCX_COURSE_NAME"; //$NON-NLS-1$

    private static final String STATE_CAMOUFLAGE_SPEED = "camouflageSpeedValue"; //$NON-NLS-1$
    private static final String STATE_IS_CAMOUFLAGE_SPEED = "isCamouflageSpeed"; //$NON-NLS-1$
    private static final String STATE_IS_EXPORT_TOUR_RANGE = "isExportTourRange"; //$NON-NLS-1$
    private static final String STATE_IS_OVERWRITE_FILES = "isOverwriteFiles"; //$NON-NLS-1$
    private static final String STATE_IS_MERGE_ALL_TOURS = "isMergeAllTours"; //$NON-NLS-1$
    private static final String STATE_EXPORT_PATH_NAME = "exportPathName"; //$NON-NLS-1$
    private static final String STATE_EXPORT_FILE_NAME = "exportFileName"; //$NON-NLS-1$

    /**
     * This is a special parameter to force elevation values from the device and not from the
     * lat/lon + srtm data <a
     * href="http://strava.github.io/api/v3/uploads/">http://strava.github.io/api/v3/uploads/</a>.
     * 
     * @since 15.6
     */
    private static final String STRAVA_WITH_BAROMETER = " with barometer"; //$NON-NLS-1$

    /*
     * Velocity (VC) context values
     */
    private static final String VC_HAS_TOUR_MARKERS = "hasTourMarkers"; //$NON-NLS-1$
    private static final String VC_HAS_TRACKS = "hasTracks"; //$NON-NLS-1$
    private static final String VC_HAS_WAY_POINTS = "hasWayPoints"; //$NON-NLS-1$
    private static final String VC_IS_EXPORT_ALL_TOUR_DATA = "isExportAllTourData"; //$NON-NLS-1$

    private static final String VC_LAP = "lap"; //$NON-NLS-1$
    private static final String VC_TOUR_DATA = "tourData"; //$NON-NLS-1$
    private static final String VC_TOUR_MARKERS = "tourMarkers"; //$NON-NLS-1$
    private static final String VC_TRACKS = "tracks"; //$NON-NLS-1$
    private static final String VC_WAY_POINTS = "wayPoints"; //$NON-NLS-1$

    private static final String ZERO = "0"; //$NON-NLS-1$

    private static final int VERTICAL_SECTION_MARGIN = 10;
    private static final int SIZING_TEXT_FIELD_WIDTH = 250;
    private static final int COMBO_HISTORY_LENGTH = 20;

    private static String _dlgDefaultMessage;
    //
    private static final DecimalFormat _nf1 = (DecimalFormat) NumberFormat.getInstance(Locale.US);
    private static final DecimalFormat _nf3 = (DecimalFormat) NumberFormat.getInstance(Locale.US);
    private static final DecimalFormat _nf8 = (DecimalFormat) NumberFormat.getInstance(Locale.US);

    private static final DateTimeFormatter _dtIso = ISODateTimeFormat.dateTimeNoMillis();
    private static final SimpleDateFormat _dateFormat = new SimpleDateFormat();

    static {

        _nf1.setMinimumFractionDigits(1);
        _nf1.setMaximumFractionDigits(1);
        _nf1.setGroupingUsed(false);

        _nf3.setMinimumFractionDigits(1);
        _nf3.setMaximumFractionDigits(3);
        _nf3.setGroupingUsed(false);

        _nf8.setMinimumFractionDigits(1);
        _nf8.setMaximumFractionDigits(8);
        _nf8.setGroupingUsed(false);

        _dtIso.withZoneUTC();
        _dateFormat.applyPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"); //$NON-NLS-1$
        _dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); //$NON-NLS-1$
    }

    private final String _formatTemplate;

    private final IDialogSettings _state = TourbookPlugin.getState("DialogExportTour"); //$NON-NLS-1$

    private final ExportTourExtension _exportExtensionPoint;

    private final ArrayList<TourData> _tourDataList;
    private final int _tourStartIndex;
    private final int _tourEndIndex;

    /**
     * Is <code>true</code> when multiple tours are selected and NOT merged into 1 file.
     */
    private boolean _isExport_MultipleToursWithMultipleFiles;

    private boolean _isInUIInit;

    /**
     * Is <code>true</code> when GPX export.
     */
    private boolean _isSetup_GPX;

    /**
     * Is <code>true</code> when TCX export.
     */
    private boolean _isSetup_TCX;

    /**
     * Is <code>true</code> when only a part is exported.
     */
    private boolean _isSetup_TourRange;

    /**
     * Is <code>true</code> when multiple tours are exported.
     */
    private boolean _isSetup_MultipleTours;

    private int _mergedDistance;
    private ZonedDateTime _mergedTime;

    private Point _shellDefaultSize;

    private float _exportState_CamouflageSpeed;
    private FileCollisionBehavior _exportState_FileCollisionBehaviour;
    private boolean _exportState_isAbsoluteDistance;
    private boolean _exportState_IsCamouflageSpeed;
    private boolean _exportState_IsDescription;
    private boolean _exportState_IsMergeTours;
    private boolean _exportState_IsOverwriteFiles;
    private boolean _exportState_IsRange;

    private boolean _exportState_GPX_IsExportMarkers;
    private boolean _exportState_GPX_IsExportAllTourData;
    private boolean _exportState_GPX_IsExportWithBarometer;

    private String _exportState_TCX_CourseName;
    private boolean _exportState_TCX_IsCourses;

    private PixelConverter _pc;

    /*
     * UI controls
     */
    private Button _btnSelectDirectory;
    private Button _btnSelectFile;

    private Button _chkCamouflageSpeed;
    private Button _chkExportTourRange;
    private Button _chkMergeAllTours;
    private Button _chkOverwriteFiles;

    private Button _chkGPX_Description;
    private Button _rdoGPX_DistanceAbsolute;
    private Button _rdoGPX_DistanceRelative;
    private Button _chkGPX_Markers;
    private Button _chkGPX_NoneGPXFields;
    private Button _chkGPX_WithBarometer;

    private Button _chkTCX_Description;
    private Button _rdoTCX_Activities;
    private Button _rdoTCX_Courses;
    private Button _rdoTCX_NameFromField;
    private Button _rdoTCX_NameFromTour;

    private Combo _comboFile;
    private Combo _comboPath;
    private Combo _comboTcxCourseName;

    private Composite _dlgContainer;
    private Composite _inputContainer;

    private Label _lblCoumouflageSpeedUnit;
    private Label _lblTcxCourseName;
    private Label _lblTcxNameFrom;

    private Spinner _spinnerCamouflageSpeed;

    private Text _txtFilePath;

    /**
     * @param parentShell
     * @param exportExtensionPoint
     * @param tourDataList
     * @param tourStartIndex
     * @param tourEndIndex
     * @param formatTemplate
     * @param isOptionDistance
     */
    public DialogExportTour(final Shell parentShell, final ExportTourExtension exportExtensionPoint,
            final ArrayList<TourData> tourDataList, final int tourStartIndex, final int tourEndIndex,
            final String formatTemplate) {

        super(parentShell);

        int shellStyle = getShellStyle();

        shellStyle = //
                SWT.NONE //
                        | SWT.TITLE | SWT.CLOSE | SWT.MIN
                        //            | SWT.MAX
                        | SWT.RESIZE | SWT.NONE;

        // make dialog resizable
        setShellStyle(shellStyle);

        _exportExtensionPoint = exportExtensionPoint;
        _formatTemplate = formatTemplate;

        _tourDataList = tourDataList;
        _tourStartIndex = tourStartIndex;
        _tourEndIndex = tourEndIndex;

        _isSetup_GPX = _exportExtensionPoint.getExportId().equalsIgnoreCase(EXPORT_ID_GPX);
        _isSetup_TCX = _exportExtensionPoint.getExportId().equalsIgnoreCase(EXPORT_ID_TCX);

        _isSetup_MultipleTours = _tourDataList.size() > 1;
        _isSetup_TourRange = _tourDataList.size() == 1 //
                && _tourStartIndex >= 0 && _tourEndIndex > -1;

        _dlgDefaultMessage = NLS.bind(Messages.dialog_export_dialog_message,
                _exportExtensionPoint.getVisibleName());

        // initialize velocity
        VelocityService.init();
    }

    @Override
    public boolean close() {

        saveState();

        return super.close();
    }

    @Override
    protected void configureShell(final Shell shell) {

        super.configureShell(shell);

        shell.setText(Messages.dialog_export_shell_text);

        shell.addListener(SWT.Resize, new Listener() {
            @Override
            public void handleEvent(final Event event) {

                // allow resizing the width but not the height

                if (_shellDefaultSize == null) {
                    _shellDefaultSize = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
                }

                final Point shellSize = shell.getSize();

                /*
                 * this is not working, the shell is flickering when the shell size is below min
                 * size and I found no way to prevent a resize :-(
                 */
                //            if (shellSize.x < _shellDefaultSize.x) {
                //               event.doit = false;
                //            }

                shellSize.x = shellSize.x < _shellDefaultSize.x ? _shellDefaultSize.x : shellSize.x;
                shellSize.y = _shellDefaultSize.y;

                shell.setSize(shellSize);
            }
        });
    }

    @Override
    public void create() {

        super.create();

        setTitle(Messages.dialog_export_dialog_title);
        setMessage(_dlgDefaultMessage);

        _isInUIInit = true;
        {
            restoreState();
        }
        _isInUIInit = false;

        //      validateFields();
        enableFields();
    }

    @Override
    protected final void createButtonsForButtonBar(final Composite parent) {

        super.createButtonsForButtonBar(parent);

        // set text for the OK button
        getButton(IDialogConstants.OK_ID).setText(Messages.dialog_export_btn_export);
    }

    @Override
    protected Control createDialogArea(final Composite parent) {

        initUI(parent);

        _dlgContainer = (Composite) super.createDialogArea(parent);

        createUI(_dlgContainer);

        return _dlgContainer;
    }

    private void createUI(final Composite parent) {

        _inputContainer = new Composite(parent, SWT.NONE);
        GridDataFactory.fillDefaults().grab(true, true).applyTo(_inputContainer);
        GridLayoutFactory.swtDefaults().margins(10, 5).applyTo(_inputContainer);
        //      container.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_RED));
        {
            createUI_10_Options(_inputContainer);
            createUI_90_ExportFile(_inputContainer);
        }
    }

    private void createUI_10_Options(final Composite parent) {

        final Composite container = new Composite(parent, SWT.NONE);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(container);
        GridLayoutFactory.fillDefaults().numColumns(2).applyTo(container);
        {
            createUI_12_OptionsLeft(container);
            createUI_14_OptionsRight(container);
        }
    }

    private void createUI_12_OptionsLeft(final Composite parent) {

        final Composite container = new Composite(parent, SWT.NONE);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(container);
        GridLayoutFactory.fillDefaults().numColumns(1).applyTo(container);
        {
            /*
             * What
             */
            final Group groupWhat = new Group(container, SWT.NONE);
            groupWhat.setText(Messages.Dialog_Export_Group_What);
            groupWhat.setToolTipText(Messages.Dialog_Export_Group_What_Tooltip);
            GridDataFactory.fillDefaults().grab(true, false).applyTo(groupWhat);
            GridLayoutFactory.swtDefaults().applyTo(groupWhat);
            //         groupWhat.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_YELLOW));
            {
                createUI_20_Option_What(groupWhat);
                createUI_40_Option_TourRange(groupWhat);
            }

            /*
             * How
             */
            final Group groupHow = new Group(container, SWT.NONE);
            groupHow.setText(Messages.Dialog_Export_Group_How);
            groupHow.setToolTipText(Messages.Dialog_Export_Group_How_Tooltip);
            GridDataFactory.fillDefaults().grab(true, false).applyTo(groupHow);
            GridLayoutFactory.swtDefaults().applyTo(groupHow);
            //         groupHow.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_YELLOW));
            {
                createUI_50_Option_How(groupHow);
            }
        }
    }

    private void createUI_14_OptionsRight(final Composite parent) {

        /*
         * Custom options
         */
        if (_isSetup_GPX) {

            final Group groupCustomGPX = new Group(parent, SWT.NONE);
            groupCustomGPX.setText(Messages.Dialog_Export_Group_Custom);
            groupCustomGPX.setToolTipText(Messages.Dialog_Export_Group_Custom_Tooltip);
            GridDataFactory.fillDefaults().grab(false, false).applyTo(groupCustomGPX);
            GridLayoutFactory.swtDefaults().applyTo(groupCustomGPX);
            {
                createUI_72_Option_GPX_Custom(groupCustomGPX);
            }
        }
    }

    private void createUI_20_Option_What(final Composite parent) {

        if (_isSetup_GPX) {

            /*
             * checkbox: export description
             */
            _chkGPX_Description = new Button(parent, SWT.CHECK);
            GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false)
                    .applyTo(_chkGPX_Description);
            _chkGPX_Description.setText(Messages.Dialog_Export_Checkbox_Description);

            /*
             * checkbox: export markers
             */
            _chkGPX_Markers = new Button(parent, SWT.CHECK);
            GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(_chkGPX_Markers);
            _chkGPX_Markers.setText(Messages.dialog_export_chk_exportMarkers);
            _chkGPX_Markers.setToolTipText(Messages.dialog_export_chk_exportMarkers_tooltip);

            /*
             * checkbox: export tour data
             */
            _chkGPX_NoneGPXFields = new Button(parent, SWT.CHECK);
            GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false)
                    .applyTo(_chkGPX_NoneGPXFields);
            _chkGPX_NoneGPXFields.setText(Messages.Dialog_Export_Checkbox_TourFields);
            _chkGPX_NoneGPXFields.setToolTipText(Messages.Dialog_Export_Checkbox_TourFields_Tooltip);

        } else if (_isSetup_TCX) {

            /*
             * checkbox: export description
             */
            _chkTCX_Description = new Button(parent, SWT.CHECK);
            GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false)
                    .applyTo(_chkTCX_Description);
            _chkTCX_Description.setText(Messages.dialog_export_chk_exportNotes);
            _chkTCX_Description.setToolTipText(Messages.dialog_export_chk_exportNotes_tooltip);
        }
    }

    private void createUI_40_Option_TourRange(final Composite parent) {

        if (_isSetup_TourRange == false) {
            return;
        }

        String tourRangeUI = null;

        final TourData tourData = _tourDataList.get(0);
        final int[] timeSerie = tourData.timeSerie;
        if (timeSerie != null) {

            final float[] distanceSerie = tourData.distanceSerie;
            final boolean isDistance = distanceSerie != null;

            final int startTime = timeSerie[_tourStartIndex];
            final int endTime = timeSerie[_tourEndIndex];

            final ZonedDateTime dtTour = tourData.getTourStartTime();

            final String uiStartTime = dtTour.plusSeconds(startTime).format(TimeTools.Formatter_Time_M);
            final String uiEndTime = dtTour.plusSeconds(endTime).format(TimeTools.Formatter_Time_M);

            if (isDistance) {

                tourRangeUI = NLS.bind(Messages.dialog_export_chk_tourRangeWithDistance, new Object[] {

                        uiStartTime, uiEndTime,

                        _nf3.format(distanceSerie[_tourStartIndex] / 1000 / net.tourbook.ui.UI.UNIT_VALUE_DISTANCE),
                        _nf3.format(distanceSerie[_tourEndIndex] / 1000 / net.tourbook.ui.UI.UNIT_VALUE_DISTANCE),

                        UI.UNIT_LABEL_DISTANCE,

                        // adjust by 1 to corresponds to the number in the tour editor
                        _tourStartIndex + 1, _tourEndIndex + 1 });

            } else {

                tourRangeUI = NLS.bind(Messages.dialog_export_chk_tourRangeWithoutDistance,
                        new Object[] { uiStartTime, uiEndTime, _tourStartIndex + 1, _tourEndIndex + 1 });
            }
        }

        /*
         * checkbox: tour range
         */
        _chkExportTourRange = new Button(parent, SWT.CHECK);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).applyTo(_chkExportTourRange);

        _chkExportTourRange
                .setText(tourRangeUI != null ? tourRangeUI : Messages.dialog_export_chk_tourRangeDisabled);

        _chkExportTourRange.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                enableFields();
            }
        });
    }

    private void createUI_50_Option_How(final Composite parent) {

        if (_isSetup_MultipleTours) {

            /*
             * checkbox: merge all tours
             */
            _chkMergeAllTours = new Button(parent, SWT.CHECK);
            GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(_chkMergeAllTours);
            _chkMergeAllTours.setText(Messages.dialog_export_chk_mergeAllTours);
            _chkMergeAllTours.setToolTipText(Messages.dialog_export_chk_mergeAllTours_tooltip);
            _chkMergeAllTours.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    enableFields();
                    setFileName();
                }
            });
        }

        createUI_60_Option_Speed(parent);

        if (_isSetup_GPX) {

            createUI_70_Option_GPX_Distance(parent);

        } else if (_isSetup_TCX) {

            createUI_80_Option_TCX_ActivitiesCourses(parent);
        }
    }

    private void createUI_60_Option_Speed(final Composite parent) {

        final Composite container = new Composite(parent, SWT.NONE);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(container);
        GridLayoutFactory.fillDefaults().numColumns(3).applyTo(container);
        {
            /*
             * checkbox: camouflage speed
             */
            _chkCamouflageSpeed = new Button(container, SWT.CHECK);
            GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(_chkCamouflageSpeed);
            _chkCamouflageSpeed.setText(Messages.dialog_export_chk_camouflageSpeed);
            _chkCamouflageSpeed.setToolTipText(Messages.dialog_export_chk_camouflageSpeed_tooltip);
            _chkCamouflageSpeed.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {

                    validateFields();
                    enableFields();

                    if (_chkCamouflageSpeed.getSelection()) {
                        _spinnerCamouflageSpeed.setFocus();
                    }
                }
            });

            // text: speed
            _spinnerCamouflageSpeed = new Spinner(container, SWT.BORDER);
            GridDataFactory.fillDefaults() //
                    .align(SWT.BEGINNING, SWT.FILL).applyTo(_spinnerCamouflageSpeed);
            _spinnerCamouflageSpeed.setToolTipText(Messages.dialog_export_chk_camouflageSpeedInput_tooltip);
            _spinnerCamouflageSpeed.setPageIncrement(10);
            _spinnerCamouflageSpeed.setMinimum(1);
            _spinnerCamouflageSpeed.setMaximum(1000);
            _spinnerCamouflageSpeed.addMouseWheelListener(new MouseWheelListener() {
                @Override
                public void mouseScrolled(final MouseEvent event) {
                    Util.adjustSpinnerValueOnMouseScroll(event);
                }
            });

            // label: unit
            _lblCoumouflageSpeedUnit = new Label(container, SWT.NONE);
            _lblCoumouflageSpeedUnit.setText(UI.SYMBOL_AVERAGE_WITH_SPACE + UI.UNIT_LABEL_SPEED);
            GridDataFactory.fillDefaults().grab(true, false).align(SWT.BEGINNING, SWT.CENTER)
                    .applyTo(_lblCoumouflageSpeedUnit);
        }
    }

    private void createUI_70_Option_GPX_Distance(final Composite parent) {

        final Composite container = new Composite(parent, SWT.NONE);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(container);
        GridLayoutFactory.fillDefaults().numColumns(3).applyTo(container);
        {
            // label
            final Label label = new Label(container, SWT.NONE);
            GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BEGINNING).applyTo(label);
            label.setText(Messages.Dialog_Export_Label_GPX_DistanceValues);

            // radio
            {
                _rdoGPX_DistanceAbsolute = new Button(container, SWT.RADIO);
                _rdoGPX_DistanceAbsolute.setText(Messages.Dialog_Export_Radio_GPX_DistanceAbsolute);
                _rdoGPX_DistanceAbsolute.setToolTipText(Messages.Dialog_Export_Radio_GPX_DistanceAbsolute_Tooltip);

                _rdoGPX_DistanceRelative = new Button(container, SWT.RADIO);
                GridDataFactory.fillDefaults().grab(true, false).applyTo(_rdoGPX_DistanceRelative);
                _rdoGPX_DistanceRelative.setText(Messages.Dialog_Export_Radio_GPX_DistanceRelative);
                _rdoGPX_DistanceRelative.setToolTipText(Messages.Dialog_Export_Radio_GPX_DistanceRelative_Tooltip);
            }
        }
    }

    private void createUI_72_Option_GPX_Custom(final Composite parent) {

        /*
         * checkbox: export with barometer
         */
        _chkGPX_WithBarometer = new Button(parent, SWT.CHECK);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(_chkGPX_WithBarometer);
        _chkGPX_WithBarometer.setText(Messages.Dialog_Export_Checkbox_WithBarometer);
        _chkGPX_WithBarometer.setToolTipText(Messages.Dialog_Export_Checkbox_WithBarometer_Tooltip);
    }

    private void createUI_80_Option_TCX_ActivitiesCourses(final Composite parent) {

        final SelectionAdapter defaultSelectionListener = new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                enableFields();
                setFileName();
            }
        };

        final SelectionAdapter nameSelectionListener = new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                updateUI_CourseName();
                enableFields();
                setFileName();
            }
        };

        final ModifyListener nameModifyListener = new ModifyListener() {
            @Override
            public void modifyText(final ModifyEvent e) {
                validateFields();
            }
        };

        // container
        final Composite container = new Composite(parent, SWT.NONE);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(container);
        GridLayoutFactory.fillDefaults().numColumns(2).applyTo(container);
        {
            {
                /*
                 * label: tcx type
                 */
                final Label label = new Label(container, SWT.NONE);
                GridDataFactory.fillDefaults().applyTo(label);
                label.setText(Messages.Dialog_Export_Label_TCX_Type);

                final Composite containerActivities = new Composite(container, SWT.NONE);
                GridDataFactory.fillDefaults().grab(true, false).applyTo(containerActivities);
                GridLayoutFactory.fillDefaults().numColumns(2).applyTo(containerActivities);
                {
                    /*
                     * radio: activities
                     */
                    _rdoTCX_Courses = new Button(containerActivities, SWT.RADIO);
                    GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(_rdoTCX_Courses);
                    _rdoTCX_Courses.setText(Messages.Dialog_Export_Radio_TCX_Courses);
                    _rdoTCX_Courses.setToolTipText(Messages.Dialog_Export_Radio_TCX_Courses_Tooltip);
                    _rdoTCX_Courses.addSelectionListener(defaultSelectionListener);

                    /*
                     * radio: activities
                     */
                    _rdoTCX_Activities = new Button(containerActivities, SWT.RADIO);
                    GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(_rdoTCX_Activities);
                    _rdoTCX_Activities.setText(Messages.Dialog_Export_Radio_TCX_Aktivities);
                    _rdoTCX_Activities.setToolTipText(Messages.Dialog_Export_Radio_TCX_Aktivities_Tooltip);
                    _rdoTCX_Activities.addSelectionListener(defaultSelectionListener);
                }
            }

            {
                /*
                 * label: course name from
                 */
                _lblTcxNameFrom = new Label(container, SWT.NONE);
                GridDataFactory.fillDefaults().applyTo(_lblTcxNameFrom);
                _lblTcxNameFrom.setText(Messages.Dialog_Export_Label_TCX_NameFrom);
                _lblTcxNameFrom.setToolTipText(Messages.Dialog_Export_Label_TCX_NameFrom_Tooltip);

                final Composite containerNameFrom = new Composite(container, SWT.NONE);
                GridDataFactory.fillDefaults().grab(true, false).applyTo(containerNameFrom);
                GridLayoutFactory.fillDefaults().numColumns(2).applyTo(containerNameFrom);
                {
                    /*
                     * radio: from tour
                     */
                    _rdoTCX_NameFromTour = new Button(containerNameFrom, SWT.RADIO);
                    GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(_rdoTCX_NameFromTour);
                    _rdoTCX_NameFromTour.setText(Messages.Dialog_Export_Radio_TCX_NameFromTour);
                    _rdoTCX_NameFromTour.addSelectionListener(nameSelectionListener);

                    /*
                     * radio: from text field
                     */
                    _rdoTCX_NameFromField = new Button(containerNameFrom, SWT.RADIO);
                    GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(_rdoTCX_NameFromField);
                    _rdoTCX_NameFromField.setText(Messages.Dialog_Export_Radio_TCX_NameFromField);
                    _rdoTCX_NameFromField.addSelectionListener(nameSelectionListener);
                }
            }

            {
                /*
                 * label: course name
                 */
                _lblTcxCourseName = new Label(container, SWT.NONE);
                GridDataFactory.fillDefaults().applyTo(_lblTcxCourseName);
                _lblTcxCourseName.setText(Messages.Dialog_Export_Label_TCX_CourseName);

                /*
                 * combo: name
                 */
                _comboTcxCourseName = new Combo(container, SWT.SINGLE | SWT.BORDER);
                GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(_comboTcxCourseName);
                _comboTcxCourseName.setVisibleItemCount(20);
                _comboTcxCourseName.addModifyListener(nameModifyListener);
            }
        }
    }

    private void createUI_90_ExportFile(final Composite parent) {

        Label label;

        final ModifyListener filePathModifyListener = new ModifyListener() {
            @Override
            public void modifyText(final ModifyEvent e) {
                validateFields();
            }
        };

        /*
         * group: filename
         */
        final Group group = new Group(parent, SWT.NONE);
        group.setText(Messages.dialog_export_group_exportFileName);
        GridDataFactory.fillDefaults().grab(true, false).indent(0, VERTICAL_SECTION_MARGIN).applyTo(group);
        GridLayoutFactory.swtDefaults().numColumns(3).applyTo(group);
        {
            /*
             * label: filename
             */
            label = new Label(group, SWT.NONE);
            label.setText(Messages.dialog_export_label_fileName);

            /*
             * combo: path
             */
            _comboFile = new Combo(group, SWT.SINGLE | SWT.BORDER);
            GridDataFactory.fillDefaults().grab(true, false).applyTo(_comboFile);
            ((GridData) _comboFile.getLayoutData()).widthHint = SIZING_TEXT_FIELD_WIDTH;
            _comboFile.setVisibleItemCount(20);
            _comboFile.addVerifyListener(net.tourbook.common.UI.verifyFilenameInput());
            _comboFile.addModifyListener(filePathModifyListener);
            _comboFile.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    validateFields();
                }
            });

            /*
             * button: browse
             */
            _btnSelectFile = new Button(group, SWT.PUSH);
            _btnSelectFile.setText(Messages.app_btn_browse);
            _btnSelectFile.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    onSelectBrowseFile();
                    validateFields();
                }
            });
            setButtonLayoutData(_btnSelectFile);

            // -----------------------------------------------------------------------------

            /*
             * label: path
             */
            label = new Label(group, SWT.NONE);
            label.setText(Messages.dialog_export_label_exportFilePath);

            /*
             * combo: path
             */
            _comboPath = new Combo(group, SWT.SINGLE | SWT.BORDER);
            GridDataFactory.fillDefaults().grab(true, false).applyTo(_comboPath);
            ((GridData) _comboPath.getLayoutData()).widthHint = SIZING_TEXT_FIELD_WIDTH;
            _comboPath.setVisibleItemCount(20);
            _comboPath.addModifyListener(filePathModifyListener);
            _comboPath.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    validateFields();
                }
            });

            /*
             * button: browse
             */
            _btnSelectDirectory = new Button(group, SWT.PUSH);
            _btnSelectDirectory.setText(Messages.app_btn_browse);
            _btnSelectDirectory.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    onSelectBrowseDirectory();
                    validateFields();
                }
            });
            setButtonLayoutData(_btnSelectDirectory);

            // -----------------------------------------------------------------------------

            /*
             * label: file path
             */
            label = new Label(group, SWT.NONE);
            label.setText(Messages.dialog_export_label_filePath);

            /*
             * text: filename
             */
            _txtFilePath = new Text(group, /* SWT.BORDER | */SWT.READ_ONLY);
            GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(_txtFilePath);
            _txtFilePath.setToolTipText(Messages.dialog_export_txt_filePath_tooltip);
            _txtFilePath.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));

            // -----------------------------------------------------------------------------

            /*
             * checkbox: overwrite files
             */
            _chkOverwriteFiles = new Button(group, SWT.CHECK);
            GridDataFactory.fillDefaults()//
                    .align(SWT.BEGINNING, SWT.CENTER).span(3, 1).indent(0, _pc.convertVerticalDLUsToPixels(4))
                    .applyTo(_chkOverwriteFiles);
            _chkOverwriteFiles.setText(Messages.dialog_export_chk_overwriteFiles);
            _chkOverwriteFiles.setToolTipText(Messages.dialog_export_chk_overwriteFiles_tooltip);
        }

    }

    private void doExport() throws IOException {

        // disable button's
        getButton(IDialogConstants.OK_ID).setEnabled(false);
        getButton(IDialogConstants.CANCEL_ID).setEnabled(false);

        _exportState_IsCamouflageSpeed = _chkCamouflageSpeed.getSelection();
        _exportState_IsOverwriteFiles = _chkOverwriteFiles.getSelection();

        _exportState_CamouflageSpeed = _spinnerCamouflageSpeed.getSelection();
        _exportState_CamouflageSpeed *= net.tourbook.ui.UI.UNIT_VALUE_DISTANCE / 3.6f;
        _exportState_FileCollisionBehaviour = new FileCollisionBehavior();

        if (_isSetup_TourRange) {
            _exportState_IsRange = _chkExportTourRange.getSelection();
        }

        if (_isSetup_MultipleTours) {
            _exportState_IsMergeTours = _chkMergeAllTours.getSelection();
        }

        if (_isSetup_GPX) {

            _exportState_isAbsoluteDistance = _rdoGPX_DistanceAbsolute.getSelection();

            _exportState_GPX_IsExportAllTourData = _chkGPX_NoneGPXFields.getSelection();
            _exportState_IsDescription = _chkGPX_Description.getSelection();
            _exportState_GPX_IsExportMarkers = _chkGPX_Markers.getSelection();
            _exportState_GPX_IsExportWithBarometer = _chkGPX_WithBarometer.getSelection();

        } else if (_isSetup_TCX) {

            // .tcx files do always contain absolute distances
            _exportState_isAbsoluteDistance = true;

            _exportState_IsDescription = _chkTCX_Description.getSelection();

            _exportState_TCX_IsCourses = _rdoTCX_Courses.getSelection();
            _exportState_TCX_CourseName = _comboTcxCourseName.getText();
        }

        final String exportFileName = _txtFilePath.getText();

        if (_tourDataList.size() == 1) {

            // export one tour

            final ArrayList<GarminTrack> tracks = new ArrayList<GarminTrack>();
            final ArrayList<TourWayPoint> wayPoints = new ArrayList<TourWayPoint>();
            final ArrayList<TourMarker> tourMarkers = new ArrayList<TourMarker>();

            final TourData tourData = _tourDataList.get(0);
            final ZonedDateTime trackStartTime = tourData.getTourStartTime();

            final GarminLap tourLap = doExport_50_Lap(tourData);

            final GarminTrack track = doExport_60_TrackPoints(tourData, trackStartTime);
            if (track != null) {
                tracks.add(track);
            }

            doExport_70_WayPoints(wayPoints, tourMarkers, tourData, trackStartTime);

            doExport_10_Tour(tourData, tracks, wayPoints, tourMarkers, tourLap, exportFileName);

        } else {

            /*
             * export multiple tours
             */

            final String exportPathName;

            if (_exportState_IsMergeTours) {
                exportPathName = exportFileName;
            } else {
                exportPathName = getExportPathName();
            }

            try {

                final IRunnableWithProgress exportRunnable = new IRunnableWithProgress() {
                    @Override
                    public void run(final IProgressMonitor monitor)
                            throws InvocationTargetException, InterruptedException {

                        try {

                            doExport_05_Runnable(monitor, exportPathName);

                        } catch (final IOException e) {
                            StatusUtil.log(e);
                        }
                    }
                };

                new ProgressMonitorDialog(Display.getCurrent().getActiveShell()).run(true, true, exportRunnable);

            } catch (final InvocationTargetException e) {
                StatusUtil.showStatus(e);
            } catch (final InterruptedException e) {
                StatusUtil.showStatus(e);
            }

        }
    }

    private void doExport_05_Runnable(final IProgressMonitor monitor, final String exportFileName)
            throws IOException {

        int exported = 0;
        final int tourSize = _tourDataList.size();

        monitor.beginTask(UI.EMPTY_STRING, tourSize);

        if (_exportState_IsMergeTours) {

            /*
             * merge all tours into one
             */

            _mergedTime = _tourDataList.get(0).getTourStartTime();
            _mergedDistance = 0;

            final ArrayList<GarminTrack> tracks = new ArrayList<GarminTrack>();
            final ArrayList<TourWayPoint> wayPoints = new ArrayList<TourWayPoint>();
            final ArrayList<TourMarker> tourMarkers = new ArrayList<TourMarker>();

            final GarminLap tourLap = new GarminLap();

            // create tracklist and lap
            for (final TourData tourData : _tourDataList) {

                if (monitor.isCanceled()) {
                    return;
                }

                monitor.worked(1);
                monitor.subTask(NLS.bind(Messages.Dialog_Export_SubTask_Export,
                        new Object[] { ++exported, tourSize, TourManager.getTourTitle(tourData) }));

                doExport_52_Laps(tourData, tourLap);

                ZonedDateTime trackStartTime;
                if (_exportState_IsCamouflageSpeed) {
                    trackStartTime = _mergedTime;
                } else {
                    trackStartTime = tourData.getTourStartTime();
                }

                final GarminTrack track = doExport_60_TrackPoints(tourData, trackStartTime);
                if (track != null) {
                    tracks.add(track);
                }

                doExport_70_WayPoints(wayPoints, tourMarkers, tourData, trackStartTime);
            }

            /*
             * There is currently no listener to stop the velocity evalute method
             */
            monitor.subTask(NLS.bind(Messages.Dialog_Export_SubTask_CreatingExportFile, exportFileName));

            doExport_10_Tour(null, tracks, wayPoints, tourMarkers, tourLap, exportFileName);

        } else {

            /*
             * export each tour separately
             */

            final IPath exportFilePath = new Path(exportFileName).addTrailingSeparator();
            final String fileExtension = _exportExtensionPoint.getFileExtension();

            for (final TourData tourData : _tourDataList) {

                if (monitor.isCanceled()) {
                    break;
                }

                // create file path name
                final String tourFileName = net.tourbook.ui.UI.format_yyyymmdd_hhmmss(tourData);

                final String exportFilePathName = exportFilePath.append(tourFileName)
                        .addFileExtension(fileExtension).toOSString();

                monitor.worked(1);
                monitor.subTask(NLS.bind(Messages.Dialog_Export_SubTask_Export,
                        new Object[] { ++exported, tourSize, exportFilePathName }));

                final ArrayList<GarminTrack> tracks = new ArrayList<GarminTrack>();
                final ArrayList<TourWayPoint> wayPoints = new ArrayList<TourWayPoint>();
                final ArrayList<TourMarker> tourMarkers = new ArrayList<TourMarker>();

                final ZonedDateTime trackStartTime = tourData.getTourStartTime();

                final GarminLap tourLap = doExport_50_Lap(tourData);

                final GarminTrack track = doExport_60_TrackPoints(tourData, trackStartTime);

                if (track != null) {
                    tracks.add(track);
                }

                doExport_70_WayPoints(wayPoints, tourMarkers, tourData, trackStartTime);

                doExport_10_Tour(tourData, tracks, wayPoints, tourMarkers, tourLap, exportFilePathName);

                // check if overwrite dialog was canceled
                if (_exportState_FileCollisionBehaviour.value == FileCollisionBehavior.DIALOG_IS_CANCELED) {
                    break;
                }
            }
        }
    }

    private void doExport_10_Tour(final TourData tourData, final ArrayList<GarminTrack> tracks,
            final ArrayList<TourWayPoint> wayPoints, final ArrayList<TourMarker> tourMarkers, final GarminLap lap,
            final String exportFileName) throws IOException {

        boolean isOverwrite = true;

        final File exportFile = new File(exportFileName);
        if (exportFile.exists()) {
            if (_exportState_IsOverwriteFiles) {
                // overwrite is enabled in the UI
            } else {
                isOverwrite = net.tourbook.ui.UI.confirmOverwrite(_exportState_FileCollisionBehaviour, exportFile);
            }
        }

        if (isOverwrite == false) {
            return;
        }

        final VelocityContext vc = new VelocityContext();

        // math tool to convert float into double
        vc.put("math", new MathTool());//$NON-NLS-1$

        if (_isSetup_GPX) {

            vc.put(VC_IS_EXPORT_ALL_TOUR_DATA, _exportState_GPX_IsExportAllTourData && tourData != null);

        } else if (_isSetup_TCX) {

            vc.put("iscourses", _exportState_TCX_IsCourses); //$NON-NLS-1$
            vc.put("coursename", _exportState_TCX_CourseName); //$NON-NLS-1$
        }

        vc.put(VC_LAP, lap);
        vc.put(VC_TRACKS, tracks);
        vc.put(VC_WAY_POINTS, wayPoints);
        vc.put(VC_TOUR_MARKERS, tourMarkers);
        vc.put(VC_TOUR_DATA, tourData);

        vc.put(VC_HAS_TOUR_MARKERS, Boolean.valueOf(tourMarkers.size() > 0));
        vc.put(VC_HAS_TRACKS, Boolean.valueOf(tracks.size() > 0));
        vc.put(VC_HAS_WAY_POINTS, Boolean.valueOf(wayPoints.size() > 0));

        vc.put("dateformat", _dateFormat); //$NON-NLS-1$
        vc.put("dtIso", _dtIso); //$NON-NLS-1$
        vc.put("nf1", _nf1); //$NON-NLS-1$
        vc.put("nf3", _nf3); //$NON-NLS-1$
        vc.put("nf8", _nf8); //$NON-NLS-1$

        doExport_20_TourValues(vc);

        final Writer exportWriter = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(exportFile), UI.UTF_8));

        final Reader templateReader = new InputStreamReader(this.getClass().getResourceAsStream(_formatTemplate));

        try {

            Velocity.evaluate(vc, exportWriter, "MyTourbook", templateReader); //$NON-NLS-1$

        } catch (final Exception e) {
            StatusUtil.showStatus(e);
        } finally {
            exportWriter.close();
        }
    }

    /**
     * Adds some values to the velocity context (e.g. date, ...).
     * 
     * @param vcContext
     *            the velocity context holding all the data
     */
    private void doExport_20_TourValues(final VelocityContext vcContext) {

        /*
         * Current time, date
         */
        final Calendar now = Calendar.getInstance();
        final Date creationDate = now.getTime();
        vcContext.put("creation_date", creationDate); //$NON-NLS-1$

        doExport_21_Creator(vcContext);
        doExport_22_MinMax_LatLon(vcContext);
        doExport_24_MinMax_Other(vcContext, creationDate);
    }

    private void doExport_21_Creator(final VelocityContext vcContext) {

        final Version version = Activator.getDefault().getVersion();

        /*
         * Version
         */
        String pluginMajorVersion = ZERO;
        String pluginMinorVersion = ZERO;
        String pluginMicroVersion = ZERO;
        String pluginQualifierVersion = ZERO;

        if (version != null) {
            pluginMajorVersion = Integer.toString(version.getMajor());
            pluginMinorVersion = Integer.toString(version.getMinor());
            pluginMicroVersion = Integer.toString(version.getMicro());
            pluginQualifierVersion = version.getQualifier();
        }

        vcContext.put("pluginMajorVersion", pluginMajorVersion); //$NON-NLS-1$
        vcContext.put("pluginMinorVersion", pluginMinorVersion); //$NON-NLS-1$
        vcContext.put("pluginMicroVersion", pluginMicroVersion); //$NON-NLS-1$
        vcContext.put("pluginQualifierVersion", pluginQualifierVersion); //$NON-NLS-1$

        /*
         * Creator
         */
        String creatorText = String.format("MyTourbook %d.%d.%d.%s - http://mytourbook.sourceforge.net", //$NON-NLS-1$
                version.getMajor(), version.getMinor(), version.getMicro(), version.getQualifier());

        if (_exportState_GPX_IsExportWithBarometer) {
            creatorText += STRAVA_WITH_BAROMETER;
        }

        vcContext.put("creator", creatorText); //$NON-NLS-1$
    }

    /**
     * Calculate min/max values for latitude/longitude.
     */
    private void doExport_22_MinMax_LatLon(final VelocityContext vcContext) {

        /*
         * Extent of waypoint, routes and tracks:
         */
        double min_latitude = 90.0;
        double min_longitude = 180.0;
        double max_latitude = -90.0;
        double max_longitude = -180.0;

        final List<?> routes = (List<?>) vcContext.get("routes"); //$NON-NLS-1$
        if (routes != null) {
            final Iterator<?> route_iterator = routes.iterator();
            while (route_iterator.hasNext()) {
                final GPSRoute route = (GPSRoute) route_iterator.next();
                min_longitude = route.getMinLongitude();
                max_longitude = route.getMaxLongitude();
                min_latitude = route.getMinLatitude();
                max_latitude = route.getMaxLatitude();
            }
        }

        final List<?> wayPoints = (List<?>) vcContext.get(VC_WAY_POINTS);
        if (wayPoints != null) {
            final Iterator<?> waypoint_iterator = wayPoints.iterator();
            while (waypoint_iterator.hasNext()) {
                final TourWayPoint waypoint = (TourWayPoint) waypoint_iterator.next();
                min_longitude = Math.min(min_longitude, waypoint.getLongitude());
                max_longitude = Math.max(max_longitude, waypoint.getLongitude());
                min_latitude = Math.min(min_latitude, waypoint.getLatitude());
                max_latitude = Math.max(max_latitude, waypoint.getLatitude());
            }
        }

        final List<?> tourMarkers = (List<?>) vcContext.get(VC_TOUR_MARKERS);
        if (tourMarkers != null) {

            for (final Object element : tourMarkers) {
                if (element instanceof TourMarker) {

                    final TourMarker tourMarker = (TourMarker) element;

                    final double longitude = tourMarker.getLongitude();
                    final double latitude = tourMarker.getLatitude();

                    if (longitude != TourDatabase.DEFAULT_DOUBLE) {

                        min_longitude = Math.min(min_longitude, longitude);
                        max_longitude = Math.max(max_longitude, longitude);
                        min_latitude = Math.min(min_latitude, latitude);
                        max_latitude = Math.max(max_latitude, latitude);
                    }
                }
            }
        }

        final List<?> tracks = (List<?>) vcContext.get(VC_TRACKS);
        if (tracks != null) {
            final Iterator<?> track_iterator = tracks.iterator();
            while (track_iterator.hasNext()) {
                final GPSTrack track = (GPSTrack) track_iterator.next();
                min_longitude = Math.min(min_longitude, track.getMinLongitude());
                max_longitude = Math.max(max_longitude, track.getMaxLongitude());
                min_latitude = Math.min(min_latitude, track.getMinLatitude());
                max_latitude = Math.max(max_latitude, track.getMaxLatitude());
            }
        }

        vcContext.put("min_latitude", new Double(min_latitude)); //$NON-NLS-1$
        vcContext.put("min_longitude", new Double(min_longitude)); //$NON-NLS-1$
        vcContext.put("max_latitude", new Double(max_latitude)); //$NON-NLS-1$
        vcContext.put("max_longitude", new Double(max_longitude)); //$NON-NLS-1$
    }

    /**
     * Min/max time, heart, cadence and other values.
     */
    private void doExport_24_MinMax_Other(final VelocityContext vcContext, final Date creationDate) {

        Date starttime = null;
        Date endtime = null;
        int heartNum = 0;
        long heartSum = 0;
        int cadNum = 0;
        long cadSum = 0;
        short maximumheartrate = 0;
        double totaldistance = 0;

        final List<?> tracks = (List<?>) vcContext.get(VC_TRACKS);

        for (final Object name : tracks) {

            final GPSTrack track = (GPSTrack) name;
            for (final Iterator<?> wpIter = track.getWaypoints().iterator(); wpIter.hasNext();) {

                final GPSTrackpoint wp = (GPSTrackpoint) wpIter.next();

                // starttime, totaltime
                if (wp.getDate() != null) {
                    if (starttime == null) {
                        starttime = wp.getDate();
                    }
                    endtime = wp.getDate();
                }

                if (wp instanceof GarminTrackpointAdapter) {

                    final GarminTrackpointAdapter gta = (GarminTrackpointAdapter) wp;

                    // average heartrate, maximum heartrate
                    if (gta.hasValidHeartrate()) {
                        heartSum += gta.getHeartrate();
                        heartNum++;
                        if (gta.getHeartrate() > maximumheartrate) {
                            maximumheartrate = gta.getHeartrate();
                        }
                    }

                    // average cadence
                    if (gta.hasValidCadence()) {
                        cadSum += gta.getCadence();
                        cadNum++;
                    }

                    // totaldistance
                    if (gta.hasValidDistance()) {
                        totaldistance = gta.getDistance();
                    }
                }
            }
        }

        if (starttime != null) {
            vcContext.put("starttime", starttime); //$NON-NLS-1$
        } else {
            vcContext.put("starttime", creationDate); //$NON-NLS-1$
        }

        if ((starttime != null) && (endtime != null)) {
            vcContext.put("totaltime", ((double) endtime.getTime() - starttime.getTime()) / 1000); //$NON-NLS-1$
        } else {
            vcContext.put("totaltime", (double) 0); //$NON-NLS-1$
        }

        vcContext.put("totaldistance", totaldistance); //$NON-NLS-1$

        if (maximumheartrate != 0) {
            vcContext.put("maximumheartrate", maximumheartrate); //$NON-NLS-1$
        }

        if (heartNum != 0) {
            vcContext.put("averageheartrate", heartSum / heartNum); //$NON-NLS-1$
        }

        if (cadNum != 0) {
            vcContext.put("averagecadence", cadSum / cadNum); //$NON-NLS-1$
        }
    }

    private GarminLap doExport_50_Lap(final TourData tourData) {

        final GarminLap lap = new GarminLap();

        /*
         * Calories
         */
        lap.setCalories(tourData.getCalories());

        /*
         * Description
         */
        if (_exportState_IsDescription) {
            final String notes = tourData.getTourDescription();
            if ((notes != null) && (notes.length() > 0)) {
                lap.setNotes(notes);
            }
        }

        return lap;
    }

    private void doExport_52_Laps(final TourData tourData, final GarminLap tourLap) {

        /*
         * Calories
         */
        int calories = tourLap.getCalories();
        calories += tourData.getCalories();
        tourLap.setCalories(calories);

        /*
         * Description
         */
        if (_exportState_IsDescription) {

            final String notes = tourData.getTourDescription();

            if ((notes != null) && (notes.length() > 0)) {

                final String lapNotes = tourLap.getNotes();

                if (lapNotes == null) {
                    tourLap.setNotes(notes);
                } else {
                    tourLap.setNotes(lapNotes + "\n" + notes); //$NON-NLS-1$
                }
            }
        }

    }

    /**
     * @param tourData
     * @param trackDateTime
     * @return Returns a track or <code>null</code> when tour data cannot be exported.
     */
    private GarminTrack doExport_60_TrackPoints(final TourData tourData, final ZonedDateTime trackDateTime) {

        final int[] timeSerie = tourData.timeSerie;

        // check if all dataseries are available
        if ((timeSerie == null) /* || (latitudeSerie == null) || (longitudeSerie == null) */) {
            return null;
        }

        final float[] altitudeSerie = tourData.altitudeSerie;
        final float[] cadenceSerie = tourData.cadenceSerie;
        final float[] distanceSerie = tourData.distanceSerie;
        final long[] gearSerie = tourData.gearSerie;
        final double[] latitudeSerie = tourData.latitudeSerie;
        final double[] longitudeSerie = tourData.longitudeSerie;
        final float[] pulseSerie = tourData.pulseSerie;
        final float[] temperatureSerie = tourData.temperatureSerie;

        final boolean isAltitude = (altitudeSerie != null) && (altitudeSerie.length > 0);
        final boolean isCadence = (cadenceSerie != null) && (cadenceSerie.length > 0);
        final boolean isDistance = (distanceSerie != null) && (distanceSerie.length > 0);
        final boolean isGear = (gearSerie != null) && (gearSerie.length > 0);
        final boolean isPulse = (pulseSerie != null) && (pulseSerie.length > 0);
        final boolean isTemperature = (temperatureSerie != null) && (temperatureSerie.length > 0);

        int prevTime = -1;
        ZonedDateTime lastTrackDateTime = null;

        // default is to use all trackpoints
        int startIndex = 0;
        int endIndex = timeSerie.length - 1;

        // adjust start/end when a part is exported
        if (_exportState_IsRange) {
            startIndex = _tourStartIndex;
            endIndex = _tourEndIndex;
        }

        final GarminTrack track = new GarminTrack();

        /*
         * Track title/description
         */
        if (_exportState_IsDescription) {

            final String tourTitle = tourData.getTourTitle();
            if (tourTitle.length() > 0) {
                track.setIdentification(tourTitle);
            }

            final String tourDescription = tourData.getTourDescription();
            if (tourDescription.length() > 0) {
                track.setComment(tourDescription);
            }
        }

        /*
         * loop: trackpoints
         */
        for (int serieIndex = startIndex; serieIndex <= endIndex; serieIndex++) {

            final GarminTrackpointD304 tp304 = new GarminTrackpointD304();
            final GarminTrackpointAdapterExtended tpExt = new GarminTrackpointAdapterExtended(tp304);

            // mark as a new track to create the <trkseg>...</trkseg> tags
            if (serieIndex == startIndex) {
                tpExt.setNewTrack(true);
            }

            if (isAltitude) {
                tpExt.setAltitude(altitudeSerie[serieIndex]);
            }

            // I don't know if this is according to the rules to have a gpx/tcx without lat/lon
            if (latitudeSerie != null && longitudeSerie != null) {
                tpExt.setLatitude(latitudeSerie[serieIndex]);
                tpExt.setLongitude(longitudeSerie[serieIndex]);
            }

            float distance = 0;
            if (isDistance) {

                if (_exportState_isAbsoluteDistance) {

                    distance = distanceSerie[serieIndex];

                } else if (distanceSerie != null) {

                    // skip first distance difference
                    if (serieIndex > startIndex) {
                        distance = distanceSerie[serieIndex] - distanceSerie[serieIndex - 1];
                    }
                }
            }

            int relativeTime;
            if (_exportState_IsCamouflageSpeed && isDistance) {

                // camouflage speed

                relativeTime = (int) (distance / _exportState_CamouflageSpeed);

            } else {

                // keep recorded speed

                relativeTime = timeSerie[serieIndex];
            }

            if (isDistance) {
                tpExt.setDistance(distance + _mergedDistance);
            }

            if (isCadence) {
                tp304.setCadence((short) cadenceSerie[serieIndex]);
            }

            if (isPulse) {
                tp304.setHeartrate((short) pulseSerie[serieIndex]);
            }

            if (isTemperature) {
                tpExt.setTemperature(temperatureSerie[serieIndex]);
            }

            if (isGear) {
                tpExt.setGear(gearSerie[serieIndex]);
            }

            // ignore trackpoints which have the same time
            if (relativeTime != prevTime) {

                lastTrackDateTime = trackDateTime.plusSeconds(relativeTime);

                tpExt.setDate(Date.from(lastTrackDateTime.toInstant()));

                track.addWaypoint(tpExt);
            }

            prevTime = relativeTime;
        }

        /*
         * Keep values for the next merged tour
         */
        if (isDistance && _exportState_isAbsoluteDistance) {

            final float distanceDiff = distanceSerie[endIndex] - distanceSerie[startIndex];
            _mergedDistance += distanceDiff;
        }

        _mergedTime = lastTrackDateTime;

        return track;
    }

    private void doExport_70_WayPoints(final ArrayList<TourWayPoint> exportedWayPoints,
            final ArrayList<TourMarker> exportedTourMarkers, final TourData tourData,
            final ZonedDateTime tourStartTime) {

        // get markers when this option is checked
        if (_exportState_GPX_IsExportMarkers == false) {
            return;
        }

        final int[] timeSerie = tourData.timeSerie;
        final float[] altitudeSerie = tourData.altitudeSerie;
        final double[] latitudeSerie = tourData.latitudeSerie;
        final double[] longitudeSerie = tourData.longitudeSerie;
        final float[] distanceSerie = tourData.distanceSerie;

        final Set<TourMarker> tourMarkers = tourData.getTourMarkers();
        final Set<TourWayPoint> tourWayPoints = tourData.getTourWayPoints();

        // ensure required dataseries are available
        if (timeSerie == null || latitudeSerie == null || longitudeSerie == null) {
            return;
        }

        final boolean isAltitude = altitudeSerie != null;
        final boolean isDistance = (distanceSerie != null) && (distanceSerie.length > 0);

        // default is to use all trackpoints
        int startIndex = 0;
        int endIndex = timeSerie.length - 1;
        boolean isRange = false;

        // adjust start/end when a part is exported
        if (_exportState_IsRange) {
            startIndex = _tourStartIndex;
            endIndex = _tourEndIndex;
            isRange = true;
        }

        /*
         * Create exported tour marker
         */
        for (final TourMarker tourMarker : tourMarkers) {

            final int serieIndex = tourMarker.getSerieIndex();

            // skip markers when they are not in the defined range
            if (isRange) {
                if ((serieIndex < startIndex) || (serieIndex > endIndex)) {
                    continue;
                }
            }

            /*
             * get distance
             */
            float distance = 0;
            if (isDistance) {

                if (_exportState_isAbsoluteDistance) {

                    distance = distanceSerie[serieIndex];

                } else if (distanceSerie != null) {

                    // skip first distance difference
                    if (serieIndex > startIndex) {
                        distance = distanceSerie[serieIndex] - distanceSerie[serieIndex - 1];
                    }
                }
            }

            /*
             * get time
             */
            int relativeTime;
            if (_exportState_IsCamouflageSpeed && isDistance) {

                // camouflage speed

                relativeTime = (int) (distance / _exportState_CamouflageSpeed);

            } else {

                // keep recorded speed

                relativeTime = timeSerie[serieIndex];
            }

            final ZonedDateTime zonedMarkerTime = tourStartTime.plusSeconds(relativeTime);
            final long absoluteMarkerTime = zonedMarkerTime.toInstant().toEpochMilli();

            /*
             * Setup exported tour marker
             */
            final TourMarker exportedTourMarker = tourMarker.clone();

            exportedTourMarker.setTime(relativeTime, absoluteMarkerTime);
            exportedTourMarker.setLatitude(latitudeSerie[serieIndex]);
            exportedTourMarker.setLongitude(longitudeSerie[serieIndex]);

            if (isAltitude) {
                exportedTourMarker.setAltitude(altitudeSerie[serieIndex]);
            }

            if (isDistance) {
                exportedTourMarker.setDistance(distance);
            }

            exportedTourMarkers.add(exportedTourMarker);
        }

        for (final TourWayPoint twp : tourWayPoints) {

            final TourWayPoint wayPoint = new TourWayPoint();

            wayPoint.setTime(twp.getTime());
            wayPoint.setLatitude(twp.getLatitude());
            wayPoint.setLongitude(twp.getLongitude());

            wayPoint.setName(twp.getName());

            // <desc>...</desc>
            final String comment = twp.getComment();
            final String description = twp.getDescription();
            final String descText = description != null ? description : comment;
            if (descText != null) {
                wayPoint.setComment(descText);
            }

            wayPoint.setAltitude(twp.getAltitude());

            wayPoint.setUrlAddress(twp.getUrlAddress());
            wayPoint.setUrlText(twp.getUrlText());
            //
            //         // <sym>...</sym>
            //         wayPoint.setSymbolName(twp.getSymbol());

            exportedWayPoints.add(wayPoint);
        }
    }

    private void enableExportButton(final boolean isEnabled) {
        final Button okButton = getButton(IDialogConstants.OK_ID);
        if (okButton != null) {
            okButton.setEnabled(isEnabled);
        }
    }

    private void enableFields() {

        final boolean isCamouflageSpeed = _chkCamouflageSpeed.getSelection();
        final boolean isSingleTour = _isSetup_MultipleTours == false;
        boolean isMergeIntoOneTour = false;

        if (_isSetup_MultipleTours) {

            isMergeIntoOneTour = _chkMergeAllTours.getSelection();
            _chkMergeAllTours.setEnabled(_isSetup_MultipleTours);
        }

        _isExport_MultipleToursWithMultipleFiles = _isSetup_MultipleTours && isMergeIntoOneTour == false;

        if (_isSetup_GPX) {

            final boolean isNoneGPX = isSingleTour || _isExport_MultipleToursWithMultipleFiles;
            _chkGPX_NoneGPXFields.setEnabled(isNoneGPX);
            if (!isNoneGPX) {
                // deselect when not checked
                _chkGPX_NoneGPXFields.setSelection(false);
            }

        } else if (_isSetup_TCX) {

            final boolean isCourse = _rdoTCX_Courses.getSelection();
            final boolean isFromField = _rdoTCX_NameFromField.getSelection();

            _lblTcxNameFrom.setEnabled(isCourse);
            _rdoTCX_NameFromTour.setEnabled(isCourse);
            _rdoTCX_NameFromField.setEnabled(isCourse);

            _lblTcxCourseName.setEnabled(isCourse && isFromField);
            _comboTcxCourseName.setEnabled(isCourse && isFromField);
        }

        _comboFile.setEnabled(isSingleTour || isMergeIntoOneTour);
        _btnSelectFile.setEnabled(isSingleTour || isMergeIntoOneTour);

        _spinnerCamouflageSpeed.setEnabled(isCamouflageSpeed);
        _lblCoumouflageSpeedUnit.setEnabled(isCamouflageSpeed);

        setFileName();
    }

    private String getCourseName() {
        return _comboTcxCourseName.getText().trim();
    }

    @Override
    protected IDialogSettings getDialogBoundsSettings() {
        // keep window size and position
        return _state;
    }

    private String getExportFileName() {
        return _comboFile.getText().trim();
    }

    private String getExportPathName() {
        return _comboPath.getText().trim();
    }

    private void initUI(final Composite parent) {

        _pc = new PixelConverter(parent);
    }

    @Override
    protected void okPressed() {

        net.tourbook.ui.UI.disableAllControls(_inputContainer);

        BusyIndicator.showWhile(Display.getCurrent(), new Runnable() {
            @Override
            public void run() {
                try {
                    doExport();
                } catch (final IOException e) {
                    StatusUtil.log(e);
                }
            }
        });

        super.okPressed();
    }

    private void onSelectBrowseDirectory() {

        final DirectoryDialog dialog = new DirectoryDialog(_dlgContainer.getShell(), SWT.SAVE);
        dialog.setText(Messages.dialog_export_dir_dialog_text);
        dialog.setMessage(Messages.dialog_export_dir_dialog_message);

        dialog.setFilterPath(getExportPathName());

        final String selectedDirectoryName = dialog.open();

        if (selectedDirectoryName != null) {
            setErrorMessage(null);
            _comboPath.setText(selectedDirectoryName);
        }
    }

    private void onSelectBrowseFile() {

        final String fileExtension = _exportExtensionPoint.getFileExtension();

        final FileDialog dialog = new FileDialog(_dlgContainer.getShell(), SWT.SAVE);
        dialog.setText(Messages.dialog_export_file_dialog_text);

        dialog.setFilterPath(getExportPathName());
        dialog.setFilterExtensions(new String[] { fileExtension });
        dialog.setFileName("*." + fileExtension);//$NON-NLS-1$

        final String selectedFilePath = dialog.open();

        if (selectedFilePath != null) {
            setErrorMessage(null);
            _comboFile.setText(new Path(selectedFilePath).toFile().getName());
        }
    }

    private void restoreState() {

        if (_isSetup_GPX) {

            final boolean isAbsoluteDistance = Util.getStateBoolean(_state, STATE_GPX_IS_ABSOLUTE_DISTANCE, true);

            _chkGPX_Description.setSelection(_state.getBoolean(STATE_GPX_IS_EXPORT_DESCRITION));
            _chkGPX_Markers.setSelection(_state.getBoolean(STATE_GPX_IS_EXPORT_MARKERS));
            _chkGPX_NoneGPXFields.setSelection(_state.getBoolean(STATE_GPX_IS_EXPORT_TOUR_DATA));
            _chkGPX_WithBarometer.setSelection(_state.getBoolean(STATE_GPX_IS_WITH_BAROMETER));

            _rdoGPX_DistanceAbsolute.setSelection(isAbsoluteDistance);
            _rdoGPX_DistanceRelative.setSelection(!isAbsoluteDistance);

        } else if (_isSetup_TCX) {

            final boolean isCourses = Util.getStateBoolean(_state, STATE_TCX_IS_COURSES, true);
            final boolean isFromTour = Util.getStateBoolean(_state, STATE_TCX_IS_NAME_FROM_TOUR, true);

            _chkTCX_Description.setSelection(_state.getBoolean(STATE_TCX_IS_EXPORT_DESCRITION));

            _rdoTCX_Courses.setSelection(isCourses);
            _rdoTCX_Activities.setSelection(!isCourses);

            _rdoTCX_NameFromTour.setSelection(isFromTour);
            _rdoTCX_NameFromField.setSelection(!isFromTour);

            UI.restoreCombo(_comboTcxCourseName, _state.getArray(STATE_TCX_COURSE_NAME));

            updateUI_CourseName();
        }

        // merge all tours
        if (_isSetup_MultipleTours) {
            _chkMergeAllTours.setSelection(_state.getBoolean(STATE_IS_MERGE_ALL_TOURS));
        }

        // export tour part
        if (_isSetup_TourRange) {
            _chkExportTourRange.setSelection(_state.getBoolean(STATE_IS_EXPORT_TOUR_RANGE));
        }

        // camouflage speed
        _chkCamouflageSpeed.setSelection(_state.getBoolean(STATE_IS_CAMOUFLAGE_SPEED));
        _spinnerCamouflageSpeed.setSelection(Util.getStateInt(_state, STATE_CAMOUFLAGE_SPEED, 10));

        // export file/path
        UI.restoreCombo(_comboFile, _state.getArray(STATE_EXPORT_FILE_NAME));
        UI.restoreCombo(_comboPath, _state.getArray(STATE_EXPORT_PATH_NAME));
        _chkOverwriteFiles.setSelection(_state.getBoolean(STATE_IS_OVERWRITE_FILES));
    }

    private void saveState() {

        if (_isSetup_GPX) {

            _state.put(STATE_GPX_IS_EXPORT_DESCRITION, _chkGPX_Description.getSelection());
            _state.put(STATE_GPX_IS_ABSOLUTE_DISTANCE, _rdoGPX_DistanceAbsolute.getSelection());
            _state.put(STATE_GPX_IS_EXPORT_MARKERS, _chkGPX_Markers.getSelection());
            _state.put(STATE_GPX_IS_EXPORT_TOUR_DATA, _chkGPX_NoneGPXFields.getSelection());
            _state.put(STATE_GPX_IS_WITH_BAROMETER, _chkGPX_WithBarometer.getSelection());

        } else if (_isSetup_TCX) {

            _state.put(STATE_TCX_IS_COURSES, _rdoTCX_Courses.getSelection());
            _state.put(STATE_TCX_IS_EXPORT_DESCRITION, _chkTCX_Description.getSelection());
            _state.put(STATE_TCX_IS_NAME_FROM_TOUR, _rdoTCX_NameFromTour.getSelection());
            _state.put(STATE_TCX_COURSE_NAME,
                    Util.getUniqueItems(_comboTcxCourseName.getItems(), getCourseName(), COMBO_HISTORY_LENGTH));
        }

        // merge all tours
        if (_isSetup_MultipleTours) {
            _state.put(STATE_IS_MERGE_ALL_TOURS, _chkMergeAllTours.getSelection());
        }

        // export tour part
        if (_isSetup_TourRange) {
            _state.put(STATE_IS_EXPORT_TOUR_RANGE, _chkExportTourRange.getSelection());
        }

        // camouflage speed
        _state.put(STATE_IS_CAMOUFLAGE_SPEED, _chkCamouflageSpeed.getSelection());
        _state.put(STATE_CAMOUFLAGE_SPEED, _spinnerCamouflageSpeed.getSelection());

        // export file/path
        if (validateFilePath()) {
            _state.put(STATE_EXPORT_PATH_NAME,
                    Util.getUniqueItems(_comboPath.getItems(), getExportPathName(), COMBO_HISTORY_LENGTH));
            _state.put(STATE_EXPORT_FILE_NAME,
                    Util.getUniqueItems(_comboFile.getItems(), getExportFileName(), COMBO_HISTORY_LENGTH));
        }
        _state.put(STATE_IS_OVERWRITE_FILES, _chkOverwriteFiles.getSelection());
    }

    private void setError(final String message) {
        setErrorMessage(message);
        enableExportButton(false);
    }

    /**
     * Set filename with the first tour date/time, when tour is merged "<#default>" is displayed
     */
    private void setFileName() {

        // search for the first tour
        TourData minTourData = null;
        final long minTourMillis = 0;

        for (final TourData tourData : _tourDataList) {

            if (minTourData == null) {
                minTourData = tourData;
            } else {

                final long tourMillis = tourData.getTourStartTime().toInstant().toEpochMilli();

                if (tourMillis < minTourMillis) {
                    minTourData = tourData;
                }
            }
        }

        if (_isExport_MultipleToursWithMultipleFiles) {

            // use default file name for each exported tour

            _comboFile.setText(Messages.dialog_export_label_DefaultFileName);

        } else if (_isSetup_TourRange) {

            // display the start date/time

            final ZonedDateTime dtTour = minTourData.getTourStartTime();

            // adjust start time
            final int startTime = minTourData.timeSerie[_tourStartIndex];
            final ZonedDateTime tourTime = dtTour.plusSeconds(startTime);

            _comboFile.setText(UI.format_yyyymmdd_hhmmss(tourTime.getYear(), tourTime.getMonthValue(),
                    tourTime.getDayOfMonth(), tourTime.getHour(), tourTime.getMinute(), tourTime.getSecond()));
        } else {

            // display the tour date/time

            _comboFile.setText(net.tourbook.ui.UI.format_yyyymmdd_hhmmss(minTourData));
        }
    }

    private void updateUI_CourseName() {

        if (_isSetup_TCX == false) {
            return;
        }

        if (_rdoTCX_NameFromTour.getSelection()) {

            /*
             * set course name from tour
             */

            String courseName = UI.EMPTY_STRING;

            for (final TourData tourData : _tourDataList) {
                final String tourTitle = tourData.getTourTitle().trim();
                if (tourTitle.length() > 0) {
                    courseName = tourTitle;
                    break;
                }
            }
            _comboTcxCourseName.setText(courseName);
        }
    }

    private void validateFields() {

        if (_isInUIInit) {
            return;
        }

        /*
         * validate fields
         */

        if (_isSetup_TCX) {
            if (_rdoTCX_Courses.getSelection() && getCourseName().length() == 0) {
                setError(Messages.Dialog_Export_Error_CourseNameIsInvalid);
                _comboTcxCourseName.setFocus();
                return;
            }
        }

        if (validateFilePath() == false) {
            return;
        }

        setErrorMessage(null);
        enableExportButton(true);
    }

    private boolean validateFilePath() {

        // check path
        IPath filePath = new Path(getExportPathName());
        if (new File(filePath.toOSString()).exists() == false) {

            // invalid path
            setError(NLS.bind(Messages.dialog_export_msg_pathIsNotAvailable, filePath.toOSString()));
            return false;
        }

        boolean returnValue = false;

        if (_isExport_MultipleToursWithMultipleFiles) {

            // only the path is checked, the file name is created automatically for each exported tour

            setMessage(_dlgDefaultMessage);

            // build file path with extension
            filePath = filePath.addTrailingSeparator().append(Messages.dialog_export_label_DefaultFileName)
                    .addFileExtension(_exportExtensionPoint.getFileExtension());

            returnValue = true;

        } else {

            String fileName = getExportFileName();

            // remove extentions
            final int extPos = fileName.indexOf('.');
            if (extPos != -1) {
                fileName = fileName.substring(0, extPos);
            }

            // build file path with extension
            filePath = filePath.addTrailingSeparator().append(fileName)
                    .addFileExtension(_exportExtensionPoint.getFileExtension());

            final File newFile = new File(filePath.toOSString());

            if ((fileName.length() == 0) || newFile.isDirectory()) {

                // invalid filename

                setError(Messages.dialog_export_msg_fileNameIsInvalid);

            } else if (newFile.exists()) {

                // file already exists

                setMessage(NLS.bind(Messages.dialog_export_msg_fileAlreadyExists, filePath.toOSString()),
                        IMessageProvider.WARNING);
                returnValue = true;

            } else {

                setMessage(_dlgDefaultMessage);

                try {
                    final boolean isFileCreated = newFile.createNewFile();

                    // name is correct

                    if (isFileCreated) {
                        // delete file because the file is created for checking validity
                        newFile.delete();
                    }
                    returnValue = true;

                } catch (final IOException ioe) {
                    setError(Messages.dialog_export_msg_fileNameIsInvalid);
                }

            }
        }

        _txtFilePath.setText(filePath.toOSString());

        return returnValue;
    }
}