/*
 *  Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
 *  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 *  This code is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License version 2 only, as
 *  published by the Free Software Foundation.  Oracle designates this
 *  particular file as subject to the "Classpath" exception as provided
 *  by Oracle in the LICENSE file that accompanied this code.
 * 
 *  This code 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
 *  version 2 for more details (a copy is included in the LICENSE file that
 *  accompanied this code).
 * 
 *  You should have received a copy of the GNU General Public License version
 *  2 along with this work; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 *  Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 *  or visit www.oracle.com if you need additional information or have any
 *  questions.
 */

package org.graalvm.visualvm.modules.tracer.impl;

import org.graalvm.visualvm.core.datasource.descriptor.DataSourceDescriptorFactory;
import org.graalvm.visualvm.modules.tracer.impl.swing.CustomComboRenderer;
import org.graalvm.visualvm.core.ui.DataSourceView;
import org.graalvm.visualvm.core.ui.components.DataViewComponent;
import org.graalvm.visualvm.modules.tracer.TracerProbe;
import org.graalvm.visualvm.modules.tracer.TracerProgressObject;
import org.graalvm.visualvm.modules.tracer.impl.options.TracerOptions;
import org.graalvm.visualvm.modules.tracer.impl.swing.DropdownButton;
import org.graalvm.visualvm.modules.tracer.impl.swing.SimpleSeparator;
import org.graalvm.visualvm.modules.tracer.impl.swing.VisibilityHandler;
import org.graalvm.visualvm.modules.tracer.impl.timeline.TimelineSupport;
import org.graalvm.visualvm.uisupport.HorizontalLayout;
import org.graalvm.visualvm.uisupport.TransparentToolBar;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Date;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import org.openide.util.ImageUtilities;
import org.openide.util.RequestProcessor;

/**
 *
 * @author Jiri Sedlacek
 */
final class TracerView extends DataSourceView {

    private static final int INDETERMINATE_PROGRESS_THRESHOLD =
                Integer.getInteger("visualvm.tracer.indeterminateProgressThreshold", 2500); // NOI18N

    private static final String IMAGE_PATH =
            "org/graalvm/visualvm/modules/tracer/impl/resources/tracer.png"; // NOI18N

    private final TracerModel model;
    private final TracerController controller;

    private DataViewComponent dvc;
    private TimelineView timelineView;
    private DetailsView detailsView;

    
    TracerView(TracerModel model, TracerController controller) {
        super(model.getDataSource(), "Tracer", new ImageIcon(
              ImageUtilities.loadImage(IMAGE_PATH, true)).getImage(), 100, false);
        this.model = model;
        this.controller = controller;
    }


    // --- DataSourceView implementation ---------------------------------------

    protected DataViewComponent createComponent() {
        PackagesView packagesView = new PackagesView(model, controller);
        timelineView = new TimelineView(model);
        detailsView = new DetailsView(model);
        MasterViewSupport masterView = new MasterViewSupport();
        
        dvc = new DataViewComponent(masterView.getView(),
                new DataViewComponent.MasterViewConfiguration(false));

        dvc.configureDetailsView(new DataViewComponent.DetailsViewConfiguration(0.33, 0, 0.33, 0, 0.5, 0.5));

        String initiallyOpened = TracerOptions.getInstance().getInitiallyOpened();

        
        dvc.configureDetailsArea(new DataViewComponent.DetailsAreaConfiguration("Probes", true), DataViewComponent.TOP_LEFT);
        dvc.addDetailsView(packagesView.getView(), DataViewComponent.TOP_LEFT);
        if (!initiallyOpened.contains(TracerOptions.VIEW_PROBES))
            dvc.hideDetailsArea(DataViewComponent.TOP_LEFT);

        dvc.configureDetailsArea(new DataViewComponent.DetailsAreaConfiguration("Timeline", true), DataViewComponent.TOP_RIGHT);
        dvc.addDetailsView(timelineView.getView(), DataViewComponent.TOP_RIGHT);
        if (!initiallyOpened.contains(TracerOptions.VIEW_TIMELINE))
            dvc.hideDetailsArea(DataViewComponent.TOP_RIGHT);

        dvc.configureDetailsArea(new DataViewComponent.DetailsAreaConfiguration("Details", true), DataViewComponent.BOTTOM_RIGHT);
        dvc.addDetailsView(detailsView.getView(), DataViewComponent.BOTTOM_RIGHT);
        if (!initiallyOpened.contains(TracerOptions.VIEW_DETAILS))
            dvc.hideDetailsArea(DataViewComponent.BOTTOM_RIGHT);

        return dvc;
    }


