package org.openswing.swing.gantt.client;

import java.beans.*;
import java.math.*;
import java.text.*;
import java.util.*;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

import org.openswing.swing.client.*;
import org.openswing.swing.gantt.java.*;
import org.openswing.swing.message.receive.java.*;
import org.openswing.swing.util.client.*;
import org.openswing.swing.util.java.*;


/**
 * <p>Title: OpenSwing Framework</p>
 * <p>Description: Panel that contains a grid on the left side (the gantt legend) and a gantt diagram on the right.
 * Each row of the gannt is defined by a GanttRowVO object, that contains the legend values for the row and a set of Appointment objects.
 * When the set of Appointment objects are read, this control analyze the objects and
 * split an Appointment in more than one, if the Appointment is spreaded on more days, so that each new Appointment is bound inside a day.
 * After editing the gantt, the set of changed Appointments of a specified row can be retrieved
 * by means of "getAppointments" method that analyzes Appointment objects: if there exists two consecutive Appointments,
 * then it joins them in a unique Appointment object, so that the method returns the minimal set of objects.</p>
 * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
 *
 * <p> This file is part of OpenSwing Framework.
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the (LGPL) Lesser General Public
 * License as published by the Free Software Foundation;
 *
 *                GNU LESSER GENERAL PUBLIC LICENSE
 *                 Version 2.1, February 1999
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *       The author may be contacted at:
 *           maurocarniel@tin.it</p>
 *
 * @author Mauro Carniel
 * @version 1.0
 */
public class GanttControl extends JPanel {

  /** starting date */
  private Date startDate = new Date();

  /** end date */
  private Date endDate = new Date();

  /** rows viewed in this panel */
  private ArrayList ganttRows = new ArrayList();

  JSplitPane splitPane = new JSplitPane();
  BorderLayout borderLayout1 = new BorderLayout();
  JScrollPane scrollPane1 = new JScrollPane();
  JScrollPane scrollPane2 = new JScrollPane();
  Table1Model model1 = new Table1Model();
  Table2Model model2 = new Table2Model();
  JTable table1 = new JTable();
  JTable table2 = new JTable();

  /** column width; default value: 150 pixels */
  private int columnWidth = 120;

  /** gantt data locator */
  private GanttDataLocator ganttDataLocator = null;

  /** gantt parameters */
  private Map ganttParameters = new HashMap();

  /** flag used to show the description above the colored rectangle */
  private boolean showDescription = true;

  /** flag used to show the appointment duration above the colored rectangle */
  private boolean showTime = true;

  /** flag used to load data the first time */
  private boolean firstTime = true;

  /** enable deleting of appointments; default value: <code>true</code> */
  private boolean enableDelete = true;

  /** enable inserting of appointments; default value: <code>true</code> */
  private boolean enableInsert = true;

  /** enable editing of appointments; default value: <code>true</code> */
  private boolean enableEdit = true;

  /** list of AppointmentChangeListener objects */
  private ArrayList appointmentChangeListeners = new ArrayList();

  /** column sizes for the grid (legend area); colleciton of pairs: column index,width */
  private Hashtable preferredSize = new Hashtable();

  /** collection of columns not visible in the grid (legend area) */
  private ArrayList visibility = new ArrayList();

  /** flag used to specify if data loading will be automatically performed when the gantt is set to visible; default value: "true". */
  private boolean autoLoadData = true;


