/*
 * MarkerBarDemo.java
 *
 * Copyright (C) 2009 Side of Software (SOS)
 * All rights reserved.
 *
 *    http://www.sideofsoftware.com
 *    info@sideofsoftware.com
 */

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.table.DefaultTableModel;
import javax.swing.text.*;
import sos.marker.*;
import java.util.List;
import javax.swing.event.DocumentListener;

/**
 * A sample application that demonstrates the use and features of Side of Software's
 * Marker Bar Library.<p>
 *
 * @author Side of Software
 */
public class MarkerBarDemo extends JFrame
{
  // when we draw the circles, turn anti-aliasing on
  static final Map<RenderingHints.Key,Object> hints = new HashMap<RenderingHints.Key,Object>();

  static {
    hints.put( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
  }

  // the color of the search matches in the text pane
  private static final Color HIGHLIGHT_COLOR = Color.ORANGE;

  private MarkerBarDemo() throws Exception
  {
    // create a map of some sample borders and give them names
    final Map<String,Border> borders = new LinkedHashMap<String,Border>();
    borders.put( "None", null );
    borders.put( "Etched Raised", BorderFactory.createEtchedBorder( EtchedBorder.RAISED ));
    borders.put( "Etched Lowered", BorderFactory.createEtchedBorder( EtchedBorder.LOWERED ));
    borders.put( "Line", BorderFactory.createLineBorder( Color.BLACK ));
    borders.put( "Top/Bottom", BorderFactory.createMatteBorder( 1, 0, 1, 0, Color.BLACK ));
    borders.put( "Sides", BorderFactory.createMatteBorder( 0, 1, 0, 1, Color.BLACK ));
    Object[] bordersArray = borders.keySet().toArray();

    // create a map of some sample renderers and give them names
    final Map<String,MarkerRenderer> renderers = new LinkedHashMap<String,MarkerRenderer>();
    MarkerRenderer markerRenderer = new DefaultMarkerRenderer();
    renderers.put( "Rectangle", markerRenderer );
    renderers.put( "Circle", new CircleMarkerRenderer() );
    Object[] renderersArray = renderers.keySet().toArray();

    // create a map of some sample colors and give them names
    final Map<String,Color> colors = new LinkedHashMap<String,Color>();
    colors.put( "None", null );
    colors.put( "Black", Color.BLACK );
    colors.put( "Blue", Color.BLUE );
    colors.put( "Cyan", Color.CYAN );
    colors.put( "Gray", Color.GRAY );
    colors.put( "Green", Color.GREEN );
    colors.put( "Light Gray", Color.LIGHT_GRAY );
    colors.put( "Magenta", Color.MAGENTA );
    colors.put( "Orange", Color.ORANGE );
    colors.put( "Pink", Color.PINK );
    colors.put( "Red", Color.RED );
    colors.put( "Yellow", Color.YELLOW );
    Object[] colorsArray = colors.keySet().toArray();

    // create a list of fifty numbers plus blank
    List<Object> digitsPlusBlank = new ArrayList<Object>();
    digitsPlusBlank.add( "" );
    for( int i = 1; i <= 50; i++ )
      digitsPlusBlank.add( i );

    // search pane
    JPanel searchPane = new JPanel();
    BoxLayout searchPaneLayout = new BoxLayout( searchPane, BoxLayout.LINE_AXIS );
    searchPane.setLayout( searchPaneLayout );
    searchPane.setBorder( BorderFactory.createEmptyBorder( 10, 4, 10, 4 ));
    final JTextField searchField = new JTextField();
    Dimension preferredSize = searchField.getPreferredSize();
    Dimension inputSize = new Dimension( 120, (int)preferredSize.getHeight() );
    searchField.setPreferredSize( inputSize );
    searchField.setMinimumSize( inputSize );
    searchField.setMaximumSize( inputSize );
    JLabel searchLabel = new JLabel( "Search:" );
    searchLabel.setLabelFor( searchField );
    searchPane.add( Box.createHorizontalGlue() );
    searchPane.add( searchLabel );
    searchPane.add( searchField );

    // text pane
    final JTextPane textPane = new JTextPane();
    Highlighter highlighter = new DefaultHighlighter();
    final Highlighter.HighlightPainter highlightPainter = new DefaultHighlighter.DefaultHighlightPainter( HIGHLIGHT_COLOR );
    textPane.setHighlighter( highlighter );

    // let's create some styles
    StyleContext styleContext = new StyleContext();
    Style defaultAttributes = styleContext.getStyle( StyleContext.DEFAULT_STYLE );
    Style bodyAttributes = styleContext.addStyle( "Body", defaultAttributes );
    StyleConstants.setFontSize( bodyAttributes, 12 );
    StyleConstants.setSpaceBelow( bodyAttributes, 12.0f );
    Style titleAttributes = styleContext.addStyle( "Title", defaultAttributes );
    StyleConstants.setFontSize( titleAttributes, 18 );
    StyleConstants.setAlignment( titleAttributes, StyleConstants.ALIGN_CENTER );
    StyleConstants.setBold( titleAttributes, true );
    StyleConstants.setSpaceBelow( titleAttributes, 12.0f );
    Style headerAttributes = styleContext.addStyle( "Header", defaultAttributes );
    StyleConstants.setFontSize( headerAttributes, 14 );
    StyleConstants.setBold( headerAttributes, true );
    DefaultStyledDocument doc = new DefaultStyledDocument( styleContext );

    // add some sample text to the text pane
    doc.insertString( 0,
        "Marker Bar Demo\n" +
        "Summary\n" +
        "This sample application uses a vertical marker bar next to a scrolling JTextPane " +
        "to show search results. As you type into the search field above, search results " +
        "are marked along the scrollbar to indicate where the matched characters fall " +
        "within this document. The markers are drawn either as a rectangle or as a circle, " +
        "depending on the option chosen in the side panel. The rectangle renderer is the " +
        "default marker renderer, while the circle renderer is a custom renderer. Any " +
        "AWT or Swing component can be used to draw the markers, making it easy to define " +
        "your own marker renderer.\n" +
        "The side panel also contains options to change the colors, borders, and preferred " +
        "sizes of the marker bar and markers. If the marker preferred size is set " +
        "to Integer.MAX_INTEGER by " +
        "Integer.MAX_INTEGER (blank in this demo), then the marker bar sizes the renderer to the full " +
        "width and height given to the marker. Otherwise, the marker bar sizes the renderer according to its " +
        "preferred and minimum sizes. If the final size is less than the entire space represented " +
        "by the marker, then the marker bar uses the renderer's X and Y alignments to position " +
        "the mark horizontally and vertically.\n" +
        "When you hover the mouser cursor over a marker, a tool tip displays, " +
        "identifying the marker, and the cursor changes icons, indicating that the " +
        "marker is clickable. In this demo, clicking on a marker merely scrolls the text pane " +
        "so that the search result represented by the clicked marker is visible. You can easily " +
        "perform another action by installing a different action listener.\n" +
        "Other Side of Software Products\n" +
        "While visiting sideofsoftware.com, please check out our other libraries. " +
        "The Print Preview Library makes it easy to add a print preview screen to your " +
        "application. The Report Library allows you to add stylish, editable tables to " +
        "your application. The Wizard Library allows you to easily create step-by-step " +
        "instructions within a dialog or window. The Dated Collections Library is a " +
        "powerful utility library that efficiently adds dates or timestamps to elements within " +
        "collections. Last, the Persistence Library turns your ordinary domain objects " +
        "into persistent database objects without requiring you to specify any relational " +
        "database mappings.",
      defaultAttributes );
    doc.setParagraphAttributes( 0, 16, titleAttributes, false );
    doc.setParagraphAttributes( 16, 8, headerAttributes, false );
    doc.setParagraphAttributes( 24, 1552, bodyAttributes, false );
    doc.setParagraphAttributes( 1576, 32, headerAttributes, false );
    doc.setParagraphAttributes( 1608, 100, bodyAttributes, false );

    textPane.setDocument( doc );
    JScrollPane scrollPane = new JScrollPane(textPane);

    final JPanel panel = new JPanel();
    
    // marker bar
    final DefaultListModel model = new DefaultListModel();
    final JMarkerBar markerBar = new JMarkerBar( model );
    markerBar.setMarkerRenderer( markerRenderer );

    // options pane
    JPanel optionsPane = new JPanel();
    optionsPane.setLayout( new BoxLayout( optionsPane, BoxLayout.PAGE_AXIS ));
    optionsPane.setBorder( BorderFactory.createEmptyBorder( 0, 20, 0, 20 ));

    // option: marker bar width
    SpinnerModel markerBarWidthSpinnerModel = new SpinnerNumberModel( 12, 0, 50, 1 );
    final JSpinner markerBarWidthSpinner = new JSpinner( markerBarWidthSpinnerModel );
    markerBarWidthSpinner.addChangeListener( new ChangeListener() {

      public void stateChanged( ChangeEvent e )
      {
        JSpinner spinner = (JSpinner)e.getSource();
        Number value = (Number)spinner.getValue();
        int v = value.intValue();
        if( v == 0 )
          markerBar.setPreferredSize( null );
        else
        {
          Dimension preferredSize = markerBar.getPreferredSize();
          Dimension newPreferredSize = new Dimension( v, preferredSize.height );
          markerBar.setPreferredSize( newPreferredSize );
        }
        panel.revalidate();
        panel.repaint();
      }
    } );
    JLabel markerBarWidthLabel = new JLabel( "Width: " );
    markerBarWidthLabel.setLabelFor( markerBarWidthSpinner );

    // option: marker bar border
    final JComboBox markerBarBorderComboBox = new JComboBox( bordersArray );
    markerBarBorderComboBox.addActionListener( new ActionListener() {

      public void actionPerformed( ActionEvent e )
      {
        String borderName = (String)markerBarBorderComboBox.getSelectedItem();
        Border border = borders.get( borderName );
        markerBar.setBorder( border );
      }
    } );
    JLabel markerBarBorderLabel = new JLabel( "Border: " );
    markerBarBorderLabel.setLabelFor( markerBarBorderComboBox );

    // option: marker bar background
    final JComboBox markerBarBackgroundComboBox = new JComboBox( colorsArray );
    markerBarBackgroundComboBox.addActionListener( new ActionListener() {

      public void actionPerformed( ActionEvent e )
      {
        String colorName = (String)markerBarBackgroundComboBox.getSelectedItem();
        Color color = colors.get( colorName );
          markerBar.setBackground( color );
      }
    } );
    JLabel markerBarBackgroundLabel = new JLabel( "Background: " );
    markerBarBackgroundLabel.setLabelFor( markerBarBackgroundComboBox );

    // option: marker foreground
    final JComboBox markerForegroundComboBox = new JComboBox( colorsArray );
    markerForegroundComboBox.addActionListener( new ActionListener() {

      public void actionPerformed( ActionEvent e )
      {
        String colorName = (String)markerForegroundComboBox.getSelectedItem();
        Color color = colors.get( colorName );
        if( color == null )
          color = new JMarkerBar().getForeground();
        markerBar.setForeground( color );
      }
    } );

    JLabel markerForegroundLabel = new JLabel( "Color: " );
    markerForegroundLabel.setLabelFor( markerForegroundComboBox );

    // option: marker height
    SpinnerModel markerHeightSpinnerModel = new SpinnerListModel( digitsPlusBlank );
    final JSpinner markerHeightSpinner = new JSpinner( markerHeightSpinnerModel );
    setSpinnerTextAlignment( markerHeightSpinner );
    markerHeightSpinner.addChangeListener( new ChangeListener() {

      public void stateChanged( ChangeEvent e )
      {
        JSpinner spinner = (JSpinner)e.getSource();
        Object value = spinner.getValue();
        int v;
        if( "".equals( value ))
          v = Integer.MAX_VALUE;
        else
          v = ((Number)value).intValue();

        for( MarkerRenderer markerRenderer : renderers.values() )
        {
          JComponent component = (JComponent)markerRenderer;
          Dimension preferredSize = component.getPreferredSize();
          Dimension newPreferredSize = new Dimension( preferredSize.width, v );
          component.setPreferredSize( newPreferredSize );
        }
        markerBar.revalidate();
        markerBar.repaint();
      }
    } );
    JLabel markerHeightLabel = new JLabel( "Height: " );
    markerHeightLabel.setLabelFor( markerHeightSpinner );

    // option: marker width
    SpinnerModel markerWidthSpinnerModel = new SpinnerListModel( digitsPlusBlank );
    final JSpinner markerWidthSpinner = new JSpinner( markerWidthSpinnerModel );
    setSpinnerTextAlignment( markerWidthSpinner );
    markerWidthSpinner.addChangeListener( new ChangeListener() {

      public void stateChanged( ChangeEvent e )
      {
        JSpinner spinner = (JSpinner)e.getSource();
        Object value = spinner.getValue();
        int v;
        if( "".equals( value ))
          v = Integer.MAX_VALUE;
        else
          v = ((Number)value).intValue();

        for( MarkerRenderer markerRenderer : renderers.values() )
        {
          JComponent component = (JComponent)markerRenderer;
          Dimension preferredSize = component.getPreferredSize();
          Dimension newPreferredSize = new Dimension( v, preferredSize.height );
          component.setPreferredSize( newPreferredSize );
        }
        markerBar.revalidate();
        markerBar.repaint();
      }
    } );
    JLabel markerWidthLabel = new JLabel( "Width: " );
    markerWidthLabel.setLabelFor( markerWidthSpinner );

    // option: marker alignment X
    SpinnerModel markerAlignmentXSpinnerModel = new SpinnerNumberModel( 0.5, 0.0, 1.0, 0.1 );
    final JSpinner markerAlignmentXSpinner = new JSpinner( markerAlignmentXSpinnerModel );
    markerAlignmentXSpinner.addChangeListener( new ChangeListener() {

      public void stateChanged( ChangeEvent e )
      {
        JSpinner spinner = (JSpinner)e.getSource();
        Number value = (Number)spinner.getValue();
        float v = value.floatValue();

        for( MarkerRenderer markerRenderer : renderers.values() )
        {
          JComponent component = (JComponent)markerRenderer;
          component.setAlignmentX( v );
        }
        markerBar.revalidate();
        markerBar.repaint();
      }
    } );
    JLabel markerAlignmentXLabel = new JLabel( "Alignment X: " );
    markerAlignmentXLabel.setLabelFor( markerAlignmentXSpinner );

    // option: marker alignment Y
    SpinnerModel markerAlignmentYSpinnerModel = new SpinnerNumberModel( 0.5, 0.0, 1.0, 0.1 );
    final JSpinner markerAlignmentYSpinner = new JSpinner( markerAlignmentYSpinnerModel );
    markerAlignmentYSpinner.addChangeListener( new ChangeListener() {

      public void stateChanged( ChangeEvent e )
      {
        JSpinner spinner = (JSpinner)e.getSource();
        Number value = (Number)spinner.getValue();
        float v = value.floatValue();

        for( MarkerRenderer markerRenderer : renderers.values() )
        {
          JComponent component = (JComponent)markerRenderer;
          component.setAlignmentY( v );
        }
        markerBar.revalidate();
        markerBar.repaint();
      }
    } );
    JLabel markerAlignmentYLabel = new JLabel( "Alignment Y: " );
    markerAlignmentYLabel.setLabelFor( markerAlignmentYSpinner );

    // option: marker border
    final JComboBox markerBorderComboBox = new JComboBox( bordersArray );
    markerBorderComboBox.addActionListener( new ActionListener() {

      public void actionPerformed( ActionEvent e )
      {
        String borderName = (String)markerBorderComboBox.getSelectedItem();
        Border border = borders.get( borderName );
        for( MarkerRenderer markerRenderer : renderers.values() )
        {
          JComponent component = (JComponent)markerRenderer;
          component.setBorder( border );
        }
        markerBar.revalidate();
        markerBar.repaint();
      }
    } );
    JLabel markerBorderLabel = new JLabel( "Border: " );
    markerBorderLabel.setLabelFor( markerBorderComboBox );

    // option: marker renderer
    final JComboBox markerShapeComboBox = new JComboBox( renderersArray );
    markerShapeComboBox.addActionListener( new ActionListener() {

      public void actionPerformed( ActionEvent e )
      {
        String shape = (String)markerShapeComboBox.getSelectedItem();
        MarkerRenderer renderer = renderers.get( shape );
        markerBar.setMarkerRenderer( renderer );
      }
    } );

    JLabel markerShapeLabel = new JLabel( "Shape: " );
    markerShapeLabel.setLabelFor( markerShapeComboBox );

    GridBagConstraints c = new GridBagConstraints();
    c.anchor = GridBagConstraints.LINE_START;
    c.fill = GridBagConstraints.HORIZONTAL;

    // create the side panel with some marker bar options
    JPanel markerBarOptionsPanel = new JPanel( new GridBagLayout() );
    markerBarOptionsPanel.setBorder( BorderFactory.createTitledBorder( "Marker Bar Options" ));
    addOptionComponent( markerBarOptionsPanel, markerBarBorderLabel, c, 0, 0 );
    addOptionComponent( markerBarOptionsPanel, markerBarBorderComboBox, c, 1, 0 );
    addOptionComponent( markerBarOptionsPanel, markerBarBackgroundLabel, c, 0, 1 );
    addOptionComponent( markerBarOptionsPanel, markerBarBackgroundComboBox, c, 1, 1 );
    addOptionComponent( markerBarOptionsPanel, markerBarWidthLabel, c, 0, 2 );
    addOptionComponent( markerBarOptionsPanel, markerBarWidthSpinner, c, 1, 2 );

    // create the side panel with some marker options
    JPanel markerOptionsPanel = new JPanel( new GridBagLayout() );
    markerOptionsPanel.setBorder( BorderFactory.createTitledBorder( "Marker Options" ));
    addOptionComponent( markerOptionsPanel, markerShapeLabel, c, 0, 0 );
    addOptionComponent( markerOptionsPanel, markerShapeComboBox, c, 1, 0 );
    addOptionComponent( markerOptionsPanel, markerBorderLabel, c, 0, 1 );
    addOptionComponent( markerOptionsPanel, markerBorderComboBox, c, 1, 1 );
    addOptionComponent( markerOptionsPanel, markerForegroundLabel, c, 0, 2 );
    addOptionComponent( markerOptionsPanel, markerForegroundComboBox, c, 1, 2 );
    addOptionComponent( markerOptionsPanel, markerWidthLabel, c, 0, 3 );
    addOptionComponent( markerOptionsPanel, markerWidthSpinner, c, 1, 3 );
    addOptionComponent( markerOptionsPanel, markerHeightLabel, c, 0, 4 );
    addOptionComponent( markerOptionsPanel, markerHeightSpinner, c, 1, 4 );
    addOptionComponent( markerOptionsPanel, markerAlignmentXLabel, c, 0, 5 );
    addOptionComponent( markerOptionsPanel, markerAlignmentXSpinner, c, 1, 5 );
    addOptionComponent( markerOptionsPanel, markerAlignmentYLabel, c, 0, 6 );
    addOptionComponent( markerOptionsPanel, markerAlignmentYSpinner, c, 1, 6 );

    markerBarOptionsPanel.setMaximumSize( markerBarOptionsPanel.getPreferredSize() );
    markerOptionsPanel.setMaximumSize( markerOptionsPanel.getPreferredSize() );
    optionsPane.add( markerBarOptionsPanel );
    optionsPane.add( Box.createVerticalStrut( 20 ));
    optionsPane.add( markerOptionsPanel );

    BorderLayout borderLayout = new BorderLayout();
    panel.setLayout( borderLayout );
    panel.add( searchPane, BorderLayout.PAGE_START );
    panel.add( optionsPane, BorderLayout.LINE_START );
    panel.add( scrollPane, BorderLayout.CENTER );
    panel.add( markerBar, BorderLayout.LINE_END );

    // put it all together
    Container contentPane = getContentPane();
    contentPane.add( panel );

    // respond to changes in the search criteria
    final Action searchAction = new AbstractAction( "Search" ) {

      public void actionPerformed( ActionEvent e )
      {
        Highlighter highlighter = textPane.getHighlighter();
        highlighter.removeAllHighlights();
        model.clear();
        Document document = textPane.getDocument();
        try
        {
          String text = document.getText( 0, document.getLength() );
          searchInput( text.toLowerCase(), searchField.getText().toLowerCase(), model, textPane, highlightPainter );
        }
        catch( BadLocationException ble )
        {
          throw new AssertionError( ble );
        }
      }
    };

    // perform searches as the user types...
    DocumentListener documentListener = new DocumentListener() {

      public void insertUpdate( DocumentEvent e )
      {
        searchAction.actionPerformed( null );
      }

      public void removeUpdate( DocumentEvent e )
      {
        searchAction.actionPerformed( null );
      }

      public void changedUpdate( DocumentEvent e )
      {
        searchAction.actionPerformed( null );
      }
    };

    // ... in the search box
    searchField.getDocument().addDocumentListener( documentListener );

    // ... in the text pane
    textPane.getDocument().addDocumentListener( documentListener );

    scrollPane.addComponentListener( new ComponentAdapter() {

      @Override
      public void componentResized( ComponentEvent e )
      {
        searchAction.actionPerformed( null );
      }

    } );

    // respond to clicks on markers
    markerBar.addActionListener( new ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
          int markerIndex = markerBar.getClickedIndex();
          Utility.scrollToMarker( textPane, markerBar, markerIndex );
        }
    });