    // --- Master view implementation ------------------------------------------

    private class MasterViewSupport {

        private static final String START_IMAGE_PATH =
            "org/graalvm/visualvm/modules/tracer/impl/resources/start.png"; // NOI18N
        private static final String STOP_IMAGE_PATH =
            "org/graalvm/visualvm/modules/tracer/impl/resources/stop.png"; // NOI18N
        private static final String ERROR_IMAGE_PATH =
            "org/graalvm/visualvm/modules/tracer/impl/resources/error.png"; // NOI18N
        private static final String SETTINGS_IMAGE_PATH =
            "org/graalvm/visualvm/modules/tracer/impl/resources/settings.png"; // NOI18N
        private static final String CLPROBE_IMAGE_PATH =
            "org/graalvm/visualvm/modules/tracer/impl/resources/probeClear.png"; // NOI18N
        private static final String CLMARK_IMAGE_PATH =
            "org/graalvm/visualvm/modules/tracer/impl/resources/markClear.png"; // NOI18N
        private static final String EXPORT_IMAGE_PATH =
            "org/graalvm/visualvm/modules/tracer/impl/resources/export.png"; // NOI18N

        private static final String SYSTEM_TOOLBAR = "systemToolbar"; // NOI18N
        private static final String CLIENT_TOOLBAR = "clientToolbar"; // NOI18N

        private int commonControlHeight;

        private AbstractButton startButton;
        private AbstractButton stopButton;

        private CardLayout toolbarLayout;
        private JPanel toolbar;
        private JPanel systemToolbar;
        private JPanel clientToolbar;

        private TransparentToolBar timelineToolbar;
        private TransparentToolBar selectionToolbar;
        private TransparentToolBar extraToolbar;

        private JButton clearRowSelectionButton;
        private JButton clearTimestampSelectionButton;

        private Action exportAllAction;
        private Action exportDetailsAction;


