view src/junotu/TabCalendarBoard.java @ 94:ce163f6cecdb

TabBoard/TabCalendarBoard: Can now use ESCAPE to go back
author Fox
date Wed, 22 Feb 2023 23:09:46 +0100
parents 1df77c040218
children 666e6253fbdf
line wrap: on
line source

package junotu;

/* TODO: Clean-up imports. */

import java.lang.RuntimeException;
import java.util.Date;
import java.util.Calendar;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

import org.apache.lucene.search.Query;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.index.Term;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;

import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import javax.swing.SpinnerDateModel;

import javax.swing.JPanel;
import javax.swing.Box;

import java.awt.BorderLayout;
import java.awt.GridBagLayout;
import javax.swing.BoxLayout;

import java.awt.Component;
import javax.swing.JButton;
import javax.swing.JSpinner;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JScrollPane;
import javax.swing.JScrollBar;
import javax.swing.JPopupMenu;
import javax.swing.JMenuItem;
import javax.swing.JCheckBoxMenuItem;

import javax.swing.BorderFactory;
import javax.swing.border.TitledBorder;

import static java.lang.Math.min;
import static java.lang.Math.max;

import junotu.Main;
import junotu.Window.Tab;
import junotu.Window.TabInterface;
import junotu.Card;
import junotu.ColumnCardWidget;

public class TabCalendarBoard extends JPanel implements TabInterface, ActionListener, MouseListener, ChangeListener {

    final static int COLUMN_CONTENT_WIDTH = 256;
    final static int COLUMN_WIDTH = COLUMN_CONTENT_WIDTH+16;
    final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
    final static SimpleDateFormat DEBUG_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public final String KEY_ACTION_BACK            = "back";
    public final String KEY_ACTION_COMMIT          = "commit";
    public final String KEY_ACTION_CARD_UP         = "card_up";
    public final String KEY_ACTION_CARD_DOWN       = "card_down";
    public final String KEY_ACTION_CARD_FULL_UP    = "card_full_up";
    public final String KEY_ACTION_CARD_FULL_DOWN  = "card_full_down";
    public final String KEY_ACTION_CARD_LEFT       = "card_left";
    public final String KEY_ACTION_CARD_RIGHT      = "card_right";
    public final String KEY_ACTION_CARD_FULL_LEFT  = "card_full_left";
    public final String KEY_ACTION_CARD_FULL_RIGHT = "card_full_right";
    
    private class ColumnWidget extends JPanel implements ActionListener, MouseListener {

	long identifier;
	boolean newCard;
	Date date;
	TabCalendarBoard parent;
	
	TitledBorder titledBorder;
	Box cards;
	JButton addCard;