  public GanttControl() {
    Calendar cal = Calendar.getInstance();
    cal.set(cal.HOUR_OF_DAY,0);
    cal.set(cal.MINUTE,0);
    cal.set(cal.SECOND,0);
    cal.set(cal.MILLISECOND,0);
    startDate = cal.getTime();
    cal.set(cal.MONTH,cal.get(cal.MONTH)+1);
    endDate = cal.getTime();
    try {
      jbInit();
      setRowHeight(35);
//      scrollPane1.getViewport().setBackground(ClientSettings.GRID_NOT_EDITABLE_CELL_BACKGROUND);
//      scrollPane2.getViewport().setBackground(ClientSettings.GRID_NOT_EDITABLE_CELL_BACKGROUND);
      table1.setBackground(ClientSettings.GRID_CELL_BACKGROUND);
      table2.setBackground(ClientSettings.GRID_CELL_BACKGROUND);
      table1.getTableHeader().setReorderingAllowed(false);
      table1.setSelectionBackground(ClientSettings.GRID_SELECTION_BACKGROUND);
      table1.setSelectionForeground(ClientSettings.GRID_SELECTION_FOREGROUND);
      table1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      table2.setShowVerticalLines(false);
      table2.setDefaultRenderer(ArrayList.class,new GanttColumnRenderer());
      table2.setDefaultEditor(ArrayList.class,new GanttColumnEditor());
      table2.getTableHeader().setResizingAllowed(false);
      table2.getTableHeader().setReorderingAllowed(false);
      table2.getColumnModel().setColumnMargin(0);

      // add mouse listener to set focus to the selected cell and to show the pop-up menu
      // used to fill in the whole day or remove all day appointments...
      table2.addMouseListener(new MouseAdapter() {

        public void mouseClicked(MouseEvent e) {
          if (!model2.isCellEditable(0,0))
            return;
          if (model2!=null &&
              model2.getRowCount()>0) {
            table2.setRowSelectionInterval(table2.rowAtPoint(e.getPoint()),table2.rowAtPoint(e.getPoint()));
            table2.setColumnSelectionInterval(table2.columnAtPoint(e.getPoint()),table2.columnAtPoint(e.getPoint()));
            table2.editCellAt(table2.rowAtPoint(e.getPoint()),table2.columnAtPoint(e.getPoint()));
          }

          if (model2!=null &&
              model2.getRowCount()>0 &&
              table2.getSelectedRow()!=-1 &&
              table2.getSelectedColumn()!=-1 &&
              SwingUtilities.isRightMouseButton(e)) {
            JPopupMenu p = new JPopupMenu();

            if (enableInsert) {
              JMenuItem sel = new JMenuItem(ClientSettings.getInstance().getResources().getResource("fill in the whole day"));
              p.add(sel);
              sel.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                  // add a new appointment for the selected day...
                  GanttRowVO ganttRow = (GanttRowVO) ganttRows.get(table2.getSelectedRow());
                  ArrayList value = (ArrayList)model2.getValueAt(table2.getSelectedRow(),table2.getSelectedColumn());
                  Appointment a,olda;
                  if (value!=null) {
                    long t1 = startDate.getTime()+86400000L*((long)table2.getSelectedColumn());
                    long t2 = t1+86400000L; // -1 ???
                    for(int i=0;i<value.size();i++) {
                      a = (Appointment)value.get(i);
                      olda = (Appointment)a.clone();
                      if (a.getStartDate().getTime()<t1 &&
                          a.getEndDate().getTime()<=t2 &&
                          enableEdit &&
                          a.isEnableEdit()) {
                        a.setEndDate(new java.sql.Timestamp(t1));
                        a.setDuration(new BigDecimal(getTime(a,table2.getSelectedRow(),table2.getSelectedColumn())));
                        fireAppointmentChangedEvent(table2.getSelectedRow(),olda,a);
                      }
                      else if (a.getEndDate().getTime()>t2 &&
                               a.getStartDate().getTime()>=t1 &&
                               enableEdit &&
                               a.isEnableEdit()) {
                        a.setStartDate(new java.sql.Timestamp(t2));
                        a.setDuration(new BigDecimal(getTime(a,table2.getSelectedRow(),table2.getSelectedColumn())));
                        fireAppointmentChangedEvent(table2.getSelectedRow(),olda,a);
                      }
                      else if (a.getEndDate().getTime()<=t2 &&
                               a.getStartDate().getTime()>=t1 &&
                               enableDelete &&
                               a.isEnableDelete()) {
                        ganttRow.getAppointments().remove(a);
                        fireAppointmentDeletedEvent(table2.getSelectedRow(),a);
                      }
                    }
                  }

                  try {
                    a = (Appointment) ganttRow.getAppointmentClass().newInstance();
                    a.setDescription("");
                    Calendar cal = Calendar.getInstance();
                    GanttWorkingHours whVO = getWhVO(table2.getSelectedRow(),table2.getSelectedColumn());
                    if (whVO==null || whVO.getMorningStartHour()==null || whVO.getAfternoonStartHour()==null) {
                      OptionPane.showMessageDialog(
                          ClientUtils.getParentFrame(GanttControl.this),
                          ClientSettings.getInstance().getResources().getResource("no working hours defined for this day"),
                          ClientSettings.getInstance().getResources().getResource("Attention"),
                          JOptionPane.ERROR_MESSAGE
                      );
                      return;
                    }

                    table2.getCellEditor().stopCellEditing();

                    cal.setTimeInMillis(whVO.getMorningStartHour().getTime());
                    int h = cal.get(cal.HOUR_OF_DAY);
                    int m = cal.get(cal.MINUTE);
                    cal.setTimeInMillis(startDate.getTime()+86400000L*(long)table2.getSelectedColumn());
                    cal.set(cal.HOUR_OF_DAY,h);
                    cal.set(cal.MINUTE,m);
                    cal.set(cal.SECOND,0);
                    cal.set(cal.MILLISECOND,0);
                    a.setStartDate(new java.sql.Timestamp(cal.getTimeInMillis()));

                    cal.setTimeInMillis(whVO.getMorningEndHour().getTime());
                    h = cal.get(cal.HOUR_OF_DAY);
                    m = cal.get(cal.MINUTE);
                    cal.setTimeInMillis(startDate.getTime()+86400000L*(long)table2.getSelectedColumn());
                    cal.set(cal.HOUR_OF_DAY,h);
                    cal.set(cal.MINUTE,m);
                    cal.set(cal.SECOND,0);
                    cal.set(cal.MILLISECOND,0);
                    a.setEndDate(new java.sql.Timestamp(cal.getTimeInMillis()));
                    a.setDuration(new BigDecimal(getTime(a,table2.getSelectedRow(),table2.getSelectedColumn())));
                    ganttRow.getAppointments().add(a);
                    fireNewAppointmentEvent(table2.getSelectedRow(),a);

                    a = (Appointment) ganttRow.getAppointmentClass().newInstance();
                    a.setDescription("");

                    cal.setTimeInMillis(whVO.getAfternoonStartHour().getTime());
                    h = cal.get(cal.HOUR_OF_DAY);
                    m = cal.get(cal.MINUTE);
                    cal.setTimeInMillis(startDate.getTime()+86400000L*(long)table2.getSelectedColumn());
                    cal.set(cal.HOUR_OF_DAY,h);
                    cal.set(cal.MINUTE,m);
                    cal.set(cal.SECOND,0);
                    cal.set(cal.MILLISECOND,0);
                    a.setStartDate(new java.sql.Timestamp(cal.getTimeInMillis()));

                    cal.setTimeInMillis(whVO.getAfternoonEndHour().getTime());
                    h = cal.get(cal.HOUR_OF_DAY);
                    m = cal.get(cal.MINUTE);
                    cal.setTimeInMillis(startDate.getTime()+86400000L*(long)table2.getSelectedColumn());
                    cal.set(cal.HOUR_OF_DAY,h);
                    cal.set(cal.MINUTE,m);
                    cal.set(cal.SECOND,0);
                    cal.set(cal.MILLISECOND,0);
                    a.setEndDate(new java.sql.Timestamp(cal.getTimeInMillis()));
                    ganttRow.getAppointments().add(a);
                    a.setDuration(new BigDecimal(getTime(a,table2.getSelectedRow(),table2.getSelectedColumn())));
                    fireNewAppointmentEvent(table2.getSelectedRow(),a);

                    table2.tableChanged(new TableModelEvent(
                        model2,
                        table2.getSelectedRow(),
                        table2.getSelectedRow(),
                        table2.getSelectedColumn()
                        ));

                  }
                  catch (Throwable ex) {
                    ex.printStackTrace();
                  }
                }
              });
            }

            if (enableDelete) {
              JMenuItem desel = new JMenuItem(ClientSettings.getInstance().getResources().getResource("remove all day appointments"));
              p.add(desel);
              desel.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                  // delete appointments in the selected day...
                  ArrayList value = (ArrayList)model2.getValueAt(table2.getSelectedRow(),table2.getSelectedColumn());
                  GanttRowVO ganttRow = (GanttRowVO) ganttRows.get(table2.getSelectedRow());
                  table2.getCellEditor().stopCellEditing();
                  Appointment a,olda;

                  if (value!=null) {
                    long t1 = startDate.getTime()+86400000L*((long)table2.getSelectedColumn());
                    long t2 = t1+86400000L; // -1 ???
                    for(int i=0;i<value.size();i++) {
                      a = (Appointment)value.get(i);
                      olda = (Appointment)a.clone();
                      if (a.getStartDate().getTime()<t1 &&
                          a.getEndDate().getTime()<=t2 &&
                          enableEdit &&
                          a.isEnableEdit()) {
                        a.setEndDate(new java.sql.Timestamp(t1));
                        a.setDuration(new BigDecimal(getTime(a,table2.getSelectedRow(),table2.getSelectedColumn())));
                        fireAppointmentChangedEvent(table2.getSelectedRow(),olda,a);
                      }
                      else if (a.getEndDate().getTime()>t2 &&
                               a.getStartDate().getTime()>=t1 &&
                               enableEdit &&
                               a.isEnableEdit()) {
                        a.setStartDate(new java.sql.Timestamp(t2));
                        a.setDuration(new BigDecimal(getTime(a,table2.getSelectedRow(),table2.getSelectedColumn())));
                        fireAppointmentChangedEvent(table2.getSelectedRow(),olda,a);
                      }
                      else if (a.getEndDate().getTime()<=t2 &&
                               a.getStartDate().getTime()>=t1 &&
                               enableDelete &&
                               a.isEnableDelete()) {
                        ganttRow.getAppointments().remove(a);
                        fireAppointmentDeletedEvent(table2.getSelectedRow(),a);
                      }
                    }
                  }

                  table2.tableChanged(new TableModelEvent(
                      model2,
                      table2.getSelectedRow(),
                      table2.getSelectedRow(),
                      table2.getSelectedColumn()
                  ));

                }
              });
            }

