// My File Manager (MFM).
//
// Copyright 2005-2006 by Stefano Gatti.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA.
//
//=====================================================================

#include "table.hxx"

/*********************************************************************
*
* class_table methods
*
*********************************************************************/


/*********************************************************************
*
* class_table
*
* Constructor
*
*********************************************************************/

class_table::class_table(int x, int y, int w, int h, char *label = NULL) : Fl_Group(x, y, w, h, label) {

// initialize vars

  wheader_min = 10;
  hheader = 20;
  hrows = 20;

  ncols = 0;
  nrows = 0;

  cols_resizable = true;
  select_mode = 2;
  selected_single_row = -1;

  header_bg_color = FL_GRAY;

  header_color = FL_BLACK;

  bg_color = FL_WHITE;

// Changed default file/directory highlight from grey to yellow:
//select_color = FL_LIGHT2;
  select_color = FL_YELLOW;

  color =  FL_BLACK;

  sort_col = -1;

  exist_header = false;
  exist_table = false;
  
  new_label = NULL;  
  old_label = NULL;

// header

  group_header = new Fl_Scroll(x, y, w, hheader, "");

  group_header->type(0);

  group_header->resizable(NULL);
  
  group_header->end();


// tabella

  group_table = new Fl_Scroll(x, y+hheader, w, h-hheader, "");

  group_table->hscrollbar.callback((Fl_Callback*) class_table::hscrollbar_cb);

  group_table->end();

  resizable(group_table);

  end();

} // class_table


/*********************************************************************
*
* set_table_bg_color
*
* Set table background color
*
*********************************************************************/

void class_table::set_table_bg_color(Fl_Color color) {

  bg_color = color;

} // set_table_bg_color


/*********************************************************************
*
* set_table_bg_select_color
*
* Set table select background color
*
*********************************************************************/

void class_table::set_table_bg_select_color(Fl_Color color) {

  select_color = color;

} // set_table_bg_select_color


/*********************************************************************
*
* col_width
*
* Set/get width of a column
*
*********************************************************************/

void class_table::col_width(int col, int width) {

  if (width >= wheader_min) {

    int i = width - header[col].button->w();

    for (int j=col+1; j<ncols; j++) {

      header[j].button->position(header[j].button->x()+i, group_header->y());

      if (exist_table)

        for (int k=0; k<nrows; k++)

          table[k].cell[j].box->position(header[j].button->x(), table[k].cell[j].box->y());

    }

    header[col].button->size(width, hheader);

    if (exist_table)

      for (int k=0; k<nrows; k++)

        table[k].cell[col].box->resize(header[col].button->x(), table[k].cell[col].box->y(), width, hrows);

    redraw();

  }

}


int class_table::class_table::col_width(int col) {

  return header[col].button->w();

}

// col_width


/*********************************************************************
*
* col_width_all
*
* Set width of all columns
*
*********************************************************************/

void class_table::col_width_all(int width) {

  int i = group_header->x();

  for (int j=0; j<ncols; j++) {

    int y = group_header->y();

    header[j].button->resize(i, y, width, hheader);

    y = y + hheader;

    if (exist_table)

      for (int k=0; k<nrows; k++) {

        table[k].cell[j].box->resize(i, y, width, hrows);

        y = y + hrows;

      }

    i = i + width;

  }

  redraw();

} // col_width_all


/*********************************************************************
*
* set_cols_resizable
*
* Permit or deny column resizing
*
*********************************************************************/

void class_table::set_cols_resizable(bool mode) {

  cols_resizable = mode;

} // set_cols_resizable


/*********************************************************************
*
* set_col_align
*
* Change alignement of a column
*
*********************************************************************/

void class_table::set_col_align(int col, Fl_Align align) {

  header[col].button->align(align | FL_ALIGN_INSIDE | FL_ALIGN_CLIP);

  if (exist_table)

    for (int i=0; i<nrows; i++)

      table[i].cell[col].box->align(align | FL_ALIGN_INSIDE | FL_ALIGN_CLIP);

} // set_col_align