	public ColumnWidget( TabCalendarBoard parent_, Card card, Date date_ )
	{
	    newCard = card == null;
	    date = date_;
	    parent = parent_;
	    
	    this.setLayout( new GridBagLayout() );
	    
	    cards = Box.createVerticalBox();
	    addCard = new JButton("+");

	    addCard.setFont( new Font( "Monospaced", Font.BOLD, 32 ) );

	    GridBagConstraints constraints = new GridBagConstraints();
	    constraints.anchor = GridBagConstraints.NORTHWEST;
	    constraints.fill = GridBagConstraints.HORIZONTAL;
	    constraints.weightx = 1.0;
	    constraints.weighty = 0.0;
	    constraints.gridx = 0;
	    constraints.gridy = 0;

	    this.add( cards, constraints );
	    constraints.gridy++;
	    this.add( addCard, constraints );
	    constraints.gridy++;
	    constraints.weighty = 1.0;
	    this.add( Box.createVerticalGlue(), constraints );

	    addCard.setPreferredSize( new Dimension( COLUMN_CONTENT_WIDTH, 64 ) );
	    addCard.setMaximumSize( new Dimension( COLUMN_CONTENT_WIDTH, 64 ) );
	    addCard.setAlignmentX( JButton.CENTER_ALIGNMENT );

	    //this.setPreferredSize( new Dimension( COLUMN_WIDTH, 384 ) );
	    this.setMaximumSize( new Dimension( COLUMN_WIDTH, 1000000 ) );

	    titledBorder = BorderFactory.createTitledBorder(
		BorderFactory.createEtchedBorder(),
	        "",
	        TitledBorder.LEADING,
	        TitledBorder.TOP,
	        new Font( "Monospaced", Font.BOLD, 16 )
	    );

	    this.setBorder(
		BorderFactory.createCompoundBorder(
		    BorderFactory.createEmptyBorder( 0, 8, 0, 8 ),
	            titledBorder
		)
	    );

	    addMouseListener(this);
	    registerKeyboardAction(
		this,
		KEY_ACTION_CARD_UP,
		KeyStroke.getKeyStroke( KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK ),
		WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
	    );
	    registerKeyboardAction(
		this,
		KEY_ACTION_CARD_DOWN,
		KeyStroke.getKeyStroke( KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK ),
		WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
	    );
	    registerKeyboardAction(
		this,
		KEY_ACTION_CARD_FULL_UP,
		KeyStroke.getKeyStroke( KeyEvent.VK_PAGE_UP, InputEvent.ALT_DOWN_MASK ),
		WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
	    );
	    registerKeyboardAction(
		this,
		KEY_ACTION_CARD_FULL_DOWN,
		KeyStroke.getKeyStroke( KeyEvent.VK_PAGE_DOWN, InputEvent.ALT_DOWN_MASK ),
		WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
	    );
	    registerKeyboardAction(
	        parent,
		KEY_ACTION_CARD_LEFT,
		KeyStroke.getKeyStroke( KeyEvent.VK_LEFT, InputEvent.ALT_DOWN_MASK ),
		WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
	    );
	    registerKeyboardAction(
	        parent,
		KEY_ACTION_CARD_RIGHT,
		KeyStroke.getKeyStroke( KeyEvent.VK_RIGHT, InputEvent.ALT_DOWN_MASK ),
		WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
	    );
	    registerKeyboardAction(
	        parent,
		KEY_ACTION_CARD_FULL_LEFT,
		KeyStroke.getKeyStroke( KeyEvent.VK_HOME, InputEvent.ALT_DOWN_MASK ),
		WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
	    );
	    registerKeyboardAction(
	        parent,
		KEY_ACTION_CARD_FULL_RIGHT,
		KeyStroke.getKeyStroke( KeyEvent.VK_END, InputEvent.ALT_DOWN_MASK ),
		WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
	    );

	    addCard.addActionListener(this);
	    
	    addCard.setToolTipText("Add card.");
	    
	    if( newCard ) {
		return;
	    }

	    identifier = card.identifierGet();
	    titleSet(card.titleGet());
	    
	    String cardsString = card.<String>tagGetAsOr( Card.TAG_CALENDAR_BOARD_COLUMN_CARDS, "" );
	    Card[] cardsSplit = TagUtility.parseCardList(cardsString);
	    
	    for( int i = 0; i < cardsSplit.length; i++ ) {
		
		if( cardsSplit[i] == null ) {
		    System.out.print("Column '"+card.titleGet()+"', identifier "+Long.toString(card.identifierGet())+": Failed to retrieve card identifier by index "+Integer.toString(i)+", aborting cards loading. Full cards tag: '"+cardsString+"'\n");
		    break;
		}

		insertCard( cardsSplit[i], -1 );
		
	    }
	    
	}

	public void titleSet( String title )
	{
	    titledBorder.setTitle(title);
	    repaint();
	}

	public String titleGet()
	{
	    return titledBorder.getTitle();
	}
	
	public void insertCard( Card card, int at )
	{
	    ColumnCardWidget cardWidget = new ColumnCardWidget( card );
	    insertCardRaw( cardWidget, at );
	}

	public void insertCardRaw( ColumnCardWidget cardWidget, int at )
	{
	    if( at == -1 ) {
		at = cards.getComponentCount();
	    }
	    cardWidget.addMouseListener(this);
	    /* TODO: Check if works properly. */
	    cards.add( cardWidget, at );
	    cards.revalidate();
	}

	public void moveCard( int at, int to )
	{
	    if( at < 0 ) {
		return;
	    }
	    ColumnCardWidget cardWidget = (ColumnCardWidget)cards.getComponent(at);
	    boolean focused = cardWidget.isSelected();
	    cards.remove(at);
	    cards.add( cardWidget, to );
	    cards.revalidate();
	    if( focused ) {
	        cardWidget.select();
	    }
	}

