JIM - Java Information Manager


Chr. Clemens Lee
 
2000-10-13


Abstract

This document describes the source code of the application JIM, the Java Information Manager.

Table of Contents

1   Starting Point
2   ToDo List
    2.1   Blocks and Depends On
        2.1.1   ToDoTask Class
        2.1.2   Gui Aspect
        2.1.3   Saving and Loading
        2.1.4   ToDo
3   ViewController
Links

1   Starting Point

Right now when we start with a literate programming approach towards Jim, there have been already two versions published. Therefore we kind of have to literate reverse engineer Jim, unless we don't want to start from scratch again.

2   ToDo List

Here is the old code of the ToDoPanel as of 2000-10-13:
/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is JIM, a Java Information Manager.
 * (http://www.kclee.com/clemens/java/jim/).
 *
 * The Initial Developer of the Original Code is Chr. Clemens Lee.
 * Portions created by Chr. Clemens Lee are Copyright (C) 2000
 * Chr. Clemens Lee. All Rights Reserved.
 *
 * Contributor(s): Chr. Clemens Lee <clemens@kclee.com>
 */

package jim;

import ccl.swing.AutoGridBagLayout;
import ccl.swing.CCLBorder;
import ccl.swing.DateField;
import ccl.swing.OKCancelPanel;
import ccl.swing.SwingUtil;
import ccl.util.Comparable;
import ccl.util.Util;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.border.BevelBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.JTextComponent;
import javax.swing.text.Keymap;
import javax.swing.text.TextAction;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

/**
 * The panel for all the ToDo stuff.
 *
 * @author  Chr. Clemens Lee
 * @version $Id: ToDoPanel.java,v 1.5 2000/09/19 18:20:55 clemens Exp clemens $
 */
public class ToDoPanel extends JPanel 
                       implements TreeSelectionListener
                                  , ListSelectionListener
                                  , ChangeListener
{
    private static final int STATE_STARTUP = 0;
    private static final int STATE_NORMAL  = STATE_STARTUP + 1;
    private static final int STATE_INPUT   = STATE_NORMAL  + 1;
    private static final int STATE_TASK    = STATE_INPUT   + 1;

    private static String S_LIST_NAME = "ToDoListPane";

    /**
     * Used by jim test.
     */
    public static String S_TASK_NAME = "ToDoTaskPane";

    private JButton _btnNewTask      = null;
    private JButton _btnEditTask     = null;
    private JButton _btnCutTask      = null;
    private JButton _btnPasteTask    = null;
    private JButton _btnNewProject   = null;
    private JButton _btnEditProject  = null;
    private JButton _btnCutProject   = null;
    private JButton _btnPasteProject = null;
    private JButton _btnDependencies = null;
    private ToDoTask _pToDoTask = null;
    private CardLayout _pCardLayout = null;
    private JPanel _pnlToDoListOrTask = null;
    private JTextField _txtTask = null;
    private JTextArea _txtComment = null;
    private JTextField _txtStart = null;
    private JTextField _txtWorkStart = null;
    private JTextField _txtDeadline = null;
    private JTextField _txtFinished = null;
    private JTextField _txtClosed = null;
    private JComboBox _cmbPriority = null;
    private OKCancelPanel _ocpTask = null;
    private JList _lstToDo = null;
    private JTextArea _txtNotes = null;
    private ViewController _pViewController = null;
    private JSplitPane _pSplitPane = null;

    /**
     * We want to rely on the fact that always one item is
     * selected in the tree. Also, always at least one node
     * has to be present in the tree.
     */
    private JTree _trToDos                 = null;
    private DefaultMutableTreeNode _tnRoot = null;
    private DefaultMutableTreeNode _ndLast = null;
    private JTextField _txtInput           = null;
    private OKCancelPanel _pOKCancelPanel  = null;

    private static final JDialog DLG_DUMMY = new JDialog();
    private WindowAdapter _waOKCancelPanel = null;
    private WindowAdapter _waOKCancelTask  = null;
    private boolean _bTaskVisible          = false;
    private boolean _bInputVisible         = false;
    private boolean _bInputVisibleBefore   = false;

    private boolean _bEditProjectName      = false;

    private int                    _state        = STATE_STARTUP;
    private DefaultMutableTreeNode _tnCutProject = null;
    private ToDoTask               _cutToDoTask  = null;

    /**
     * This method en- and disables buttons etc. depending
     * on the current state of the application.<p>
     *
     * At appropriate places in the code this method has
     * to be invoked to keep the state uptodate.
     */
    private void _setState( int state_ ) {
        if ( state_ == STATE_NORMAL ) {
            // Ensable all buttons except ok and cancel.
            // Ensable tree as well.

            boolean bTaskSelected = _lstToDo
                                    .getSelectedIndex() 
                                     != -1;

            // one project should always be selected in tree
            _btnNewProject  .setEnabled( true                  );
            _btnEditProject .setEnabled( true                  );
            _btnCutProject  .setEnabled( true                  );
            _btnPasteProject.setEnabled( _tnCutProject != null );
            _btnNewTask     .setEnabled( true                  );
            _btnEditTask    .setEnabled( bTaskSelected         );
            _btnCutTask     .setEnabled( bTaskSelected         );
            _btnPasteTask   .setEnabled( _cutToDoTask != null  );

            _trToDos        .setEnabled( true                  );
        } else if ( state_ == STATE_INPUT ) {
            Util.panicIf( _state != STATE_NORMAL );

            // Disable all buttons except ok and cancel.
            // Disable tree as well.
            _btnNewProject  .setEnabled( false );
            _btnEditProject .setEnabled( false );
            _btnCutProject  .setEnabled( false );
            _btnPasteProject.setEnabled( false );
            _btnNewTask     .setEnabled( false );
            _btnEditTask    .setEnabled( false );
            _btnCutTask     .setEnabled( false );
            _btnPasteTask   .setEnabled( false );

            _trToDos        .setEnabled( false );
        } 

        _state = state_;
    }

    private void _setInputVisible( boolean bVisible_ ) {
        if ( _bInputVisible == bVisible_ ) {
            // nothing to do
            return;
        }

        String sProjectName = _txtInput.getText();
        if ( _bEditProjectName ) {
            sProjectName = _getSelectedProject().toString();
            Util.debug( this
                        , "_setInputVisible(..).sProjectName: "
                          + sProjectName );
        }

        _txtInput.setText   ( sProjectName );
        _txtInput.setVisible( bVisible_    );
        _pOKCancelPanel.setVisible( bVisible_ );
        _txtInput.invalidate();
        _pOKCancelPanel.invalidate();
        validate();
        _pOKCancelPanel.validate();

        if ( bVisible_ ) {
            _setState( STATE_INPUT );
            _txtInput.requestFocus();
            _waOKCancelPanel.windowOpened( null );
            _bInputVisibleBefore = true;
        } else {
            _setState( STATE_NORMAL );
            if ( _bInputVisibleBefore ) {
                _waOKCancelPanel.windowClosed( null );
                _bInputVisibleBefore = false;
            }
        }

        if ( _trToDos != null && _txtInput != null ) {
            _trToDos.setEnabled( !bVisible_ );
            _txtNotes.setEnabled( !bVisible_ );
        }

        _bInputVisible = bVisible_;
    }

    private void _sort() {
	Util.debug( this, "_sort().START: " + (new Date()) );
        // create comparator
        Comparable pComparable = new ToDoComparable();
        /*Comparable() {
            public int compare( Object oFirst_, Object oSecond_ ) {
                ToDoTask tdFirst = (ToDoTask)oFirst_;
                ToDoTask tdSecond = (ToDoTask)oSecond_;
		//Util.debug( this, "compare(..).tdFirst: " +
		//	    tdFirst );
		//Util.debug( this, "compare(..).tdSecond: " +
		//tdSecond );
                int retVal = tdFirst.getFinished().compareTo( tdSecond.getFinished() );
                if ( retVal == 0 ) {
                    String sDeadline1 = tdFirst.getDeadline();
                    String sDeadline2 = tdSecond.getDeadline();
                    if ( Util.isEmpty( sDeadline1 ) ) {
                        sDeadline1 = "Z";
                    }
                    if ( Util.isEmpty( sDeadline2 ) ) {
                        sDeadline2 = "Z";
                    }
                    retVal = sDeadline1.compareTo( sDeadline2 );
                }
                if ( retVal == 0 ) {
                    retVal = tdFirst.getPriority().compareTo( tdSecond.getPriority() );
                }
                if ( retVal == 0 ) {
                    retVal = tdFirst.getStart().compareTo( tdSecond.getStart() );
                }
                if ( retVal == 0 ) {
                    retVal = tdFirst.getTitle().compareTo( tdSecond.getTitle() );
                }
		//Util.debug( this, "compare(..).retVal: " +
		//  retVal );

                return retVal;
            }
        };*/

	// get selected node
	DefaultMutableTreeNode pSelectedNode = (DefaultMutableTreeNode)_trToDos.
	    getLastSelectedPathComponent();
        if ( pSelectedNode == null ) {
	    Util.debug( this, "_sort().NOTHING_SELECFTED" );

            return;
        }
        Vector vToDoTasks = (Vector)pSelectedNode.getUserObject();
	Util.debug( this, "_sort().vToDoTasks.size: " + 
		    vToDoTasks.size() );

	// sort
	Util.sortFast( vToDoTasks, pComparable );
	pSelectedNode.setUserObject( vToDoTasks );

	// fill list model
        DefaultListModel dlmToDo = (DefaultListModel)_lstToDo.getModel();
        /*Enumeration eToDoTasks = dlmToDo.elements();
        Vector vToDoTasks = Util.sort( eToDoTasks, pComparable );*/
        _lstToDo.clearSelection();
        dlmToDo.removeAllElements();
        Enumeration eToDoTasks = vToDoTasks.elements();
	int index = 0;
        while( eToDoTasks.hasMoreElements() ) {
	    index++;
            dlmToDo.addElement( "" + index + " " + eToDoTasks.nextElement().toString() );
        }
	Util.debug( this, "_sort().END: " + (new Date()) );
    }

        // create comparator
    public static class ToDoComparable implements Comparable {
	public int compare( Object oFirst_, Object oSecond_ ) {
	    ToDoTask tdFirst = (ToDoTask)oFirst_;
	    ToDoTask tdSecond = (ToDoTask)oSecond_;
	    /*Util.debug( this, "compare(..).tdFirst: " +
	      tdFirst );
	      Util.debug( this, "compare(..).tdSecond: " +
	      tdSecond );*/
	    int retVal = tdFirst.getEnd().compareTo( tdSecond.getEnd() );
            // for closed and finished, order is reverse
            if ( (!tdFirst.isOpen()) && (!tdSecond.isOpen()) ) {
                retVal *= -1;
            }
	    if ( retVal == 0 ) {
		String sDeadline1 = tdFirst.getDeadline();
		String sDeadline2 = tdSecond.getDeadline();
		if ( Util.isEmpty( sDeadline1 ) ) {
		    sDeadline1 = "z";
		}
		if ( Util.isEmpty( sDeadline2 ) ) {
		    sDeadline2 = "z";
		}
		retVal = sDeadline1.compareTo( sDeadline2 );
	    }
	    if ( retVal == 0 ) {
		retVal = tdFirst.getPriority().compareTo( tdSecond.getPriority() );
	    }
	    if ( retVal == 0 ) {
		retVal = tdFirst.getStart().compareTo( tdSecond.getStart() );
	    }
	    if ( retVal == 0 ) {
		retVal = tdFirst.getTitle().compareTo( tdSecond.getTitle() );
	    }
	    /*Util.debug( this, "compare(..).retVal: " +
	      retVal );*/
	    
	    return retVal;
	}
    };

    public static Comparable _pComparable = new ToDoComparable();

    private void _insert( ToDoTask pToDoTask_ ) {
	Util.debug( this, "_insert().START: " + (new Date()) );

	// get selected node
	DefaultMutableTreeNode pSelectedNode = (DefaultMutableTreeNode)_trToDos.
	    getLastSelectedPathComponent();
        if ( pSelectedNode == null ) {
	    Util.debug( this, "_insert().NOTHING_SELECFTED" );

            return;
        }
        Vector vToDoTasks = (Vector)pSelectedNode.getUserObject();
	Util.debug( this, "_insert().vToDoTasks.size: " + 
		    vToDoTasks.size() );

	// sort
	int selectedIndex = Util.insert( vToDoTasks, pToDoTask_, _pComparable );
	pSelectedNode.setUserObject( vToDoTasks );

	// fill list model
        DefaultListModel dlmToDo = (DefaultListModel)_lstToDo.getModel();
        /*Enumeration eToDoTasks = dlmToDo.elements();
	  Vector vToDoTasks = Util.sort( eToDoTasks, pComparable );*/
        /*_lstToDo.clearSelection();
        dlmToDo.removeAllElements();
        Enumeration eToDoTasks = vToDoTasks.elements();
	int index = 0;
        while( eToDoTasks.hasMoreElements() ) {
	    index++;
            dlmToDo.addElement( "" + index + " " + eToDoTasks.nextElement().toString() );
	    }*/
	dlmToDo.insertElementAt( "" + (selectedIndex + 1) + " " + pToDoTask_.toString(), selectedIndex );
	for( int index = 0; index < vToDoTasks.size(); index++ ) {
	    dlmToDo.setElementAt( "" + (index + 1) + " " + vToDoTasks.elementAt( index ), index );
	}
	_lstToDo.setSelectedIndex( selectedIndex );
	Util.debug( this, "_insert().END: " + (new Date()) );
    }


    private void _setTaskVisible( boolean bVisible_ ) {
        if ( _bTaskVisible == bVisible_ ) {
            // nothing to do
            return;
        }

        if ( bVisible_ ) {
            _setState( STATE_TASK );
            _trToDos.setEnabled( false );
            _pCardLayout.show( _pnlToDoListOrTask, S_TASK_NAME );
            _txtStart.setText( _pToDoTask.getStart() );
            _txtTask.setText( _pToDoTask.getTitle() );
            _txtTask.requestFocus();

            _txtComment.setText( _pToDoTask.getComment() );
            _txtClosed.setText( _pToDoTask.getClosed() );
            Util.debug( this, "_setTaskVisible(..)._pToDoTask.getFinished(): --->" +
                        _pToDoTask.getFinished() + "<---" );
            _txtFinished.setText( _pToDoTask.getFinished() );
            _txtDeadline.setText( _pToDoTask.getDeadline() );
            _cmbPriority.setSelectedItem( _pToDoTask.getPriority() );
            //_btnNewTask .setEnabled( false );
            //_btnEditTask.setEnabled( false );
            //_btnCutTask .setEnabled( false );
        } else {
            _setState( STATE_NORMAL );
            _pCardLayout.show( _pnlToDoListOrTask, S_LIST_NAME );
            //_btnNewTask .setEnabled( true );
            //_btnEditTask.setEnabled( true );
	    //_btnCutTask .setEnabled( true );
            //_trToDos.setEnabled( true );
        }

        _bTaskVisible = bVisible_;
    }

    /**
     * Use this method to also get the project name,
     * just append a 'toString()' method.<p>
     *
     * You receive the original object, not a cloned object.
     */
    private ToDoTaskVector _getSelectedProject() {
	DefaultMutableTreeNode pSelectedNode = (DefaultMutableTreeNode)_trToDos.
	    getLastSelectedPathComponent();
        if ( pSelectedNode == null ) {
            return null;
        }

        ToDoTaskVector vToDoTasks = (ToDoTaskVector)pSelectedNode.getUserObject();
	
	return vToDoTasks;
    }

    private void _commitTask() {
	Util.debug( this, "_commitTask().START: " + (new Date()) );
	_pViewController.setModified( true );
        _pToDoTask.setStart( _txtStart.getText() );
        _pToDoTask.setTitle( _txtTask.getText() );
        _pToDoTask.setComment( _txtComment.getText() );
        _pToDoTask.setClosed( _txtClosed.getText() );
        _pToDoTask.setFinished( _txtFinished.getText() );
        _pToDoTask.setDeadline( _txtDeadline.getText() );
        _pToDoTask.setPriority( (ToDoTask.Priority)_cmbPriority.getSelectedItem() );
        // is it a new task?
	ToDoTaskVector vToDoTaskVector = _getSelectedProject();
	Util.panicIf ( vToDoTaskVector == null );
	int index = vToDoTaskVector.indexOf( _pToDoTask );
        if ( index != -1 ) {
            vToDoTaskVector.removeElementAt( index );
	    DefaultListModel dlmToDo = (DefaultListModel)_lstToDo.getModel();
	    dlmToDo.removeElementAt( index );
        }

	_insert( _pToDoTask );
        /*_sort();
	int selectIndex = _getSelectedProject().indexOf( _pToDoTask );
        _lstToDo.setSelectedIndex( selectIndex );*/
	Util.debug( this, "_commitTask().END: " + (new Date()) );
    }
    private JPanel _createToDoTaskPane() {
        JPanel pnlToDoTask = new JPanel();
        pnlToDoTask.setName( S_TASK_NAME );
        pnlToDoTask.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) );

        AutoGridBagLayout agblTask = new AutoGridBagLayout();
        pnlToDoTask.setLayout( agblTask );
        
        agblTask.setAnchor( GridBagConstraints.WEST );

        // start date
        pnlToDoTask.add( new JLabel( "Start Date:   " ) );
        String sTimezonID = _pViewController.getInit().
               getKeyValue( "TimezoneID" ); 
        _txtStart = new DateField( sTimezonID );
        _txtStart.setName( "StartText" );
        //_txtStart.setBorder( SwingUtil.createCCLBorder() );
        _txtStart.setMinimumSize( new Dimension( 100, 24 ) );
        _txtStart.setPreferredSize( new Dimension( 100, 24 ) );
        pnlToDoTask.add( _txtStart );

        agblTask.endLine();

        // work start
        pnlToDoTask.add( new JLabel( "Work start:   " ) );
        _txtWorkStart = new DateField( sTimezonID );
        //_txtWorkStart.setBorder( SwingUtil.createCCLBorder() );
        _txtWorkStart.setMinimumSize( new Dimension( 100, 24 ) );
        _txtWorkStart.setPreferredSize( new Dimension( 100, 24 ) );
        pnlToDoTask.add( _txtWorkStart );

        agblTask.endLine();

        // deadline
        pnlToDoTask.add( new JLabel( "Deadline:   " ) );
        _txtDeadline = new DateField( sTimezonID );
        //_txtDeadline.setBorder( SwingUtil.createCCLBorder() );
        _txtDeadline.setMinimumSize( new Dimension( 100, 24 ) );
        _txtDeadline.setPreferredSize( new Dimension( 100, 24 ) );
        agblTask.setFillVertical();
        pnlToDoTask.add( _txtDeadline );
        agblTask.setFillNone();

        agblTask.endLine();

        // title
        agblTask.setAnchor( GridBagConstraints.NORTHWEST );
        pnlToDoTask.add( new JLabel( "Task:   " ) );
        _txtTask = new JTextField();
        _txtTask.setName( "TitleText" );
        _txtTask.setBorder( SwingUtil.createCCLBorder() );
        _txtTask.setPreferredSize( new Dimension( 100, 24 ) );
        agblTask.setFillHorizontal();
        agblTask.setExpandHorizontal();
        agblTask.setExtend( 2, 1 );
        pnlToDoTask.add( _txtTask );
        agblTask.setExtend( 1, 1 );
        agblTask.setFillNone();
        agblTask.setExpandNone();
        agblTask.endLine();

        // comment
        pnlToDoTask.add( new JLabel( "Comment:   " ) );
        _txtComment = new JTextArea();
        _pViewController.addDateAction( _txtComment );
        _txtComment.setName( "CommentText" );
        _txtComment.setBackground( Color.white );
        JScrollPane scpComment = new JScrollPane( _txtComment );
        scpComment.setBorder( SwingUtil.createCCLBorder() );
        agblTask.setFillBoth();
        agblTask.setExpandBoth();
        agblTask.setExtend( 2, 1 );
        pnlToDoTask.add( scpComment );
        agblTask.setExtend( 1, 1 );
        agblTask.setFillNone();
        agblTask.setExpandNone();
        agblTask.endLine();

        // priority
        pnlToDoTask.add( new JLabel( "Priority:   " ) );
        _cmbPriority = new JComboBox();
        _cmbPriority.addItem( ToDoTask.A );
        _cmbPriority.addItem( ToDoTask.B );
        _cmbPriority.addItem( ToDoTask.C );
        _cmbPriority.addItem( ToDoTask.D );
        pnlToDoTask.add( _cmbPriority );

        // dependencies
        _btnDependencies = new JButton( "Dependencies" );
        //_btnDependencies.addActionListener( this );
        agblTask.setAnchor( AutoGridBagLayout.EAST );
        pnlToDoTask.add( _btnDependencies );
        agblTask.setAnchor( AutoGridBagLayout.WEST );

        agblTask.endLine();

        // closed
        pnlToDoTask.add( new JLabel( "Closed:   " ) );
        _txtClosed = new DateField( sTimezonID );
        //_txtClosed.setBorder( SwingUtil.createCCLBorder() );
        _txtClosed.setMinimumSize( new Dimension( 100, 24 ) );
        _txtClosed.setPreferredSize( new Dimension( 100, 24 ) );
        agblTask.setFillVertical();
        pnlToDoTask.add( _txtClosed );
        agblTask.setFillNone();

        agblTask.endLine();

        // finished
        pnlToDoTask.add( new JLabel( "Finished:   " ) );
        _txtFinished = new DateField( sTimezonID );
        _txtFinished.setName( "FinishedText" );
        //_txtFinished.setBorder( SwingUtil.createCCLBorder() );
        _txtFinished.setMinimumSize( new Dimension( 100, 24 ) );
        _txtFinished.setPreferredSize( new Dimension( 100, 24 ) );
        agblTask.setFillVertical();
        pnlToDoTask.add( _txtFinished );
        agblTask.setFillNone();

        agblTask.endLine();


        // OK or cancel
        pnlToDoTask.add( new JLabel( " " ) );
        agblTask.endLine();
        
        agblTask.skip();
        agblTask.setAnchor( GridBagConstraints.EAST );
        
        _ocpTask = new OKCancelPanel( DLG_DUMMY, 0 );
        JButton btnOK = _ocpTask.getOKButton();
        _pViewController.getRootPane().setDefaultButton( btnOK );
        _waOKCancelTask = _pOKCancelPanel.getWindowAdapter();
        DLG_DUMMY.removeWindowListener( _waOKCancelTask );
        agblTask.setExtend( 2, 1 );
        pnlToDoTask.add( _ocpTask );
        agblTask.setExtend( 1, 1 );
        
        btnOK.addActionListener
               ( new ActionListener() {
                   public void actionPerformed( ActionEvent pActionEvent_ ) {
                       //String sInput = _txtInput.getText().trim();
                       _commitTask();
                       _setTaskVisible( false );
                   }
               } );
        _ocpTask.getCancelButton().addActionListener
               ( new ActionListener() {
                   public void actionPerformed( ActionEvent pActionEvent_ ) {
                       _setTaskVisible( false );
                   }
               } );

        agblTask.endLine();

        return pnlToDoTask;
    }
    private String _getTimezoneID() {
        return _pViewController.getInit().getKeyValue( "TimezoneID" );
    }

    public ToDoPanel( ViewController pViewController_ ) {
        super();

        _pViewController = pViewController_;

        AutoGridBagLayout pAutoGridBagLayout = 
               new AutoGridBagLayout( 2 );

        setLayout( pAutoGridBagLayout );
        setBorder( BorderFactory.createEmptyBorder( 7, 5, 5, 5 ) );
        pAutoGridBagLayout.setAnchor( GridBagConstraints.WEST );

        // buttons
        {
            _btnNewProject = new JButton( "New Project" );
            _btnNewProject.setName( "AddButton" );
            _btnNewProject.addActionListener
                   ( new ActionListener() {
                       public void actionPerformed( ActionEvent pActionEvent_ ) {
                           _setInputVisible( true );
                       }
                   } );
            add( _btnNewProject );
            
            _btnEditProject = new JButton( "Edit Project" );
            _btnEditProject.setName( "EditButton" );
            _btnEditProject.addActionListener
                   ( new ActionListener() {
                       public void actionPerformed( ActionEvent pActionEvent_ ) {
                           _bEditProjectName = true;
                           _setInputVisible( true );
                       }
                   } );
            add( _btnEditProject );
            
            _btnCutProject = new JButton( "Cut Project" );
            _btnCutProject.setName( "DeleteButton" );
            _btnCutProject.addActionListener
                   ( new ActionListener() {
                       public void actionPerformed( ActionEvent pActionEvent_ ) {
                           _setInputVisible( false );
                           deleteNote();
                       }
                   } );
            pAutoGridBagLayout.setFillHorizontal();
            add( _btnCutProject );
            
            _btnPasteProject = new JButton( "Paste Project" );
            _btnPasteProject.setName( "PasteButton" );
            _btnPasteProject.setEnabled( false );
            _btnPasteProject.addActionListener
                   ( new ActionListener() {
                       public void actionPerformed( ActionEvent pActionEvent_ ) {
                           _setInputVisible( false );
                           pasteProject();
                       }
                   } );
            add( _btnPasteProject );
        }

        // input field
        {
            add( new JLabel( " " ) );

            _txtInput = new JTextField();
            _txtInput.setName( "InputText" );
            _txtInput.setBorder( SwingUtil.createCCLBorder() );
            pAutoGridBagLayout.setExpandHorizontal();
            pAutoGridBagLayout.setFillHorizontal();
            add( _txtInput );
            pAutoGridBagLayout.setExpandNone();
            pAutoGridBagLayout.setFillNone();
            
            _pOKCancelPanel = new OKCancelPanel( DLG_DUMMY, 0 );
            JButton btnOK = _pOKCancelPanel.getOKButton();
            _pViewController.getRootPane().setDefaultButton( btnOK );
            _waOKCancelPanel = _pOKCancelPanel.getWindowAdapter();
            DLG_DUMMY.removeWindowListener( _waOKCancelPanel );
            add( _pOKCancelPanel );

            _pOKCancelPanel.getOKButton().addActionListener
                   ( new ActionListener() {
                       public void actionPerformed( ActionEvent pActionEvent_ ) {
                           String sInput = _txtInput.getText().trim();
                           if ( _bEditProjectName ) {
                               editProject( sInput );
                               _bEditProjectName = false;
                           } else {
                               createNewProject( sInput );
                           }
                           _setInputVisible( false );
                       }
                   } );
            _pOKCancelPanel.getCancelButton().addActionListener
                   ( new ActionListener() {
                       public void actionPerformed( ActionEvent pActionEvent_ ) {
                           _setInputVisible( false );
                       }
                   } );
            pAutoGridBagLayout.endLine();
        }

        // ToDoListPanel
        JScrollPane scpToDoList = null;
        {
            DefaultListModel dlmToDo = new DefaultListModel();
            _lstToDo = new JList( dlmToDo );
            _lstToDo.setName( "ToDoList" );
            _lstToDo.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
            _lstToDo.addListSelectionListener( this );
            scpToDoList = new JScrollPane( _lstToDo );
            scpToDoList.setBorder( SwingUtil.createCCLBorder() );
        }

        // ToDoTaskPanel
        JPanel pnlToDoTask = _createToDoTaskPane();

        // ToDoListOrTaskPanel
        {
            _pnlToDoListOrTask = new JPanel();
            _pCardLayout = new CardLayout();
            _pnlToDoListOrTask.setLayout( _pCardLayout );
            _pnlToDoListOrTask.add( scpToDoList, S_LIST_NAME );
            _pnlToDoListOrTask.add( pnlToDoTask, S_TASK_NAME );
        }

        // todo buttons
        {
            pAutoGridBagLayout.setFillHorizontal();
            _btnNewTask = new JButton( "New Task" );
            _btnNewTask.setName( "NewTask" );
            _btnNewTask.addActionListener
                   ( new ActionListener() {
                       public void actionPerformed( ActionEvent pActionEvent_ ) {
                           _pToDoTask = new ToDoTask( _getTimezoneID() );
                           _setTaskVisible( true );
                       }
                   } );
            add( _btnNewTask );

            _btnEditTask = new JButton( "Edit Task" );
            _btnEditTask.setName( "EditTask" );
            _btnEditTask.addActionListener
                   ( new ActionListener() {
                       public void actionPerformed( ActionEvent pActionEvent_ ) {
			   ToDoTaskVector vToDoTaskVector = _getSelectedProject();
			   int index = _lstToDo.getSelectedIndex();
                           if ( index == -1 ) {
                               SwingUtil.showMessage( _pViewController,
                                                      "No ToDo-task selected!" );

                               return;
                           }
                           _pToDoTask = (ToDoTask)vToDoTaskVector.elementAt( index );
                           _setTaskVisible( true );
                       }
                   } );
            add( _btnEditTask );

            _btnCutTask = new JButton( "Cut Task" );
            _btnCutTask.setName( "DeleteTask" );
            _btnCutTask.addActionListener
                   ( new ActionListener() {
                       public void actionPerformed( ActionEvent pActionEvent_ ) {
                           cutTask();
                       }
                   } );
            add( _btnCutTask );

            _btnPasteTask = new JButton( "Paste Task" );
            _btnPasteTask.setName( "PasteTask" );
            _btnPasteTask.addActionListener
                   ( new ActionListener() {
                       public void actionPerformed( ActionEvent pActionEvent_ ) {
                           pasteTask();
                       }
                   } );
            add( _btnPasteTask );

            pAutoGridBagLayout.endLine();
        }

        _txtNotes = new JTextArea();
        JScrollPane scpText = new JScrollPane( _txtNotes );
        scpText.setBorder( SwingUtil.createCCLBorder() );

        DocumentListener pDocumentListener = new DocumentListener() {
            public void insertUpdate(DocumentEvent e) {
                if ( _bValueChanged ) {
                    return;
                }
                _pViewController.stateChanged( null );
            }

            public void removeUpdate(DocumentEvent e) {
                if ( _bValueChanged ) {
                    return;
                }
                _pViewController.stateChanged( null );
            }

            public void changedUpdate(DocumentEvent e) {
                if ( _bValueChanged ) {
                    return;
                }
                _pViewController.stateChanged( null );
            }
        };
        _txtNotes.getDocument().addDocumentListener( pDocumentListener );
        _txtNotes.setName( "_txtNotes" );

        // set date insertion text action
        TextAction pDateTextAction = new DateTextAction();
        Keymap kmOld = _txtNotes.getKeymap();
        Keymap kmNew = _txtNotes.addKeymap( null, kmOld );
        KeyStroke ksCtrlD = KeyStroke.getKeyStroke
               ( KeyEvent.VK_D, Event.CTRL_MASK );
        kmNew.removeKeyStrokeBinding( ksCtrlD );
        kmNew.addActionForKeyStroke
               ( ksCtrlD, pDateTextAction);
        _txtNotes.setKeymap( kmNew );

        _tnRoot = createRootNode();
        _trToDos = new JTree( _tnRoot );
        _trToDos.setName( "ToDoTree" );
        _trToDos.getSelectionModel().
               setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION );
        JScrollPane scpTree = new JScrollPane( _trToDos );
        scpTree.setBorder( SwingUtil.createCCLBorder() );

        _trToDos.addTreeSelectionListener( this );
        _trToDos.setSelectionInterval( 0, 0 );

        _pSplitPane = new JSplitPane();
        _pSplitPane.setBorder( BorderFactory.createEmptyBorder( 3, 0, 0, 0 ) );
        _pSplitPane.setLeftComponent( scpTree );
        _pSplitPane.setRightComponent( _pnlToDoListOrTask );
        _pSplitPane.setDividerSize( 2 );
        _pSplitPane.setDividerLocation( 300 );
        Util.debug( this, "<init>(..).splitpane.size: " +
                    _pSplitPane.getSize() );
        Util.debug( this, "<init>(..).splitpane.dividerLocation: " +
                    _pSplitPane.getDividerLocation() );

        pAutoGridBagLayout.setExtend( 7, 1 );
        pAutoGridBagLayout.setExpandBoth();
        pAutoGridBagLayout.setFillBoth();

        add( _pSplitPane );

        // true necessary or set visible will have no effect
        _bInputVisible = true;
        _setInputVisible( false );
    }

    public boolean isTaskVisible() {
        return _bTaskVisible;
    }

    public void deleteNote() {
        // might not be necessary, but can't hurt
        saveLastNode();

        // get selected note
        TreePath pTreePath = _trToDos.getSelectionPath();
        DefaultMutableTreeNode tnSelected = 
               (DefaultMutableTreeNode)
               pTreePath.getLastPathComponent();

        // don't delete top node
        DefaultMutableTreeNode tnParent = (DefaultMutableTreeNode)tnSelected.getParent();
        if ( tnParent == null ) {
            SwingUtil.showMessage( _pViewController, "Sorry, you can't delete this project!" );
            
            return;
        }

        // sure to delete this note?
        boolean bDelete = SwingUtil.isOKOrCancel
               ( _pViewController, 
                 "Is it OK to delete note\n'" +
                 tnSelected.toString() +
                 "'\nand all of its sub projects?" );

        _tnCutProject = tnSelected;

        // delete it
        if ( bDelete ) {
            DefaultTreeModel pModel = (DefaultTreeModel)_trToDos.getModel();
            TreePath tpParent = pTreePath.getParentPath();
            pModel.removeNodeFromParent( tnSelected );
            
            // make sure something is selected
            _trToDos.setSelectionPath( tpParent );
        }

        _pViewController.setModified( true );
        _btnPasteProject.setEnabled( true );
    }

    public void pasteProject() {
        saveLastNode();

        if ( _tnCutProject == null ) {
            // nothing to paste
            return;
        }

        // find parent
        TreePath pTreePath = _trToDos.getSelectionPath();
        DefaultMutableTreeNode tnParent = (DefaultMutableTreeNode)
               pTreePath.getLastPathComponent();

        // find new index
        Vector pVector = (Vector)_tnCutProject.getUserObject();
        int newIndex = 0;
        Enumeration eChildren = tnParent.children();
        while( eChildren.hasMoreElements() ) {
            DefaultMutableTreeNode tnNext = (DefaultMutableTreeNode)
                   eChildren.nextElement();
            if ( pVector.toString().compareTo
                 ( tnNext.getUserObject().toString() ) < 0 )
            {
                break;
            }
            newIndex++;
        }

        // insert
        DefaultMutableTreeNode tnNew =
               cloneProjectNode( _tnCutProject );
        ((DefaultTreeModel)_trToDos.getModel()).
               insertNodeInto( tnNew, tnParent, newIndex );

        _pViewController.setModified( true );
    }

    public void createNewProject( String sKey_ ) {
        ToDoTaskVector vProject = new ToDoTaskVector( sKey_ );
        
        // find parent
        TreePath pTreePath = _trToDos.getSelectionPath();
        DefaultMutableTreeNode tnParent = (DefaultMutableTreeNode)
               pTreePath.getLastPathComponent();

        // find new index
        int newIndex = 0;
        Enumeration eChildren = tnParent.children();
        while( eChildren.hasMoreElements() ) {
            DefaultMutableTreeNode tnNext = (DefaultMutableTreeNode)
                   eChildren.nextElement();
            if ( vProject.toString().compareTo
                 ( tnNext.getUserObject().toString() ) < 0 )
            {
                break;
            }
            newIndex++;
        }

        // insert
        DefaultMutableTreeNode tnNew =
               new DefaultMutableTreeNode( vProject );
        ((DefaultTreeModel)_trToDos.getModel()).
               insertNodeInto( tnNew, tnParent, newIndex );

        _pViewController.setModified( true );
    }

    public void editProject( String sKey_ ) {
        ToDoTaskVector vProject = _getSelectedProject();
        
        vProject.setName( sKey_ );

        // update tree view
        TreePath pTreePath = _trToDos.getSelectionPath();
        DefaultMutableTreeNode tnSelected = (DefaultMutableTreeNode)
               pTreePath.getLastPathComponent();
        ((DefaultTreeModel)_trToDos.getModel())
               .valueForPathChanged( pTreePath
                                     , vProject );
        
        _pViewController.setModified( true );
    }

    public synchronized void saveLastNode() {
	/*
	  // no real need to save this,
	  // because we always work on vector directly and use list just for presentation.
	  if ( _ndLast != null ) {
	  Vector vProject = (Vector)_ndLast.getUserObject();
	  vProject.removeAllElements();
	  DefaultListModel dlmToDo = (DefaultListModel)_lstToDo.getModel();
	  Enumeration pEnumeration = dlmToDo.elements();
	  while( pEnumeration.hasMoreElements() ) {
	  vProject.addElement( pEnumeration.nextElement() );
	  }
	  }*/
    }

    public void cutTask() {
        _pViewController.setModified( true );
        ToDoTaskVector pToDoTaskVector = _getSelectedProject();
        int index = _lstToDo.getSelectedIndex();
        if ( index == -1 ) {
            Util.printlnErr( "Cut ToDo Task should not be enabled if no task is selected!" );

            SwingUtil.showMessage( _pViewController,
                                   "No ToDo-task selected to cut!" );
            
            return;
        }

        _cutToDoTask = (ToDoTask)((ToDoTask)pToDoTaskVector.elementAt( index )).clone();
        pToDoTaskVector.removeElementAt( index );

        DefaultListModel dlmToDo = (DefaultListModel)_lstToDo.getModel();
        dlmToDo.removeElementAt( index );
        while( index < pToDoTaskVector.size() ) {
            dlmToDo.setElementAt( "" + (index + 1) + " " + pToDoTaskVector.elementAt( index ), index );
            index++;
        }

        _setState( STATE_NORMAL );
    }

    public void pasteTask() {
        if ( _cutToDoTask == null ) {
            // nothing to paste
            Util.printlnErr( "Paste ToDo Task button should be disabled if nothing is to be pasted!" );

            return;
        }

        _pViewController.setModified( true );

        // insert task into project
        _insert( (ToDoTask)_cutToDoTask.clone() );

        _setState( STATE_NORMAL );
    }

    public void insertDate() {
        if ( !_txtNotes.isEnabled() ) {
            return;
        }
        SwingUtil.invokeLaterIfNecessary( new Runnable() {
            public void run() {
                Date pDate = new Date();
                SimpleDateFormat pDateFormat
                       = new SimpleDateFormat ("yyyy-MM-dd");
                _txtNotes.replaceSelection( pDateFormat.format( pDate ) );
                }
        } );
    }

    public class DateTextAction extends TextAction {
        public DateTextAction() {
            super( "DateTextAction" );
        }

        public void actionPerformed( ActionEvent pActionEvent_ ) {
            JTextComponent txtTarget = getTextComponent( pActionEvent_ );
            if ( txtTarget.getName() != null &&
                 txtTarget.getName().equals( "_txtNotes" ) ) 
            {
                insertDate();
            }
        }
    }

    private boolean _bValueChanged = false;

    public synchronized void valueChanged( TreeSelectionEvent pTreeSelectionEvent_ ) {
        //saveLastNode();

        DefaultMutableTreeNode pNode = (DefaultMutableTreeNode)_trToDos.
               getLastSelectedPathComponent();
        if ( pNode == null ) {
            return;
        }
        // set list
        Vector vToDoTasks = (Vector)pNode.getUserObject();
        DefaultListModel dlmToDo = (DefaultListModel)_lstToDo.getModel();
        _lstToDo.clearSelection();
        dlmToDo.removeAllElements();
        Enumeration pEnumeration = vToDoTasks.elements();
	int index = 0;
        while( pEnumeration.hasMoreElements() ) {
	    index++;
            dlmToDo.addElement( "" + index + " " + pEnumeration.nextElement().toString() );
        }
        //_sort();

        _setState( STATE_NORMAL );

        //_ndLast = pNode;
    }

    public void valueChanged( ListSelectionEvent e ) {
        _setState( STATE_NORMAL );
    }

    public static final String getNotesName() {
        return getToDoName();
    }

    public static final String getToDoName() {
        return "ToDo";
    }

    public String getName() {
        return getToDoName();
    }

    public DefaultMutableTreeNode getToDos() {
        saveLastNode();
        
        return _tnRoot;
    }

    public void setNotes( DefaultMutableTreeNode tnRoot_ ) {
        _ndLast = null;
        _tnRoot = tnRoot_;
        _trToDos.setModel( new DefaultTreeModel( _tnRoot ) );
        _trToDos.setSelectionInterval( 0, 0 );
    }

    public static DefaultMutableTreeNode createRootNode() {
        DefaultMutableTreeNode tnRoot = new DefaultMutableTreeNode( new ToDoTaskVector( getToDoName() ) );

        return tnRoot;
    }

    public static DefaultMutableTreeNode cloneProjectNode( DefaultMutableTreeNode tnProject_ ) {
        DefaultMutableTreeNode tnRetVal = null;

        if ( tnProject_ == null ) {
            return tnRetVal;
        }

        // clone vector
        ToDoTaskVector pToDoTaskVector = (ToDoTaskVector)
               (ToDoTaskVector)tnProject_.getUserObject();
        ToDoTaskVector vDestination = new ToDoTaskVector( pToDoTaskVector.toString() );
        Enumeration pEnumeration = pToDoTaskVector.elements();
        while( pEnumeration.hasMoreElements() ) {            
            vDestination.addElement( ((ToDoTask)pEnumeration.nextElement()).clone() );
        }

        tnRetVal = new DefaultMutableTreeNode( vDestination );
        
        // go through children
        for ( int child = 0; child < tnProject_.getChildCount(); child++ ) {
            DefaultMutableTreeNode tnNextChild = (DefaultMutableTreeNode)tnProject_.getChildAt( child );
            DefaultMutableTreeNode tnNextClone = cloneProjectNode( tnNextChild );
            
            // add child to return value
            tnRetVal.add( tnNextClone );
        }

        return tnRetVal;
    }

    /*public Vector getToDoTasks() {
        DefaultListModel dlmToDo = (DefaultListModel)_lstToDo.getModel();

        Vector vRetVal = new Vector();

        Enumeration eToDoTasks = dlmToDo.elements();
        while( eToDoTasks.hasMoreElements() ) {
            vRetVal.addElement( eToDoTasks.nextElement() );
        }

        return vRetVal;
	}*/

    public void setToDos( DefaultMutableTreeNode tnRoot_ ) {
        _tnRoot = tnRoot_;
        _trToDos.setModel( new DefaultTreeModel( _tnRoot ) );
        _trToDos.setSelectionInterval( 0, 0 );
	//_sort();
    }

    public void setToDoTasks( ToDoTaskVector vToDoTasks_ ) {
        DefaultMutableTreeNode tnRoot = new DefaultMutableTreeNode( vToDoTasks_ );
        _tnRoot = tnRoot;
        _trToDos.setModel( new DefaultTreeModel( _tnRoot ) );
        _trToDos.setSelectionInterval( 0, 0 );

        /*_lstToDo.clearSelection();
        DefaultListModel dlmToDo = (DefaultListModel)_lstToDo.getModel();
        dlmToDo.removeAllElements();

        Enumeration eToDoTasks = vToDoTasks_.elements();
        while( eToDoTasks.hasMoreElements() ) {
            dlmToDo.addElement( eToDoTasks.nextElement() );
        }
        _sort();*/
    }
    
    public ToDoTask getToDoTask() {
        return _pToDoTask;
    }

    public void stateChanged( ChangeEvent pChangeEvent_ ) {
        // must be tabbed pane
        JTabbedPane pTabbedPane = (JTabbedPane)pChangeEvent_.getSource();
        if ( pTabbedPane.getSelectedIndex() == 1 ) {
            _pSplitPane.setDividerLocation( 300 );
            pTabbedPane.removeChangeListener( this );
        }
    }
}