/*********************************************************************
*
* set_row_color
*
* Change foreground color of a row
*
*********************************************************************/

void class_table::set_row_color(int row, Fl_Color color) {

  if (exist_table)

    for (int i=0; i<ncols; i++)

      table[row].cell[i].box->labelcolor(color);

} // set_row_color


/*********************************************************************
*
* set_header_bg_color
*
* Change header background color
*
*********************************************************************/

void class_table::set_header_bg_color(Fl_Color color) {

  header_bg_color = color;

  for (int i=0; i<ncols; i++)

    header[i].button->color(color);

} // set_header_bg_color


/*********************************************************************
*
* set_header_color
*
* Change header foreground color
*
*********************************************************************/

void class_table::set_header_color(Fl_Color color) {

  header_color = color;

  for (int i=0; i<ncols; i++)

    header[i].button->labelcolor(color);

} // set_header_color


/*********************************************************************
*
* select_row
*
* Change row selection status
*
*********************************************************************/

void class_table::select_row(int row){

  if (row >= 0 && row <nrows) {

    if (select_mode == 1) {

      change_row_bg_color(selected_single_row, bg_color);

      change_row_bg_color(row, select_color);

      selected_single_row = row;

    }

    else if  (select_mode == 2) {

      table[row].selected = true;
      change_row_bg_color(row, select_color);

    }

  }

  else {

    change_row_bg_color(selected_single_row, bg_color);

    selected_single_row = -1;

  }

}


void class_table::select_row(int row, bool select) {

  if (row >= 0 && row <nrows) {

    if (select_mode == 1) {

      if (select) {

        change_row_bg_color(selected_single_row, bg_color);
        change_row_bg_color(row, select_color);

        selected_single_row = row;

      }

    }

    else if (select_mode == 2) {

      if (select)

        change_row_bg_color(row, select_color);

      else

        change_row_bg_color(row, bg_color);

      table[row].selected = select;

    }

  }

}



void class_table::select_row(bool select) {

  if (select_mode == 2)

    for (int i=0; i<nrows; i++) {

      if (select)

        change_row_bg_color(i, select_color);

      else

        change_row_bg_color(i, bg_color);

      table[i].selected = select;

    }

} // select_row
		

/*********************************************************************
*
* row_selected
*
* return the selected row (in single row mode) or return if a row is selected
*
*********************************************************************/

int class_table::row_selected() {

  return selected_single_row;

}


bool class_table::row_selected(int row) {

  return table[row].selected;

}

// row_selected


/*********************************************************************
*
* row_select_mode
*
* Set/get the row selected mode
*
*********************************************************************/

void class_table::row_select_mode(int mode) {

  select_mode = mode;

}


int class_table::row_select_mode() {

  return select_mode;

}

// row_select_mode


/*********************************************************************
*
* row_data
*
* Set/get the pointer associated to a row
*
*********************************************************************/

void class_table::row_data(int row, void *data) {

  table[row].data = data;

}


void* class_table::row_data(int row) {

  return table[row].data;

}

//  row_data;


/*********************************************************************
*
* header_label
*
* Set/get header label associated to a column
*
*********************************************************************/

void class_table::header_label(int col, char *label) {

  header[col].button->label(label);

}

char* class_table::header_label(int col) {

  return (char *) header[col].button->label();

}

// header_label


/*********************************************************************
*
* cell_label
*
* Set/get cell label associated to a cell
*
*********************************************************************/

void class_table::cell_label(int col, int row, char *label) {

  if (label_copy)

    table[row].cell[col].box->copy_label(label);

  else

    table[row].cell[col].box->label(label);

}

char* class_table::cell_label(int col, int row) {

  return (char *) table[row].cell[col].box->label();

}

// cell_label


/*********************************************************************
*
* change_row_bg_color
*
* Change background color of a row
*
*********************************************************************/

void class_table::change_row_bg_color(int row, Fl_Color color) {

  if (row >= 0 && row <nrows) {

    for (int i=0; i<ncols; i++)

      table[row].cell[i].box->color(color);

    redraw();

  }

} // change_row_bg_color


