XXX  Changed Code
XXX  Deleted Code

     New Code for sections as listed below as colored
XXX  Default setting for the number of parallel downloads - supporting XFS plugins

     Added to button to options menu and status bar, with settings in the connections tab of the preferences
XXX  Button to stop the auto-downloading of files in the queue
  Does not change the status of any files, other than those currently downloading (see below)
  Still allows the user to force downloads to start

XXX  When stopping auto downloads, any files currently downloading can:
  All continue, All stop, or Stop only if they can be resumed (default)

XXX  Auto downloading will stop when connection errors occur more than the given rate (default 3errors in <30sec)
  eg. when internet connection is lost

XXX  Download Scheduler to start and stop the download queue
  This extension will manipulate the stop auto-download button at the times provided by the user.
  The first time that will trigger the scheduler is the first time in the list. 
    eg. now=09:01, list="+09:00,-11:00" so the scheduler will trigger tomorrow and do nothing today
  When the scheduler is triggered the time is moved to the end of the list
  This allows flexibility for daily downloading timeframes to be set, or timeframes can be different every day
    eg. list="+09:00,-11:00" so every day between 9AM and 11AM downloads can start automatically
    eg. list="+09:00,-11:00,+10:00,-12:00,+11:00,-13:00" so downloads can auto start for 2 hours each day from 9AM on day1, 10AM on day2, and 11AM on day3
    eg. list="+09:00,-11:00,+11:00" so every day auto-downloads start from 9AM, will stop for a moment start again at 11AM (a scheduled change takes less than a minute to execute)
    eg. list="+09:00,-11:00,-9:01" so day1 downloads can start automatically between 9AM and 11AM, day2 no auto-downloading
  The scheduler will disable itself when it finds the letter X in the schedule list, user action needed to restart the scheduler.
    eg. list="+09:00,x,-11:00" so at 9AM auto-downloads can start and the scheduler will be disabled

XXX  Java does not always correctly read the operating system's timezone - inconveniant for the scheduler
  Easiest way to solve for all users was to allow manual correction in the options

Files Changed:
\src\cz\vity\freerapid\model\PluginMetaData.java
\src\cz\vity\freerapid\core\UserProp.java
\src\cz\vity\freerapid\gui\managers\ProcessManager.java
\src\cz\vity\freerapid\gui\actions\OptionsActions.java
\src\cz\vity\freerapid\gui\managers\MenuManager.java
\src\cz\vity\freerapid\gui\managers\StatusBarManager.java
\src\cz\vity\freerapid\swing\TrayIconSupport.java
\src\cz\vity\freerapid\core\resources\MainApp.properties
\src\cz\vity\freerapid\gui\managers\DataManager.java
\src\cz\vity\freerapid\core\tasks\DownloadTask.java
\src\cz\vity\freerapid\gui\managers\ManagerDirector.java
\src\cz\vity\freerapid\gui\dialogs\userprefs\ConnectionsTab.java	
\src\cz\vity\freerapid\gui\dialogs\userprefs\resources\UserPreferencesDialog.properties
\src\cz\vity\freerapid\gui\managers\SchedulerManager.java
\src\cz\vity\freerapid\core\resources\icons\stopsign.png

==========================================
==========================================

\src\cz\vity\freerapid\model\PluginMetaData.java
            if (maxAllowedDownloads == -1) maxAllowedDownloads = maxParallelDownloads;
            if (maxAllowedDownloads == -1) maxAllowedDownloads = DescriptorUtils.getAttribute("defaultDownloads", maxParallelDownloads, descriptor);