2.1   Blocks and Depends On

In order to have a 'depends on' or 'blocks' feature like in the Bugzilla tracking system, we need object identity for each task. Therefore we did add this feature to Jim's todo tasks.

2.1.1   ToDoTask Class

Unfortunately we have to recapitulate again the code we have so far (as of 2000-10-15):
/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is JIM, a Java Information Manager.
 * (http://www.kclee.com/clemens/java/jim/).
 *
 * The Initial Developer of the Original Code is Chr. Clemens Lee.
 * Portions created by Chr. Clemens Lee are Copyright (C) 2000
 * Chr. Clemens Lee. All Rights Reserved.
 *
 * Contributor(s): Chr. Clemens Lee <clemens@kclee.com>
 */

package jim;

import ccl.util.Util;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Vector;

/**
 * 
 * @author  Chr. Clemens Lee
 * @version $Id: ToDoTask.java,v 1.3 2000/01/24 23:48:30 clemens Exp clemens $
 */
public class ToDoTask implements Cloneable {
    public static Priority A = new Priority( "A", 1.0f );
    public static Priority B = new Priority( "B", 2.0f );
    public static Priority C = new Priority( "C", 3.0f );
    public static Priority D = new Priority( "D", 4.0f );

    private String _sTitle   = null;
    private String _sComment = null;
    private Date   _dtStart  = null;

    /** 
     * Each task should have a distinct ID. In practice
     * we want to use the current time in millis.
     */
    private long   _lId      = 0;

    /**
     * No need to work on it earlier. If null, work could or
     * should start immediately.
     */
    private Date _dtWorkStart = null;

    /**
     * Should be finished at this day. If null, it should be
     * finished as soon as possible or when you find the time
     * (or take as much time as you want :-).
     */
    private Date _dtDeadline = null;

    /**
     * Either _dtFinished or _dtClosed have to be set to either
     * make clear this job is done or will nether be done because
     * it is not necessary to finish.
     */
    private Date _dtFinished = null;
	
    /**
     * Either _dtFinished or _dtClosed have to be set to either
     * make clear this job is done or will nether be done because
     * it is not necessary to finish.
     */
    private Date _dtClosed = null;

    /**
     * 0 means, your life depends on it. Top priority is 1.0.
     * Lowest priority should be 3.0 - 5.0, haven't made up
     * my mind yet.
     */
    private Priority _pPriority = null;

    private String _sTimezoneID = null;
    static private long _lLastId = -1;

    static private synchronized long _getNextId() {
        long lNextId = new Date().getTime();
        while( lNextId <= _lLastId ) {
            lNextId++;
        }

        _lLastId = lNextId;

        return lNextId;
    }
    public ToDoTask() {
        super();

        _dtStart     = new Date();
        _sTitle      = "";
        _sComment    = "";
        _pPriority   = C;
        _sTimezoneID = null;
        _lId         = _getNextId();
    }

    public ToDoTask( String sTimezoneID_ ) {
        this();

        _sTimezoneID = sTimezoneID_;
    }

    public void setPriority( Priority pPriority_ ) {
        _pPriority = pPriority_;
    }

    public void setPriority( String sPriority_ ) {
        if ( sPriority_ == null ) {
            _pPriority = C;
        } else if ( sPriority_.equals( "A" ) ) {
            _pPriority = A;
        } else if ( sPriority_.equals( "B" ) ) {
            _pPriority = B;
        } else if ( sPriority_.equals( "D" ) ) {
            _pPriority = D;
        } else {
            _pPriority = C;
        }
    }

    public Priority getPriority() {
        return _pPriority;
    }

    /**
     * The ToDo task is neither finished nor closed.
     */
    public boolean isOpen() {
        return ((_dtFinished == null) &&
                (_dtClosed == null));
    }

    public String getStart() {
        return Util.getStandardDate( _dtStart );
    }

    /**
     * For example: 1999-11-26
     *
     * @return   true on parse error, still current date is set.
     */
    public boolean setStart( String sStart_ ) {
        try {
            String sYear = sStart_.substring( 0, 4 );
            String sMonth = sStart_.substring( 5, 7 );
            String sDay = sStart_.substring( 8, 10 );
            int year = Util.atoi( sYear );
            int month = Util.atoi( sMonth ) - 1;
            int day = Util.atoi( sDay );
            Util.debug( this, "setStart(..).year: " + year );
            Util.debug( this, "setStart(..).month: " + month );
            Util.debug( this, "setStart(..).day: " + day );
            Calendar pCalendar = new GregorianCalendar();
            pCalendar.setTime( _dtStart );
            pCalendar.set( Calendar.YEAR, year );
            pCalendar.set( Calendar.MONTH, month );
            pCalendar.set( Calendar.DAY_OF_MONTH, day );
            _dtStart.setTime( pCalendar.getTime().getTime() );
            Util.debug( this, "setStart(..)._dtStart: " + _dtStart );
        } catch( Exception pException ) {
            Util.debug( this, "setStart(..).pException: " + pException );
            _dtStart = new Date();
            
            return true;
        }
        Util.panicIf( !sStart_.equals( getStart() ),
                      "_dtStart: " + _dtStart );

        return false;
    }

    public void setTitle( String sTitle_ ) {
        _sTitle = sTitle_;
    }

    /**
     * Returns 0 for green, 1 for yellow, 2 for red,
     * 3 for finished, 4 for closed.
     */
    public int getStatus() {
        if ( isFinished() ) {
            return 3;
        }
        if ( isClosed() ) {
            return 4;
        }

        if ( !Util.isEmpty( getDeadline() ) ) {
            String sToday = Util.getDate
                   ( Util.getCalendar( _sTimezoneID ) );
            if ( sToday.compareTo( getDeadline() ) <= 0 ) {
                return 1;
            } else {
                return 2;
            }
        }

        return 0;
    }

    private static final String[] AS_STATUS_HTML = 
    {
        "<font color=\"00FF00\">Green</font>",
        "<font color=\"00FFFF\">Yellow</font>",
        "<font color=\"FF0000\">Red</font>",
        "<font color=\"0000FF\">Done</font>",
        "<font color=\"000000\">Closed</font>"
    };
        
    public String getStatusHTML() {
        return AS_STATUS_HTML[ getStatus() ];
    }

    public String getDate() {
        int status = getStatus();
        if ( status == 3 ) {
            return getFinished();
        } else if ( status == 4 ) {
            return getClosed();
        } else if ( status == 0 ) {
            return getStart();
        }

        // yellow or red
        return getDeadline();
    }

    public String toString() {
        String sRetVal = getPriority() + "   ";
        if ( isFinished() ) {
            sRetVal += "+   " + getFinished() + "   ";
        }
        if ( isClosed() ) {
            sRetVal += "-   " + getClosed() + "   ";
        }
        if ( isOpen() ) {
            if ( !Util.isEmpty( getDeadline() ) ) {
                String sToday = Util.getStandardDate( new Date() );
                if ( sToday.compareTo( getDeadline() ) <= 0 ) {
                    sRetVal += "Y   " + getDeadline() + "   ";
                } else {
                    sRetVal += "R   " + getDeadline() + "   ";
                }
            } else {
                sRetVal += "G   " + getStart() + "   ";
            }
        }
        sRetVal += getTitle();

        return sRetVal;
    }

    public String getTitle() {
        return _sTitle;
    }

    public String getComment() {
        return _sComment;
    }

    public void setComment( String sComment_ ) {
        Util.panicIf( sComment_ == null );

        _sComment = sComment_;
    }

    public boolean isFinished() {
        if ( Util.isEmpty( getFinished() ) ) {
            return false;
        }

        return true;
    }

    public boolean isClosed() {
        return !Util.isEmpty( getClosed() );
    }
    
    public void setClosed( String sClosed_ ) {
        _dtClosed = Util.stringToDate( sClosed_ );
    }

    public void setFinished( String sFinished_ ) {
        _dtFinished = Util.stringToDate( sFinished_ );
    }

    public void setDeadline( String sDeadline_ ) {
        _dtDeadline = Util.stringToDate( sDeadline_ );
    }

    /**
     * @return   "" or well defined string.
     */
    public String getDeadline() {
        String sDeadline = "";
        if ( _dtDeadline != null ) {
            sDeadline = Util.getStandardDate( _dtDeadline );
        }

        return sDeadline;
    }

    /**
     * @return   "" or well defined string.
     */
    public String getClosed() {
        String sClosed = "";
        if ( _dtClosed != null ) {
            sClosed = Util.getStandardDate( _dtClosed );
        }

        return sClosed;
    }

    /**
     * @return   "" or well defined string.
     */
    public String getFinished() {
        String sFinished = "";
        if ( _dtFinished != null ) {
            sFinished = Util.getStandardDate( _dtFinished );
        }

        return sFinished;
    }

    /**
     * End date is either closed date, finished date, or null.
     */
    public String getEnd() {
        String sRetVal = getClosed();
        if ( Util.isEmpty( sRetVal ) ) {
            sRetVal = getFinished();
        }

        return sRetVal;
    }

    public long getId() {
        return _lId;
    }
    public void setId( long lId_ ) {
        _lId = lId_;
    }
    /**
     * Warning, this returns only true if two tasks really are
     * the same, not if they have equal values.
     */
    public boolean equals( Object oToDoTask ) {
        return( this == oToDoTask );
    }

    public Object clone() {
        ToDoTask pToDoTask = new ToDoTask( _sTimezoneID );
        pToDoTask._sTitle = this._sTitle;
        pToDoTask._sComment = this._sComment;

        pToDoTask._dtStart = null;
        if ( this._dtStart != null ) {
            pToDoTask._dtStart = new Date();
            pToDoTask._dtStart.setTime( this._dtStart.getTime() );
        }

        pToDoTask._dtWorkStart = null;
        if ( this._dtWorkStart != null ) {
            pToDoTask._dtWorkStart = new Date();
            pToDoTask._dtWorkStart.setTime( this._dtWorkStart.getTime() );
        }

        pToDoTask._dtDeadline = null;
        if ( this._dtDeadline != null ) {
            pToDoTask._dtDeadline = new Date();
            pToDoTask._dtDeadline.setTime( this._dtDeadline.getTime() );
        }

        pToDoTask._dtFinished = null;
        if ( this._dtFinished != null ) {
            pToDoTask._dtFinished = new Date();
            pToDoTask._dtFinished.setTime( this._dtFinished.getTime() );
        }

        pToDoTask._dtClosed = null;
        if ( this._dtClosed != null ) {
            pToDoTask._dtClosed = new Date();
            pToDoTask._dtClosed.setTime( this._dtClosed.getTime() );
        }

        pToDoTask._pPriority = this._pPriority;

        pToDoTask._lId       = this._lId;

        return pToDoTask;
    }

    public static class Priority extends Object {
        private String _sName = null;
        private String _sComment = null;
        private float _fPriority = 3.0f;

        private Priority( String sName_, float fPriority_ ) {
            super();
            
            _sName = sName_;
            _fPriority = fPriority_;
        }

        public String toString() {
            return _sName;
        }

        public int compareTo( Priority pPriority_ ) {
            return toString().compareTo( pPriority_.toString() );
        }
    }
}
With our first approach adding ids to todo tasks, we used the current time as a long value. But since the user might have to key in the id in the gui, we might want to start counting from 1, so it's easier for the user to remember ids.

Therefore we need to remember the highest id which has been given to any ToDoTask object. This will be a static variable in ToDoTask:

    static private long _lLastId = 0;
The first id we want to use is 1. Therefore we will assign to each new ToDoTask the id:
    static private synchronized long _getNextId() {
        long lNextId = _lLastId + 1;

        _lLastId = lNextId;

        return lNextId;
    }
But when we load a new file Jim has to track the highest id assigned so far. Also, the highest id has to be reset to zero before a new file is loaded.
    public void setId( long lId_ ) {
        _lId = lId_;

        if ( _lId > _lLastId ) {
	         _lLastId = _lId;
        }
    }

    static public void resetLastId() {
        _lLastId = 0;
    }
This code belongs into the open(file) class in the ViewController:
            ToDoTask.resetLastId();

2.1.2   Gui Aspect

We need a user interface for "depends on" and "blocks" tasks. We use simple JTextFields for this purpose where the user can key in a comma separated list of task ids. For the user to be able to do this, he must see the task ids.

Let's have a quick gui layout overview for the ToDoPanel:

*-------------------------------------------------------------*
|             |            |             |           |        |           
| Start date: | ********** |             |       Id: | XXXXXX |
|             |            |             |           |        |
| Work start: | ********** |             |           |        |
|             |            |             |           |        |
| Deadline:   | ********** |             |           |        |
|             |            |             |           |        |
| Task:       | ********************************************* |
|             |            |             |                    |
| Comment:    | ********************************************* |
|             | ********************************************* |
|             | ********************************************* |
|             | ********************************************* |
|             | ********************************************* |
|             | ********************************************* |
|             |            |             |           |        |
| Priority:   | **         |             |           |        |
|             |            |             |           |        |
| Closed:     | ********** | Depends on: | ****************** |
|             |            |             |           |        |
| Finished:   | ********** | Blocks:     | ****************** |
|             |            |             |           |        |
|             |            |             |           |        |
|             |            |             |           |        |
|             |            |                <  OK  > <Cancel> |
|             |            |             |           |        |
*-------------------------------------------------------------*
This results in the following code in the ToDoPanel constructor:
    private JTextField _txtDependsOn = null;
    private JTextField _txtBlocks    = null;
    private JLabel     _lblId        = null;

    private JPanel _createToDoTaskPane() {
        String sTimezonID = _pViewController.getInit().
               getKeyValue( "TimezoneID" ); 

        _txtStart = new DateField( sTimezonID );
        _txtStart.setName         ( "StartText" );
        _txtStart.setMinimumSize  ( new Dimension( 100, 24 ) );
        _txtStart.setPreferredSize( new Dimension( 100, 24 ) );

        _lblId = new JLabel( "XXXXXX" );

        _txtWorkStart = new DateField( sTimezonID );
        _txtWorkStart.setMinimumSize  ( new Dimension( 100, 24 ) );
        _txtWorkStart.setPreferredSize( new Dimension( 100, 24 ) );

        _txtDeadline = new DateField( sTimezonID );
        _txtDeadline.setMinimumSize  ( new Dimension( 100, 24 ) );
        _txtDeadline.setPreferredSize( new Dimension( 100, 24 ) );

        _txtTask = new JTextField();
        _txtTask.setName         ( "TitleText"                 );
        _txtTask.setBorder       ( SwingUtil.createCCLBorder() );
        _txtTask.setPreferredSize( new Dimension( 100, 24 )    );

        _txtComment = new JTextArea();
        _txtComment.setName( "CommentText" );
        _txtComment.setBackground( Color.white );
        JScrollPane scpComment = new JScrollPane( _txtComment );
        scpComment.setBorder( SwingUtil.createCCLBorder() );
        _pViewController.addDateAction( _txtComment );

        _cmbPriority = new JComboBox();
        _cmbPriority.addItem( ToDoTask.A );
        _cmbPriority.addItem( ToDoTask.B );
        _cmbPriority.addItem( ToDoTask.C );
        _cmbPriority.addItem( ToDoTask.D );

        _txtClosed = new DateField( sTimezonID );
        _txtClosed.setMinimumSize  ( new Dimension( 100, 24 ) );
        _txtClosed.setPreferredSize( new Dimension( 100, 24 ) );

        _txtFinished = new DateField( sTimezonID );
        _txtFinished.setName         ( "FinishedText"           );
        _txtFinished.setMinimumSize  ( new Dimension( 100, 24 ) );
        _txtFinished.setPreferredSize( new Dimension( 100, 24 ) );

        _txtDependsOn = new JTextField();
        _txtBlocks    = new JTextField();
        _txtDependsOn.setBorder( SwingUtil.createCCLBorder() );
        _txtBlocks   .setBorder( SwingUtil.createCCLBorder() );

        _ocpTask = new OKCancelPanel( DLG_DUMMY, 0 );
        JButton btnOK = _ocpTask.getOKButton();
        _pViewController.getRootPane().setDefaultButton( btnOK );
        _waOKCancelTask = _pOKCancelPanel.getWindowAdapter();
        DLG_DUMMY.removeWindowListener( _waOKCancelTask );

        btnOK.addActionListener
               ( new ActionListener() {
                   public void actionPerformed( ActionEvent pActionEvent_ ) {
                       _commitTask();
                       _setTaskVisible( false );
                   }
               } );
        _ocpTask.getCancelButton().addActionListener
               ( new ActionListener() {
                   public void actionPerformed( ActionEvent pActionEvent_ ) {
                       _setTaskVisible( false );
                   }
               } );


        JPanel pnlToDoTask = new JPanel();
        pnlToDoTask.setName( S_TASK_NAME );
        pnlToDoTask.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) );

        AutoGridBagLayout agblTask = new AutoGridBagLayout();

        agblTask.setLayoutOn        ( pnlToDoTask                       );
        agblTask.setAnchor          ( AutoGridBagLayout.WEST            );

        // start date
        agblTask.add                ( pnlToDoTask
                                      , new JLabel( "Start date:   " )  );
        agblTask.add                ( pnlToDoTask
                                      , _txtStart                       );

        // Id
        agblTask.skip               ();

        agblTask.setAnchor          ( AutoGridBagLayout.EAST            );
        agblTask.setExpandHorizontal();
        agblTask.add                ( pnlToDoTask
                                      , new JLabel( "Id:   " )          );
        agblTask.setExpandNone      ();
        agblTask.add                ( pnlToDoTask, _lblId               );
        agblTask.setAnchor          ( AutoGridBagLayout.WEST            );
        agblTask.endLine            ();

        // work start
        agblTask.add                ( pnlToDoTask
                                      , new JLabel( "Work start:   " )  );
        agblTask.add                ( pnlToDoTask, _txtWorkStart        );
        agblTask.endLine            ();

        // deadline
        agblTask.add                ( pnlToDoTask
                                      , new JLabel( "Deadline:   " )    );
        agblTask.setFillVertical    ();
        agblTask.add                ( pnlToDoTask, _txtDeadline         );
        agblTask.setFillNone        ();

        agblTask.endLine            ();

        // title
        agblTask.add                ( pnlToDoTask
                                      , new JLabel( "Task:   " )        );
        agblTask.setFillHorizontal  ();
        agblTask.setExpandHorizontal();
        agblTask.setExtend          ( 4, 1                              );
        agblTask.add                ( pnlToDoTask, _txtTask             );
        agblTask.setExtend          ( 1, 1                              );
        agblTask.setFillNone        ();
        agblTask.setExpandNone      ();
        agblTask.endLine            ();

        // comment
        agblTask.setAnchor          ( AutoGridBagLayout.NORTHWEST       );
        agblTask.add                ( pnlToDoTask
                                      , new JLabel( "Comment:   " )     );
        agblTask.setAnchor          ( AutoGridBagLayout.WEST            );
        agblTask.setFillBoth        ();
        agblTask.setExpandBoth      ();
        agblTask.setExtend          ( 4, 1                              );
        agblTask.add                ( pnlToDoTask, scpComment           );
        agblTask.setExtend          ( 1, 1                              );
        agblTask.setFillNone        ();
        agblTask.setExpandNone      ();
        agblTask.endLine            ();

        // priority
        agblTask.add                ( pnlToDoTask
                                      , new JLabel( "Priority:   " )    );
        agblTask.add                ( pnlToDoTask, _cmbPriority         );
        agblTask.endLine();

        // closed
        agblTask.add                ( pnlToDoTask
                                      , new JLabel( "Closed:   " )      );
        agblTask.setFillVertical    ();
        agblTask.add                ( pnlToDoTask, _txtClosed           );
        agblTask.setFillNone        ();

        // Depends on
        agblTask.add                ( pnlToDoTask
                                      , new JLabel( "  Depends on: " )  );
        agblTask.setFillBoth        ();
        agblTask.setExpandHorizontal();
        agblTask.setExtend          ( 2, 1                              );
        agblTask.add                ( pnlToDoTask, _txtDependsOn        );
        agblTask.setExtend          ( 1, 1                              );
        agblTask.setExpandNone      ();
        agblTask.setFillNone        ();
        agblTask.endLine            ();

        // finished
        agblTask.add                ( pnlToDoTask
                                      , new JLabel( "Finished:   " )    );
        agblTask.setFillVertical    ();
        agblTask.add                ( pnlToDoTask, _txtFinished         );
        agblTask.setFillNone        ();

        // Blocks
        agblTask.add                ( pnlToDoTask
                                      , new JLabel( "  Blocks: " )      );
        agblTask.setFillBoth        ();
        agblTask.setExpandHorizontal();
        agblTask.setExtend          ( 2, 1                              );
        agblTask.add                ( pnlToDoTask, _txtBlocks           );
        agblTask.setExtend          ( 1, 1                              );
        agblTask.setExpandNone      ();
        agblTask.setFillNone        ();
        agblTask.endLine            ();


        // OK or cancel
        agblTask.add                ( pnlToDoTask, new JLabel( " " )    );
        agblTask.endLine            ();
        
        agblTask.setAnchor          ( GridBagConstraints.EAST           );
        
        agblTask.setExtend          ( 5, 1                              );
        pnlToDoTask.add             ( _ocpTask                          );
        agblTask.setExtend          ( 1, 1                              );
        agblTask.endLine            ();
        
        return pnlToDoTask;
    }