/*********************************************************************
*
* double_click_cb
*
* Double click on table callback
*
*********************************************************************/

void class_table::double_click_cb(int row) {

} // double_click_cb


/*********************************************************************
*
* button_2_cb
*
* Button 2 click on table callback
*
*********************************************************************/

void class_table::button_2_cb(int row) {


} // button_2_cb


/*********************************************************************
*
* button_3_cb
*
* Button 3 click on table callback
*
*********************************************************************/

void class_table::button_3_cb(int row) {

} // button_3_cb


/*********************************************************************
*
* button_2_drag_cb
*
* Button 2 drag on table callback
*
*********************************************************************/

void class_table::button_2_drag_cb(int row) {

} // button_2_drag_cb


/*********************************************************************
*
* button_3_drag_cb
*
* Button 3 drag on table callback
*
*********************************************************************/

void class_table::button_3_drag_cb(int row) {

} // button_3_drag_cb


/*********************************************************************
*
* button_2_release_cb
*
* Button 2 release on table callback
*
*********************************************************************/

void class_table::button_2_release_cb(int row) {

} // button_2_release_cb


/*********************************************************************
*
* button_3_release_cb
*
* Button 3 release on table callback
*
*********************************************************************/

void class_table::button_3_release_cb(int row) {

} // button_3_release_cb


/*********************************************************************
*
* hscrollbar_cb
*
* Horizontal scrollbars callback
*
*********************************************************************/

void class_table::hscrollbar_cb(Fl_Widget* o, void*) {

  Fl_Scroll* s = (Fl_Scroll*)(o->parent());

  s->position(int(((Fl_Scrollbar*)o)->value()), s->yposition());

  class_table* t = (class_table*)(s->parent());

  t->group_header->position(s->xposition(), 0);

} // hscrollbar_cb


/*********************************************************************
*
* header_cb
*
* Header callback
*
*********************************************************************/

void class_table::header_cb(int col) {

  if (sort_col == col)

    up_dir = !up_dir;

  else

    up_dir = true;

  sort_table(col, up_dir);

} // header_cb


/*********************************************************************
*
* sort_table
*
* Sort table according passed colum and direction
*
*********************************************************************/

void class_table::sort_table(int col, bool dir_up) {

  if (select_mode == SELECT_MODE_SINGLE)

    select_row(-1);

  sort_table_header(col, dir_up);

  sort_func(col, dir_up);

} //sort_table


/*********************************************************************
*
* sort_table_header
*
* Write sort simbols on header
*
*********************************************************************/

void class_table::sort_table_header(int col, bool dir_up) {

  int dir;

  if (sort_col > -1 && old_label != NULL)
  
    header[sort_col].button->label(old_label);

  if (col >= 0 && col < ncols) {

    if (sort_col != col)

      old_label = (char *) header[col].button->label();

    if (dir_up)

      dir = 8;

    else

      dir = 2;

    if (new_label != NULL) 

      free(new_label);

    asprintf(&new_label, "%s @-5%iDnArrow", old_label, dir);

    header[col].button->label(new_label);

  }

  sort_col = col;

} //sort_table_header


/*********************************************************************
*
* sort_func
*
* Performing sort
*
*********************************************************************/

void class_table::sort_func(int col, bool dir_up) {

  struct_row  temp_row;

  int i,j;

  size_t size_row = sizeof(struct_row);

// actually use bubble sort :-(

  for (i = 1; i < nrows; i++)

    for (j = 0; j < i; j++)

      if (compare(col, i, j) == dir_up) {

        memcpy (&temp_row, &table[i], size_row);
        memcpy (&table[i], &table[j], size_row);
        memcpy (&table[j], &temp_row, size_row);

      }

  group_table->position(0, 0);

  for (j=0; j<ncols; j++) {

    int y = group_table->y();

    for (int k=0; k<nrows; k++) {

      table[k].cell[j].box->position(table[k].cell[j].box->x(), y);

      y = y + hrows;

    }

  }

  redraw();

} // sort_func