    setTitle( "Marker Bar Library Demo by Side of Software" );
    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    setPreferredSize( new Dimension( 600, 400 ));
    pack();

    // start with a search
    searchField.setText( "em" );
  }

  /**
   * Adds a component to a grid-bag panel.
   */
  private static void addOptionComponent( JPanel panel, JComponent component, GridBagConstraints constraints, int x, int y )
  {
    constraints.gridx = x;
    constraints.gridy = y;
    panel.add( component, constraints );
  }

  /**
   * Right-aligns a spinner.
   */
  private static void setSpinnerTextAlignment( JSpinner spinner )
  {
    JSpinner.DefaultEditor defaultEditor = (JSpinner.DefaultEditor)spinner.getEditor();
    JTextField textField = defaultEditor.getTextField();
    textField.setHorizontalAlignment( JTextField.RIGHT );
  }

  /**
   * Searches the input text and add highlights to the text pane.
   */
  private void searchInput( String inputText, String searchText, DefaultListModel model, JTextPane textPane, Highlighter.HighlightPainter painter )
  {
    if( searchText == null || searchText.length() == 0 )
      return;

    Highlighter highlighter = textPane.getHighlighter();

    String regEx = Pattern.quote( searchText );
    Pattern pattern = Pattern.compile( regEx );
    Matcher matcher = pattern.matcher( inputText );
    int ct = 1;
    while( matcher.find() )
    {
      int start = matcher.start();
      int end = matcher.end();

      try
      {
        // highlight the search result in the text pane
        highlighter.addHighlight( start, end, painter );

        // utility method to convert text indices to a marker range
        double[] markerRange = Utility.textRangeToMarkerRange( textPane, start, end );

        // create a marker (let's just use the default)
        Marker marker = new DefaultMarker( markerRange[0], markerRange[1], "Match " + ct );

        // add the marker to the marker bar model
        model.addElement( marker );
        ct++;
      }
      catch( BadLocationException ble )
      {
        // shouldn't be a bad location
      }
    }
  }

  public static void main( String[] args )
  {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          try
          {
            UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
            new MarkerBarDemo().setVisible(true);
          }
          catch( Exception e )
          {
            throw new AssertionError( e );
          }
        }
    });
  }


  /**
   * A custom marker renderer that draws markers as circles.
   */
  public static class CircleMarkerRenderer extends JPanel implements MarkerRenderer
  {
    @Override
    public void paint( Graphics g )
    {
      Dimension size = getSize();
      int radius = Math.min( size.width, size.height ) - 1;

      Graphics2D g2 = (Graphics2D)g;

      // turn on anti-aliasing for crisper rendering
      g2.addRenderingHints( hints );

      g2.setColor( Color.DARK_GRAY );
      g2.drawOval( 0, 0, radius, radius );
      Color color = getBackground();
      g2.setPaint( new GradientPaint( 0.0f, 0.0f, color.brighter(), 0.0f, radius, color.darker() ));
      g2.fillOval( 0, 0, radius, radius );
    }

    public Component getMarkerRendererComponent( JMarkerBar markerBar, int markerIndex, Marker marker )
    {
      Color color = markerBar.getForeground();
      setBackground( color );
      return this;
    }
  }
}