view src/junotu/TabCalendarBoard.java @ 122:ad6ad9f695bb default tip

TabEdit: Started on card options pop-up menu
author Fox
date Sat, 25 Nov 2023 16:40:05 +0100
parents 652027516bd6
children
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 TITLE_DATE_FORMAT = new SimpleDateFormat("dd MMMM, yyyy");
	final static SimpleDateFormat DAY_OF_THE_WEEK_FORMAT = new SimpleDateFormat("EEEE");
	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.");
			this.setToolTipText( DAY_OF_THE_WEEK_FORMAT.format(date) );
			
			if( newCard ) {
				return;
			}
			
			identifier = card.identifierGet().longValue();
			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)+". Full cards tag: '"+cardsString+"'\n");
					insertCard( null, -1 );
					continue;
				}
				
				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, false );
				}
			} 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 );
		
		scroll.getHorizontalScrollBar().setUnitIncrement(COLUMN_WIDTH);
		scroll.getVerticalScrollBar().setUnitIncrement(64); /* TODO: FIXME: Magic number. */
		
		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 ).cards;
		
		if( cards.length == 0 ) {
			card = new Card();
			identifier = Main.database.cardAdd(card);
			boardSave();
		} else if( cards.length == 1 ) {
			card = cards[0];
			identifier = card.identifierGet().longValue();
		} else {
			throw new RuntimeException();
		}
		
		optionOnlyFilledColumns = card.optionGetBool( Card.TAG_CALENDAR_BOARD_OPTION_ONLY_FILLED, false );
		menu_onlyFilled.setSelected(optionOnlyFilledColumns);
		
		populateColumns();
		scroll.getHorizontalScrollBar().setValue(0);
		
	}
	
	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 );
		
		card.optionSetBool( Card.TAG_CALENDAR_BOARD_OPTION_ONLY_FILLED, optionOnlyFilledColumns );
		
		Component[] columnsList = columns.getComponents();
		for( int i = 0; i < columnsList.length; i++ ) {
			ColumnWidget column = (ColumnWidget)columnsList[i];
			column.save();
		}
		
		Main.database.cardUpdate( card, true );
		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 ).cards;
			
			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( TITLE_DATE_FORMAT.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();
		}
	}
	
}