You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1254 lines
39KB

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