	public ColumnCardWidget popCard( int at )
	{
	    if( at < 0 ) {
		return null;
	    }
	    ColumnCardWidget cardWidget = (ColumnCardWidget)cards.getComponent(at);
	    cards.remove(at);
	    cardWidget.removeMouseListener(this);
	    checkCardCount();
	    return cardWidget;
	}

	public int selectedCard()
	{
	    Component[] cardList = cards.getComponents();
	    for( int i = 0; i < cardList.length; i++ ) {
		if( ((ColumnCardWidget)cardList[i]).isSelected() ) {
		    return i;
		}
	    }
	    System.out.print("Selected card not found.");
	    return -1;
	}

	public int cardCount()
	{
	    return cards.getComponentCount();
	}

	public void save()
	{
	    Component[] cardList = cards.getComponents();
	    String cardIdentifiers = "";
	    for( int i = 0; i < cardList.length; i++ ) {
		ColumnCardWidget cardWidget = (ColumnCardWidget)cardList[i];
		cardWidget.save();
		
		if( cardIdentifiers.length() > 0 ) {
		    cardIdentifiers += " ";
		}
		cardIdentifiers += Long.toString(cardWidget.identifier);
	    }
	    
	    if( cardList.length == 0 ) {
		if( !newCard ) {
		    Main.database.cardDeleteByIdentifier(identifier);
		    identifier = -1;
		    newCard = true;
		}
		return;
	    }

	    Card card;

	    if( newCard ) {
	        card = new Card();
	    } else {
		try {
		    card = Main.database.cardGetByIdentifier(identifier);
		} catch( Exception e ) {
		    throw new RuntimeException(e);
		}

		if( card == null ) {
		    throw new RuntimeException("Board column update: card not found.");
		}
	    }

	    card.titleSet( titleGet() );
	    card.tagValueSetOnly( Card.TAG_CALENDAR_BOARD_COLUMN, null );
	    card.tagValueSetOnly( Card.TAG_CALENDAR_BOARD_COLUMN_DATE, DATE_FORMAT.format(date) );
	    card.tagValueSetOnly( Card.TAG_CALENDAR_BOARD_COLUMN_CARDS, cardIdentifiers );

	    try {
		if( newCard ) {
		    identifier = Main.database.cardAdd( card );
		    newCard = false;
		} else {
		    Main.database.cardUpdate(card);
		}
	    } catch( Exception e ) {
		throw new RuntimeException(e);
	    }
	    
	}

	public void delete()
	{
	    save();
	    parent = null;
	    if( !newCard ) {
		Main.database.cardDeleteByIdentifier(identifier);
	    }
	}

	public void checkCardCount()
	{
	    if( cardCount() == 0 ) {
	        columnIsEmpty(this);
	    }
	}

	public void actionPerformed( ActionEvent e )
	{
	    Object source = e.getSource();
	    if( source == this ) {
		int selected = selectedCard();
		int length = cards.getComponentCount();
		
		switch( e.getActionCommand() ) {

		case KEY_ACTION_CARD_UP: {
		    System.out.print("Move card up.\n");
		    moveCard( selected, max(selected-1, 0) );
		    break;
		}
		    
		case KEY_ACTION_CARD_DOWN: {
		    System.out.print("Move card down.\n");
		    moveCard( selected, min(selected+1, cards.getComponentCount()-1) );
		    break;
		}
		    
		case KEY_ACTION_CARD_FULL_UP: {
		    System.out.print("Move card full up.\n");
		    moveCard( selected, 0 );
		    break;
		}
		    
		case KEY_ACTION_CARD_FULL_DOWN: {
		    System.out.print("Move card full down.\n");
		    moveCard( selected, cards.getComponentCount()-1 );
		    break;
		}
		    
		}
	    } else if( source == addCard ) {
		insertCard( null, -1 );
	    }
	}

	public void mouseClicked( MouseEvent e )
	{
	    Object source = e.getSource();
	    if( source instanceof ColumnCardWidget ) {
		if( e.getButton() == MouseEvent.BUTTON2 ) {
		    ColumnCardWidget cardWidget = (ColumnCardWidget)e.getSource();
		    cardWidget.delete();
		    cards.remove(cardWidget);
		    cards.revalidate();
		    checkCardCount();
		}
	    }
	}
	