        DataViewComponent.MasterView getView() {
            JComponent view = createComponents();
            initListeners();
            refreshState(model.areProbesDefined());

            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    Dimension size = new Dimension(commonControlHeight, commonControlHeight);
                    createTimelineToolbar(size);
                    createSelectionToolbar(size);
                    createExtraToolbar(size);
                }
            });

            return new DataViewComponent.MasterView("Tracer", null, view);
        }


        private void refreshState(boolean probesDefined) {
            switch (controller.getState()) {
                case TracerController.STATE_SESSION_RUNNING:
                    startButton.setEnabled(false);
                    startButton.setSelected(false);
                    stopButton.setEnabled(true);
                    stopButton.requestFocusInWindow();
                    resetSystemToolbar();
                    updateViewsOnSessionStart();
                    break;
                case TracerController.STATE_SESSION_INACTIVE:
                    startButton.setEnabled(probesDefined);
                    startButton.setSelected(false);
                    stopButton.setEnabled(false);
                    if (startButton.isEnabled() && KeyboardFocusManager.
                        getCurrentKeyboardFocusManager().getFocusOwner() == null)
                        startButton.requestFocusInWindow();
                    break;
                case TracerController.STATE_SESSION_IMPOSSIBLE:
                    startButton.setEnabled(false);
                    stopButton.setEnabled(false);
                    break;
                case TracerController.STATE_SESSION_STARTING:
                    startButton.setEnabled(false);
                    stopButton.setEnabled(false);
                    startButton.setFocusable(false);
                    startButton.setFocusable(true);
                    showProgress();
                    timelineView.reset();
                    break;
                case TracerController.STATE_SESSION_STOPPING:
                    startButton.setEnabled(false);
                    startButton.setSelected(false);
                    stopButton.setEnabled(false);
                    stopButton.setFocusable(false);
                    stopButton.setFocusable(true);
                    String error = controller.getErrorMessage();
                    if (error != null) showMessage(error);
                    break;
            }
        }

        private void initListeners() {
            controller.addListener(new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent evt) {
                    refreshState(model.areProbesDefined());
                }
            });

            model.addListener(new TracerModel.Listener() {
                public void probeAdded(TracerProbe probe) {
                    refreshState(true);
                    updateViewsOnProbesChange(true);
                }
                public void probeRemoved(TracerProbe probe, boolean probesDefined) {
                    refreshState(probesDefined);
                    updateViewsOnProbesChange(probesDefined);
                }
            });

            final boolean dynamicSelection = TracerOptions.getInstance().getSelectionToolbar() == TracerOptions.SHOW_AS_NEEDED;
            model.getTimelineSupport().addSelectionListener(
                    new TimelineSupport.SelectionListener() {
                public void rowSelectionChanged(boolean rowsSelected) {
                    updateViewsOnSelectionChange(rowsSelected);
                    updateSelectionToolbar(dynamicSelection);
                }
                public void timeSelectionChanged(boolean timestampsSelected, boolean justHovering) {
                    updateSelectionToolbar(dynamicSelection);
                }
            });

            if (TracerOptions.getInstance().getTimelineToolbar() == TracerOptions.SHOW_AS_NEEDED)
                timelineView.registerViewListener(new VisibilityHandler() {
                    public void shown()  { showTimelineToolbar(); }
                    public void hidden() { hideTimelineToolbar(); }
                });
            
            detailsView.registerViewListener(new VisibilityHandler() {
                public void shown()  {}
                public void hidden() {
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            // Doesn't clear the selection when switching tabs and
                            // manipulating topcomponents
                            if (!detailsView.isShowing() && timelineView.isShowing())
                                clearSelections();
                        }
                    });
                }
            });

            if (TracerOptions.getInstance().getExtraToolbar() == TracerOptions.SHOW_AS_NEEDED)
                model.getTimelineSupport().addValuesListener(new TimelineSupport.ValuesListener() {
                    public void valuesAdded() { showExtraToolbar(); }
                    public void valuesReset() { hideExtraToolbar(); }
                });
        }

        private void clearSelections() {
            if (TracerOptions.getInstance().isClearSelection()) {
                timelineView.resetSelection();
//                model.getTimelineSupport().resetSelectedTimestamps();
            }
        }

        private void updateViewsOnSessionStart() {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    String views = TracerOptions.getInstance().getOnSessionStart();
                    updateViews(views);
                }
            });
        }

        private void updateViewsOnProbesChange(boolean probesDefined) {
            String views = probesDefined ?
                TracerOptions.getInstance().getOnProbeAdded() :
                TracerOptions.getInstance().getOnProbeAdded2();
            updateViews(views);
        }

        private void updateViewsOnSelectionChange(boolean rowsSelected) {
            String views = rowsSelected ?
                TracerOptions.getInstance().getOnRowSelected() :
                TracerOptions.getInstance().getOnRowSelected2();
            updateViews(views);
        }

        private void updateViews(String views) {
            if (!views.equals(TracerOptions.VIEWS_UNCHANGED)) {
                // Probes
                setProbesVisible(views.contains(TracerOptions.VIEW_PROBES));
                // Timeline
                setTimelineVisible(views.contains(TracerOptions.VIEW_TIMELINE));
                // Details
                setDetailsVisible(views.contains(TracerOptions.VIEW_DETAILS));
            }
        }

        private void setProbesVisible(boolean visible) {
            if (visible) dvc.showDetailsArea(DataViewComponent.TOP_LEFT);
            else dvc.hideDetailsArea(DataViewComponent.TOP_LEFT);
        }

        private void setTimelineVisible(boolean visible) {
            if (visible) dvc.showDetailsArea(DataViewComponent.TOP_RIGHT);
            else dvc.hideDetailsArea(DataViewComponent.TOP_RIGHT);
        }

        private void setDetailsVisible(boolean visible) {
            if (visible) dvc.showDetailsArea(DataViewComponent.BOTTOM_RIGHT);
            else dvc.hideDetailsArea(DataViewComponent.BOTTOM_RIGHT);
        }

        private void showMessage(String text) {
            setSystemToolbarItem(new JLabel(text, new ImageIcon(ImageUtilities.
                                 loadImage(ERROR_IMAGE_PATH)), JLabel.CENTER));
        }

        private void showProgress() {
            final TracerProgressObject progress = controller.getProgress();
            if (progress != null) {
                final JProgressBar p = new JProgressBar(0, progress.getSteps());
                p.setPreferredSize(new Dimension(120, p.getPreferredSize().height + 2));
                p.setBorder(BorderFactory.createEmptyBorder());
                String text = progress.getText();
                final JLabel l = new JLabel(text == null ? "" : text); // NOI18N
                l.setBorder(BorderFactory.createEmptyBorder(0, 7, 0, 0));
                p.setValue(progress.getCurrentStep());
                final Timer t = new Timer(INDETERMINATE_PROGRESS_THRESHOLD, null);
                t.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        p.setIndeterminate(true);
                        t.stop();
                    }
                });
                progress.addListener(new TracerProgressObject.Listener() {
                    public void progressChanged(int addedSteps, int currentStep, String text) {
                        t.stop();
                        p.setIndeterminate(false);
                        p.setValue(currentStep);
                        l.setText(text == null ? "" : text); // NOI18N
                        if (!progress.isFinished()) t.start();
                    }
                });
                JLabel s = new JLabel("Starting:");
                s.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 4));
                JPanel container = new JPanel(new HorizontalLayout(true));
                container.setOpaque(false);
                container.add(s);
                container.add(p);
                container.add(l);
                setSystemToolbarItem(container);
            }
        }

        private void setSystemToolbarItem(Component c) {
            clearSystemToolbar();
            systemToolbar.add(c);
            systemToolbar.revalidate();
            systemToolbar.repaint();
            toolbarLayout.show(toolbar, SYSTEM_TOOLBAR);
        }

        private void resetSystemToolbar() {
            toolbarLayout.show(toolbar, CLIENT_TOOLBAR);
            clearSystemToolbar();
        }

        private void clearSystemToolbar() {
            while (systemToolbar.getComponentCount() > 1)
                systemToolbar.remove(systemToolbar.getComponentCount() - 1);
            systemToolbar.revalidate();
            systemToolbar.repaint();
        }


        private void createTimelineToolbar(Dimension size) {
            timelineToolbar = new TransparentToolBar();

            JButton c1 = new JButton(timelineView.zoomInAction());
            c1.setMinimumSize(size);
            c1.setPreferredSize(size);
            c1.setMaximumSize(size);
            timelineToolbar.addItem(c1);

            JButton c2 = new JButton(timelineView.zoomOutAction());
            c2.setMinimumSize(size);
            c2.setPreferredSize(size);
            c2.setMaximumSize(size);
            timelineToolbar.addItem(c2);

            JButton c3 = new JButton(timelineView.toggleViewAction());
            c3.setMinimumSize(size);
            c3.setPreferredSize(size);
            c3.setMaximumSize(size);
            timelineToolbar.addItem(c3);

            JPanel sp1 = new JPanel(null) {
                public Dimension getPreferredSize() {
                    Dimension d = super.getPreferredSize();
                    d.width = 14;
                    return d;
                }
            };
            timelineToolbar.addItem(sp1);

            ButtonGroup bg = new ButtonGroup();

            AbstractButton b1 = timelineView.mouseZoom();
            bg.add(b1);
            b1.setMinimumSize(size);
            b1.setPreferredSize(size);
            b1.setMaximumSize(size);
            timelineToolbar.addItem(b1);

            AbstractButton b2 = timelineView.mouseHScroll();
            bg.add(b2);
            b2.setMinimumSize(size);
            b2.setPreferredSize(size);
            b2.setMaximumSize(size);
            timelineToolbar.addItem(b2);

            AbstractButton b3 = timelineView.mouseVScroll();
            bg.add(b3);
            b3.setMinimumSize(size);
            b3.setPreferredSize(size);
            b3.setMaximumSize(size);
            timelineToolbar.addItem(b3);

            JPanel sp2 = new JPanel(null) {
                public Dimension getPreferredSize() {
                    Dimension d = super.getPreferredSize();
                    d.width = 14;
                    return d;
                }
            };
            timelineToolbar.addItem(sp2);

            DropdownButton d = new DropdownButton(new ImageIcon(
                    ImageUtilities.loadImage(SETTINGS_IMAGE_PATH)));
            d.setToolTipText("Settings");
            Action action21 = new AbstractAction("Show min/max values") {
                public Object getValue(String key) {
                    if (DropdownButton.KEY_BOOLVALUE.equals(key)) {
                        putValue(DropdownButton.KEY_BOOLVALUE, model.
                                 getTimelineSupport().isShowValuesEnabled());
                    }
                    return super.getValue(key);
                }
                public void actionPerformed(ActionEvent e) {
                    model.getTimelineSupport().setShowValuesEnabled(!model.
                            getTimelineSupport().isShowValuesEnabled());
                }
            };
            action21.putValue(DropdownButton.KEY_CLASS, Boolean.class);
            d.addAction(action21);

            Action action22 = new AbstractAction("Show row legend") {
                public Object getValue(String key) {
                    if (DropdownButton.KEY_BOOLVALUE.equals(key)) {
                        putValue(DropdownButton.KEY_BOOLVALUE, model.
                                 getTimelineSupport().isShowLegendEnabled());
                    }
                    return super.getValue(key);
                }
                public void actionPerformed(ActionEvent e) {
                    model.getTimelineSupport().setShowLegendEnabled(!model.
                            getTimelineSupport().isShowLegendEnabled());
                }
            };
            action22.putValue(DropdownButton.KEY_CLASS, Boolean.class);
            d.addAction(action22);

            d.setMinimumSize(size);
            d.setPreferredSize(size);
            d.setMaximumSize(size);
            timelineToolbar.addItem(d);

            TracerOptions options = TracerOptions.getInstance();
            int tbVis = options.getTimelineToolbar();
            if (tbVis == TracerOptions.SHOW_NEVER)
                timelineToolbar.setVisible(false);
            else if (tbVis == TracerOptions.SHOW_AS_NEEDED)
                timelineToolbar.setVisible(timelineView.isShowing());
            
            addClientToobarItem(timelineToolbar);
        }

        private void hideTimelineToolbar() {
            if (timelineToolbar != null) timelineToolbar.setVisible(false);
        }

        private void showTimelineToolbar() {
            if (timelineToolbar != null) timelineToolbar.setVisible(true);
        }


        private void createSelectionToolbar(Dimension size) {
            selectionToolbar = new TransparentToolBar();
            final TimelineSupport support = model.getTimelineSupport();

            clearRowSelectionButton = new JButton(new ImageIcon(
                    ImageUtilities.loadImage(CLPROBE_IMAGE_PATH))) {
                protected void fireActionPerformed(ActionEvent e) {
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            timelineView.resetSelection();
                        }
                    });
                }
            };
            clearRowSelectionButton.setToolTipText("Clear selected probes");
            clearRowSelectionButton.setMinimumSize(size);
            clearRowSelectionButton.setPreferredSize(size);
            clearRowSelectionButton.setMaximumSize(size);
            selectionToolbar.addItem(clearRowSelectionButton);

            clearTimestampSelectionButton = new JButton(new ImageIcon(
                    ImageUtilities.loadImage(CLMARK_IMAGE_PATH))) {
                protected void fireActionPerformed(ActionEvent e) {
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            support.resetSelectedTimestamps();
                        }
                    });
                }
            };
            clearTimestampSelectionButton.setToolTipText("Clear marks");
            clearTimestampSelectionButton.setMinimumSize(size);
            clearTimestampSelectionButton.setPreferredSize(size);
            clearTimestampSelectionButton.setMaximumSize(size);
            selectionToolbar.addItem(clearTimestampSelectionButton);

            TracerOptions options = TracerOptions.getInstance();
            int tbVis = options.getSelectionToolbar();
            if (tbVis == TracerOptions.SHOW_AS_NEEDED) {
                updateSelectionToolbar(true);
            } else {
                if (tbVis == TracerOptions.SHOW_NEVER)
                    selectionToolbar.setVisible(false);
                updateSelectionToolbar(false);
            }

            addClientToobarItem(selectionToolbar);
        }

        private void updateSelectionToolbar(boolean dynamicSelection) {
            TimelineSupport support = model.getTimelineSupport();
            boolean rowSelection = support.isRowSelection();
            boolean timestampSelection = support.isTimestampSelection(false);
            clearRowSelectionButton.setEnabled(rowSelection);
            clearTimestampSelectionButton.setEnabled(timestampSelection);
            if (dynamicSelection)
                selectionToolbar.setVisible(rowSelection || timestampSelection);
        }


        private void createExtraToolbar(Dimension size) {
            extraToolbar = new TransparentToolBar();

            DropdownButton d = new DropdownButton(new ImageIcon(
                    ImageUtilities.loadImage(EXPORT_IMAGE_PATH)));
            d.setToolTipText("Export data");
            exportAllAction = new AbstractAction("Export all data") {
                public void actionPerformed(ActionEvent e) {
                    String title = DataSourceDescriptorFactory.
                                   getDescriptor(model.getDataSource()).getName();
                    title = "Exported Tracer Data for " + title + " at " +
                            new Date(System.currentTimeMillis()).toString();
                    model.getTimelineSupport().exportAllValues(title);
                }
                public boolean isEnabled() {
                    return model.getTimelineSupport().hasData();
                }
            };
            d.addAction(exportAllAction);

            exportDetailsAction = new AbstractAction("Export Details table") {
                public void actionPerformed(ActionEvent e) {
                    String title = DataSourceDescriptorFactory.
                                   getDescriptor(model.getDataSource()).getName();
                    title = "Exported Tracer Details for " + title + " at " +
                            new Date(System.currentTimeMillis()).toString();
                    model.getTimelineSupport().exportDetailsValues(title);
                }
                public boolean isEnabled() {
                    return detailsView.hasData();
                }
            };
            d.addAction(exportDetailsAction);

            d.setMinimumSize(size);
            d.setPreferredSize(size);
            d.setMaximumSize(size);
            extraToolbar.addItem(d);

            TracerOptions options = TracerOptions.getInstance();
            int tbVis = options.getExtraToolbar();
            if (tbVis == TracerOptions.SHOW_NEVER)
                extraToolbar.setVisible(false);
            else if (tbVis == TracerOptions.SHOW_AS_NEEDED)
                extraToolbar.setVisible(exportAllAction.isEnabled());

            addClientToobarItem(extraToolbar);
        }

        private void hideExtraToolbar() {
            if (extraToolbar != null) extraToolbar.setVisible(false);
        }

        private void showExtraToolbar() {
            if (extraToolbar != null) extraToolbar.setVisible(true);
        }

        private void addClientToobarItem(Component c) {
            final SimpleSeparator s = createToolbarSeparator();
            new VisibilityHandler() {
                public void shown() { s.setVisible(true); }
                public void hidden() { s.setVisible(false); }
            }.handle(c);
            s.setVisible(c.isShowing());
            clientToolbar.add(s);
            clientToolbar.add(c);
            clientToolbar.revalidate();
            clientToolbar.repaint();
        }

        private void removeClientToolbarItem(Component c) {
            int index = -1;
            for (int i = 0; i < clientToolbar.getComponentCount(); i++)
                if (clientToolbar.getComponent(i) == c) {
                    index = i;
                    break;
                }
            if (index != -1) {
                clientToolbar.remove(index);
                clientToolbar.remove(index - 1); // separator
                clientToolbar.revalidate();
                clientToolbar.repaint();
            }
        }

        private SimpleSeparator createToolbarSeparator() {
            SimpleSeparator separator = new SimpleSeparator(SwingConstants.VERTICAL);
            separator.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8));
            Dimension dim = separator.getPreferredSize();
            dim.height = commonControlHeight;
            separator.setPreferredSize(dim);
            return separator;
        }

        private JComponent createComponents() {
            final int refHeight = new TransparentToolBar().getPreferredSize().height;
            JPanel view = new JPanel(new HorizontalLayout(true)) {
                public Dimension getPreferredSize() {
                    Dimension dim = super.getPreferredSize();
                    Insets i = getInsets();
                    dim.height = Math.max(dim.height, refHeight + i.top + i.bottom + 4);
                    return dim;
                }
            };
            view.setBorder(BorderFactory.createEmptyBorder(10, 8, 10, 8));
            view.setOpaque(false);

            startButton = new JToggleButton("Start", new ImageIcon(ImageUtilities.
                                                     loadImage(START_IMAGE_PATH))) {
                protected void fireActionPerformed(ActionEvent e) {
                    RequestProcessor.getDefault().post(new Runnable() {
                        public void run() { controller.startSession(); }
                    });
                }
            };
            startButton.setToolTipText("Starts new Tracer session");
            Insets i = startButton.getMargin();
            i.left = Math.min(i.left, 10);
            i.right = i.left;
            startButton.setMargin(i);
            view.add(startButton);

            JPanel buttonGap = new JPanel(null);
            buttonGap.setOpaque(false);
            buttonGap.setPreferredSize(new Dimension(6, 1));
            view.add(buttonGap);

            stopButton = new JButton("Stop", new ImageIcon(ImageUtilities.
                                             loadImage(STOP_IMAGE_PATH))) {
                protected void fireActionPerformed(ActionEvent e) {
                    startButton.requestFocusInWindow();
                    RequestProcessor.getDefault().post(new Runnable() {
                        public void run() { controller.stopSession(); }
                    });
                }
            };
            stopButton.setToolTipText("Stops current Tracer session");
            ((JButton)stopButton).setDefaultCapable(false);
            stopButton.setMargin(i);
            view.add(stopButton);

            Dimension size1 = startButton.getPreferredSize();
            commonControlHeight = size1.height;

            Dimension size2 = stopButton.getPreferredSize();
            commonControlHeight = Math.max(commonControlHeight, size2.height);

            Action tmpAction = new AbstractAction(null, startButton.getIcon()) {
                public void actionPerformed(ActionEvent e) {}
            };
            commonControlHeight = Math.max(commonControlHeight, new JToolBar().
                                           add(tmpAction).getPreferredSize().height);

            size1.height = commonControlHeight;
            startButton.setMinimumSize(size1);
            startButton.setPreferredSize(size1);
            startButton.setMaximumSize(size1);

            size2.height = commonControlHeight;
            stopButton.setMinimumSize(size2);
            stopButton.setPreferredSize(size2);
            stopButton.setMaximumSize(size2);

            toolbarLayout = new CardLayout(0, 0);
            toolbar = new JPanel(toolbarLayout);
            toolbar.setOpaque(false);

            systemToolbar = new JPanel(new HorizontalLayout(false));
            systemToolbar.setOpaque(false);
            systemToolbar.add(createToolbarSeparator());
            toolbar.add(systemToolbar, SYSTEM_TOOLBAR);

            clientToolbar = new JPanel(new HorizontalLayout(false));
            clientToolbar.setOpaque(false);
            toolbar.add(clientToolbar, CLIENT_TOOLBAR);

            toolbarLayout.show(toolbar, CLIENT_TOOLBAR);
            view.add(toolbar);

            if (TracerOptions.getInstance().isRefreshCustomizable()) {
                JPanel refreshRateContainer = new JPanel(new HorizontalLayout(true, 4));
                refreshRateContainer.setOpaque(false);

                JLabel refreshRateLabel = new JLabel("Sample:");
                refreshRateLabel.setToolTipText("Tracer sampling frequency");
                refreshRateContainer.add(refreshRateLabel);

                Integer[] refreshRates = new Integer[] { 100, 200, 500, 1000, 2000, 5000, 10000 };
                final JComboBox refreshCombo = new JComboBox(refreshRates) {
                    public Dimension getMinimumSize() { return getPreferredSize(); }
                    public Dimension getMaximumSize() { return getPreferredSize(); }
                };
                refreshCombo.setToolTipText("Tracer sampling frequency");
                refreshCombo.setEditable(false);
                refreshCombo.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        SwingUtilities.invokeLater(new Runnable() {
                            public void run() {
                                controller.setRefreshRate(
                                        (Integer)refreshCombo.getSelectedItem());
                            }
                        });
                    }
                });
                refreshCombo.setSelectedItem(Integer.valueOf(controller.getRefreshRate()));
                refreshCombo.setRenderer(new CustomComboRenderer.Number(refreshCombo, null, false));
                refreshRateContainer.add(refreshCombo);

                JLabel refreshUnitsLabel = new JLabel("ms");
                refreshUnitsLabel.setToolTipText("Tracer sampling frequency");
                refreshRateContainer.add(refreshUnitsLabel);

                addClientToobarItem(refreshRateContainer);
            }

            return view;
        }

    }

}