The id label is set in method '_setTaskVisible(..)':
            _lblId.setText( Util.ltoa( _pToDoTask.getId() ) );
Now we have our gui created and the id field is already set. But once the user added some bug ids, this has to be stored in the ToDoTask object. Also when a ToDoTask becomes visible the text fields need to have their text set as well.
The methods used to retrieve data from an ToDoTask object are defined in 'Saving and Loading'.

At some point we need to convert a vector of strings to a comma separated string and vice versa. These two routines could either go into the ToDoPanel or ToDoTask class. I vote for the ToDoTask class.

In the '_setTaskVisible' method the dependencies and blocks are set.

            _txtDependsOn.setText( _pToDoTask.getDependenciesString() );
            _txtBlocks   .setText( _pToDoTask.getBlocksString      () );
And in the '_commitTask' method the result is set back to the ToDoTask object.
        _pToDoTask.setDependencies( _txtDependsOn.getText() );
        _pToDoTask.setBlocks      ( _txtBlocks   .getText() );

2.1.3   Saving and Loading

Right now the user is responsible to separate all input ids with a comma. The depends on and blocks data is not used directly inside Jim anyway. Its current use will be for xml to html conversions. Maybe we need for each id a separate node. We store two vectors in ToDoTask objects.
    /**
     * This vector contains id strings to other tasks which
     * have to be finished before this one can be finished.
     */
    private Vector _vDependencies = new Vector();

    /**
     * This vector contains id strings to other tasks which
     * can't be finished unless this task is finished.
     */
    private Vector _vBlocks       = new Vector();

    public void setDependencies( Vector vDependencies_ ) {
        _vDependencies = (Vector)vDependencies_.clone();
    }

    public Vector getDependencies() {
        return (Vector)_vDependencies.clone();
    }

    public void setBlocks( Vector vBlocks_ ) {
        _vBlocks = (Vector)vBlocks_.clone();
    }

    public Vector getBlocks() {
        return (Vector)_vBlocks.clone();
    }

    public void setDependencies( String sDependencies_ ) {
        _vDependencies = Util.stringToLines( Util.replace( sDependencies_, " ", "" ), ',' );
    }

    public void setBlocks( String sBlocks_ ) {
        _vBlocks = Util.stringToLines( Util.replace( sBlocks_, " ", "" ), ',' );
    }

    public String getDependenciesString() {
        return Util.concat( _vDependencies, ", " );
    }

    public String getBlocksString() {
        return Util.concat( _vBlocks, ", " );
    }