	public void mouseEntered( MouseEvent e ) {}
	public void mouseExited( MouseEvent e ) {}
	public void mousePressed( MouseEvent e ) {}
	public void mouseReleased( MouseEvent e ) {}
	
    }

    long identifier = -1;
    Box columns;
    JScrollPane scroll;

    boolean optionOnlyFilledColumns;

    JButton back;
    JButton options;
    JSpinner dateRangeBegin;
    JSpinner dateRangeEnd;
    JPopupMenu menu;

    JCheckBoxMenuItem menu_onlyFilled;

    public TabCalendarBoard()
    {
	this.setLayout( new BorderLayout() );

	back = new JButton("Back");
	options = new JButton("=");
	dateRangeBegin = new JSpinner( new SpinnerDateModel() );
	dateRangeEnd = new JSpinner( new SpinnerDateModel() );
	JSpinner.DateEditor dateRangeBeginEditor = (JSpinner.DateEditor)dateRangeBegin.getEditor();
	JSpinner.DateEditor dateRangeEndEditor = (JSpinner.DateEditor)dateRangeEnd.getEditor();
	Calendar calendar = Calendar.getInstance();

	menu = new JPopupMenu("Options");
	menu_onlyFilled = new JCheckBoxMenuItem("Show only filled days");
	menu.add(menu_onlyFilled);
	    
	Box bottom = Box.createHorizontalBox();
	columns = Box.createHorizontalBox();
	scroll = new JScrollPane( columns );
	
	bottom.add( back );
	bottom.add( Box.createHorizontalGlue() );
	bottom.add( dateRangeBegin );
	bottom.add( dateRangeEnd );
	bottom.add( options );
	this.add( scroll, BorderLayout.CENTER );
	this.add( bottom, BorderLayout.SOUTH );

	calendar.set( Calendar.HOUR_OF_DAY, 0 );
	calendar.set( Calendar.MINUTE, 0 );
	calendar.set( Calendar.SECOND, 0 );
	calendar.set( Calendar.MILLISECOND, 0 );
	
	dateRangeBeginEditor.getFormat().applyPattern("yyyy-MM-dd EEEE");
	dateRangeEndEditor.getFormat().applyPattern("yyyy-MM-dd EEEE");

	dateRangeBeginEditor.getModel().setValue(calendar.getTime());
	calendar.add( Calendar.DAY_OF_MONTH, 6 );
	dateRangeEndEditor.getModel().setValue(calendar.getTime());

	dateRangeBeginEditor.getModel().setCalendarField( Calendar.DAY_OF_MONTH );
	dateRangeEndEditor.getModel().setCalendarField( Calendar.DAY_OF_MONTH );

	dateRangeBegin.addChangeListener(this);
	dateRangeEnd.addChangeListener(this);

	back.addActionListener(this);
	options.addActionListener(this);
	menu_onlyFilled.addActionListener(this);

	registerKeyboardAction( this, KEY_ACTION_BACK, KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ), WHEN_IN_FOCUSED_WINDOW );
	registerKeyboardAction( this, KEY_ACTION_COMMIT, KeyStroke.getKeyStroke( KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK ), WHEN_IN_FOCUSED_WINDOW );