            p.show(e.getComponent(),e.getX(),e.getY());
          }

        }
      });



      init();

      scrollPane2.getHorizontalScrollBar().addAdjustmentListener(new AdjustmentListener() {
        public void adjustmentValueChanged(AdjustmentEvent e) {
          table2.repaint();
        }
      });
      scrollPane1.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
        public void adjustmentValueChanged(AdjustmentEvent e) {
          if (scrollPane2.getVerticalScrollBar().getValue()!=scrollPane1.getVerticalScrollBar().getValue())
            scrollPane2.getVerticalScrollBar().setValue(scrollPane1.getVerticalScrollBar().getValue());
        }
      });
      scrollPane2.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
        public void adjustmentValueChanged(AdjustmentEvent e) {
          if (scrollPane1.getVerticalScrollBar().getValue()!=scrollPane2.getVerticalScrollBar().getValue())
            scrollPane1.getVerticalScrollBar().setValue(scrollPane2.getVerticalScrollBar().getValue());
        }
      });

    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }


  /**
   * Set start date
   * @param startDate start date
   */
  public final void setStartDate(Date startDate) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(startDate);
    cal.set(cal.HOUR_OF_DAY,0);
    cal.set(cal.MINUTE,0);
    cal.set(cal.SECOND,0);
    cal.set(cal.MILLISECOND,0);
    this.startDate = cal.getTime();
    init(model1.getColumnNames());
  }


  /**
   * Set end date.
   * @param endDate end date
   */
  public final void setEndDate(Date endDate) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(endDate);
    cal.set(cal.HOUR_OF_DAY,0);
    cal.set(cal.MINUTE,0);
    cal.set(cal.SECOND,0);
    cal.set(cal.MILLISECOND,0);
    this.endDate = cal.getTime();
    init(model1.getColumnNames());
  }


  private void jbInit() throws Exception {
    this.setLayout(borderLayout1);
    splitPane.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
    splitPane.setDividerSize(1);
    table2.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    scrollPane1.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
    scrollPane2.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
    this.add(splitPane,  BorderLayout.CENTER);
    splitPane.add(scrollPane1, JSplitPane.TOP);
    splitPane.add(scrollPane2, JSplitPane.BOTTOM);
    scrollPane2.getViewport().add(table2, null);
    scrollPane1.getViewport().add(table1, null);
    splitPane.setDividerLocation(150);
  }


  /**
   * Create the grid content with no grid on the left.
   */
  private void init() {
    init(new String[0]);
  }


  /**
   * Create the grid content.
   * @param columnNames column identifiers for the grid on the left
   */
  public void init(String[] columnNames) {
    ganttRows.clear();
    boolean lastEnabled = model1.isEnabled();
    model1 = new Table1Model();
    model1.resetModel(columnNames);
    model1.setEnabled(lastEnabled);
    lastEnabled = model2.isEnabled();
    model2 = new Table2Model();
    model2.resetModel();
    model2.setEnabled(lastEnabled);
    for(int i=0;i<table2.getColumnModel().getColumnCount();i++)
      table2.getColumnModel().getColumn(i).setPreferredWidth(columnWidth);
    table1.repaint();
    table2.repaint();
    if (columnNames.length==0)
      splitPane.setDividerLocation(0);
    else if (splitPane.getDividerLocation()==0)
      splitPane.setDividerLocation(200);
  }


  /**
   * Set columns width.
   * @param width column width
   */
  public final void setColumnWidth(int width) {
    this.columnWidth = columnWidth;
    for(int i=0;i<table2.getColumnModel().getColumnCount();i++)
      table2.getColumnModel().getColumn(i).setPreferredWidth(columnWidth);
    table2.repaint();
  }


  /**
   * @return column width
   */
  public final int getColumnWidth() {
    return columnWidth;
  }



  /**
   * Method called by the JVM to load gannt.
   */
  public final void addNotify() {
  super.addNotify();
    if (!Beans.isDesignTime() && this.isVisible() && firstTime) {
      firstTime = false;
      if (autoLoadData)
        reloadData();
    }
  }


  /**
   * Load data in the gantt panel.
   */
  public void reloadData() {
    ganttRows.clear();
    table1.repaint();
    table2.repaint();
    Response res = ganttDataLocator.loadData(ganttParameters);
    if (res.isError()) {
      OptionPane.showMessageDialog(
          ClientUtils.getParentFrame(this),
          ClientSettings.getInstance().getResources().getResource("Error while loading data")+":\n"+ClientSettings.getInstance().getResources().getResource(res.getErrorMessage()),
          ClientSettings.getInstance().getResources().getResource("Loading Data Error"),
          JOptionPane.ERROR_MESSAGE
      );
    }
    else {
      java.util.List rows = ( (VOListResponse) res).getRows();
      GanttRowVO vo = null;
      for(int i=0;i<rows.size();i++) {
        vo = (GanttRowVO)rows.get(i);
        addRow(vo);
      }
    }
  }



  /**
   * Add a row to the panel (grid on the left + gantt).
   */
  public final void addRow(GanttRowVO ganttRow) {
    ganttRows.add(ganttRow);
    table1.revalidate();
    table2.revalidate();
    table1.repaint();
    table2.repaint();
  }



  /**
   * Set gantt editability.
   * @param enabled <code>true</code> if the gantt is editable, <code>false</code> otherwise
   */
  public final void setGanttEnabled(boolean enabled) {
    model2.setEnabled(enabled);
    table2.repaint();
  }


  /**
   * @return gantt editability
   */
  public final boolean isGanttEnabled() {
    return model2.isEnabled();
  }



  /**
   * Set editability for the grid on the left.
   * @param enabled <code>true</code> if the grid on the left is editable, <code>false</code> otherwise
   */
  public final void setGridEnabled(boolean enabled) {
    model1.setEnabled(enabled);
    if (enabled)
      table1.setBackground(ClientSettings.GRID_CELL_BACKGROUND);
    else
      table1.setBackground(ClientSettings.GRID_NOT_EDITABLE_CELL_BACKGROUND);

    table1.repaint();
  }


  /**
   * @return editability for the grid on the left
   */
  public final boolean isGridEnabled() {
    return model1.isEnabled();
  }


  /**
   * Set row height
   * @param height row height
   */
  public final void setRowHeight(int height) {
    table1.setRowHeight(height);
    table2.setRowHeight(height);
  }


  /**
   * @return row height
   */
  public final int getRowHeight() {
    return table1.getRowHeight();
  }


  /**
   * Set divider location.
   * @param location divider location
   */
  public final void setDividerLocation(int location) {
    splitPane.setDividerLocation(location);
  }


  /**
   * @return divider location
   */
  public final int getDividerLocation() {
    return splitPane.getDividerLocation();
  }


  /**
   * @return gantt data locator
   */
  public final GanttDataLocator getGanttDataLocator() {
    return ganttDataLocator;
  }


  /**
   * Set the gantt data locator.
   * @param ganttDataLocator gantt data locator
   */
  public final void setGanttDataLocator(GanttDataLocator ganttDataLocator) {
    this.ganttDataLocator = ganttDataLocator;
  }


  /**
   * @return gantt parameters
   */
  public final Map getGanttParameters() {
    return ganttParameters;
  }


  /**
   * Set the gantt parameters.
   * @param ganttParameters gantt parameters
   */
  public final void setGanttParameters(Map ganttParameters) {
    this.ganttParameters = ganttParameters;
  }


  /**
   * @param row row in the gantt diagram
   * @return set of Appointment objects defined in the specified row
   */
  public final HashSet getAppointments(int row) {
    GanttRowVO vo = (GanttRowVO)ganttRows.get(row);
    return vo.getAppointments();
  }


  /**
   * @return gantt rows number
   */
  public final int getRowCount() {
    return ganttRows.size();
  }


  /**
   * @return show the description above the colored rectangle
   */
  public final boolean isShowDescription() {
    return showDescription;
  }


  /**
   * Show the description above the colored rectangle.
   * @param showDescription show the description above the colored rectangle
   */
  public final void setShowDescription(boolean showDescription) {
    this.showDescription = showDescription;
  }


  /**
   * @return start date in the gantt
   */
  public final Date getStartDate() {
    return startDate;
  }


  /**
   * @return end date in the gantt
   */
  public final Date getEndDate() {
    return endDate;
  }


  private GanttWorkingHours getWhVO(int row,int column) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(new java.util.Date(startDate.getTime()+(long)column*86400000L));
    GanttWorkingHours whVO = null;
    if (cal.get(cal.DAY_OF_WEEK)==cal.SUNDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getSundayWorkingHours();
    else if (cal.get(cal.DAY_OF_WEEK)==cal.MONDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getMondayWorkingHours();
    else if (cal.get(cal.DAY_OF_WEEK)==cal.TUESDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getTuesdayWorkingHours();
    else if (cal.get(cal.DAY_OF_WEEK)==cal.WEDNESDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getWednesdayWorkingHours();
    else if (cal.get(cal.DAY_OF_WEEK)==cal.THURSDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getThursdayWorkingHours();
    else if (cal.get(cal.DAY_OF_WEEK)==cal.FRIDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getFridayWorkingHours();
    else if (cal.get(cal.DAY_OF_WEEK)==cal.SATURDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getSaturdayWorkingHours();
    return whVO;
  }


  /**
   * @return enable deleting of appointments
   */
  public final boolean isEnableDelete() {
    return enableDelete;
  }


  /**
   * Enable deleting of appointments.
   * @param enableDelete enable deleting of appointments
   */
  public final void setEnableDelete(boolean enableDelete) {
    this.enableDelete = enableDelete;
  }


  /**
   * @return enable inserting of appointments
   */
  public final boolean isEnableInsert() {
    return enableInsert;
  }


  /**
   * Enable inserting of appointments.
   * @param enableInsert enable inserting of appointments
   */
  public final void setEnableInsert(boolean enableInsert) {
    this.enableInsert = enableInsert;
  }


  /**
   * @return show the appointment duration above the colored rectangle
   */
  public final boolean isShowTime() {
    return showTime;
  }


  /**
   * Used to show the appointment duration above the colored rectangle.
   * @param showTime show the appointment duration above the colored rectangle
   */
  public final void setShowTime(boolean showTime) {
    this.showTime = showTime;
  }


  /**
   * Add a AppointmentChangeListener listener to the current gantt control.
   * @param listener listener to add
   */
  public final void addAppointmentChangeListener(AppointmentChangeListener listener) {
    appointmentChangeListeners.add(listener);
  }


  /**
   * Remove the specified AppointmentChangeListener listener from the current gantt control.
   * @param listener listener to remove
   */
  public final void removeAppointmentChangeListener(AppointmentChangeListener listener) {
    appointmentChangeListeners.remove(listener);
  }


  /**
   * Fire a new appointment event.
   * @param rowNumber row number in the GanttControl
   * @param appointment Appointment that fires the event
   */
  protected void fireNewAppointmentEvent(int rowNum,Appointment app) {
    for(int i=0;i<appointmentChangeListeners.size();i++) {
      ((AppointmentChangeListener)appointmentChangeListeners.get(i)).appointmentChange(new AppointmentChangeEvent(
        AppointmentChangeEvent.NEW_APPOINTMENT,
        rowNum,
        app,
        this
      ));
    }
  }


  /**
   * Fire an appointment changed event.
   * @param rowNumber row number in the GanttControl
   * @param appointment Appointment that fires the event
   */
  protected void fireAppointmentChangedEvent(int rowNum,Appointment oldapp,Appointment newapp) {
    for(int i=0;i<appointmentChangeListeners.size();i++) {
      ((AppointmentChangeListener)appointmentChangeListeners.get(i)).appointmentChange(new AppointmentChangeEvent(
        AppointmentChangeEvent.APPOINTMENT_CHANGED,
        rowNum,
        oldapp,
        newapp,
        this
      ));
    }
  }


  /**
   * Fire an appointment deleted event.
   * @param rowNumber row number in the GanttControl
   * @param appointment Appointment that fires the event
   */
  protected void fireAppointmentDeletedEvent(int rowNum,Appointment app) {
    for(int i=0;i<appointmentChangeListeners.size();i++) {
      ((AppointmentChangeListener)appointmentChangeListeners.get(i)).appointmentChange(new AppointmentChangeEvent(
        AppointmentChangeEvent.APPOINTMENT_DELETED,
        rowNum,
        app,
        this
      ));
    }
  }


  /**
   * @param rowNumber row number
   * @param colIndex column index
   * @return Object[] grid content (in the legend area), related to the specified row number
   */
  public final Object getValueAt(int rowNumber,int colIndex) {
    return model1.getValueAt(rowNumber,colIndex);
  }


  /**
   * Set column width.
   * @param columnIndex column index in the grid (legend area)
   * @param width column width to set
   */
  public final void setGridColumnWidth(int columnIndex,int width) {
    table1.getColumnModel().getColumn(columnIndex).setPreferredWidth(width);
    preferredSize.put(new Integer(columnIndex),new Integer(width));
  }


  /**
   * Set column visibility.
   * @param columnIndex column index in the grid (legend area)
   * @param visible column visibility
   */
  public final void setGridColumnVisible(int columnIndex,boolean visible) {
    if (!visible) {
      table1.getColumnModel().removeColumn(table1.getColumnModel().getColumn(columnIndex));
      visibility.add(new Integer(columnIndex));
    }
  }


  /**
   * @return appointment duration (in minutes(
   */
  private long getTime(Appointment a,int row,int column) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(new java.util.Date(startDate.getTime()+(long)column*86400000L));
    GanttWorkingHours whVO = null;
    if (cal.get(cal.DAY_OF_WEEK)==cal.SUNDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getSundayWorkingHours();
    else if (cal.get(cal.DAY_OF_WEEK)==cal.MONDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getMondayWorkingHours();
    else if (cal.get(cal.DAY_OF_WEEK)==cal.TUESDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getTuesdayWorkingHours();
    else if (cal.get(cal.DAY_OF_WEEK)==cal.WEDNESDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getWednesdayWorkingHours();
    else if (cal.get(cal.DAY_OF_WEEK)==cal.THURSDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getThursdayWorkingHours();
    else if (cal.get(cal.DAY_OF_WEEK)==cal.FRIDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getFridayWorkingHours();
    else if (cal.get(cal.DAY_OF_WEEK)==cal.SATURDAY)
      whVO = ((GanttRowVO)ganttRows.get(row)).getSaturdayWorkingHours();

    long m1 = startDate.getTime()+((long)column)*86400000L;
    long m2 = startDate.getTime()+((long)column)*86400000L;
    long a1 = startDate.getTime()+((long)column)*86400000L+86400000L;
    long a2 = startDate.getTime()+((long)column)*86400000L+86400000L;
    if (whVO!=null) {
      if (whVO.getMorningStartHour()!=null) {
        cal.setTime(whVO.getMorningStartHour());
        m1 = startDate.getTime()+((long)column)*86400000L+(whVO!=null?cal.get(cal.HOUR_OF_DAY)*3600000L+cal.get(cal.MINUTE)*60000:0L);
      }
      if (whVO.getMorningEndHour()!=null) {
        cal.setTime(whVO.getMorningEndHour());
        m2 = startDate.getTime()+((long)column)*86400000L+(whVO!=null?cal.get(cal.HOUR_OF_DAY)*3600000L+cal.get(cal.MINUTE)*60000:0L);
      }
      if (whVO.getAfternoonStartHour()!=null) {
        cal.setTime(whVO.getAfternoonStartHour());
        a1 = startDate.getTime()+((long)column)*86400000L+(whVO!=null?cal.get(cal.HOUR_OF_DAY)*3600000L+cal.get(cal.MINUTE)*60000:86400000L);
      }
      if (whVO.getAfternoonEndHour()!=null) {
        cal.setTime(whVO.getAfternoonEndHour());
        a2 = startDate.getTime()+((long)column)*86400000L+(whVO!=null?cal.get(cal.HOUR_OF_DAY)*3600000L+cal.get(cal.MINUTE)*60000:86400000L);
      }
    }

    long d1 = 0;
    long d2 = 0;
    long d3 = 0;
    long d4 = 0;
    long delta = 0;
    if (a.getStartDate().getTime()<=m1) {
      d1 = m1;
      if (a.getEndDate().getTime()<=m2)
        d2 = a.getEndDate().getTime();
      else {
        d2 = m2;
        if (a.getEndDate().getTime()>=a1) {
          d3 = a1;
          if (a.getEndDate().getTime()<=a2)
            d4 = a.getEndDate().getTime();
          else {
            d4 = a2;
            delta = getTime(a,row,column+1);
            if (delta<0)
              delta = 0;
          }
        }
        else {

        }
      }
    }
    else {
      if (a.getStartDate().getTime()>m2) {
        if (a.getStartDate().getTime()>a1) {
          d3 = a.getStartDate().getTime();
          if (a.getEndDate().getTime()<a2)
            d4 = a.getEndDate().getTime();
          else {
            d4 = a2;
            delta = getTime(a,row,column+1);
            if (delta<0)
              delta = 0;
          }
        }
        else {
          if (a1==a2)
            d3 = a.getStartDate().getTime();
          else
            d3 = a1;
          if (a.getEndDate().getTime()<a2)
            d4 = a.getEndDate().getTime();
          else {
            d4 = a2;
            delta = getTime(a,row,column+1);
            if (delta<0)
              delta = 0;
          }
        }
      }
      else {
        d1 = a.getStartDate().getTime();
        if (a.getEndDate().getTime()<=m2)
          d2 = a.getEndDate().getTime();
        else {
          if (a.getEndDate().getTime()<a1)
            d2 = m2;
          else {
            d2 = m2;
            d3 = a1;
            if (a.getEndDate().getTime()<=a2)
              d4 = a.getEndDate().getTime();
            else {
              d4 = a2;
              delta = getTime(a,row,column+1);
              if (delta<0)
                delta = 0;
            }
          }
        }
      }
    }

    return delta+(d2-d1+d4-d3)/60000L;
  }


  /**
   * @return enable editing of appointments
   */
  public final boolean isEnableEdit() {
    return enableEdit;
  }

  /**
   * enable editing of appointments; default value: <code>true</code>.
   * @param enableEdit enable editing of appointments
   */
  public final void setEnableEdit(boolean enableEdit) {
    this.enableEdit = enableEdit;
  }


  /**
   * @return <code>true</code> to automatically load data when the gantt is showed, <code>false</code> otherwise
   */
  public final boolean isAutoLoadData() {
    return this.autoLoadData;
  }


  /**
   * Define if grid must be automatically loaded when it is showed.
   * @param autoLoadData <code>true</code> to automatically load data when the gantt is showed, <code>false</code> otherwise
   */
  public final void setAutoLoadData(boolean autoLoadData) {
    this.autoLoadData = autoLoadData;
  }


















  /**
   * <p>Title: OpenSwing Framework</p>
   * <p>Description: Inner class used to define the table model linked to the grid on the left.</p>
   * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
   * @author Mauro Carniel
   * @version 1.0
   */
  class Table1Model extends AbstractTableModel {

    /** column identifiers */
    private String[] cols = new String[0];

    /** flag used to set grid editability */
    private boolean enabled = true;


    public Table1Model() { }


    /**
     * @return column names
     */
    public final String[] getColumnNames() {
      return cols;
    }


    /**
     * Set grid editability.
     * @param enabled <code>true</code> if the grid is editable, <code>false</code> otherwise
     */
    public final void setEnabled(boolean enabled) {
      this.enabled = enabled;
    }


    /**
     * return grid editability
     */
    public final boolean isEnabled() {
      return this.enabled;
    }


    /**
     * Recalculate date interval and rows.
     */
    public final void resetModel(String[] cols) {
      this.cols = cols;
      table1.setModel(this);
      Enumeration keys = preferredSize.keys();
      Integer colIndex = null;
      while(keys.hasMoreElements()) {
        colIndex = (Integer)keys.nextElement();
        table1.getColumnModel().getColumn(colIndex.intValue()).setPreferredWidth(((Integer)preferredSize.get(colIndex)).intValue());
      }
      for(int i=0;i<visibility.size();i++)
        table1.getColumnModel().removeColumn(table1.getColumnModel().getColumn(((Integer)visibility.get(i)).intValue()));
    }


    public int getColumnCount() {
      return cols.length;
    }


    public String getColumnName(int columnIndex) {
      return cols[columnIndex];
    }


    public int getRowCount() {
      return ganttRows.size();
    }


    public Class getColumnClass(int columnIndex) {
      return ganttRows.size()>0 && getValueAt(0,columnIndex)!=null ?
             getValueAt(0,columnIndex).getClass() :
             Object.class;
    }


    public boolean isCellEditable(int rowIndex, int columnIndex) {
      return enabled;
    }


    public Object getValueAt(int rowIndex, int columnIndex) {
      return ((GanttRowVO)ganttRows.get(rowIndex)).getLegend()[columnIndex];
    }


    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
      ((GanttRowVO)ganttRows.get(rowIndex)).getLegend()[columnIndex] = aValue;
    }


  } // end Table1Model



  /**
   * <p>Title: OpenSwing Framework</p>
   * <p>Description: Inner class used to define the table model linked to the gantt grid.</p>
   * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
   * @author Mauro Carniel
   * @version 1.0
   */
  class Table2Model extends AbstractTableModel {

    /** column identifiers */
    private ArrayList dates = new ArrayList();

    /** flag used to set grid editability */
    private boolean gridEnabled = true;

    public Table2Model() { }


    /**
     * Set grid editability.
     * @param enabled <code>true</code> if the grid is editable, <code>false</code> otherwise
     */
    public final void setEnabled(boolean enabled) {
      this.gridEnabled = enabled;
    }


    /**
     * return grid editability
     */
    public final boolean isEnabled() {
      return this.gridEnabled;
    }


    /**
     * Recalculate date interval and rows.
     */
    public final void resetModel() {
      SimpleDateFormat sdf = new SimpleDateFormat(ClientSettings.getInstance().getResources().getDateMask(Consts.TYPE_DATE));
      Calendar cal = Calendar.getInstance();
      cal.setTime(startDate);
      while(cal.getTimeInMillis()<=endDate.getTime()) {
        dates.add(sdf.format(cal.getTime()));
        cal.set(cal.DAY_OF_MONTH,cal.get(cal.DAY_OF_MONTH)+1);
      }
      table2.setModel(this);
    }


    public int getColumnCount() {
      return dates.size();
    }


    public String getColumnName(int columnIndex) {
      return (String)dates.get(columnIndex);
    }


    public int getRowCount() {
      return ganttRows.size();
    }


    public Class getColumnClass(int columnIndex) {
      return ganttRows.size()>0 && getValueAt(0,columnIndex)!=null ?
             getValueAt(0,columnIndex).getClass() :
             Object.class;
    }


    public boolean isCellEditable(int rowIndex, int columnIndex) {
      return gridEnabled;
    }


    /**
     * @return ArrayList list of Appointment objects defined inside the specified day
     */
    public Object getValueAt(int rowIndex, int columnIndex) {
      long t1 = startDate.getTime()+86400000L*((long)columnIndex);
      long t2 = t1+86400000L; // -1 ???
      ArrayList list = new ArrayList();
      HashSet alist = ((GanttRowVO)ganttRows.get(rowIndex)).getAppointments();
      Appointment a = null;
      Iterator it = alist.iterator();
      while(it.hasNext()) {
        a = (Appointment)it.next();
        if (a.getEndDate().getTime()>=t1 && a.getEndDate().getTime()<=t2 ||
            a.getStartDate().getTime()>=t1 && a.getStartDate().getTime()<=t2 ||
            a.getStartDate().getTime()<=t1 && a.getEndDate().getTime()>=t2)
          list.add(a);
      }
      return list;
    }


    /**
     * Set the ArrayList of Appointment objects defined inside the specified day.
     */
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
      long t1 = startDate.getTime()+86400000L*columnIndex;
      long t2 = t1+86400000L; // -1 ???
      ArrayList list = (ArrayList)aValue;
      HashSet alist = ((GanttRowVO)ganttRows.get(rowIndex)).getAppointments();
      Appointment a = null;
      Iterator it = alist.iterator();
      HashSet toRemove = new HashSet();
      while(it.hasNext()) {
        a = (Appointment)it.next();
        if (a.getEndDate().getTime()>=t1 && a.getEndDate().getTime()<=t2 ||
            a.getStartDate().getTime()>=t1 && a.getStartDate().getTime()<=t2 ||
            a.getStartDate().getTime()<=t1 && a.getEndDate().getTime()>=t2)
          toRemove.add(a);
      }
      alist.removeAll(toRemove);
      alist.addAll(list);
    }


  } // end Table2Model





  /**
   * <p>Title: OpenSwing Framework</p>
   * <p>Description: Interface used as callback in GanttCell class.</p>
   * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
   * <p> </p>
   * @author Mauro Carniel
   * @version 1.0
   */
  interface OldAppointmentCallback {

    public void setLastDescr(String lastDescr);

    public String getLastDescr();

    public void setLastAppointment(Appointment appointment);

    public Appointment getLastAppointment();

  }


  /**
   * <p>Title: OpenSwing Framework</p>
   * <p>Description: Inner class used to render the gantt columns (gantt bar).</p>
   * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
   * @author Mauro Carniel
   * @version 1.0
   */
  class GanttColumnRenderer extends DefaultTableCellRenderer implements OldAppointmentCallback {

    /** cell content */
    private GanttCell canvas = new GanttCell();

    private Calendar cal = Calendar.getInstance();

    /** last description */
    private String lastDescr = null;

    /** last row */
    private int lastRow = -1;

    /** last Appointment processed (viewed) */
    private Appointment appointment = null;


    public void setLastDescr(String lastDescr) {
      this.lastDescr = lastDescr;
    }


    public String getLastDescr() {
      return lastDescr;
    }


    public void setLastAppointment(Appointment appointment) {
      this.appointment = appointment;
    }

    public Appointment getLastAppointment() {
      return appointment;
    }


    public Component getTableCellRendererComponent(JTable table, Object value,
                          boolean isSelected, boolean hasFocus, int row, int column) {
      if (row!=lastRow) {
        lastDescr = null;
        lastRow = row;
      }

      long width = 86400000L;
      cal.setTime(new java.util.Date(startDate.getTime()+(long)column*86400000L));
      GanttWorkingHours whVO = null;
      if (cal.get(cal.DAY_OF_WEEK)==cal.SUNDAY)
        whVO = ((GanttRowVO)ganttRows.get(row)).getSundayWorkingHours();
      else if (cal.get(cal.DAY_OF_WEEK)==cal.MONDAY)
        whVO = ((GanttRowVO)ganttRows.get(row)).getMondayWorkingHours();
      else if (cal.get(cal.DAY_OF_WEEK)==cal.TUESDAY)
        whVO = ((GanttRowVO)ganttRows.get(row)).getTuesdayWorkingHours();
      else if (cal.get(cal.DAY_OF_WEEK)==cal.WEDNESDAY)
        whVO = ((GanttRowVO)ganttRows.get(row)).getWednesdayWorkingHours();
      else if (cal.get(cal.DAY_OF_WEEK)==cal.THURSDAY)
        whVO = ((GanttRowVO)ganttRows.get(row)).getThursdayWorkingHours();
      else if (cal.get(cal.DAY_OF_WEEK)==cal.FRIDAY)
        whVO = ((GanttRowVO)ganttRows.get(row)).getFridayWorkingHours();
      else if (cal.get(cal.DAY_OF_WEEK)==cal.SATURDAY)
        whVO = ((GanttRowVO)ganttRows.get(row)).getSaturdayWorkingHours();
      if (whVO!=null)
        width = (whVO.getMorningEndHour()!=null?(whVO.getMorningEndHour().getTime()-whVO.getMorningStartHour().getTime()):0)+
                (whVO.getAfternoonEndHour()!=null?(whVO.getAfternoonEndHour().getTime()-whVO.getAfternoonStartHour().getTime()):0);

      long m1 = startDate.getTime()+((long)column)*86400000L;
      long m2 = startDate.getTime()+((long)column)*86400000L;
      long a1 = startDate.getTime()+((long)column)*86400000L+86400000L;
      long a2 = startDate.getTime()+((long)column)*86400000L+86400000L;
      if (whVO!=null) {
        if (whVO.getMorningStartHour()!=null) {
          cal.setTime(whVO.getMorningStartHour());
          m1 = startDate.getTime()+((long)column)*86400000L+(whVO!=null?cal.get(cal.HOUR_OF_DAY)*3600000L+cal.get(cal.MINUTE)*60000:0L);
        }
        if (whVO.getMorningEndHour()!=null) {
          cal.setTime(whVO.getMorningEndHour());
          m2 = startDate.getTime()+((long)column)*86400000L+(whVO!=null?cal.get(cal.HOUR_OF_DAY)*3600000L+cal.get(cal.MINUTE)*60000:0L);
        }
        if (whVO.getAfternoonStartHour()!=null) {
          cal.setTime(whVO.getAfternoonStartHour());
          a1 = startDate.getTime()+((long)column)*86400000L+(whVO!=null?cal.get(cal.HOUR_OF_DAY)*3600000L+cal.get(cal.MINUTE)*60000:86400000L);
        }
        if (whVO.getAfternoonEndHour()!=null) {
          cal.setTime(whVO.getAfternoonEndHour());
          a2 = startDate.getTime()+((long)column)*86400000L+(whVO!=null?cal.get(cal.HOUR_OF_DAY)*3600000L+cal.get(cal.MINUTE)*60000:86400000L);
        }
      }

      canvas.setAppointments(
        (ArrayList)value,
        startDate.getTime()+((long)column)*86400000L,
        startDate.getTime()+(((long)column)+1L)*86400000L,
        width,
        m1,
        m2,
        a1,
        a2,
        row,
        this,
        column
      );
      return canvas;
    }


  } // end GanttColumnRenderer




  /**
   * <p>Title: OpenSwing Framework</p>
   * <p>Description: Gantt Cell viewer, used by cell renderer/editor.</p>
   * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
   * @author Mauro Carniel
   * @version 1.0
   */
  class GanttCell extends JPanel {

    private ArrayList appointments = new ArrayList();
    private long t1;
    private long t2;
    private long w;
    private long m1;
    private long m2;
    private long a1;
    private long a2;
    private int row;
    private OldAppointmentCallback callback;
    private int column;

    public void setAppointments(ArrayList appointments,long t1,long t2,long w,long m1,long m2,long a1,long a2,int row,OldAppointmentCallback callback,int column) {
      this.appointments = appointments;
      this.t1 = t1;
      this.t2 = t2;
      this.w = w;
      this.m1 = m1;
      this.m2 = m2;
      this.a1 = a1;
      this.a2 = a2;
      this.row = row;
      this.callback = callback;
      this.column = column;
    }


    public void paint(Graphics g) {
      super.paint(g);
      Appointment a = null;
      int x,len;
      long minutes;
      int delta;
      String time = null;
      if (model2.isEnabled())
        g.setColor(ClientSettings.GRID_CELL_BACKGROUND);
      else
        g.setColor(ClientSettings.GRID_NOT_EDITABLE_CELL_BACKGROUND);
      g.fillRect(0,0,getWidth(),getHeight());
      for(int i=0;i<appointments.size();i++) {
        a = (Appointment)appointments.get(i);
        if (a.getStartDate().getTime()<m1)
          x = 0;
        else {
          if (a.getStartDate().getTime()>m2)
            x = (int)(getWidth()*(m2-m1+a.getStartDate().getTime()-a1)/w);
          else
            x = (int)(getWidth()*(a.getStartDate().getTime()-m1)/w);
        }
        if (a.getEndDate().getTime()>a2)
          len = getWidth()-x;
        else {
          if (a.getEndDate().getTime()<=a1)
            len = (int) (getWidth() * (a.getEndDate().getTime() - m1) / w);
          else
            len = (int) (getWidth() * (m2-m1+a.getEndDate().getTime() - a1) / w); // ???
        }
        if (a.getBackgroundColor()!=null) {
          g.setColor(a.getBackgroundColor());
          g.fillRect(
              x,
              0,
              len,
              getHeight()
          );
        }
        g.setColor(a.getForegroundColor()==null?Color.black:a.getForegroundColor());
        g.fillRect(
          x,
          20,
          len,
          10
        );
        delta = 0;
//        if (showDescription && a.getDescription()!=null && !a.getDescription().equals(callback.getLastDescr()) && len>0) {
        if (showDescription && a.getDescription()!=null && len>0) {
          g.setColor(Color.black);

          if (!a.getDescription().equals(callback.getLastDescr())) {
            callback.setLastDescr(a.getDescription());
            g.drawString(
              callback.getLastDescr().length()>15?
              callback.getLastDescr().substring(0,15):
              callback.getLastDescr(),
              x+2,
              12
            );
            delta = g.getFontMetrics().stringWidth(
                callback.getLastDescr().length()>15?
                callback.getLastDescr().substring(0,15):
                callback.getLastDescr()
            )+2;
          }
          if (showTime) {
            g.setColor(Color.black);
            minutes = getTime(a,row,column);
            time = (minutes/60)+":"+(minutes%60);
            if (!a.equals(callback.getLastAppointment())) {
              callback.setLastAppointment(a);
              g.drawString("["+time+"]",x+2+delta,12);
            }
          }
        }
        if (!showDescription && showTime) {
          g.setColor(Color.black);
          minutes = getTime(a,row,column);
          time = (minutes/60)+":"+(minutes%60);
          if (!a.equals(callback.getLastAppointment())) {
            callback.setLastAppointment(a);
            g.drawString("["+time+"]",x+2+delta,12);
          }
        }

      } // end for
    }



  } // end GanttCell



  /**
   * <p>Title: OpenSwing Framework</p>
   * <p>Description: Column editor used to resize the gantt bar.</p>
   * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
   * @author Mauro Carniel
   * @version 1.0
   */
  public class GanttColumnEditor extends AbstractCellEditor implements TableCellEditor,OldAppointmentCallback {

    private GanttCell label = new GanttCell();
    private JTable table = null;

    /** list of Appointment objects stored in the editing cell */
    private ArrayList value = null;

    /** flag used to store when the mouse is left clicked */
    private boolean isLeftMouseButtonPressed = false;

    /** x pixel stored when user press the mouse the first time */
    private long lastX = -1;

    /** column index related to the edited cell */
    private int lastCellIndex = -1;

    /** flag used to store editing cell selection */
    private boolean isSelected = false;

    /** temporary variable */
    private Calendar cal = Calendar.getInstance();

    /** last description */
    private String lastDescr = null;

    /** last row */
    private int lastRow = -1;

    /** last Appointment processed (viewed) */
    private Appointment appointment = null;


    public void setLastDescr(String lastDescr) {
      this.lastDescr = lastDescr;
    }


    public String getLastDescr() {
      return lastDescr;
    }


    public void setLastAppointment(Appointment appointment) {
      this.appointment = appointment;
    }

    public Appointment getLastAppointment() {
      return appointment;
    }


    public GanttColumnEditor() {
      label.addMouseListener(new MouseAdapter() {
        public void mousePressed(MouseEvent e) {
          if (!table.getModel().isCellEditable(0,0))
            return;
          isLeftMouseButtonPressed = SwingUtilities.isLeftMouseButton(e);
          lastX = e.getX();
        }
        public void mouseReleased(MouseEvent e) {
          if (!table.getModel().isCellEditable(0,0))
            return;
          if (isLeftMouseButtonPressed) {
//            int currentCellIndex = table.convertColumnIndexToModel(table.getSelectedColumn());

            long width1 = 86400000L;
            long m1 = 0;
            GanttWorkingHours whVO1 = getWhVO(lastRow,lastCellIndex);
            if (whVO1!=null) {
              width1 = (whVO1.getMorningEndHour()!=null?(whVO1.getMorningEndHour().getTime()-whVO1.getMorningStartHour().getTime()):0)+
                      (whVO1.getAfternoonEndHour()!=null?(whVO1.getAfternoonEndHour().getTime()-whVO1.getAfternoonStartHour().getTime()):0);

              cal.setTime(whVO1.getMorningStartHour());
              m1 = cal.get(cal.HOUR_OF_DAY)*3600000;
            }
            int currentColIndex = (int)(e.getX()+lastCellIndex*(long)label.getWidth())/label.getWidth();
            long width2 = 86400000L;
            long m2 = 0;
            GanttWorkingHours whVO2 = getWhVO(lastRow,currentColIndex);
            if (whVO2!=null) {
              width2 = (whVO2.getMorningEndHour()!=null?(whVO2.getMorningEndHour().getTime()-whVO2.getMorningStartHour().getTime()):0)+
                      (whVO2.getAfternoonEndHour()!=null?(whVO2.getAfternoonEndHour().getTime()-whVO2.getAfternoonStartHour().getTime()):0);

              cal.setTime(whVO2.getMorningStartHour());
              m2 = cal.get(cal.HOUR_OF_DAY)*3600000;
            }
            long x = 0;
            if (e.getX()>0)
              x = e.getX()%label.getWidth();
            else
              x = label.getWidth()-((-e.getX())%label.getWidth());

            if (value!=null && lastX!=e.getX()) {
              if (lastX<e.getX()) {
                // enlarge appointment width: find out if there exists an appointment at lastX coordinate...
                Appointment a,olda;
                boolean exists = false;
                long t1 = startDate.getTime()+86400000L*lastCellIndex+m1+width1*lastX/(long)label.getWidth();
                long t2 = startDate.getTime()+86400000L*currentColIndex+m2+width2*x/(long)label.getWidth();
                for(int i=0;i<value.size();i++) {
                  a = (Appointment)value.get(i);
                  olda = (Appointment)a.clone();
                  if (a.getStartDate().getTime()<=t1 && t1<=a.getEndDate().getTime()) {
                    if (enableEdit && a.isEnableEdit()) {
                      a.setEndDate(new java.sql.Timestamp(t2));
                      a.setDuration(new BigDecimal(getTime(a, lastRow, lastCellIndex)));
                      fireAppointmentChangedEvent(table2.getSelectedRow(),olda,a);
                    }
                    exists = true;
                    break;
                  }
                }

                if (!exists && enableInsert) {
                  // no appointment found: it will be created another one...
                  try {
                    GanttRowVO ganttRow = (GanttRowVO) ganttRows.get(lastRow);
                    a = (Appointment) ganttRow.getAppointmentClass().newInstance();
                    a.setDescription("");
                    a.setStartDate(new java.sql.Timestamp(t1));
                    a.setEndDate(new java.sql.Timestamp(t2));
                    a.setDuration(new BigDecimal(getTime(a,lastRow,lastCellIndex)));
                    fireNewAppointmentEvent(table2.getSelectedRow(),a);
                    value.add(a);
                  }
                  catch (Throwable ex) {
                    ex.printStackTrace();
                  }

                }
              }
              else {
                // reduce appointment width or delete appointment...
                Appointment a,olda;
//                long t2 = startDate.getTime()+86400000L*x/(long)label.getWidth();
                long t2 = startDate.getTime()+86400000L*currentColIndex+m2+width2*x/(long)label.getWidth();
                for(int i=0;i<value.size();i++) {
                  a = (Appointment)value.get(i);
                  olda = (Appointment)a.clone();
                  if (a.getStartDate().getTime()<=t2 && t2<=a.getEndDate().getTime()) {
                    if (enableEdit && a.isEnableEdit()) {
                      a.setEndDate(new java.sql.Timestamp(t2));
                      if (a.getStartDate().getTime()>=a.getEndDate().getTime() && enableDelete && a.isEnableDelete()) {
                        GanttRowVO ganttRow = (GanttRowVO) ganttRows.get(lastRow);
                        ganttRow.getAppointments().remove(a);
                        fireAppointmentDeletedEvent(table2.getSelectedRow(),a);
                      }
                      else
                        fireAppointmentChangedEvent(table2.getSelectedRow(),olda,a);

                    }
                    break;
                  }
                }
              }

              if (table!=null)
                table.editingStopped(null);
              table2.repaint();

            }
          }
          isLeftMouseButtonPressed = false;
        }
      });
    }


    public Component getTableCellEditorComponent(JTable table, Object value,
                                                 boolean isSelected,
                                                 int row, int column) {
      this.table = table;


      if (row!=lastRow) {
        lastDescr = null;
        lastRow = row;
      }

      long width = 86400000L;
      cal.setTime(new java.util.Date(startDate.getTime()+(long)column*86400000L));
      GanttWorkingHours whVO = getWhVO(row,column);
      if (whVO!=null)
        width = (whVO.getMorningEndHour()!=null?(whVO.getMorningEndHour().getTime()-whVO.getMorningStartHour().getTime()):0)+
                (whVO.getAfternoonEndHour()!=null?(whVO.getAfternoonEndHour().getTime()-whVO.getAfternoonStartHour().getTime()):0);

      this.value = (ArrayList)value;
      this.isSelected = isSelected;
      lastCellIndex = table.convertColumnIndexToModel(column);

      long m1 = startDate.getTime()+((long)column)*86400000L;
      long m2 = startDate.getTime()+((long)column)*86400000L;
      long a1 = startDate.getTime()+((long)column)*86400000L+86400000L;
      long a2 = startDate.getTime()+((long)column)*86400000L+86400000L;
      if (whVO!=null) {
        if (whVO.getMorningStartHour()!=null) {
          cal.setTime(whVO.getMorningStartHour());
          m1 = startDate.getTime()+((long)column)*86400000L+(whVO!=null?cal.get(cal.HOUR_OF_DAY)*3600000L+cal.get(cal.MINUTE)*60000:0L);
        }
        if (whVO.getMorningEndHour()!=null) {
          cal.setTime(whVO.getMorningEndHour());
          m2 = startDate.getTime()+((long)column)*86400000L+(whVO!=null?cal.get(cal.HOUR_OF_DAY)*3600000L+cal.get(cal.MINUTE)*60000:0L);
        }
        if (whVO.getAfternoonStartHour()!=null) {
          cal.setTime(whVO.getAfternoonStartHour());
          a1 = startDate.getTime()+((long)column)*86400000L+(whVO!=null?cal.get(cal.HOUR_OF_DAY)*3600000L+cal.get(cal.MINUTE)*60000:86400000L);
        }
        if (whVO.getAfternoonEndHour()!=null) {
          cal.setTime(whVO.getAfternoonEndHour());
          a2 = startDate.getTime()+((long)column)*86400000L+(whVO!=null?cal.get(cal.HOUR_OF_DAY)*3600000L+cal.get(cal.MINUTE)*60000:86400000L);
        }
      }

      label.setAppointments(
        (ArrayList)value,
        startDate.getTime()+((long)column)*86400000L,
        startDate.getTime()+(((long)column)+1L)*86400000L,
        width,
        m1,
        m2,
        a1,
        a2,
        row,
        this,
        column
      );

     label.repaint();

     return label;
   }


   public Object getCellEditorValue() {
     return value;
   }



  } // end GanttColumnEditor




}