Save

For saving it is time to dump the dom and just print out xml strings into a file. Much simpler and no memory problems. I don't know why I got the other idea in the first place. Well, it maybe was easier to convert java text to xml text.

Inside the loop in '_createTODOProjectElement' we add the dependencies and blocks vector. Do we create a parent node first for them are do we add a variable number of <DEPENDSON> and <BLOCKS> nodes directly into the <TODO> node? Having a parent node might add more complexity to id, so we go without it.

            Enumeration eDependencies = pToDoTask.getDependencies().elements();
            while( eDependencies.hasMoreElements() ) {
                String sDependsOn = (String)eDependencies.nextElement();

                ElementNode enDependsOn = (ElementNode)pXmlDocument_.createElement( "DEPENDSON" );
                enToDo.appendChild( enDependsOn );

                pText = pXmlDocument_.createTextNode( sDependsOn );
                enDependsOn.appendChild( pText );
            }

            Enumeration eBlocks = pToDoTask.getBlocks().elements();
            while( eBlocks.hasMoreElements() ) {
                String sBlocks = (String)eBlocks.nextElement();

                ElementNode enBlocks = (ElementNode)pXmlDocument_.createElement( "BLOCKS" );
                enToDo.appendChild( enBlocks );

                pText = pXmlDocument_.createTextNode( sBlocks );
                enBlocks.appendChild( pText );
            }
It is important that the <PRIORITY> tag is the last to be use when saving as this will be used with sax to find the end of a task when loading! Load

We will need a method in ToDoTask to add a single dependson or block id.

    public void addDependency( String sId_ ) {
        _vDependencies.addElement( sId_ );
    }

    public void addBlock( String sId_ ) {
        _vBlocks.addElement( sId_ );
    }
We then use this in the JimDocumentHandler.

2.1.4   ToDo

There are no checks what the user does. Does the id exist at all? Are there circles? The user also is not able to select dependencies. A depends on should automatically add a blocks somewhere elsea and vice versa. What happens with deleting other tasks. To keep the consistency it check and be still reasonably fast we might need a database optimized for xml handling (ObjectStore? Something freeware?).

When adding a new task and then cancelling it the largest id still gets increased.

3   ViewController

jim.ViewController is the central gui and infrastructure class.

Links


Last revised:   2000-10-15