	back.setToolTipText("Go back to where the calendar board was accessed from. Can also use [ESC].");
	options.setToolTipText("Show calendar options.");
	menu_onlyFilled.setToolTipText("If checked, days with no cards will be hidden.");
	
    }

    public void boardEdit()
    {
	if( identifier != -1 ) {
	    populateColumns();
	    return;
	}
	
	Card card;
	
	Query query = new WildcardQuery( new Term(Card.TAG_CALENDAR_BOARD, "*") );
	Card cards[] = Main.database.searchCustom( query, 1, false );

        if( cards.length == 0 ) {
	    card = new Card();
	    identifier = Main.database.cardAdd(card);
	    boardSave();
	} else if( cards.length == 1 ) {
	    card = cards[0];
	    identifier = card.identifierGet();
	} else {
	    throw new RuntimeException();
	}
	
	optionOnlyFilledColumns = card.tagHas( Card.TAG_CALENDAR_BOARD_OPTION_ONLY_FILLED );
	menu_onlyFilled.setSelected(optionOnlyFilledColumns);

	populateColumns();
	
    }

    public Card boardSave()
    {
	Card card;
	
	card = Main.database.cardGetByIdentifier(identifier);
	
	if( card == null ) {
	    throw new RuntimeException(""+identifier);
	}

	card.titleSet( "JUnotu calendar board" );
	card.tagValueSetOnly( Card.TAG_CALENDAR_BOARD, null );

	// TODO: Maybe card should have convenience functions for reading and writing options?
	if( optionOnlyFilledColumns ) {
	    card.tagValueSetOnly( Card.TAG_CALENDAR_BOARD_OPTION_ONLY_FILLED, null );
	} else {
	    card.tagRemove( Card.TAG_CALENDAR_BOARD_OPTION_ONLY_FILLED );
	}

	Component[] columnsList = columns.getComponents();
	for( int i = 0; i < columnsList.length; i++ ) {
	    ColumnWidget column = (ColumnWidget)columnsList[i];
	    column.save();
	}

	Main.database.cardUpdate(card);
	Main.refreshSearches();
	
	return card;
    }

    public void boardReset()
    {
	columns.removeAll();
    }

    public void populateColumns()
    {
	JSpinner.DateEditor dateRangeBeginEditor = (JSpinner.DateEditor)dateRangeBegin.getEditor();
	JSpinner.DateEditor dateRangeEndEditor = (JSpinner.DateEditor)dateRangeEnd.getEditor();
	
	Date begin = dateRangeBeginEditor.getModel().getDate();
	Date end = dateRangeEndEditor.getModel().getDate();

	Component[] columnList = columns.getComponents();
	
	if( begin.after(end) ) {
	    ColumnWidget columnWidget;
	    for( int i = 0; i < columnList.length; i++ ) {
	        columnWidget = (ColumnWidget)columnList[i];
		columnWidget.save();
	    }
	    columns.removeAll();
	    columns.validate();
	    columns.repaint();
	    return;
	}

	for( int i = 0; i < columnList.length; i++ ) {
	    ColumnWidget columnWidget = (ColumnWidget)columnList[i];
	    if( columnWidget.date.before(begin) || columnWidget.date.after(end) ) {
		columnWidget.save();
		columns.remove( columnWidget );
	    }
	}

	Term term = new Term(Card.TAG_CALENDAR_BOARD_COLUMN_DATE);
	
	Calendar cur = Calendar.getInstance();
	cur.setTime(begin);
	Date curTime = begin;
	int insertPosition = 0;
	for(
	    ;
	    (curTime = cur.getTime()).before(end) || curTime.equals(end);
	    cur.add( Calendar.DAY_OF_MONTH, 1 )
	) {
	    int column = findColumn(curTime);
	    if( column != -1 ) {
		ColumnWidget columnWidget = getColumnByIndex(column);
		if( !optionOnlyFilledColumns || columnWidget.cardCount() != 0 ) {
		    insertPosition = column+1;
		} else {
		    columnWidget.save();
		    columns.remove( columnWidget );
		}
		continue;
	    }
	    
	    Card card = null;
	    
	    term = term.createTerm( DATE_FORMAT.format(curTime) );
	    Card cards[] = Main.database.searchCustom( new TermQuery(term), 1, false );

	    if( cards.length != 0 ) {
		card = cards[0];
	    }
	    
	    if( optionOnlyFilledColumns && card == null ) {
		continue;
	    }
	    
	    insertColumn( curTime, card, insertPosition );
	    insertPosition++;
	}
	
	columns.validate();
	columns.repaint();
    }

    public void insertColumn( Date date, Card card, int insertPosition )
    {
	ColumnWidget column = new ColumnWidget(this, card, date);
	column.titleSet( DateFormat.getDateInstance().format(date) );
	insertColumnRaw(column, insertPosition);
    }

    public void insertColumnRaw( ColumnWidget column, int insertPosition )
    {
	column.addMouseListener(this);
	columns.add(column, insertPosition);
	columns.revalidate();
    }

    public int findColumn( ColumnWidget columnWidget )
    {
	Component[] columnsList = columns.getComponents();
	for( int i = 0; i < columnsList.length; i++ ) {
	    if( columnsList[i] == columnWidget ) {
		return i;
	    }
	}
	return -1;
    }

    public int findColumn( Date date )
    {
	Component[] columnsList = columns.getComponents();
	for( int i = 0; i < columnsList.length; i++ ) {
	    if( ((ColumnWidget)columnsList[i]).date.equals(date) ) {
		return i;
	    }
	}
	return -1;
    }

    public ColumnWidget getColumnByIndex( int index )
    {
	Component[] columnsList = columns.getComponents();
	return (ColumnWidget)columnsList[index];
    }

    public void removeColumn( ColumnWidget columnWidget ) {
	columnWidget.delete();
	columns.remove( columnWidget );
	columns.validate();
	columns.repaint();
    }

    public void columnIsEmpty( ColumnWidget columnWidget ) {
	if( optionOnlyFilledColumns ) {
	    removeColumn(columnWidget);
	}
    }

    public void moveCard( int from, int at, int to )
    {
	ColumnWidget fromColumn = (ColumnWidget)columns.getComponent(from);
	ColumnWidget toColumn = (ColumnWidget)columns.getComponent(to);
	ColumnCardWidget cardWidget = fromColumn.popCard(at);
	if( cardWidget == null ) {
	    return;
	}
	toColumn.insertCardRaw(cardWidget, min(at, toColumn.cardCount()) );
	cardWidget.select();
    }

    public void buttonClickedAsCard()
    {
	Card card = boardSave();
	Window window = (Window)this.getTopLevelAncestor();
	window.tabEdit.cardEdit(card);
	window.tabSwitch( Tab.EDIT );
    }

    public void buttonClickedBack()
    {
	boardSave();
	boardReset();
	Window window = (Window)this.getTopLevelAncestor();
	window.tabSwitch( Tab.SEARCH );
    }

    public void onSwitchedTo()
    {
	boardEdit();
    }

    public void actionPerformed( ActionEvent e )
    {
	Object source = e.getSource();
	if( source == this ){
	    switch( e.getActionCommand() ) {
		
	    case KEY_ACTION_COMMIT: {
		boardSave();
		return;
	    }

	    case KEY_ACTION_BACK: {
		buttonClickedBack();
		return;
	    }
		
	    }
	} else if( source == back ) {
	    buttonClickedBack();
	    return;
	} else if( source == options ) {
	    menu.show( (Component)source, 0, 0 );
	    return;
	} else if( source == menu_onlyFilled ) {
	    optionOnlyFilledColumns = menu_onlyFilled.isSelected();
	    populateColumns();
	    return;
	}

	if( source instanceof ColumnWidget ) {
	    ColumnWidget sourceColumn = (ColumnWidget)e.getSource();
	    int columnIndex = findColumn(sourceColumn);
	    switch( e.getActionCommand() ){

	    case KEY_ACTION_CARD_LEFT: {
		moveCard( columnIndex, sourceColumn.selectedCard(), max( columnIndex-1, 0) );
		break;
	    }
	    case KEY_ACTION_CARD_RIGHT: {
		moveCard( columnIndex, sourceColumn.selectedCard(), min( columnIndex+1, columns.getComponentCount()-1) );
		break;
	    }
	    case KEY_ACTION_CARD_FULL_LEFT: {
		moveCard( columnIndex, sourceColumn.selectedCard(), 0 );
		break;
	    }
	    case KEY_ACTION_CARD_FULL_RIGHT: {
		moveCard( columnIndex, sourceColumn.selectedCard(), columns.getComponentCount()-1 );
		break;
	    }
	    
	    }
	}
    }

    public void mouseClicked( MouseEvent e )
    {
	if( e.getButton() == MouseEvent.BUTTON2 ) {
	    if( !(e.getSource() instanceof ColumnWidget) )
		return;

	    removeColumn( (ColumnWidget)e.getSource() );
	}
    }
    
    public void mouseEntered( MouseEvent e ) {}
    public void mouseExited( MouseEvent e ) {}
    public void mousePressed( MouseEvent e ) {}
    public void mouseReleased( MouseEvent e ) {}

    public void stateChanged( ChangeEvent e )
    {
	Object source = e.getSource();
	if( source == dateRangeBegin || source == dateRangeEnd ) {
	    populateColumns();
	}
    }
    
}