ref: 182dda4dd9335704b78347f74c37f7343c4c65cd
dir: /DoConfig/fltk/src/Fl_Table.cxx/
// // "$Id$" // // Fl_Table -- A table widget // // Copyright 2002 by Greg Ercolano. // Copyright (c) 2004 O'ksi'D // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this // file is missing or damaged, see the license at: // // http://www.fltk.org/COPYING.php // // Please report all bugs and problems on the following page: // // http://www.fltk.org/str.php // #include <stdio.h> // fprintf #include <FL/fl_draw.H> #include <FL/Fl_Table.H> #if defined(USE_UTF8) && ( defined(MICROSOFT) || defined(LINUX) ) #include <FL/fl_utf8.H> // currently only Windows and Linux #endif // Scroll display so 'row' is at top void Fl_Table::row_position(int row) { if ( _row_position == row ) return; // OPTIMIZATION: no change? avoid redraw if ( row < 0 ) row = 0; else if ( row >= rows() ) row = rows() - 1; if ( table_h <= tih ) return; // don't scroll if table smaller than window double newtop = row_scroll_position(row); if ( newtop > vscrollbar->maximum() ) { newtop = vscrollbar->maximum(); } vscrollbar->Fl_Slider::value(newtop); table_scrolled(); redraw(); _row_position = row; // HACK: override what table_scrolled() came up with } // Scroll display so 'col' is at left void Fl_Table::col_position(int col) { if ( _col_position == col ) return; // OPTIMIZATION: no change? avoid redraw if ( col < 0 ) col = 0; else if ( col >= cols() ) col = cols() - 1; if ( table_w <= tiw ) return; // don't scroll if table smaller than window double newleft = col_scroll_position(col); if ( newleft > hscrollbar->maximum() ) { newleft = hscrollbar->maximum(); } hscrollbar->Fl_Slider::value(newleft); table_scrolled(); redraw(); _col_position = col; // HACK: override what table_scrolled() came up with } // Find scroll position of a row (in pixels) long Fl_Table::row_scroll_position(int row) { int startrow = 0; long scroll = 0; // OPTIMIZATION: // Attempt to use precomputed row scroll position // if ( toprow_scrollpos != -1 && row >= toprow ) { scroll = toprow_scrollpos; startrow = toprow; } for ( int t=startrow; t<row; t++ ) { scroll += row_height(t); } return(scroll); } // Find scroll position of a column (in pixels) long Fl_Table::col_scroll_position(int col) { int startcol = 0; long scroll = 0; // OPTIMIZATION: // Attempt to use precomputed row scroll position // if ( leftcol_scrollpos != -1 && col >= leftcol ) { scroll = leftcol_scrollpos; startcol = leftcol; } for ( int t=startcol; t<col; t++ ) { scroll += col_width(t); } return(scroll); } // Ctor Fl_Table::Fl_Table(int X, int Y, int W, int H, const char *l) : Fl_Group(X,Y,W,H,l) { _rows = 0; _cols = 0; _row_header_w = 40; _col_header_h = 18; _row_header = 0; _col_header = 0; _row_header_color = color(); _col_header_color = color(); _row_resize = 0; _col_resize = 0; _row_resize_min = 1; _col_resize_min = 1; _redraw_toprow = -1; _redraw_botrow = -1; _redraw_leftcol = -1; _redraw_rightcol = -1; table_w = 0; table_h = 0; toprow = 0; botrow = 0; leftcol = 0; rightcol = 0; toprow_scrollpos = -1; leftcol_scrollpos = -1; _last_cursor = FL_CURSOR_DEFAULT; _resizing_col = -1; _resizing_row = -1; _dragging_x = -1; _dragging_y = -1; _last_row = -1; _auto_drag = 0; current_col = -1; current_row = -1; select_row = -1; select_col = -1; #if FLTK_ABI_VERSION >= 10301 _scrollbar_size = 0; #endif #if FLTK_ABI_VERSION >= 10303 flags_ = 0; // TABCELLNAV off #endif box(FL_THIN_DOWN_FRAME); vscrollbar = new Fl_Scrollbar(x()+w()-Fl::scrollbar_size(), y(), Fl::scrollbar_size(), h()-Fl::scrollbar_size()); vscrollbar->type(FL_VERTICAL); vscrollbar->callback(scroll_cb, (void*)this); hscrollbar = new Fl_Scrollbar(x(), y()+h()-Fl::scrollbar_size(), w(), Fl::scrollbar_size()); hscrollbar->type(FL_HORIZONTAL); hscrollbar->callback(scroll_cb, (void*)this); table = new Fl_Scroll(x(), y(), w(), h()); table->box(FL_NO_BOX); table->type(0); // don't show Fl_Scroll's scrollbars -- use our own table->hide(); // hide unless children are present table->end(); table_resized(); redraw(); Fl_Group::end(); // end the group's begin() table->begin(); // leave with fltk children getting added to the scroll } // Dtor Fl_Table::~Fl_Table() { // The parent Fl_Group takes care of destroying scrollbars } // Set height of a row void Fl_Table::row_height(int row, int height) { if ( row < 0 ) return; if ( row < (int)_rowheights.size() && _rowheights[row] == height ) { return; // OPTIMIZATION: no change? avoid redraw } // Add row heights, even if none yet int now_size = (int)_rowheights.size(); if ( row >= now_size ) { _rowheights.size(row); while (now_size < row) _rowheights[now_size++] = height; } _rowheights[row] = height; table_resized(); if ( row <= botrow ) { // OPTIMIZATION: only redraw if onscreen or above screen redraw(); } // ROW RESIZE CALLBACK if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) { do_callback(CONTEXT_RC_RESIZE, row, 0); } } // Set width of a column void Fl_Table::col_width(int col, int width) { if ( col < 0 ) return; if ( col < (int)_colwidths.size() && _colwidths[col] == width ) { return; // OPTIMIZATION: no change? avoid redraw } // Add column widths, even if none yet int now_size = (int)_colwidths.size(); if ( col >= now_size ) { _colwidths.size(col+1); while (now_size < col) { _colwidths[now_size++] = width; } } _colwidths[col] = width; table_resized(); if ( col <= rightcol ) { // OPTIMIZATION: only redraw if onscreen or to the left redraw(); } // COLUMN RESIZE CALLBACK if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) { do_callback(CONTEXT_RC_RESIZE, 0, col); } } // Return row/col clamped to reality int Fl_Table::row_col_clamp(TableContext context, int &R, int &C) { int clamped = 0; if ( R < 0 ) { R = 0; clamped = 1; } if ( C < 0 ) { C = 0; clamped = 1; } switch ( context ) { case CONTEXT_COL_HEADER: // Allow col headers to draw even if no rows if ( R >= _rows && R != 0 ) { R = _rows - 1; clamped = 1; } break; case CONTEXT_ROW_HEADER: // Allow row headers to draw even if no columns if ( C >= _cols && C != 0 ) { C = _cols - 1; clamped = 1; } break; case CONTEXT_CELL: default: // CLAMP R/C TO _rows/_cols if ( R >= _rows ) { R = _rows - 1; clamped = 1; } if ( C >= _cols ) { C = _cols - 1; clamped = 1; } break; } return(clamped); } // Return bounding region for given context void Fl_Table::get_bounds(TableContext context, int &X, int &Y, int &W, int &H) { switch ( context ) { case CONTEXT_COL_HEADER: // Column header clipping. X = tox; Y = wiy; W = tow; H = col_header_height(); return; case CONTEXT_ROW_HEADER: // Row header clipping. X = wix; Y = toy; W = row_header_width(); H = toh; return; case CONTEXT_TABLE: // Table inner dimensions X = tix; Y = tiy; W = tiw; H = tih; return; // TODO: Add other contexts.. default: fprintf(stderr, "Fl_Table::get_bounds(): context %d unimplemented\n", (int)context); return; } //NOTREACHED } // Find row/col beneath cursor // // Returns R/C and context. // Also returns resizeflag, if mouse is hovered over a resize boundary. // Fl_Table::TableContext Fl_Table::cursor2rowcol(int &R, int &C, ResizeFlag &resizeflag) { // return values R = C = 0; resizeflag = RESIZE_NONE; // Row header? int X, Y, W, H; if ( row_header() ) { // Inside a row heading? get_bounds(CONTEXT_ROW_HEADER, X, Y, W, H); if ( Fl::event_inside(X, Y, W, H) ) { // Scan visible rows until found for ( R = toprow; R <= botrow; R++ ) { find_cell(CONTEXT_ROW_HEADER, R, 0, X, Y, W, H); if ( Fl::event_y() >= Y && Fl::event_y() < (Y+H) ) { // Found row? // If cursor over resize boundary, and resize enabled, // enable the appropriate resize flag. // if ( row_resize() ) { if ( Fl::event_y() <= (Y+3-0) ) { resizeflag = RESIZE_ROW_ABOVE; } if ( Fl::event_y() >= (Y+H-3) ) { resizeflag = RESIZE_ROW_BELOW; } } return(CONTEXT_ROW_HEADER); } } // Must be in row header dead zone return(CONTEXT_NONE); } } // Column header? if ( col_header() ) { // Inside a column heading? get_bounds(CONTEXT_COL_HEADER, X, Y, W, H); if ( Fl::event_inside(X, Y, W, H) ) { // Scan visible columns until found for ( C = leftcol; C <= rightcol; C++ ) { find_cell(CONTEXT_COL_HEADER, 0, C, X, Y, W, H); if ( Fl::event_x() >= X && Fl::event_x() < (X+W) ) { // Found column? // If cursor over resize boundary, and resize enabled, // enable the appropriate resize flag. // if ( col_resize() ) { if ( Fl::event_x() <= (X+3-0) ) { resizeflag = RESIZE_COL_LEFT; } if ( Fl::event_x() >= (X+W-3) ) { resizeflag = RESIZE_COL_RIGHT; } } return(CONTEXT_COL_HEADER); } } // Must be in column header dead zone return(CONTEXT_NONE); } } // Mouse somewhere in table? // Scan visible r/c's until we find it. // if ( Fl::event_inside(tox, toy, tow, toh) ) { for ( R = toprow; R <= botrow; R++ ) { find_cell(CONTEXT_CELL, R, C, X, Y, W, H); if ( Fl::event_y() < Y ) break; // OPT: thanks lars if ( Fl::event_y() >= (Y+H) ) continue; // OPT: " " for ( C = leftcol; C <= rightcol; C++ ) { find_cell(CONTEXT_CELL, R, C, X, Y, W, H); if ( Fl::event_inside(X, Y, W, H) ) { return(CONTEXT_CELL); // found it } } } // Must be in a dead zone of the table R = C = 0; return(CONTEXT_TABLE); } // Somewhere else return(CONTEXT_NONE); } // Find X/Y/W/H for cell at R/C // If R or C are out of range, returns -1 // with X/Y/W/H set to zero. // int Fl_Table::find_cell(TableContext context, int R, int C, int &X, int &Y, int &W, int &H) { if ( row_col_clamp(context, R, C) ) { // row or col out of range? error X=Y=W=H=0; return(-1); } X = col_scroll_position(C) - hscrollbar->value() + tix; Y = row_scroll_position(R) - vscrollbar->value() + tiy; W = col_width(C); H = row_height(R); switch ( context ) { case CONTEXT_COL_HEADER: Y = wiy; H = col_header_height(); return(0); case CONTEXT_ROW_HEADER: X = wix; W = row_header_width(); return(0); case CONTEXT_CELL: return(0); case CONTEXT_TABLE: return(0); // TODO -- HANDLE OTHER CONTEXTS default: fprintf(stderr, "Fl_Table::find_cell: unknown context %d\n", (int)context); return(-1); } //NOTREACHED } // Enable automatic scroll-selection void Fl_Table::_start_auto_drag() { if (_auto_drag) return; _auto_drag = 1; Fl::add_timeout(0.3, _auto_drag_cb2, this); } // Disable automatic scroll-selection void Fl_Table::_stop_auto_drag() { if (!_auto_drag) return; Fl::remove_timeout(_auto_drag_cb2, this); _auto_drag = 0; } void Fl_Table::_auto_drag_cb2(void *d) { ((Fl_Table*)d)->_auto_drag_cb(); } // Handle automatic scroll-selection if mouse selection dragged off table edge void Fl_Table::_auto_drag_cb() { int lx = Fl::e_x; int ly = Fl::e_y; if (_selecting == CONTEXT_COL_HEADER) { ly = y() + col_header_height(); } else if (_selecting == CONTEXT_ROW_HEADER) { lx = x() + row_header_width(); } if (lx > x() + w() - 20) { Fl::e_x = x() + w() - 20; if (hscrollbar->visible()) ((Fl_Slider*)hscrollbar)->value(hscrollbar->clamp(hscrollbar->value() + 30)); hscrollbar->do_callback(); _dragging_x = Fl::e_x - 30; } else if (lx < (x() + row_header_width())) { Fl::e_x = x() + row_header_width() + 1; if (hscrollbar->visible()) { ((Fl_Slider*)hscrollbar)->value(hscrollbar->clamp(hscrollbar->value() - 30)); } hscrollbar->do_callback(); _dragging_x = Fl::e_x + 30; } if (ly > y() + h() - 20) { Fl::e_y = y() + h() - 20; if (vscrollbar->visible()) { ((Fl_Slider*)vscrollbar)->value(vscrollbar->clamp(vscrollbar->value() + 30)); } vscrollbar->do_callback(); _dragging_y = Fl::e_y - 30; } else if (ly < (y() + col_header_height())) { Fl::e_y = y() + col_header_height() + 1; if (vscrollbar->visible()) { ((Fl_Slider*)vscrollbar)->value(vscrollbar->clamp(vscrollbar->value() - 30)); } vscrollbar->do_callback(); _dragging_y = Fl::e_y + 30; } _auto_drag = 2; handle(FL_DRAG); _auto_drag = 1; Fl::e_x = lx; Fl::e_y = ly; Fl::check(); Fl::flush(); if (Fl::event_buttons() && _auto_drag) { Fl::add_timeout(0.05, _auto_drag_cb2, this); } } // Recalculate the window dimensions void Fl_Table::recalc_dimensions() { // Recalc to* (Table Outer), ti* (Table Inner), wi* ( Widget Inner) wix = ( x() + Fl::box_dx(box())); tox = wix; tix = tox + Fl::box_dx(table->box()); wiy = ( y() + Fl::box_dy(box())); toy = wiy; tiy = toy + Fl::box_dy(table->box()); wiw = ( w() - Fl::box_dw(box())); tow = wiw; tiw = tow - Fl::box_dw(table->box()); wih = ( h() - Fl::box_dh(box())); toh = wih; tih = toh - Fl::box_dh(table->box()); // Trim window if headers enabled if ( col_header() ) { tiy += col_header_height(); toy += col_header_height(); tih -= col_header_height(); toh -= col_header_height(); } if ( row_header() ) { tix += row_header_width(); tox += row_header_width(); tiw -= row_header_width(); tow -= row_header_width(); } // Make scroll bars disappear if window large enough { // First pass: can hide via window size? int hidev = (table_h <= tih); int hideh = (table_w <= tiw); #if FLTK_ABI_VERSION >= 10301 // NEW int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size(); #else // OLD int scrollsize = Fl::scrollbar_size(); #endif // Second pass: Check for interference if ( !hideh && hidev ) { hidev = (( table_h - tih + scrollsize ) <= 0 ); } if ( !hidev && hideh ) { hideh = (( table_w - tiw + scrollsize ) <= 0 ); } // Determine scrollbar visibility, trim ti[xywh]/to[xywh] if ( hidev ) { vscrollbar->hide(); } else { vscrollbar->show(); tiw -= scrollsize; tow -= scrollsize; } if ( hideh ) { hscrollbar->hide(); } else { hscrollbar->show(); tih -= scrollsize; toh -= scrollsize; } } // Resize the child table table->resize(tox, toy, tow, toh); table->init_sizes(); } // Recalculate internals after a scroll. // // Call this if table has been scrolled or resized. // Does not handle redraw(). // TODO: Assumes ti[xywh] has already been recalculated. // void Fl_Table::table_scrolled() { // Find top row int y, row, voff = vscrollbar->value(); for ( row=y=0; row < _rows; row++ ) { y += row_height(row); if ( y > voff ) { y -= row_height(row); break; } } _row_position = toprow = ( row >= _rows ) ? (row - 1) : row; toprow_scrollpos = y; // OPTIMIZATION: save for later use // Find bottom row voff = vscrollbar->value() + tih; for ( ; row < _rows; row++ ) { y += row_height(row); if ( y >= voff ) { break; } } botrow = ( row >= _rows ) ? (row - 1) : row; // Left column int x, col, hoff = hscrollbar->value(); for ( col=x=0; col < _cols; col++ ) { x += col_width(col); if ( x > hoff ) { x -= col_width(col); break; } } _col_position = leftcol = ( col >= _cols ) ? (col - 1) : col; leftcol_scrollpos = x; // OPTIMIZATION: save for later use // Right column // Work with data left over from leftcol calculation // hoff = hscrollbar->value() + tiw; for ( ; col < _cols; col++ ) { x += col_width(col); if ( x >= hoff ) { break; } } rightcol = ( col >= _cols ) ? (col - 1) : col; // First tell children to scroll draw_cell(CONTEXT_RC_RESIZE, 0,0,0,0,0,0); } // Table resized: recalc internal data // Call this whenever the window is resized. // Recalculates the scrollbar sizes. // Makes no assumptions about any pre-initialized data. // void Fl_Table::table_resized() { table_h = row_scroll_position(rows()); table_w = col_scroll_position(cols()); recalc_dimensions(); // Recalc scrollbar sizes // Clamp scrollbar value() after a resize. // Resize scrollbars to enforce a constant trough width after a window resize. // { // Vertical scrollbar float vscrolltab = ( table_h == 0 || tih > table_h ) ? 1 : (float)tih / table_h; float hscrolltab = ( table_w == 0 || tiw > table_w ) ? 1 : (float)tiw / table_w; #if FLTK_ABI_VERSION >= 10301 // NEW int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size(); #else // OLD int scrollsize = Fl::scrollbar_size(); #endif vscrollbar->bounds(0, table_h-tih); vscrollbar->precision(10); vscrollbar->slider_size(vscrolltab); vscrollbar->resize(wix+wiw-scrollsize, wiy, scrollsize, wih - ((hscrollbar->visible())?scrollsize:0)); vscrollbar->Fl_Valuator::value(vscrollbar->clamp(vscrollbar->value())); // Horizontal scrollbar hscrollbar->bounds(0, table_w-tiw); hscrollbar->precision(10); hscrollbar->slider_size(hscrolltab); hscrollbar->resize(wix, wiy+wih-scrollsize, wiw - ((vscrollbar->visible())?scrollsize:0), scrollsize); hscrollbar->Fl_Valuator::value(hscrollbar->clamp(hscrollbar->value())); } // Tell FLTK child widgets were resized Fl_Group::init_sizes(); // Recalc top/bot/left/right table_scrolled(); // DO *NOT* REDRAW -- LEAVE THIS UP TO THE CALLER // redraw(); } // Someone moved a scrollbar void Fl_Table::scroll_cb(Fl_Widget*w, void *data) { Fl_Table *o = (Fl_Table*)data; o->recalc_dimensions(); // recalc tix, tiy, etc. o->table_scrolled(); o->redraw(); } // Set number of rows void Fl_Table::rows(int val) { int oldrows = _rows; _rows = val; { int default_h = ( _rowheights.size() > 0 ) ? _rowheights.back() : 25; int now_size = _rowheights.size(); _rowheights.size(val); // enlarge or shrink as needed while ( now_size < val ) { _rowheights[now_size++] = default_h; // fill new } } table_resized(); // OPTIMIZATION: redraw only if change is visible. if ( val >= oldrows && oldrows > botrow ) { // NO REDRAW } else { redraw(); } } // Set number of cols void Fl_Table::cols(int val) { _cols = val; { int default_w = ( _colwidths.size() > 0 ) ? _colwidths[_colwidths.size()-1] : 80; int now_size = _colwidths.size(); _colwidths.size(val); // enlarge or shrink as needed while ( now_size < val ) { _colwidths[now_size++] = default_w; // fill new } } table_resized(); redraw(); } // Change mouse cursor to different type void Fl_Table::change_cursor(Fl_Cursor newcursor) { if ( newcursor != _last_cursor ) { fl_cursor(newcursor, FL_BLACK, FL_WHITE); _last_cursor = newcursor; } } void Fl_Table::damage_zone(int r1, int c1, int r2, int c2, int r3, int c3) { int R1 = r1, C1 = c1; int R2 = r2, C2 = c2; if (r1 > R2) R2 = r1; if (r2 < R1) R1 = r2; if (r3 > R2) R2 = r3; if (r3 < R1) R1 = r3; if (c1 > C2) C2 = c1; if (c2 < C1) C1 = c2; if (c3 > C2) C2 = c3; if (c3 < C1) C1 = c3; if (R1 < 0) { if (R2 < 0) return; R1 = 0; } if (C1 < 0) { if (C2 < 0) return; C1 = 0; } if (R1 < toprow) R1 = toprow; if (R2 > botrow) R2 = botrow; if (C1 < leftcol) C1 = leftcol; if (C2 > rightcol) C2 = rightcol; redraw_range(R1, R2, C1, C2); } int Fl_Table::move_cursor(int R, int C, int shiftselect) { if (select_row == -1) R++; if (select_col == -1) C++; R += select_row; C += select_col; if (R < 0) R = 0; if (R >= rows()) R = rows() - 1; if (C < 0) C = 0; if (C >= cols()) C = cols() - 1; if (R == select_row && C == select_col) return 0; damage_zone(current_row, current_col, select_row, select_col, R, C); select_row = R; select_col = C; if (!shiftselect || !Fl::event_state(FL_SHIFT)) { current_row = R; current_col = C; } if (R < toprow + 1 || R > botrow - 1) row_position(R); if (C < leftcol + 1 || C > rightcol - 1) col_position(C); return 1; } int Fl_Table::move_cursor(int R, int C) { return move_cursor(R,C,1); } //#define DEBUG 1 #ifdef DEBUG #include <FL/names.h> #define PRINTEVENT \ fprintf(stderr,"Table %s: ** Event: %s --\n", (label()?label():"none"), fl_eventnames[event]); #else #define PRINTEVENT #endif // Handle FLTK events int Fl_Table::handle(int event) { PRINTEVENT; int ret = Fl_Group::handle(event); // let FLTK group handle events first // Which row/column are we over? int R, C; // row/column being worked on ResizeFlag resizeflag; // which resizing area are we over? (0=none) TableContext context = cursor2rowcol(R, C, resizeflag); if (ret) { if (Fl::event_inside(hscrollbar) || Fl::event_inside(vscrollbar)) return 1; if ( context != CONTEXT_ROW_HEADER && // mouse not in row header (STR#2742) context != CONTEXT_COL_HEADER && // mouse not in col header (STR#2742) Fl::focus() != this && // we don't have focus? contains(Fl::focus())) { // focus is a child? return 1; } } // Make snapshots of realtime event states *before* we service user's cb, // which may do things like post popup menus that return with unexpected button states. int _event_button = Fl::event_button(); int _event_clicks = Fl::event_clicks(); int _event_x = Fl::event_x(); int _event_y = Fl::event_y(); int _event_key = Fl::event_key(); #if FLTK_ABI_VERSION >= 10303 int _event_state = Fl::event_state(); #endif Fl_Widget *_focus = Fl::focus(); switch ( event ) { case FL_PUSH: // Single left-click on table? do user's callback with CONTEXT_TABLE if (_event_button == 1 && !_event_clicks) { if (_focus == this) { take_focus(); do_callback(CONTEXT_TABLE, -1, -1); ret = 1; } damage_zone(current_row, current_col, select_row, select_col, R, C); if (context == CONTEXT_CELL) { current_row = select_row = R; current_col = select_col = C; _selecting = CONTEXT_CELL; } else { // Clear selection if not resizing row/col if ( !resizeflag ) { current_row = select_row = -1; current_col = select_col = -1; } } } // A click on table with user's callback defined? // Need this for eg. right click to pop up a menu // if ( Fl_Widget::callback() && // callback defined? resizeflag == RESIZE_NONE ) { // not resizing? do_callback(context, R, C); // do callback with context (cell, header, etc) } // Handle selection if handling a left-click // Use snapshot of _event_button we made before servicing user's cb's // to avoid checking realtime state of buttons which may have changed // during the user's callbacks. // switch ( context ) { case CONTEXT_CELL: // FL_PUSH on a cell? ret = 1; // express interest in FL_RELEASE break; case CONTEXT_NONE: // FL_PUSH on table corner? if ( _event_button == 1 && _event_x < x() + row_header_width()) { current_col = 0; select_col = cols() - 1; current_row = 0; select_row = rows() - 1; damage_zone(current_row, current_col, select_row, select_col); ret = 1; } break; case CONTEXT_COL_HEADER: // FL_PUSH on a column header? if ( _event_button == 1) { // Resizing? Handle it if ( resizeflag ) { // Start resize if left click on column border. // "ret=1" ensures we get drag events from now on. // (C-1) is used if mouse is over the left hand side // of cell, so we resize the next column on the left. // _resizing_col = ( resizeflag & RESIZE_COL_LEFT ) ? C-1 : C; _resizing_row = -1; _dragging_x = _event_x; ret = 1; } else { // Not resizing? Select the column if ( Fl::focus() != this && contains(Fl::focus()) ) return 0; // STR #3018 - item 1 current_col = select_col = C; current_row = 0; select_row = rows() - 1; _selecting = CONTEXT_COL_HEADER; damage_zone(current_row, current_col, select_row, select_col); ret = 1; } } break; case CONTEXT_ROW_HEADER: // FL_PUSH on a row header? if ( _event_button == 1 ) { // Resizing? Handle it if ( resizeflag ) { // Start resize if left mouse clicked on row border. // "ret = 1" ensures we get drag events from now on. // (R-1) is used if mouse is over the top of the cell, // so that we resize the row above. // _resizing_row = ( resizeflag & RESIZE_ROW_ABOVE ) ? R-1 : R; _resizing_col = -1; _dragging_y = _event_y; ret = 1; } else { // Not resizing? Select the row if ( Fl::focus() != this && contains(Fl::focus()) ) return 0; // STR #3018 - item 1 current_row = select_row = R; current_col = 0; select_col = cols() - 1; _selecting = CONTEXT_ROW_HEADER; damage_zone(current_row, current_col, select_row, select_col); ret = 1; } } break; default: ret = 0; // express disinterest break; } _last_row = R; break; case FL_DRAG: if (_auto_drag == 1) { ret = 1; break; } if ( _resizing_col > -1 ) { // Dragging column? // // Let user drag even /outside/ the row/col widget. // Don't allow column width smaller than 1. // Continue to show FL_CURSOR_WE at all times during drag. // int offset = _dragging_x - _event_x; int new_w = col_width(_resizing_col) - offset; if ( new_w < _col_resize_min ) new_w = _col_resize_min; col_width(_resizing_col, new_w); _dragging_x = _event_x; table_resized(); redraw(); change_cursor(FL_CURSOR_WE); ret = 1; if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) { do_callback(CONTEXT_RC_RESIZE, R, C); } } else if ( _resizing_row > -1 ) { // Dragging row? // // Let user drag even /outside/ the row/col widget. // Don't allow row width smaller than 1. // Continue to show FL_CURSOR_NS at all times during drag. // int offset = _dragging_y - _event_y; int new_h = row_height(_resizing_row) - offset; if ( new_h < _row_resize_min ) new_h = _row_resize_min; row_height(_resizing_row, new_h); _dragging_y = _event_y; table_resized(); redraw(); change_cursor(FL_CURSOR_NS); ret = 1; if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) { do_callback(CONTEXT_RC_RESIZE, R, C); } } else { if (_event_button == 1 && _selecting == CONTEXT_CELL && context == CONTEXT_CELL) { // Dragging a cell selection? if ( _event_clicks ) break; // STR #3018 - item 2 if (select_row != R || select_col != C) { damage_zone(current_row, current_col, select_row, select_col, R, C); } select_row = R; select_col = C; ret = 1; } else if (_event_button == 1 && _selecting == CONTEXT_ROW_HEADER && context & (CONTEXT_ROW_HEADER|CONTEXT_COL_HEADER|CONTEXT_CELL)) { if (select_row != R) { damage_zone(current_row, current_col, select_row, select_col, R, C); } select_row = R; ret = 1; } else if (_event_button == 1 && _selecting == CONTEXT_COL_HEADER && context & (CONTEXT_ROW_HEADER|CONTEXT_COL_HEADER|CONTEXT_CELL)) { if (select_col != C) { damage_zone(current_row, current_col, select_row, select_col, R, C); } select_col = C; ret = 1; } } // Enable autodrag if not resizing, and mouse has moved off table edge if ( _resizing_row < 0 && _resizing_col < 0 && _auto_drag == 0 && ( _event_x > x() + w() - 20 || _event_x < x() + row_header_width() || _event_y > y() + h() - 20 || _event_y < y() + col_header_height() ) ) { _start_auto_drag(); } break; case FL_RELEASE: _stop_auto_drag(); switch ( context ) { case CONTEXT_ROW_HEADER: // release on row header case CONTEXT_COL_HEADER: // release on col header case CONTEXT_CELL: // release on a cell case CONTEXT_TABLE: // release on dead zone if ( _resizing_col == -1 && // not resizing a column _resizing_row == -1 && // not resizing a row Fl_Widget::callback() && // callback defined when() & FL_WHEN_RELEASE && // on button release _last_row == R ) { // release on same row PUSHed? // Need this for eg. left clicking on a cell to select it do_callback(context, R, C); } break; default: break; } if ( _event_button == 1 ) { change_cursor(FL_CURSOR_DEFAULT); _resizing_col = -1; _resizing_row = -1; ret = 1; } break; case FL_MOVE: if ( context == CONTEXT_COL_HEADER && // in column header? resizeflag ) { // resize + near boundary? change_cursor(FL_CURSOR_WE); // show resize cursor } else if ( context == CONTEXT_ROW_HEADER && // in row header? resizeflag ) { // resize + near boundary? change_cursor(FL_CURSOR_NS); // show resize cursor } else { change_cursor(FL_CURSOR_DEFAULT); // normal cursor } ret = 1; break; case FL_ENTER: // See FLTK event docs on the FL_ENTER widget if (!ret) take_focus(); ret = 1; //FALLTHROUGH case FL_LEAVE: // We want to track the mouse if resizing is allowed. if ( resizeflag ) { ret = 1; } if ( event == FL_LEAVE ) { _stop_auto_drag(); change_cursor(FL_CURSOR_DEFAULT); } break; case FL_FOCUS: Fl::focus(this); //FALLTHROUGH case FL_UNFOCUS: _stop_auto_drag(); ret = 1; break; case FL_KEYBOARD: { ret = 0; int is_row = select_row; int is_col = select_col; switch(_event_key) { case FL_Home: ret = move_cursor(0, -1000000); break; case FL_End: ret = move_cursor(0, 1000000); break; case FL_Page_Up: ret = move_cursor(-(botrow - toprow - 1), 0); break; case FL_Page_Down: ret = move_cursor(botrow - toprow - 1 , 0); break; case FL_Left: ret = move_cursor(0, -1); break; case FL_Right: ret = move_cursor(0, 1); break; case FL_Up: ret = move_cursor(-1, 0); break; case FL_Down: ret = move_cursor(1, 0); break; case FL_Tab: #if FLTK_ABI_VERSION >= 10303 if ( !tab_cell_nav() ) break; // not navigating cells? let fltk handle it (STR#2862) if ( _event_state & FL_SHIFT ) { ret = move_cursor(0, -1, 0); // shift-tab -> left } else { ret = move_cursor(0, 1, 0); // tab -> right } break; #else break; // without tab_cell_nav(), Fl_Table should default to navigating widgets, not cells #endif } if (ret && Fl::focus() != this) { do_callback(CONTEXT_TABLE, -1, -1); take_focus(); } //if (!ret && Fl_Widget::callback() && when() & FL_WHEN_NOT_CHANGED ) if ( Fl_Widget::callback() && ( ( !ret && when() & FL_WHEN_NOT_CHANGED ) || ( is_row!= select_row || is_col!= select_col ) ) ) { do_callback(CONTEXT_CELL, select_row, select_col); //damage_zone(current_row, current_col, select_row, select_col); ret = 1; } break; } default: change_cursor(FL_CURSOR_DEFAULT); break; } return(ret); } // Resize FLTK override // Handle resize events if user resizes parent window. // void Fl_Table::resize(int X, int Y, int W, int H) { // Tell group to resize, and recalc our own widget as well Fl_Group::resize(X, Y, W, H); table_resized(); redraw(); } // Draw a cell void Fl_Table::_redraw_cell(TableContext context, int r, int c) { if ( r < 0 || c < 0 ) return; int X,Y,W,H; find_cell(context, r, c, X, Y, W, H); // find positions of cell draw_cell(context, r, c, X, Y, W, H); // call users' function to draw it } /** See if the cell at row \p r and column \p c is selected. \returns 1 if the cell is selected, 0 if not. */ int Fl_Table::is_selected(int r, int c) { int s_left, s_right, s_top, s_bottom; if (select_col > current_col) { s_left = current_col; s_right = select_col; } else { s_right = current_col; s_left = select_col; } if (select_row > current_row) { s_top = current_row; s_bottom = select_row; } else { s_bottom = current_row; s_top = select_row; } if (r >= s_top && r <= s_bottom && c >= s_left && c <= s_right) { return 1; } return 0; } /** Gets the region of cells selected (highlighted). \param[in] row_top Returns the top row of selection area \param[in] col_left Returns the left column of selection area \param[in] row_bot Returns the bottom row of selection area \param[in] col_right Returns the right column of selection area */ void Fl_Table::get_selection(int& row_top, int& col_left, int& row_bot, int& col_right) { if (select_col > current_col) { col_left = current_col; col_right = select_col; } else { col_right = current_col; col_left = select_col; } if (select_row > current_row) { row_top = current_row; row_bot = select_row; } else { row_bot = current_row; row_top = select_row; } } /** Sets the region of cells to be selected (highlighted). So for instance, set_selection(0,0,0,0) selects the top/left cell in the table. And set_selection(0,0,1,1) selects the four cells in rows 0 and 1, column 0 and 1. \param[in] row_top Top row of selection area \param[in] col_left Left column of selection area \param[in] row_bot Bottom row of selection area \param[in] col_right Right column of selection area */ void Fl_Table::set_selection(int row_top, int col_left, int row_bot, int col_right) { damage_zone(current_row, current_col, select_row, select_col); current_col = col_left; current_row = row_top; select_col = col_right; select_row = row_bot; damage_zone(current_row, current_col, select_row, select_col); } // Draw the entire Fl_Table // Override the draw() routine to draw the table. // Then tell the group to draw over us. // void Fl_Table::draw() { #if FLTK_ABI_VERSION >= 10301 // NEW int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size(); #else // OLD int scrollsize = Fl::scrollbar_size(); #endif // Check if scrollbar size changed if ( ( vscrollbar && (scrollsize != vscrollbar->w()) ) || ( hscrollbar && (scrollsize != hscrollbar->h()) ) ) { // handle size change, min/max, table dim's, etc table_resized(); } draw_cell(CONTEXT_STARTPAGE, 0, 0, // let user's drawing routine tix, tiy, tiw, tih); // prep new page // Let fltk widgets draw themselves first. Do this after // draw_cell(CONTEXT_STARTPAGE) in case user moves widgets around. // Use window 'inner' clip to prevent drawing into table border. // (unfortunately this clips FLTK's border, so we must draw it explicity below) // fl_push_clip(wix, wiy, wiw, wih); { Fl_Group::draw(); } fl_pop_clip(); // Explicitly draw border around widget, if any draw_box(box(), x(), y(), w(), h(), color()); // If Fl_Scroll 'table' is hidden, draw its box // Do this after Fl_Group::draw() so we draw over scrollbars // that leak around the border. // if ( ! table->visible() ) { if ( damage() & FL_DAMAGE_ALL || damage() & FL_DAMAGE_CHILD ) { draw_box(table->box(), tox, toy, tow, toh, table->color()); } } // Clip all further drawing to the inner widget dimensions fl_push_clip(wix, wiy, wiw, wih); { // Only redraw a few cells? if ( ! ( damage() & FL_DAMAGE_ALL ) && _redraw_leftcol != -1 ) { fl_push_clip(tix, tiy, tiw, tih); for ( int c = _redraw_leftcol; c <= _redraw_rightcol; c++ ) { for ( int r = _redraw_toprow; r <= _redraw_botrow; r++ ) { _redraw_cell(CONTEXT_CELL, r, c); } } fl_pop_clip(); } if ( damage() & FL_DAMAGE_ALL ) { int X,Y,W,H; // Draw row headers, if any if ( row_header() ) { get_bounds(CONTEXT_ROW_HEADER, X, Y, W, H); fl_push_clip(X,Y,W,H); for ( int r = toprow; r <= botrow; r++ ) { _redraw_cell(CONTEXT_ROW_HEADER, r, 0); } fl_pop_clip(); } // Draw column headers, if any if ( col_header() ) { get_bounds(CONTEXT_COL_HEADER, X, Y, W, H); fl_push_clip(X,Y,W,H); for ( int c = leftcol; c <= rightcol; c++ ) { _redraw_cell(CONTEXT_COL_HEADER, 0, c); } fl_pop_clip(); } // Draw all cells. // This includes cells partially obscured off edges of table. // No longer do this last; you might think it would be nice // to draw over dead zones, but on redraws it flickers. Avoid // drawing over deadzones; prevent deadzones by sizing columns. // fl_push_clip(tix, tiy, tiw, tih); { for ( int r = toprow; r <= botrow; r++ ) { for ( int c = leftcol; c <= rightcol; c++ ) { _redraw_cell(CONTEXT_CELL, r, c); } } } fl_pop_clip(); // Draw little rectangle in corner of headers if ( row_header() && col_header() ) { fl_rectf(wix, wiy, row_header_width(), col_header_height(), color()); } // Table has a boxtype? Close those few dead pixels if ( table->box() ) { if ( col_header() ) { fl_rectf(tox, wiy, Fl::box_dx(table->box()), col_header_height(), color()); } if ( row_header() ) { fl_rectf(wix, toy, row_header_width(), Fl::box_dx(table->box()), color()); } } // Table width smaller than window? Fill remainder with rectangle if ( table_w < tiw ) { fl_rectf(tix + table_w, tiy, tiw - table_w, tih, color()); // Col header? fill that too if ( col_header() ) { fl_rectf(tix + table_w, wiy, // get that corner just right.. (tiw - table_w + Fl::box_dw(table->box()) - Fl::box_dx(table->box())), col_header_height(), color()); } } // Table height smaller than window? Fill remainder with rectangle if ( table_h < tih ) { fl_rectf(tix, tiy + table_h, tiw, tih - table_h, color()); if ( row_header() ) { // NOTE: // Careful with that lower corner; don't use tih; when eg. // table->box(FL_THIN_UP_FRAME) and hscrollbar hidden, // leaves a row of dead pixels. // fl_rectf(wix, tiy + table_h, row_header_width(), (wiy+wih) - (tiy+table_h) - ( hscrollbar->visible() ? scrollsize : 0), color()); } } } // Both scrollbars? Draw little box in lower right if ( vscrollbar->visible() && hscrollbar->visible() ) { fl_rectf(vscrollbar->x(), hscrollbar->y(), vscrollbar->w(), hscrollbar->h(), color()); } draw_cell(CONTEXT_ENDPAGE, 0, 0, // let user's drawing tix, tiy, tiw, tih); // routines cleanup _redraw_leftcol = _redraw_rightcol = _redraw_toprow = _redraw_botrow = -1; } fl_pop_clip(); } // // End of "$Id$". //