/*********************************************************************
*
* compare
*
* Sort table according passed colum and direction
*
*********************************************************************/

bool class_table::compare(int col, int row1, int row2){

  if (strcmp(table[row1].cell[col].box->label(), table[row2].cell[col].box->label())>0)

    return false;

  else

    return true;

} // compare


/*********************************************************************
*
* create_header
*
* Create a new header
*
*********************************************************************/

void class_table::create_header(int nc, bool lcopy) {

  group_header->clear();
  group_table->clear();

  if (exist_header)

    delete [] header;

  if (exist_table) {

    for (int i=0; i<nrows; i++)

      delete [] table[i].cell;

    delete [] table;

  }

  exist_header = false;

  exist_table = false;

  ncols = nc;

  label_header_copy = lcopy;

  current(group_header);

  header = new struct_button[ncols];

  for (int j=0; j<ncols; j++) {

    header[j].button = new Fl_Button(group_header->x(), group_header->y(), 0, hheader, " ");

    header[j].button->clear_visible_focus();

    header[j].button->align(FL_ALIGN_INSIDE | FL_ALIGN_CLIP | FL_ALIGN_LEFT);

    header[j].button->box(FL_UP_BOX);

    header[j].button->color(header_bg_color);

    header[j].button->labelcolor(header_color);

  }
  

  redraw();
  

} // create_header


/*********************************************************************
*
* create_table
*
* Create a new table
*
*********************************************************************/

void class_table::create_table(int nr, bool lcopy) {

  group_table->clear();

  if (exist_table) {

    for (int i=0; i<nrows; i++)

      delete [] table[i].cell;

    delete [] table;

  }

  nrows = nr;
  label_copy = lcopy;

  class_table::current((Fl_Group *) group_table);

  table = new struct_row[nrows];

  for (int i=0; i<nrows; i++) {

    table[i].data = NULL;

    table[i].cell = new struct_cell[ncols];

    table[i].selected = false;

    int y = group_table->y()+i*hrows;

    for (int j=0; j<ncols; j++) {

      table[i].cell[j].box = new Fl_Box(header[j].button->x(), y, header[j].button->w(), hrows, "");

      table[i].cell[j].box->align(header[j].button->align());

      table[i].cell[j].box->box(FL_FLAT_BOX);

      table[i].cell[j].box->color(bg_color);

      table[i].cell[j].box->labelcolor(color);

    }

  }

  exist_table = true;

  redraw();

} // create_table


/*********************************************************************
*
* get_resize_col
*
* In resize mode return col to resize, otherwise return -1
*
*********************************************************************/

int class_table::get_resize_col(int x, int y) {

  int col = -1;
  int end_col;

  if (y > group_header->y() && y < group_table->y() && cols_resizable) {

    for (int i=0; i<ncols; i++) {

      end_col = header[i].button->x() + header[i].button->w();

      if (end_col + 5 > x) {

        if (end_col - 5 < x)

          col = i;

        break;

      }

    }

  }

  return col;

} // get_resize_col


/*********************************************************************
*
* get_select_row
*
* Return the row at x, y
*
*********************************************************************/

int class_table::get_select_row(int x, int y) {

  int row = -1;

  if (x >= group_table->x() && x < group_table->x()+group_table->w() &&
            y >= group_table->y() && y < group_table->y()+group_table->h()) {

    row = (y - group_table->y() + group_table->yposition())/hrows;

    if (row >= nrows)

      row = -1;

  }

  return row;

} // get_select_row


/*********************************************************************
*
* get_pointed_row
*
* Return the pointed row
*
*********************************************************************/

int class_table::get_pointed_row() {

  return get_select_row(Fl::event_x(), Fl::event_y());

} // get_pointed_row


/*********************************************************************
*
* get_selected_rows
*
* Return the selected row number
*
*********************************************************************/

int class_table::get_selected_rows() {

  int ret = 0;

  if (select_mode == 1 && row_selected() >= 0)

    ret = 1;

  else if (select_mode == 2)

    for (int i=0; i<nrows; i++)

      if (table[i].selected)

        ret++;

  return ret;

} // get_selected_rows


