themes: Workaround for bug where a background color of RGB 0,0,0 in Black color schem...
[ntk.git] / src / Fl_Table.cxx
blobdc6f2633866c1d459c6c9df66389c6cc8c8d4bd0
1 //
2 // "$Id: Fl_Table.cxx 7950 2010-12-05 01:22:53Z greg.ercolano $"
3 //
4 // Fl_Table -- A table widget
5 //
6 // Copyright 2002 by Greg Ercolano.
7 // Copyright (c) 2004 O'ksi'D
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Library General Public
11 // License as published by the Free Software Foundation; either
12 // version 2 of the License, or (at your option) any later version.
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Library General Public License for more details.
18 //
19 // You should have received a copy of the GNU Library General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
22 // USA.
25 #include <stdio.h> // fprintf
26 #include <FL/fl_draw.H>
27 #include <FL/Fl_Table.H>
29 #if defined(USE_UTF8) && ( defined(MICROSOFT) || defined(LINUX) )
30 #include <FL/fl_utf8.H> // currently only Windows and Linux
31 #endif
33 #define SCROLLBAR_SIZE 16
35 // Scroll display so 'row' is at top
36 void Fl_Table::row_position(int row) {
37 if ( _row_position == row ) return; // OPTIMIZATION: no change? avoid redraw
38 if ( row < 0 ) row = 0;
39 else if ( row >= rows() ) row = rows() - 1;
40 if ( table_h <= tih ) return; // don't scroll if table smaller than window
41 double newtop = row_scroll_position(row);
42 if ( newtop > vscrollbar->maximum() ) {
43 newtop = vscrollbar->maximum();
45 vscrollbar->Fl_Slider::value(newtop);
46 table_scrolled();
47 redraw();
48 _row_position = row; // HACK: override what table_scrolled() came up with
51 // Scroll display so 'col' is at left
52 void Fl_Table::col_position(int col) {
53 if ( _col_position == col ) return; // OPTIMIZATION: no change? avoid redraw
54 if ( col < 0 ) col = 0;
55 else if ( col >= cols() ) col = cols() - 1;
56 if ( table_w <= tiw ) return; // don't scroll if table smaller than window
57 double newleft = col_scroll_position(col);
58 if ( newleft > hscrollbar->maximum() ) {
59 newleft = hscrollbar->maximum();
61 hscrollbar->Fl_Slider::value(newleft);
62 table_scrolled();
63 redraw();
64 _col_position = col; // HACK: override what table_scrolled() came up with
67 // Find scroll position of a row (in pixels)
68 long Fl_Table::row_scroll_position(int row) {
69 int startrow = 0;
70 long scroll = 0;
71 // OPTIMIZATION:
72 // Attempt to use precomputed row scroll position
74 if ( toprow_scrollpos != -1 && row >= toprow ) {
75 scroll = toprow_scrollpos;
76 startrow = toprow;
78 for ( int t=startrow; t<row; t++ ) {
79 scroll += row_height(t);
81 return(scroll);
84 // Find scroll position of a column (in pixels)
85 long Fl_Table::col_scroll_position(int col) {
86 int startcol = 0;
87 long scroll = 0;
88 // OPTIMIZATION:
89 // Attempt to use precomputed row scroll position
91 if ( leftcol_scrollpos != -1 && col >= leftcol ) {
92 scroll = leftcol_scrollpos;
93 startcol = leftcol;
95 for ( int t=startcol; t<col; t++ ) {
96 scroll += col_width(t);
98 return(scroll);
101 // Ctor
102 Fl_Table::Fl_Table(int X, int Y, int W, int H, const char *l) : Fl_Group(X,Y,W,H,l) {
103 _rows = 0;
104 _cols = 0;
105 _row_header_w = 40;
106 _col_header_h = 18;
107 _row_header = 0;
108 _col_header = 0;
109 _row_header_color = color();
110 _col_header_color = color();
111 _row_resize = 0;
112 _col_resize = 0;
113 _row_resize_min = 1;
114 _col_resize_min = 1;
115 _redraw_toprow = -1;
116 _redraw_botrow = -1;
117 _redraw_leftcol = -1;
118 _redraw_rightcol = -1;
119 table_w = 0;
120 table_h = 0;
121 toprow = 0;
122 botrow = 0;
123 leftcol = 0;
124 rightcol = 0;
125 toprow_scrollpos = -1;
126 leftcol_scrollpos = -1;
127 _last_cursor = FL_CURSOR_DEFAULT;
128 _resizing_col = -1;
129 _resizing_row = -1;
130 _dragging_x = -1;
131 _dragging_y = -1;
132 _last_row = -1;
133 _auto_drag = 0;
134 current_col = -1;
135 current_row = -1;
136 select_row = -1;
137 select_col = -1;
139 box(FL_THIN_DOWN_FRAME);
141 vscrollbar = new Fl_Scrollbar(x()+w()-SCROLLBAR_SIZE, y(),
142 SCROLLBAR_SIZE, h()-SCROLLBAR_SIZE);
143 vscrollbar->type(FL_VERTICAL);
144 vscrollbar->callback(scroll_cb, (void*)this);
146 hscrollbar = new Fl_Scrollbar(x(), y()+h()-SCROLLBAR_SIZE,
147 w(), SCROLLBAR_SIZE);
148 hscrollbar->type(FL_HORIZONTAL);
149 hscrollbar->callback(scroll_cb, (void*)this);
151 table = new Fl_Scroll(x(), y(), w(), h());
152 table->box(FL_NO_BOX);
153 table->type(0); // don't show Fl_Scroll's scrollbars -- use our own
154 table->hide(); // hide unless children are present
155 table->end();
157 table_resized();
158 redraw();
160 Fl_Group::end(); // end the group's begin()
162 table->begin(); // leave with fltk children getting added to the scroll
165 // Dtor
166 Fl_Table::~Fl_Table() {
167 // The parent Fl_Group takes care of destroying scrollbars
170 // Set height of a row
171 void Fl_Table::row_height(int row, int height) {
172 if ( row < 0 ) return;
173 if ( row < (int)_rowheights.size() && _rowheights[row] == height ) {
174 return; // OPTIMIZATION: no change? avoid redraw
176 // Add row heights, even if none yet
177 int now_size = (int)_rowheights.size();
178 if ( row >= now_size ) {
179 _rowheights.size(row);
180 while (now_size < row)
181 _rowheights[now_size++] = height;
183 _rowheights[row] = height;
184 table_resized();
185 if ( row <= botrow ) { // OPTIMIZATION: only redraw if onscreen or above screen
186 redraw();
188 // ROW RESIZE CALLBACK
189 if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) {
190 do_callback(CONTEXT_RC_RESIZE, row, 0);
194 // Set width of a column
195 void Fl_Table::col_width(int col, int width)
197 if ( col < 0 ) return;
198 if ( col < (int)_colwidths.size() && _colwidths[col] == width ) {
199 return; // OPTIMIZATION: no change? avoid redraw
201 // Add column widths, even if none yet
202 int now_size = (int)_colwidths.size();
203 if ( col >= now_size ) {
204 _colwidths.size(col);
205 while (now_size < col) {
206 _colwidths[now_size++] = width;
209 _colwidths[col] = width;
210 table_resized();
211 if ( col <= rightcol ) { // OPTIMIZATION: only redraw if onscreen or to the left
212 redraw();
214 // COLUMN RESIZE CALLBACK
215 if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) {
216 do_callback(CONTEXT_RC_RESIZE, 0, col);
220 // Return row/col clamped to reality
221 int Fl_Table::row_col_clamp(TableContext context, int &R, int &C) {
222 int clamped = 0;
223 if ( R < 0 ) { R = 0; clamped = 1; }
224 if ( C < 0 ) { C = 0; clamped = 1; }
225 switch ( context ) {
226 case CONTEXT_COL_HEADER:
227 // Allow col headers to draw even if no rows
228 if ( R >= _rows && R != 0 ) { R = _rows - 1; clamped = 1; }
229 break;
231 case CONTEXT_ROW_HEADER:
232 // Allow row headers to draw even if no columns
233 if ( C >= _cols && C != 0 ) { C = _cols - 1; clamped = 1; }
234 break;
236 case CONTEXT_CELL:
237 default:
238 // CLAMP R/C TO _rows/_cols
239 if ( R >= _rows ) { R = _rows - 1; clamped = 1; }
240 if ( C >= _cols ) { C = _cols - 1; clamped = 1; }
241 break;
243 return(clamped);
246 // Return bounding region for given context
247 void Fl_Table::get_bounds(TableContext context, int &X, int &Y, int &W, int &H) {
248 switch ( context ) {
249 case CONTEXT_COL_HEADER:
250 // Column header clipping.
251 X = tox;
252 Y = wiy;
253 W = tow;
254 H = col_header_height();
255 return;
257 case CONTEXT_ROW_HEADER:
258 // Row header clipping.
259 X = wix;
260 Y = toy;
261 W = row_header_width();
262 H = toh;
263 return;
265 case CONTEXT_TABLE:
266 // Table inner dimensions
267 X = tix; Y = tiy; W = tiw; H = tih;
268 return;
270 // TODO: Add other contexts..
271 default:
272 fprintf(stderr, "Fl_Table::get_bounds(): context %d unimplemented\n", (int)context);
273 return;
275 //NOTREACHED
278 // Find row/col beneath cursor
280 // Returns R/C and context.
281 // Also returns resizeflag, if mouse is hovered over a resize boundary.
283 Fl_Table::TableContext Fl_Table::cursor2rowcol(int &R, int &C, ResizeFlag &resizeflag) {
284 // return values
285 R = C = 0;
286 resizeflag = RESIZE_NONE;
287 // Row header?
288 int X, Y, W, H;
289 if ( row_header() ) {
290 // Inside a row heading?
291 get_bounds(CONTEXT_ROW_HEADER, X, Y, W, H);
292 if ( Fl::event_inside(X, Y, W, H) ) {
293 // Scan visible rows until found
294 for ( R = toprow; R <= botrow; R++ ) {
295 find_cell(CONTEXT_ROW_HEADER, R, 0, X, Y, W, H);
296 if ( Fl::event_y() >= Y && Fl::event_y() < (Y+H) ) {
297 // Found row?
298 // If cursor over resize boundary, and resize enabled,
299 // enable the appropriate resize flag.
301 if ( row_resize() ) {
302 if ( Fl::event_y() <= (Y+3-0) ) { resizeflag = RESIZE_ROW_ABOVE; }
303 if ( Fl::event_y() >= (Y+H-3) ) { resizeflag = RESIZE_ROW_BELOW; }
305 return(CONTEXT_ROW_HEADER);
308 // Must be in row header dead zone
309 return(CONTEXT_NONE);
312 // Column header?
313 if ( col_header() ) {
314 // Inside a column heading?
315 get_bounds(CONTEXT_COL_HEADER, X, Y, W, H);
316 if ( Fl::event_inside(X, Y, W, H) ) {
317 // Scan visible columns until found
318 for ( C = leftcol; C <= rightcol; C++ ) {
319 find_cell(CONTEXT_COL_HEADER, 0, C, X, Y, W, H);
320 if ( Fl::event_x() >= X && Fl::event_x() < (X+W) ) {
321 // Found column?
322 // If cursor over resize boundary, and resize enabled,
323 // enable the appropriate resize flag.
325 if ( col_resize() ) {
326 if ( Fl::event_x() <= (X+3-0) ) { resizeflag = RESIZE_COL_LEFT; }
327 if ( Fl::event_x() >= (X+W-3) ) { resizeflag = RESIZE_COL_RIGHT; }
329 return(CONTEXT_COL_HEADER);
332 // Must be in column header dead zone
333 return(CONTEXT_NONE);
336 // Mouse somewhere in table?
337 // Scan visible r/c's until we find it.
339 if ( Fl::event_inside(tox, toy, tow, toh) ) {
340 for ( R = toprow; R <= botrow; R++ ) {
341 find_cell(CONTEXT_CELL, R, C, X, Y, W, H);
342 if ( Fl::event_y() < Y ) break; // OPT: thanks lars
343 if ( Fl::event_y() >= (Y+H) ) continue; // OPT: " "
344 for ( C = leftcol; C <= rightcol; C++ ) {
345 find_cell(CONTEXT_CELL, R, C, X, Y, W, H);
346 if ( Fl::event_inside(X, Y, W, H) ) {
347 return(CONTEXT_CELL); // found it
351 // Must be in a dead zone of the table
352 R = C = 0;
353 return(CONTEXT_TABLE);
355 // Somewhere else
356 return(CONTEXT_NONE);
359 // Find X/Y/W/H for cell at R/C
360 // If R or C are out of range, returns -1
361 // with X/Y/W/H set to zero.
363 int Fl_Table::find_cell(TableContext context, int R, int C, int &X, int &Y, int &W, int &H) {
364 if ( row_col_clamp(context, R, C) ) { // row or col out of range? error
365 X=Y=W=H=0;
366 return(-1);
368 X = col_scroll_position(C) - hscrollbar->value() + tix;
369 Y = row_scroll_position(R) - vscrollbar->value() + tiy;
370 W = col_width(C);
371 H = row_height(R);
373 switch ( context ) {
374 case CONTEXT_COL_HEADER:
375 Y = wiy;
376 H = col_header_height();
377 return(0);
379 case CONTEXT_ROW_HEADER:
380 X = wix;
381 W = row_header_width();
382 return(0);
384 case CONTEXT_CELL:
385 return(0);
387 case CONTEXT_TABLE:
388 return(0);
390 // TODO -- HANDLE OTHER CONTEXTS
391 default:
392 fprintf(stderr, "Fl_Table::find_cell: unknown context %d\n", (int)context);
393 return(-1);
395 //NOTREACHED
398 // Enable automatic scroll-selection
399 void Fl_Table::_start_auto_drag() {
400 if (_auto_drag) return;
401 _auto_drag = 1;
402 Fl::add_timeout(0.3, _auto_drag_cb2, this);
405 // Disable automatic scroll-selection
406 void Fl_Table::_stop_auto_drag() {
407 if (!_auto_drag) return;
408 Fl::remove_timeout(_auto_drag_cb2, this);
409 _auto_drag = 0;
412 void Fl_Table::_auto_drag_cb2(void *d) {
413 ((Fl_Table*)d)->_auto_drag_cb();
416 // Handle automatic scroll-selection if mouse selection dragged off table edge
417 void Fl_Table::_auto_drag_cb() {
418 int lx = Fl::e_x;
419 int ly = Fl::e_y;
420 if (_selecting == CONTEXT_COL_HEADER)
421 { ly = y() + col_header_height(); }
422 else if (_selecting == CONTEXT_ROW_HEADER)
423 { lx = x() + row_header_width(); }
424 if (lx > x() + w() - 20) {
425 Fl::e_x = x() + w() - 20;
426 if (hscrollbar->visible())
427 ((Fl_Slider*)hscrollbar)->value(hscrollbar->clamp(hscrollbar->value() + 30));
428 hscrollbar->do_callback();
429 _dragging_x = Fl::e_x - 30;
431 else if (lx < (x() + row_header_width())) {
432 Fl::e_x = x() + row_header_width() + 1;
433 if (hscrollbar->visible()) {
434 ((Fl_Slider*)hscrollbar)->value(hscrollbar->clamp(hscrollbar->value() - 30));
436 hscrollbar->do_callback();
437 _dragging_x = Fl::e_x + 30;
439 if (ly > y() + h() - 20) {
440 Fl::e_y = y() + h() - 20;
441 if (vscrollbar->visible()) {
442 ((Fl_Slider*)vscrollbar)->value(vscrollbar->clamp(vscrollbar->value() + 30));
444 vscrollbar->do_callback();
445 _dragging_y = Fl::e_y - 30;
447 else if (ly < (y() + col_header_height())) {
448 Fl::e_y = y() + col_header_height() + 1;
449 if (vscrollbar->visible()) {
450 ((Fl_Slider*)vscrollbar)->value(vscrollbar->clamp(vscrollbar->value() - 30));
452 vscrollbar->do_callback();
453 _dragging_y = Fl::e_y + 30;
455 _auto_drag = 2;
456 handle(FL_DRAG);
457 _auto_drag = 1;
458 Fl::e_x = lx;
459 Fl::e_y = ly;
460 Fl::check();
461 Fl::flush();
462 if (Fl::event_buttons() && _auto_drag) {
463 Fl::add_timeout(0.05, _auto_drag_cb2, this);
467 // Recalculate the window dimensions
468 void Fl_Table::recalc_dimensions() {
469 // Recalc to* (Table Outer), ti* (Table Inner), wi* ( Widget Inner)
470 wix = ( x() + Fl::box_dx(box())); tox = wix; tix = tox + Fl::box_dx(table->box());
471 wiy = ( y() + Fl::box_dy(box())); toy = wiy; tiy = toy + Fl::box_dy(table->box());
472 wiw = ( w() - Fl::box_dw(box())); tow = wiw; tiw = tow - Fl::box_dw(table->box());
473 wih = ( h() - Fl::box_dh(box())); toh = wih; tih = toh - Fl::box_dh(table->box());
474 // Trim window if headers enabled
475 if ( col_header() ) {
476 tiy += col_header_height(); toy += col_header_height();
477 tih -= col_header_height(); toh -= col_header_height();
479 if ( row_header() ) {
480 tix += row_header_width(); tox += row_header_width();
481 tiw -= row_header_width(); tow -= row_header_width();
483 // Make scroll bars disappear if window large enough
485 // First pass: can hide via window size?
486 int hidev = (table_h <= tih);
487 int hideh = (table_w <= tiw);
488 // Second pass: Check for interference
489 if ( !hideh & hidev ) { hidev = (( table_h - tih + SCROLLBAR_SIZE ) <= 0 ); }
490 if ( !hidev & hideh ) { hideh = (( table_w - tiw + SCROLLBAR_SIZE ) <= 0 ); }
491 // Determine scrollbar visibility, trim ti[xywh]/to[xywh]
492 if ( hidev ) { vscrollbar->hide(); }
493 else { vscrollbar->show(); tiw -= SCROLLBAR_SIZE; tow -= SCROLLBAR_SIZE; }
494 if ( hideh ) { hscrollbar->hide(); }
495 else { hscrollbar->show(); tih -= SCROLLBAR_SIZE; toh -= SCROLLBAR_SIZE; }
497 // Resize the child table
498 table->resize(tox, toy, tow, toh);
499 table->init_sizes();
502 // Recalculate internals after a scroll.
504 // Call this if table has been scrolled or resized.
505 // Does not handle redraw().
506 // TODO: Assumes ti[xywh] has already been recalculated.
508 void Fl_Table::table_scrolled() {
509 // Find top row
510 int y, row, voff = vscrollbar->value();
511 for ( row=y=0; row < _rows; row++ ) {
512 y += row_height(row);
513 if ( y > voff ) { y -= row_height(row); break; }
515 _row_position = toprow = ( row >= _rows ) ? (row - 1) : row;
516 toprow_scrollpos = y; // OPTIMIZATION: save for later use
517 // Find bottom row
518 voff = vscrollbar->value() + tih;
519 for ( ; row < _rows; row++ ) {
520 y += row_height(row);
521 if ( y >= voff ) { break; }
523 botrow = ( row >= _rows ) ? (row - 1) : row;
524 // Left column
525 int x, col, hoff = hscrollbar->value();
526 for ( col=x=0; col < _cols; col++ ) {
527 x += col_width(col);
528 if ( x > hoff ) { x -= col_width(col); break; }
530 _col_position = leftcol = ( col >= _cols ) ? (col - 1) : col;
531 leftcol_scrollpos = x; // OPTIMIZATION: save for later use
532 // Right column
533 // Work with data left over from leftcol calculation
535 hoff = hscrollbar->value() + tiw;
536 for ( ; col < _cols; col++ ) {
537 x += col_width(col);
538 if ( x >= hoff ) { break; }
540 rightcol = ( col >= _cols ) ? (col - 1) : col;
541 // First tell children to scroll
542 draw_cell(CONTEXT_RC_RESIZE, 0,0,0,0,0,0);
545 // Table resized: recalc internal data
546 // Call this whenever the window is resized.
547 // Recalculates the scrollbar sizes.
548 // Makes no assumptions about any pre-initialized data.
550 void Fl_Table::table_resized() {
551 table_h = row_scroll_position(rows());
552 table_w = col_scroll_position(cols());
553 recalc_dimensions();
554 // Recalc scrollbar sizes
555 // Clamp scrollbar value() after a resize.
556 // Resize scrollbars to enforce a constant trough width after a window resize.
559 // Vertical scrollbar
560 float vscrolltab = ( table_h == 0 || tih > table_h ) ? 1 : (float)tih / table_h;
561 float hscrolltab = ( table_w == 0 || tiw > table_w ) ? 1 : (float)tiw / table_w;
562 vscrollbar->bounds(0, table_h-tih);
563 vscrollbar->precision(10);
564 vscrollbar->slider_size(vscrolltab);
565 vscrollbar->resize(wix+wiw-SCROLLBAR_SIZE, wiy,
566 SCROLLBAR_SIZE,
567 wih - ((hscrollbar->visible())?SCROLLBAR_SIZE:0));
568 vscrollbar->Fl_Valuator::value(vscrollbar->clamp(vscrollbar->value()));
569 // Horizontal scrollbar
570 hscrollbar->bounds(0, table_w-tiw);
571 hscrollbar->precision(10);
572 hscrollbar->slider_size(hscrolltab);
573 hscrollbar->resize(wix, wiy+wih-SCROLLBAR_SIZE,
574 wiw - ((vscrollbar->visible())?SCROLLBAR_SIZE:0),
575 SCROLLBAR_SIZE);
576 hscrollbar->Fl_Valuator::value(hscrollbar->clamp(hscrollbar->value()));
579 // Tell FLTK child widgets were resized
580 Fl_Group::init_sizes();
582 // Recalc top/bot/left/right
583 table_scrolled();
585 // DO *NOT* REDRAW -- LEAVE THIS UP TO THE CALLER
586 // redraw();
589 // Someone moved a scrollbar
590 void Fl_Table::scroll_cb(Fl_Widget*w, void *data) {
591 Fl_Table *o = (Fl_Table*)data;
592 o->recalc_dimensions(); // recalc tix, tiy, etc.
593 o->table_scrolled();
594 o->redraw();
597 // Set number of rows
598 void Fl_Table::rows(int val) {
599 int oldrows = _rows;
600 _rows = val;
602 int default_h = ( _rowheights.size() > 0 ) ? _rowheights.back() : 25;
603 int now_size = _rowheights.size();
604 _rowheights.size(val); // enlarge or shrink as needed
605 while ( now_size < val ) {
606 _rowheights[now_size++] = default_h; // fill new
609 table_resized();
611 // OPTIMIZATION: redraw only if change is visible.
612 if ( val >= oldrows && oldrows > botrow ) {
613 // NO REDRAW
614 } else {
615 redraw();
619 // Set number of cols
620 void Fl_Table::cols(int val) {
621 _cols = val;
623 int default_w = ( _colwidths.size() > 0 ) ? _colwidths[_colwidths.size()-1] : 80;
624 int now_size = _colwidths.size();
625 _colwidths.size(val); // enlarge or shrink as needed
626 while ( now_size < val ) {
627 _colwidths[now_size++] = default_w; // fill new
630 table_resized();
631 redraw();
634 // Change mouse cursor to different type
635 void Fl_Table::change_cursor(Fl_Cursor newcursor) {
636 if ( newcursor != _last_cursor ) {
637 fl_cursor(newcursor, FL_BLACK, FL_WHITE);
638 _last_cursor = newcursor;
642 void Fl_Table::damage_zone(int r1, int c1, int r2, int c2, int r3, int c3) {
643 int R1 = r1, C1 = c1;
644 int R2 = r2, C2 = c2;
645 if (r1 > R2) R2 = r1;
646 if (r2 < R1) R1 = r2;
647 if (r3 > R2) R2 = r3;
648 if (r3 < R1) R1 = r3;
649 if (c1 > C2) C2 = c1;
650 if (c2 < C1) C1 = c2;
651 if (c3 > C2) C2 = c3;
652 if (c3 < C1) C1 = c3;
653 if (R1 < 0) {
654 if (R2 < 0) return;
655 R1 = 0;
657 if (C1 < 0) {
658 if (C2 < 0) return;
659 C1 = 0;
661 if (R1 < toprow) R1 = toprow;
662 if (R2 > botrow) R2 = botrow;
663 if (C1 < leftcol) C1 = leftcol;
664 if (C2 > rightcol) C2 = rightcol;
665 redraw_range(R1, R2, C1, C2);
668 int Fl_Table::move_cursor(int R, int C) {
669 if (select_row == -1) R++;
670 if (select_col == -1) C++;
671 R += select_row;
672 C += select_col;
673 if (R < 0) R = 0;
674 if (R >= rows()) R = rows() - 1;
675 if (C < 0) C = 0;
676 if (C >= cols()) C = cols() - 1;
677 if (R == select_row && C == select_col) return 0;
678 damage_zone(current_row, current_col, select_row, select_col, R, C);
679 select_row = R;
680 select_col = C;
681 if (!Fl::event_state(FL_SHIFT)) {
682 current_row = R;
683 current_col = C;
685 if (R < toprow + 1 || R > botrow - 1) row_position(R);
686 if (C < leftcol + 1 || C > rightcol - 1) col_position(C);
687 return 1;
690 // #define DEBUG 1
691 #ifdef DEBUG
692 #include "eventnames.h"
693 #define PRINTEVENT \
694 fprintf(stderr,"Table %s: ** Event: %s --\n", (label()?label():"none"), eventnames[event]);
695 #else
696 #define PRINTEVENT
697 #endif
699 // Handle FLTK events
700 int Fl_Table::handle(int event) {
701 PRINTEVENT;
702 int ret = Fl_Group::handle(event); // let FLTK group handle events first
703 if (ret) {
704 if (Fl::event_inside(hscrollbar) || Fl::event_inside(vscrollbar)) return 1;
705 if (Fl::focus() != this && contains(Fl::focus())) return 1;
707 // Which row/column are we over?
708 int R, C; // row/column being worked on
709 ResizeFlag resizeflag; // which resizing area are we over? (0=none)
710 TableContext context = cursor2rowcol(R, C, resizeflag);
711 switch ( event ) {
712 case FL_PUSH:
713 if (Fl::event_button() == 1 && !Fl::event_clicks()) {
714 if (Fl::focus() != this) {
715 take_focus();
716 do_callback(CONTEXT_TABLE, -1, -1);
717 ret = 1;
719 damage_zone(current_row, current_col, select_row, select_col, R, C);
720 if (context == CONTEXT_CELL) {
721 current_row = select_row = R;
722 current_col = select_col = C;
723 _selecting = CONTEXT_CELL;
724 } else {
725 current_row = select_row = -1;
726 current_col = select_col = -1;
729 // Need this for eg. right click to pop up a menu
730 if ( Fl_Widget::callback() && // callback defined?
731 resizeflag == RESIZE_NONE ) { // not resizing?
732 do_callback(context, R, C); // do callback
734 switch ( context ) {
735 case CONTEXT_CELL:
736 // FL_PUSH on a cell?
737 ret = 1; // express interest in FL_RELEASE
738 break;
740 case CONTEXT_NONE:
741 // FL_PUSH on table corner?
742 if ( Fl::event_button() == 1 &&
743 Fl::event_x() < x() + row_header_width()) {
744 current_col = 0;
745 select_col = cols() - 1;
746 current_row = 0;
747 select_row = rows() - 1;
748 damage_zone(current_row, current_col, select_row, select_col);
749 ret = 1;
751 break;
753 case CONTEXT_COL_HEADER:
754 // FL_PUSH on a column header?
755 if ( Fl::event_button() == 1) {
756 // Resizing? Handle it
757 if ( resizeflag ) {
758 // Start resize if left click on column border.
759 // "ret=1" ensures we get drag events from now on.
760 // (C-1) is used if mouse is over the left hand side
761 // of cell, so we resize the next column on the left.
763 _resizing_col = ( resizeflag & RESIZE_COL_LEFT ) ? C-1 : C;
764 _resizing_row = -1;
765 _dragging_x = Fl::event_x();
766 ret = 1;
767 } else {
768 // Not resizing? Select the column
769 current_col = select_col = C;
770 current_row = 0;
771 select_row = rows() - 1;
772 _selecting = CONTEXT_COL_HEADER;
773 damage_zone(current_row, current_col, select_row, select_col);
774 ret = 1;
777 break;
779 case CONTEXT_ROW_HEADER:
780 // FL_PUSH on a row header?
781 if ( Fl::event_button() == 1 ) {
782 // Resizing? Handle it
783 if ( resizeflag ) {
784 // Start resize if left mouse clicked on row border.
785 // "ret = 1" ensures we get drag events from now on.
786 // (R-1) is used if mouse is over the top of the cell,
787 // so that we resize the row above.
789 _resizing_row = ( resizeflag & RESIZE_ROW_ABOVE ) ? R-1 : R;
790 _resizing_col = -1;
791 _dragging_y = Fl::event_y();
792 ret = 1;
793 } else {
794 // Not resizing? Select the row
795 current_row = select_row = R;
796 current_col = 0;
797 select_col = cols() - 1;
798 _selecting = CONTEXT_ROW_HEADER;
799 damage_zone(current_row, current_col, select_row, select_col);
800 ret = 1;
803 break;
805 default:
806 ret = 0; // express disinterest
807 break;
809 _last_row = R;
810 break;
812 case FL_DRAG:
813 if (_auto_drag == 1) {
814 ret = 1;
815 break;
817 if ( _resizing_col > -1 ) {
818 // Dragging column?
820 // Let user drag even /outside/ the row/col widget.
821 // Don't allow column width smaller than 1.
822 // Continue to show FL_CURSOR_WE at all times during drag.
824 int offset = _dragging_x - Fl::event_x();
825 int new_w = col_width(_resizing_col) - offset;
826 if ( new_w < _col_resize_min ) new_w = _col_resize_min;
827 col_width(_resizing_col, new_w);
828 _dragging_x = Fl::event_x();
829 table_resized();
830 redraw();
831 change_cursor(FL_CURSOR_WE);
832 ret = 1;
833 if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) {
834 do_callback(CONTEXT_RC_RESIZE, R, C);
837 else if ( _resizing_row > -1 ) {
838 // Dragging row?
840 // Let user drag even /outside/ the row/col widget.
841 // Don't allow row width smaller than 1.
842 // Continue to show FL_CURSOR_NS at all times during drag.
844 int offset = _dragging_y - Fl::event_y();
845 int new_h = row_height(_resizing_row) - offset;
846 if ( new_h < _row_resize_min ) new_h = _row_resize_min;
847 row_height(_resizing_row, new_h);
848 _dragging_y = Fl::event_y();
849 table_resized();
850 redraw();
851 change_cursor(FL_CURSOR_NS);
852 ret = 1;
853 if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) {
854 do_callback(CONTEXT_RC_RESIZE, R, C);
856 } else {
857 if (Fl::event_button() == 1 &&
858 _selecting == CONTEXT_CELL &&
859 context == CONTEXT_CELL) {
860 if (select_row != R || select_col != C) {
861 damage_zone(current_row, current_col, select_row, select_col, R, C);
863 select_row = R;
864 select_col = C;
865 ret = 1;
867 else if (Fl::event_button() == 1 &&
868 _selecting == CONTEXT_ROW_HEADER &&
869 context & (CONTEXT_ROW_HEADER|CONTEXT_COL_HEADER|CONTEXT_CELL)) {
870 if (select_row != R) {
871 damage_zone(current_row, current_col, select_row, select_col, R, C);
873 select_row = R;
874 ret = 1;
876 else if (Fl::event_button() == 1 &&
877 _selecting == CONTEXT_COL_HEADER
878 && context & (CONTEXT_ROW_HEADER|CONTEXT_COL_HEADER|CONTEXT_CELL)) {
879 if (select_col != C) {
880 damage_zone(current_row, current_col, select_row, select_col, R, C);
882 select_col = C;
883 ret = 1;
886 // Enable autodrag if not resizing, and mouse has moved off table edge
887 if ( _resizing_row < 0 && _resizing_col < 0 && _auto_drag == 0 &&
888 ( Fl::event_x() > x() + w() - 20 ||
889 Fl::event_x() < x() + row_header_width() ||
890 Fl::event_y() > y() + h() - 20 ||
891 Fl::event_y() < y() + col_header_height()
892 ) ) {
893 _start_auto_drag();
895 break;
897 case FL_RELEASE:
898 _stop_auto_drag();
899 switch ( context ) {
900 case CONTEXT_ROW_HEADER: // release on row header
901 case CONTEXT_COL_HEADER: // release on col header
902 case CONTEXT_CELL: // release on a cell
903 case CONTEXT_TABLE: // release on dead zone
904 if ( _resizing_col == -1 && // not resizing a column
905 _resizing_row == -1 && // not resizing a row
906 Fl_Widget::callback() && // callback defined
907 when() & FL_WHEN_RELEASE && // on button release
908 _last_row == R ) { // release on same row PUSHed?
909 // Need this for eg. left clicking on a cell to select it
910 do_callback(context, R, C);
912 break;
914 default:
915 break;
917 if ( Fl::event_button() == 1 ) {
918 change_cursor(FL_CURSOR_DEFAULT);
919 _resizing_col = -1;
920 _resizing_row = -1;
921 ret = 1;
923 break;
925 case FL_MOVE:
926 if ( context == CONTEXT_COL_HEADER && // in column header?
927 resizeflag ) { // resize + near boundary?
928 change_cursor(FL_CURSOR_WE); // show resize cursor
930 else if ( context == CONTEXT_ROW_HEADER && // in row header?
931 resizeflag ) { // resize + near boundary?
932 change_cursor(FL_CURSOR_NS); // show resize cursor
933 } else {
934 change_cursor(FL_CURSOR_DEFAULT); // normal cursor
936 ret = 1;
937 break;
939 case FL_ENTER: // See FLTK event docs on the FL_ENTER widget
940 if (!ret) take_focus();
941 ret = 1;
942 //FALLTHROUGH
944 case FL_LEAVE: // We want to track the mouse if resizing is allowed.
945 if ( resizeflag ) {
946 ret = 1;
948 if ( event == FL_LEAVE ) {
949 _stop_auto_drag();
950 change_cursor(FL_CURSOR_DEFAULT);
952 break;
954 case FL_FOCUS:
955 Fl::focus(this);
956 //FALLTHROUGH
958 case FL_UNFOCUS:
959 _stop_auto_drag();
960 ret = 1;
961 break;
963 case FL_KEYBOARD: {
964 ret = 0;
965 int is_row = select_row;
966 int is_col = select_col;
967 switch(Fl::event_key()) {
968 case FL_Home:
969 ret = move_cursor(0, -1000000);
970 break;
971 case FL_End:
972 ret = move_cursor(0, 1000000);
973 break;
974 case FL_Page_Up:
975 ret = move_cursor(-(botrow - toprow - 1), 0);
976 break;
977 case FL_Page_Down:
978 ret = move_cursor(botrow - toprow - 1 , 0);
979 break;
980 case FL_Left:
981 ret = move_cursor(0, -1);
982 break;
983 case FL_Right:
984 ret = move_cursor(0, 1);
985 break;
986 case FL_Up:
987 ret = move_cursor(-1, 0);
988 break;
989 case FL_Down:
990 ret = move_cursor(1, 0);
991 break;
992 case FL_Tab:
993 if ( Fl::event_state() & FL_SHIFT ) {
994 ret = move_cursor(0, -1); // shift-tab -> left
995 } else {
996 ret = move_cursor(0, 1); // tab -> right
998 break;
1000 if (ret && Fl::focus() != this) {
1001 do_callback(CONTEXT_TABLE, -1, -1);
1002 take_focus();
1004 //if (!ret && Fl_Widget::callback() && when() & FL_WHEN_NOT_CHANGED )
1005 if ( Fl_Widget::callback() &&
1007 ( !ret && when() & FL_WHEN_NOT_CHANGED ) ||
1008 ( is_row!= select_row || is_col!= select_col )
1011 do_callback(CONTEXT_CELL, select_row, select_col);
1012 //damage_zone(current_row, current_col, select_row, select_col);
1013 ret = 1;
1015 break;
1018 default:
1019 change_cursor(FL_CURSOR_DEFAULT);
1020 break;
1022 return(ret);
1025 // Resize FLTK override
1026 // Handle resize events if user resizes parent window.
1028 void Fl_Table::resize(int X, int Y, int W, int H) {
1029 // Tell group to resize, and recalc our own widget as well
1030 Fl_Group::resize(X, Y, W, H);
1031 table_resized();
1032 redraw();
1035 // Draw a cell
1036 void Fl_Table::_redraw_cell(TableContext context, int r, int c) {
1037 if ( r < 0 || c < 0 ) return;
1038 int X,Y,W,H;
1039 find_cell(context, r, c, X, Y, W, H); // find positions of cell
1040 draw_cell(context, r, c, X, Y, W, H); // call users' function to draw it
1044 See if the cell at row \p r and column \p c is selected.
1045 \returns 1 if the cell is selected, 0 if not.
1047 int Fl_Table::is_selected(int r, int c) {
1048 int s_left, s_right, s_top, s_bottom;
1050 if (select_col > current_col) {
1051 s_left = current_col;
1052 s_right = select_col;
1053 } else {
1054 s_right = current_col;
1055 s_left = select_col;
1057 if (select_row > current_row) {
1058 s_top = current_row;
1059 s_bottom = select_row;
1060 } else {
1061 s_bottom = current_row;
1062 s_top = select_row;
1064 if (r >= s_top && r <= s_bottom && c >= s_left && c <= s_right) {
1065 return 1;
1067 return 0;
1071 Gets the region of cells selected (highlighted).
1073 \param[in] row_top Returns the top row of selection area
1074 \param[in] col_left Returns the left column of selection area
1075 \param[in] row_bot Returns the bottom row of selection area
1076 \param[in] col_right Returns the right column of selection area
1078 void Fl_Table::get_selection(int& row_top, int& col_left, int& row_bot, int& col_right) {
1079 if (select_col > current_col) {
1080 col_left = current_col;
1081 col_right = select_col;
1082 } else {
1083 col_right = current_col;
1084 col_left = select_col;
1086 if (select_row > current_row) {
1087 row_top = current_row;
1088 row_bot = select_row;
1089 } else {
1090 row_bot = current_row;
1091 row_top = select_row;
1096 Sets the region of cells to be selected (highlighted).
1098 So for instance, set_selection(0,0,0,0) selects the top/left cell in the table.
1099 And set_selection(0,0,1,1) selects the four cells in rows 0 and 1, column 0 and 1.
1101 \param[in] row_top Top row of selection area
1102 \param[in] col_left Left column of selection area
1103 \param[in] row_bot Bottom row of selection area
1104 \param[in] col_right Right column of selection area
1106 void Fl_Table::set_selection(int row_top, int col_left, int row_bot, int col_right) {
1107 damage_zone(current_row, current_col, select_row, select_col);
1108 current_col = col_left;
1109 current_row = row_top;
1110 select_col = col_right;
1111 select_row = row_bot;
1112 damage_zone(current_row, current_col, select_row, select_col);
1115 // Draw the entire Fl_Table
1116 // Override the draw() routine to draw the table.
1117 // Then tell the group to draw over us.
1119 void Fl_Table::draw() {
1120 draw_cell(CONTEXT_STARTPAGE, 0, 0, // let user's drawing routine
1121 tix, tiy, tiw, tih); // prep new page
1123 // Let fltk widgets draw themselves first. Do this after
1124 // draw_cell(CONTEXT_STARTPAGE) in case user moves widgets around.
1125 // Use window 'inner' clip to prevent drawing into table border.
1126 // (unfortunately this clips FLTK's border, so we must draw it explicity below)
1128 fl_push_clip(wix, wiy, wiw, wih);
1130 Fl_Group::draw();
1132 fl_pop_clip();
1134 // Explicitly draw border around widget, if any
1135 draw_box(box(), x(), y(), w(), h(), color());
1137 // If Fl_Scroll 'table' is hidden, draw its box
1138 // Do this after Fl_Group::draw() so we draw over scrollbars
1139 // that leak around the border.
1141 if ( ! table->visible() ) {
1142 if ( damage() & FL_DAMAGE_ALL || damage() & FL_DAMAGE_CHILD ) {
1143 draw_box(table->box(), tox, toy, tow, toh, table->color());
1146 // Clip all further drawing to the inner widget dimensions
1147 fl_push_clip(wix, wiy, wiw, wih);
1149 // Only redraw a few cells?
1150 if ( ! ( damage() & FL_DAMAGE_ALL ) && _redraw_leftcol != -1 ) {
1151 fl_push_clip(tix, tiy, tiw, tih);
1152 for ( int c = _redraw_leftcol; c <= _redraw_rightcol; c++ ) {
1153 for ( int r = _redraw_toprow; r <= _redraw_botrow; r++ ) {
1154 _redraw_cell(CONTEXT_CELL, r, c);
1157 fl_pop_clip();
1159 if ( damage() & FL_DAMAGE_ALL ) {
1160 int X,Y,W,H;
1161 // Draw row headers, if any
1162 if ( row_header() ) {
1163 get_bounds(CONTEXT_ROW_HEADER, X, Y, W, H);
1164 fl_push_clip(X,Y,W,H);
1165 for ( int r = toprow; r <= botrow; r++ ) {
1166 _redraw_cell(CONTEXT_ROW_HEADER, r, 0);
1168 fl_pop_clip();
1170 // Draw column headers, if any
1171 if ( col_header() ) {
1172 get_bounds(CONTEXT_COL_HEADER, X, Y, W, H);
1173 fl_push_clip(X,Y,W,H);
1174 for ( int c = leftcol; c <= rightcol; c++ ) {
1175 _redraw_cell(CONTEXT_COL_HEADER, 0, c);
1177 fl_pop_clip();
1179 // Draw all cells.
1180 // This includes cells partially obscured off edges of table.
1181 // No longer do this last; you might think it would be nice
1182 // to draw over dead zones, but on redraws it flickers. Avoid
1183 // drawing over deadzones; prevent deadzones by sizing columns.
1185 fl_push_clip(tix, tiy, tiw, tih); {
1186 for ( int r = toprow; r <= botrow; r++ ) {
1187 for ( int c = leftcol; c <= rightcol; c++ ) {
1188 _redraw_cell(CONTEXT_CELL, r, c);
1192 fl_pop_clip();
1193 // Draw little rectangle in corner of headers
1194 if ( row_header() && col_header() ) {
1195 fl_rectf(wix, wiy, row_header_width(), col_header_height(), color());
1198 // Table has a boxtype? Close those few dead pixels
1199 if ( table->box() ) {
1200 if ( col_header() ) {
1201 fl_rectf(tox, wiy, Fl::box_dx(table->box()), col_header_height(), color());
1203 if ( row_header() ) {
1204 fl_rectf(wix, toy, row_header_width(), Fl::box_dx(table->box()), color());
1208 // Table width smaller than window? Fill remainder with rectangle
1209 if ( table_w < tiw ) {
1210 fl_rectf(tix + table_w, tiy, tiw - table_w, tih, color());
1211 // Col header? fill that too
1212 if ( col_header() ) {
1213 fl_rectf(tix + table_w,
1214 wiy,
1215 // get that corner just right..
1216 (tiw - table_w + Fl::box_dw(table->box()) -
1217 Fl::box_dx(table->box())),
1218 col_header_height(),
1219 color());
1222 // Table height smaller than window? Fill remainder with rectangle
1223 if ( table_h < tih ) {
1224 fl_rectf(tix, tiy + table_h, tiw, tih - table_h, color());
1225 if ( row_header() ) {
1226 // NOTE:
1227 // Careful with that lower corner; don't use tih; when eg.
1228 // table->box(FL_THIN_UPFRAME) and hscrollbar hidden,
1229 // leaves a row of dead pixels.
1231 fl_rectf(wix, tiy + table_h, row_header_width(),
1232 (wiy+wih) - (tiy+table_h) -
1233 ( hscrollbar->visible() ? SCROLLBAR_SIZE : 0),
1234 color());
1238 // Both scrollbars? Draw little box in lower right
1239 if ( vscrollbar->visible() && hscrollbar->visible() ) {
1240 fl_rectf(vscrollbar->x(), hscrollbar->y(),
1241 vscrollbar->w(), hscrollbar->h(), color());
1243 draw_cell(CONTEXT_ENDPAGE, 0, 0, // let user's drawing
1244 tix, tiy, tiw, tih); // routines cleanup
1246 _redraw_leftcol = _redraw_rightcol = _redraw_toprow = _redraw_botrow = -1;
1248 fl_pop_clip();
1252 // End of "$Id: Fl_Table.cxx 7950 2010-12-05 01:22:53Z greg.ercolano $".