\src\cz\vity\freerapid\core\UserProp.java
    public static final String STOP_AUTO_QUEUE = "stopAutoQueue";
    public static final boolean STOP_AUTO_QUEUE_DEFAULT = false;

    public static final String STOP_DOWNLOADS_WITH_QUEUE = "stopDownloadsWithQueue";
    public static final boolean STOP_DOWNLOADS_WITH_QUEUE_DEFAULT = true;

    public static final String STOP_ONLY_RESUMABLE_DOWNLOADS_WITH_QUEUE = "stopOnlyResumableDownloadsWithQueue";
    public static final boolean STOP_ONLY_RESUMABLE_DOWNLOADS_WITH_QUEUE_DEFAULT = true;

    public static final String STOP_QUEUE_ERROR_MAX_COUNT = "stopQueueErrorMaxCount";
    public static final int STOP_QUEUE_ERROR_MAX_COUNT_DEFAULT = 3;

    public static final String STOP_QUEUE_ERROR_TIME_FRAME = "stopQueueErrorTimeFrame";
    public static final int STOP_QUEUE_ERROR_TIME_FRAME_DEFAULT = 30;

    public static final String SCHEDULER_ENABLED = "schedulerEnabled";
    public static final boolean SCHEDULER_ENABLED_DEFAULT = false;

    public static final String SCHEDULER_TIMES = "schedulerTimes";
    public static final String SCHEDULER_TIMES_DEFAULT = "+1:00,-5:00,+13:00,x,-17:00";

    public static final String SCHEDULER_TIMEZONE_CORRECTION = "schedulerTimezoneCorrection";
    public static final int SCHEDULER_TIMEZONE_CORRECTION_DEFAULT = 0;

\src\cz\vity\freerapid\gui\managers\ProcessManager.java
    public ProcessManager(ManagerDirector director, ApplicationContext context) {
  ...
                if (UserProp.MAX_DOWNLOADS_AT_A_TIME.equals(evt.getKey()))
                    queueUpdated();
                if (UserProp.STOP_AUTO_QUEUE.equals(evt.getKey())) {
                    if (!AppPrefs.getProperty(UserProp.STOP_AUTO_QUEUE, UserProp.STOP_AUTO_QUEUE_DEFAULT))
                        queueUpdated();
                    else
                        dataManager.stopDownloading();
                }
  ...
    private boolean canCreateAnotherConnection(final boolean forceDownload) {
        synchronized (downloadingLock) {
            final int downloading = getDownloading();
            if (downloading == ClientManager.MAX_DOWNLOADING) {
                return false;
            } else {
            if (forceDownload)
                return true;
            if (AppPrefs.getProperty(UserProp.STOP_AUTO_QUEUE, UserProp.STOP_AUTO_QUEUE_DEFAULT))
                return false;
            final int maxDownloads = Math.min(AppPrefs.getProperty(UserProp.MAX_DOWNLOADS_AT_A_TIME, UserProp.MAX_DOWNLOADS_AT_A_TIME_DEFAULT), ClientManager.MAX_DOWNLOADING);
            return maxDownloads > downloading;
            }
        }
    }

\src\cz\vity\freerapid\gui\actions\OptionsActions.java
    @Action
    public void stopAutoQueueAction() {
        AppPrefs.negateProperty(UserProp.STOP_AUTO_QUEUE, UserProp.STOP_AUTO_QUEUE_DEFAULT);
    }

\src\cz\vity\freerapid\gui\managers\MenuManager.java
        final Object[] optionsMenuActionNames = {
                "options",
                MENU_SEPARATOR,
                CHECKED + "monitorClipboardAction",
                MENU_SEPARATOR,
                CHECKED + "stopAutoQueueAction",
                MENU_SEPARATOR,
                CHECKED + "quietModeAction",
                MENU_SEPARATOR,
  ...
        PropertyConnector.connectAndUpdate(BindUtils.getPrefsValueModel(UserProp.CLIPBOARD_MONITORING, UserProp.CLIPBOARD_MONITORING_DEFAULT), map.get("monitorClipboardAction"), "selected");
        PropertyConnector.connectAndUpdate(BindUtils.getPrefsValueModel(UserProp.STOP_AUTO_QUEUE, UserProp.STOP_AUTO_QUEUE_DEFAULT), map.get("stopAutoQueueAction"), "selected");
        PropertyConnector.connectAndUpdate(BindUtils.getPrefsValueModel(UserProp.QUIET_MODE_ENABLED, UserProp.QUIET_MODE_ENABLED_DEFAULT), map.get("quietModeAction"), "selected");

\src\cz\vity\freerapid\gui\managers\StatusBarManager.java
    public JXStatusBar getStatusBar() {
  ...
            JLabel stopAutoQueue = new JLabel();
            stopAutoQueue.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    AppPrefs.negateProperty(UserProp.STOP_AUTO_QUEUE, UserProp.STOP_AUTO_QUEUE_DEFAULT);
                }
            });
  ...
            resourceMap.injectComponent(clipboardMonitoring);
            stopAutoQueue.setName("labelStopAutoQueue");
            resourceMap.injectComponent(stopAutoQueue);
            quietMode.setName("labelQuietMode");
  ...
            clipboardMonitoring.setPreferredSize(new Dimension(17, BAR_HEIGHT));
            stopAutoQueue.setPreferredSize(new Dimension(17, BAR_HEIGHT));
            quietMode.setPreferredSize(new Dimension(17, BAR_HEIGHT));
  ...
            PropertyConnector.connectAndUpdate(BindUtils.getPrefsValueModel(UserProp.CLIPBOARD_MONITORING, UserProp.CLIPBOARD_MONITORING_DEFAULT), clipboardMonitoring, "enabled");
            PropertyConnector.connectAndUpdate(BindUtils.getPrefsValueModel(UserProp.STOP_AUTO_QUEUE, UserProp.STOP_AUTO_QUEUE_DEFAULT), stopAutoQueue, "enabled");
            PropertyConnector.connectAndUpdate(BindUtils.getPrefsValueModel(UserProp.QUIET_MODE_ENABLED, UserProp.QUIET_MODE_ENABLED_DEFAULT), quietMode, "enabled");
  ...
            statusbar.add(clipboardMonitoring, JXStatusBar.Constraint.ResizeBehavior.FIXED);
            statusbar.add(stopAutoQueue, JXStatusBar.Constraint.ResizeBehavior.FIXED);
            statusbar.add(quietMode, JXStatusBar.Constraint.ResizeBehavior.FIXED);