/*********************************************************************
*
* get_rows
*
* Return number of rows
*
*********************************************************************/

int class_table::get_rows() {

  return nrows;

} //get_rows


 /*********************************************************************
*
* get_cols
*
* Return number of columns
*
*********************************************************************/

int class_table::get_cols() {

  return ncols;

} //get_cols


/*********************************************************************
*
* row_visible
*
* Scroll list until row become visible
*
*********************************************************************/

void class_table::row_visible(int row) {

  if (row*hrows < group_table->yposition())
							
	  group_table->position(group_table->xposition(), row*hrows);
		
	else if ((row+1)*hrows > group_table->yposition()+group_table->h()) 
							
    group_table->position(group_table->xposition(), (row+1)*hrows-group_table->h());


} // row_visible


/*********************************************************************
*
* handle
*
* Handle of class_table
*
*********************************************************************/

int class_table::handle(int event) {

  static bool resize_flag,
              click_table;

  static int select_flag,
             resize_col,
             row,
             start_row;

  int ret = 0;

  static int posx, posy;
		
	if (event != FL_KEYDOWN && resize_flag == 0)
	
    ret = Fl_Group::handle(event);

	switch (event) {

    case FL_LEAVE:
		
		  if (get_resize_col(Fl::event_x(), Fl::event_y()) == -1) {
		
        resize_flag = 0;
			
     		fl_cursor(FL_CURSOR_DEFAULT, FL_BLACK, FL_WHITE);
				
			}
								
  		ret = 1;
				
			break;

	  case FL_MOVE:

      resize_col = get_resize_col(Fl::event_x(), Fl::event_y());

		  if (resize_col >= 0) {

        resize_flag = 1;

			  fl_cursor(FL_CURSOR_WE, FL_BLACK, FL_WHITE);

			}

		  else {

        resize_flag = 0;

			  fl_cursor(FL_CURSOR_DEFAULT, FL_BLACK, FL_WHITE);

			}
			
			ret = 1;			

		  break;

	  case FL_PUSH:

      posx = Fl::event_x();
      posy = Fl::event_y();
      
      click_table = false;

// click on header

      if (posy < group_table->y() && Fl::event_button()==1 && !resize_flag) {

        for (int i=ncols-1; i>=0; i--)

          if (header[i].button->x()<posx) {

            if (header[i].button->x()+header[i].button->w()>posx)

              header_cb(i);

            break;

          }

      }

// click on table

      else if (posx < group_table->scrollbar.x() && posy < group_table->hscrollbar.y() && posy > group_table->y()) {

        click_table = true;

// button 1 pressed

        if (Fl::event_button()==1) {
        
// button 1 double click

          if (Fl::event_clicks()) {

            double_click_cb(get_select_row(posx, Fl::event_y()));

          }

          else {

            if (select_mode == 1)

              select_row(get_select_row(posx, Fl::event_y()));

            else if (select_mode == 2) {
						
              row = get_select_row(posx, Fl::event_y());
						
						  if (Fl::event_shift()) {

                if (row > start_row)

                  for (int i = start_row; i<= row; i++)

                    select_row(i, true);

                else

                  for (int i = row; i<= start_row; i++)
								
                    select_row(i, true);																																				
  						
							}					
							
							else if (Fl::event_ctrl())

                select_row(row, !table[row].selected);
  
              else {

                select_row(false);
   
                select_row(row);

              }

              select_flag = 2 - table[row].selected;
							
							start_row = row;
								  	        
            }

          }

        }
        
// button 2 pressed

        else if (Fl::event_button()==2) {

          button_2_cb(get_select_row(posx, Fl::event_y()));

        }

// button 3 pressed

        else if (Fl::event_button()==3) {

          button_3_cb(get_select_row(posx, Fl::event_y()));

        }

      }

      ret = 1;

      break;

    case FL_RELEASE:
    
      if (click_table) {

// button 2 released

        if (Fl::event_button()==2) {

          button_2_release_cb(get_select_row(posx, Fl::event_y()));
          
        }
        
// button 3 released

        else if (Fl::event_button()==3) {

          button_3_release_cb(get_select_row(posx, Fl::event_y()));

        }
        
      }
      
      ret = 1;

      select_flag = 0;

      break;

    case FL_DRAG:
    
      if (click_table) {
      
// button 2 drag

        if (Fl::event_button()==2) {

          button_2_drag_cb(get_select_row(posx, Fl::event_y()));

        }

// button 3 drag

        else if (Fl::event_button()==3) {

          button_3_drag_cb(get_select_row(posx, Fl::event_y()));

        }
        
        else {
				
          if (Fl::event_y() >= group_table->hscrollbar.y()) {

            if (row < nrows) {

              row++;

              int y = (row+2)*hrows-group_table->hscrollbar.y();
															
							if (y < 0) 
							
							  y = 0;
							
              group_table->position(group_table->xposition(), y);

            }

          }

          else if (Fl::event_y() < group_table->y()) {

            if (row > 0) {

              row--;

              int y = row*hrows;
							
							if (y > (nrows+2)*hrows-group_table->hscrollbar.y())
							
							  y = (row+2)*hrows-group_table->hscrollbar.y();							
								
							if (y < 0)
							
							  y = 0;	
															
              group_table->position(group_table->xposition(), y);

            }

          }

          else if (Fl::event_y() >= group_table->y()) 
					
             row = get_select_row(posx, Fl::event_y());					
						 
          if (row>start_row) {

            for(int count_row = start_row+1; count_row <= row; count_row++)

              if (select_flag == 1)

                select_row(count_row, true);

              else if (select_flag == 2)

                select_row(count_row, false);

          } 
					
					else if (row<start_row) {

            for(int count_row = row; count_row < start_row; count_row++)

              if (select_flag == 1)

                select_row(count_row, true);

              else if (select_flag == 2)

                select_row(count_row, false);

          }
					
					start_row = row;
						 						
        }

      }

      if (resize_flag) {

        col_width(resize_col, col_width(resize_col) + Fl::event_x() - posx);

        posx = Fl::event_x();

      }

			ret = 1;			      

      break;			
			
    case FL_FOCUS:
		
		  ret = 1;
			
			break;
			
    case FL_UNFOCUS:
		
			ret = 0;

			break;

    case FL_KEYDOWN:
		
      switch (Fl::event_key()) {

        case FL_Up:
				
				  if (get_selected_rows() == 0) {
					
					  start_row = 0;				
						
            if (select_mode > 0) {			

              select_row(start_row);				
							
							row_visible(start_row);														
						
					  }							
						
  			  }						
				
				  else if (start_row > 0) {	
						
            if (select_mode == 1)
					
	  				  start_row = row_selected();
						
		  			else if (!Fl::event_shift()) 
					
    	  		  select_row(false);						
												
            if (select_mode > 0) {			

              select_row(--start_row);				
							
							row_visible(start_row);
													
					  }							

				  }							
											
          ret = 1;

          break;				

        case FL_Down:
				
				  if (get_selected_rows() == 0) {
					
					  start_row = 0;				
						
            if (select_mode > 0) {			

              select_row(start_row);				
							
							row_visible(start_row);														
						
					  }							
						
  			  }													
									
				  else if (start_row >= 0 && start_row < nrows-1) {	
						
            if (select_mode == 1)
					
	  				  start_row = row_selected();
						
		  			else if (!Fl::event_shift()) 
					
    	  		  select_row(false);						
												
            if (select_mode > 0) {			

              select_row(++start_row);				
							
							row_visible(start_row);														
						
					  }							

				  }							
											
          ret = 1;								

          break;										
					
        default:
												
					ret = group_table->scrollbar.handle(event);										
									
					break;																																		
															
      }											
						
			
      break;				
			         			
    default:
    
	    ret = group_table->handle(event);
        
			break;
			
  }	

  return ret;

} // handle