\src\cz\vity\freerapid\swing\TrayIconSupport.java
    private PopupMenu buildPopmenu(final MainApp app, ResourceMap map) {
  ...
        final CheckboxMenuItem stopAutoQueue = new CheckboxMenuItem(map.getString("stopAutoQueueActionTray"));
        stopAutoQueue.setState(AppPrefs.getProperty(UserProp.STOP_AUTO_QUEUE, UserProp.STOP_AUTO_QUEUE_DEFAULT));
        stopAutoQueue.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                final boolean b = stopAutoQueue.getState();
                AppPrefs.storeProperty(UserProp.STOP_AUTO_QUEUE, b);
            }
        });
  ...
                } else if (UserProp.CLIPBOARD_MONITORING.equals(evt.getKey())) {
                    clipboardMonitoring.setState(AppPrefs.getProperty(UserProp.CLIPBOARD_MONITORING, UserProp.CLIPBOARD_MONITORING_DEFAULT));
                } else if (UserProp.STOP_AUTO_QUEUE.equals(evt.getKey())) {
                    stopAutoQueue.setState(AppPrefs.getProperty(UserProp.STOP_AUTO_QUEUE, UserProp.STOP_AUTO_QUEUE_DEFAULT));
                } else if (UserProp.QUIET_MODE_ENABLED.equals(evt.getKey())) {
                    quietMode.setState(QuietMode.getInstance().isEnabled());
  ...
        popup.add(clipboardMonitoring);
        popup.add(stopAutoQueue);
        popup.add(quietMode);

\src\cz\vity\freerapid\core\resources\MainApp.properties
stopAutoQueueAction.Action.text=&Stop Auto-Downloading
stopAutoQueueAction.Action.shortDescription=Toggle Auto-Downloading
stopAutoQueueAction.Action.smallIcon=${iconsPath}stopsign.png
    ...
labelStopAutoQueue.icon=${iconsPath}stopsign.png
labelStopAutoQueue.toolTipText=Stop Auto-Downloading

\src\cz\vity\freerapid\gui\managers\DataManager.java
    public void stopDownloading() {
        if (AppPrefs.getProperty(UserProp.STOP_DOWNLOADS_WITH_QUEUE, UserProp.STOP_DOWNLOADS_WITH_QUEUE_DEFAULT)) {
            final List<DownloadFile> toResumeList = new LinkedList<DownloadFile>();
            synchronized (lock) {
                for (DownloadFile file : downloadFiles) {
                    final boolean resumeCheck = AppPrefs.getProperty(UserProp.STOP_ONLY_RESUMABLE_DOWNLOADS_WITH_QUEUE, UserProp.STOP_ONLY_RESUMABLE_DOWNLOADS_WITH_QUEUE_DEFAULT);
                    if (!resumeCheck || file.isResumeSupported()) {
                        final DownloadTask task = file.getTask();
                        if (task != null && !task.isTerminated()) {
                            file.resetErrorAttempts();
                            toResumeList.add(file);
                            file.setState(PAUSED);
                            task.cancel(true);
                        }
                    }
                }
            }
            try {
                Thread.sleep(700);//time to cancel DownloadTask properly
            } catch (InterruptedException e) {
                //ignore
            }
            addToQueue(toResumeList);
        }
    }

\src\cz\vity\freerapid\core\tasks\DownloadTask.java
    private static final int connectErrorMaxCount = AppPrefs.getProperty(UserProp.STOP_QUEUE_ERROR_MAX_COUNT, UserProp.STOP_QUEUE_ERROR_MAX_COUNT_DEFAULT);
    private static final int connectErrorTimeFrame = AppPrefs.getProperty(UserProp.STOP_QUEUE_ERROR_TIME_FRAME, UserProp.STOP_QUEUE_ERROR_TIME_FRAME_DEFAULT);;
    private static int connectErrorCount = 0;
    private static long[] lastConnectErrorTime = new long[connectErrorMaxCount];

    public DownloadTask(Application application) {
  ...
        final boolean connectError = cause instanceof NoRouteToHostException || cause instanceof ConnectException || cause instanceof UnknownHostException;

        if (connectError) {    // stop the que if more than MaxCount errors within TimeFrame seconds
            final long nowSec = System.currentTimeMillis() / 1000;
            for (int iPos=0; (iPos<connectErrorMaxCount) && (lastConnectErrorTime[iPos] > 0); iPos++) {
                if ((nowSec - lastConnectErrorTime[iPos]) > connectErrorTimeFrame) {  // too long between errors
                    for (int jPos=iPos; jPos<connectErrorMaxCount-1; jPos++)
                       lastConnectErrorTime[jPos] = lastConnectErrorTime[jPos+1];
                    iPos--;
                    lastConnectErrorTime[connectErrorMaxCount-1] = 0;
                    connectErrorCount--;
                }
           }
            if (connectErrorCount == connectErrorMaxCount) {
              for (int jPos=0; jPos<connectErrorMaxCount-1; jPos++)
                  lastConnectErrorTime[jPos] = lastConnectErrorTime[jPos+1];
                connectErrorCount--;
            }
            lastConnectErrorTime[connectErrorCount] = nowSec;
            connectErrorCount++;
            if (connectErrorCount >= connectErrorMaxCount) {
                AppPrefs.storeProperty(UserProp.STOP_AUTO_QUEUE, true);
            }
        } else {
            connectErrorCount = 0;
            lastConnectErrorTime = new long[connectErrorMaxCount];
        }

\src\cz\vity\freerapid\gui\managers\ManagerDirector.java
    private SchedulerManager schedulerManager;
  ...
    public void initComponents() {
        this.schedulerManager = new SchedulerManager();
  ...
    public void guiIsReady() {
        schedulerManager.initManager();
  ...
    public SchedulerManager getSchedulerManager() {
        return schedulerManager;
    }

\src\cz\vity\freerapid\gui\dialogs\userprefs\ConnectionsTab.java
    public void init() {
        PropertyConnector.connectAndUpdate(useProxyList, actionMap.get("btnSelectProxyListAction"), "enabled");
        final ValueModel stopDownloads = bind(checkStopDownloads, UserProp.STOP_DOWNLOADS_WITH_QUEUE, UserProp.STOP_DOWNLOADS_WITH_QUEUE_DEFAULT);
        PropertyConnector.connectAndUpdate(stopDownloads, checkStopResumableDownloads, "enabled");
        bind(checkStopResumableDownloads, UserProp.STOP_ONLY_RESUMABLE_DOWNLOADS_WITH_QUEUE, UserProp.STOP_ONLY_RESUMABLE_DOWNLOADS_WITH_QUEUE_DEFAULT);
        bind(spinnerStopErrorMax, UserProp.STOP_QUEUE_ERROR_MAX_COUNT, UserProp.STOP_QUEUE_ERROR_MAX_COUNT_DEFAULT, 1, 50, 1);
        bind(spinnerStopErrorTimeFrame, UserProp.STOP_QUEUE_ERROR_TIME_FRAME, UserProp.STOP_QUEUE_ERROR_TIME_FRAME_DEFAULT, 10, 300, 10);
        bind(checkUseScheduler, UserProp.SCHEDULER_ENABLED, UserProp.SCHEDULER_ENABLED_DEFAULT);
        bind(spinnerSchedulerTimezoneCorrection, UserProp.SCHEDULER_TIMEZONE_CORRECTION, UserProp.SCHEDULER_TIMEZONE_CORRECTION_DEFAULT, -1440, 1440, 30);
  ...
        spinnerSchedulerTimezoneCorrection.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                AppPrefs.storeProperty(UserProp.SCHEDULER_TIMEZONE_CORRECTION, (Integer) spinnerSchedulerTimezoneCorrection.getValue());
                final int currentTime[] = managerDirector.getSchedulerManager().getCurrentTimeHMS();
                final String spacer = currentTime[1] < 10 ? "0" : "";
                labelCurrentTime.setText(currentTime[0]+":"+spacer+currentTime[1]);
            }
        });
  ...
        fieldSchedulerValues.setText(AppPrefs.getProperty(UserProp.SCHEDULER_TIMES, UserProp.SCHEDULER_TIMES_DEFAULT));
        fieldSchedulerValues.getDocument().addDocumentListener(new DocumentListener() {
            @Override
            public void insertUpdate(DocumentEvent e) {
                changedUpdate(e);
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                changedUpdate(e);
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                dialog.getModel().setBuffering(true);
            }
        });
  ...
    public void apply() {
        AppPrefs.storeProperty(UserProp.SPEED_LIMIT_SPEEDS, fieldFileSpeedLimiterValues.getText());
        AppPrefs.storeProperty(UserProp.SCHEDULER_TIMES, fieldSchedulerValues.getText());
  ...
    public void build(final CellConstraints cc) {
        JPanel panelFileSpeedLimiter = new JPanel();
        JPanel panelStopDownloads = new JPanel();
        JPanel panelScheduler = new JPanel();
  ...
        checkStopDownloads = new JCheckBox();
        checkStopDownloads.setName("checkStopDownloads");
        checkStopResumableDownloads = new JCheckBox();
        checkStopResumableDownloads.setName("checkStopResumableDownloads");
        JLabel labelStopErrorDesc = new JLabel();
        labelStopErrorDesc.setName("labelStopErrorDesc");
        spinnerStopErrorMax = new JSpinner();
        spinnerStopErrorMax.setModel(new SpinnerNumberModel(1, 1, 50, 1));
        spinnerStopErrorMax.setName("spinnerStopErrorMax");
        JLabel labelStopErrorMax = new JLabel();
        labelStopErrorMax.setName("labelStopErrorMax");
        spinnerStopErrorTimeFrame = new JSpinner();
        spinnerStopErrorTimeFrame.setModel(new SpinnerNumberModel(10, 10, 300, 10));
        spinnerStopErrorTimeFrame.setName("spinnerStopErrorTimeFrame");
        JLabel labelStopErrorTimeFrame = new JLabel();
        labelStopErrorTimeFrame.setName("labelStopErrorTimeFrame");

        checkUseScheduler = new JCheckBox();
        checkUseScheduler.setName("checkUseScheduler");
        JLabel labelSchedulerValues = new JLabel();
        labelSchedulerValues.setName("labelSchedulerValues");
        fieldSchedulerValues = new JTextField();
        fieldSchedulerValues.setName("fieldSchedulerValues");
        JLabel labelSchedulerValuesDesc = new JLabel();
        labelSchedulerValuesDesc.setName("labelSchedulerValuesDesc");
        JLabel labelCurrentTimeDesc = new JLabel();
        labelCurrentTimeDesc.setName("labelCurrentTimeDesc");
        labelCurrentTime = new JLabel();
        final int currentTime[] = managerDirector.getSchedulerManager().getCurrentTimeHMS();
        final String spacer = currentTime[1] < 10 ? "0" : "";
        labelCurrentTime.setText(currentTime[0]+":"+spacer+currentTime[1]);
        labelCurrentTime.setName("labelCurrentTime");
        JLabel labelSchedulerTimezoneCorrectionValue = new JLabel();
        labelSchedulerTimezoneCorrectionValue.setName("labelSchedulerTimezoneCorrectionValue");
        spinnerSchedulerTimezoneCorrection = new JSpinner();
        spinnerSchedulerTimezoneCorrection.setModel(new SpinnerNumberModel(0, -1440, 1440, 30));
        spinnerSchedulerTimezoneCorrection.setName("spinnerSchedulerTimezoneCorrection");
        JLabel labelSchedulerTimezoneCorrectionDesc = new JLabel();
        labelSchedulerTimezoneCorrectionDesc.setName("labelSchedulerTimezoneCorrectionDesc");
  ...
    //======== panelStopDownloads ========
        {
            panelStopDownloads.setBorder(new TitledBorder(null, resourceMap.getString("panelStopDownloads.border"), TitledBorder.LEADING, TitledBorder.TOP));

            PanelBuilder panelStopDownloadsBuilder = new PanelBuilder(new FormLayout(
                    new ColumnSpec[]{
                            new ColumnSpec(ColumnSpec.LEFT, Sizes.dluX(0), FormSpec.NO_GROW),
                            FormFactory.LABEL_COMPONENT_GAP_COLSPEC,
                            FormFactory.DEFAULT_COLSPEC,
                            FormFactory.LABEL_COMPONENT_GAP_COLSPEC,
                            FormFactory.DEFAULT_COLSPEC,
                            FormFactory.LABEL_COMPONENT_GAP_COLSPEC,
                            FormFactory.DEFAULT_COLSPEC,
                            FormFactory.LABEL_COMPONENT_GAP_COLSPEC,
                            FormFactory.DEFAULT_COLSPEC,
                            FormFactory.LABEL_COMPONENT_GAP_COLSPEC,
                            new ColumnSpec(ColumnSpec.LEFT, Sizes.DEFAULT, FormSpec.DEFAULT_GROW),
                            FormFactory.LABEL_COMPONENT_GAP_COLSPEC,
                            FormFactory.DEFAULT_COLSPEC
                    },
                    new RowSpec[]{
                            FormFactory.DEFAULT_ROWSPEC,
                            FormFactory.LINE_GAP_ROWSPEC,
                            FormFactory.DEFAULT_ROWSPEC,
                            FormFactory.LINE_GAP_ROWSPEC,
                            FormFactory.NARROW_LINE_GAP_ROWSPEC
                    }), panelStopDownloads);

            panelStopDownloadsBuilder.add(checkStopDownloads, cc.xyw(3, 1, 5));
            panelStopDownloadsBuilder.add(checkStopResumableDownloads, cc.xyw(9, 1, 5));
            panelStopDownloadsBuilder.add(labelStopErrorDesc, cc.xy(3, 3));
            panelStopDownloadsBuilder.add(spinnerStopErrorMax, cc.xy(5, 3));
            panelStopDownloadsBuilder.add(labelStopErrorMax, cc.xy(7, 3));
            panelStopDownloadsBuilder.add(spinnerStopErrorTimeFrame, cc.xy(9, 3));
            panelStopDownloadsBuilder.add(labelStopErrorTimeFrame, cc.xy(11, 3));
        }

        //======== panelScheduler ========
        {
            panelScheduler.setBorder(new TitledBorder(null, resourceMap.getString("panelScheduler.border"), TitledBorder.LEADING, TitledBorder.TOP));

            PanelBuilder panelSchedulerBuilder = new PanelBuilder(new FormLayout(
                    new ColumnSpec[]{
                            new ColumnSpec(ColumnSpec.LEFT, Sizes.dluX(0), FormSpec.NO_GROW),
                            FormFactory.LABEL_COMPONENT_GAP_COLSPEC,
                            FormFactory.DEFAULT_COLSPEC,
                            FormFactory.LABEL_COMPONENT_GAP_COLSPEC,
                            FormFactory.DEFAULT_COLSPEC,
                            FormFactory.LABEL_COMPONENT_GAP_COLSPEC,
                            new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, FormSpec.DEFAULT_GROW),
                            FormFactory.LABEL_COMPONENT_GAP_COLSPEC,
                            FormFactory.DEFAULT_COLSPEC
                    },
                    new RowSpec[]{
                            FormFactory.DEFAULT_ROWSPEC,
                            FormFactory.LINE_GAP_ROWSPEC,
                            FormFactory.DEFAULT_ROWSPEC,
                            FormFactory.LINE_GAP_ROWSPEC,
                            FormFactory.DEFAULT_ROWSPEC,
                            FormFactory.LINE_GAP_ROWSPEC,
                            FormFactory.DEFAULT_ROWSPEC,
                            FormFactory.LINE_GAP_ROWSPEC,
                            FormFactory.DEFAULT_ROWSPEC,
                            FormFactory.LINE_GAP_ROWSPEC,
                            FormFactory.DEFAULT_ROWSPEC,
                            FormFactory.LINE_GAP_ROWSPEC,
                            FormFactory.NARROW_LINE_GAP_ROWSPEC
                    }), panelScheduler);

            panelSchedulerBuilder.add(checkUseScheduler, cc.xyw(3, 1, 7));
            panelSchedulerBuilder.add(labelSchedulerValues, cc.xyw(3, 3, 7));
            panelSchedulerBuilder.add(fieldSchedulerValues, cc.xyw(3, 5, 7));
            panelSchedulerBuilder.add(labelSchedulerValuesDesc, cc.xyw(3, 7, 7));

            panelSchedulerBuilder.add(labelCurrentTimeDesc, cc.xy(3, 9));
            panelSchedulerBuilder.add(labelCurrentTime, cc.xy(5, 9));
            panelSchedulerBuilder.add(labelSchedulerTimezoneCorrectionValue, cc.xy(3, 11));
            panelSchedulerBuilder.add(spinnerSchedulerTimezoneCorrection, cc.xy(5, 11));
            panelSchedulerBuilder.add(labelSchedulerTimezoneCorrectionDesc, cc.xy(7, 11));
        }
  ...
        PanelBuilder thisBuilder = new PanelBuilder(new FormLayout(
                new ColumnSpec[]{
                        new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, FormSpec.DEFAULT_GROW),
                        FormFactory.LABEL_COMPONENT_GAP_COLSPEC,
                        new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, FormSpec.DEFAULT_GROW)
                },
                new RowSpec[]{
                        new RowSpec(RowSpec.FILL, Sizes.DEFAULT, FormSpec.NO_GROW),
                        FormFactory.RELATED_GAP_ROWSPEC,
                        new RowSpec(RowSpec.FILL, Sizes.DEFAULT, FormSpec.NO_GROW),
                        FormFactory.LINE_GAP_ROWSPEC,
                        FormFactory.DEFAULT_ROWSPEC,
                        FormFactory.LINE_GAP_ROWSPEC,
                        FormFactory.DEFAULT_ROWSPEC,
  ...
        thisBuilder.add(panelFileSpeedLimiter, cc.xyw(3, 7, 1));
        thisBuilder.add(panelStopDownloads, cc.xyw(1, 9, 3));
        thisBuilder.add(panelScheduler, cc.xyw(1, 11, 3));
        thisBuilder.add(labelRequiresRestart, cc.xyw(1, 9, 3));
        thisBuilder.add(labelRequiresRestart, cc.xyw(1, 13, 3));
  ...
    private JTextField fieldFileSpeedLimiterValues;
    private JCheckBox checkStopDownloads;
    private JCheckBox checkStopResumableDownloads;
    private JSpinner spinnerStopErrorMax;
    private JSpinner spinnerStopErrorTimeFrame;
    private JCheckBox checkUseScheduler;
    private JTextField fieldSchedulerValues;
    private JLabel labelCurrentTime;
    private JSpinner spinnerSchedulerTimezoneCorrection;

\src\cz\vity\freerapid\gui\dialogs\userprefs\resources\UserPreferencesDialog.properties
panelStopDownloads.border=Stop auto-downloading
checkStopDownloads.text=Stop files currently downloading
checkStopResumableDownloads.text=Stop only the files that can be resumed
labelStopErrorDesc.text=Stop auto-downloading if
labelStopErrorMax.text=Connection errors occur within
labelStopErrorTimeFrame.text=Seconds
panelScheduler.border=Download scheduler
checkUseScheduler.text=Enable download scheduler
labelSchedulerValues.text=Values for download scheduler ([+/-]HH:MM)
fieldSchedulerValues.toolTipText=Separate values with commas.  Scheduler processes from left to right (First value is the next scheduled event).
labelSchedulerValuesDesc.text=<html><small>Separate values with commas.  Scheduler processes from left to right (First value is the next scheduled event).<br/>'+HH:MM' Starts downloads at the given time.  '-HH:MM' Stops downloads at the given time.  'x' Disables the scheduler  </small></html>
labelCurrentTimeDesc.text=Java's current time
labelSchedulerTimezoneCorrectionValue.text=Timezone correction
labelSchedulerTimezoneCorrectionDesc.text=minutes(+/-). Correct Java's time to your local time.
labelCurrentTimeDesc.toolTipText=Java does not always correctly read the operating system's timezone
labelCurrentTime.toolTipText=Java does not always correctly read the operating system's timezone
labelSchedulerTimezoneCorrectionValue.toolTipText=Java does not always correctly read the operating system's timezone
labelSchedulerTimezoneCorrectionDesc.toolTipText=Java does not always correctly read the operating system's timezone
spinnerSchedulerTimezoneCorrection.toolTipText=Java does not always correctly read the operating system's timezone