2 // "$Id: table-spreadsheet-with-keyboard-nav.cxx 8321 2011-01-28 01:34:22Z greg.ercolano $"
4 // Simple example of an interactive spreadsheet using Fl_Table.
5 // Uses Mr. Satan's technique of instancing an Fl_Input around.
6 // Modified to test Jean-Marc's mods for keyboard nav and mouse selection.
8 // Fl_Table[1.00/LGPL] 04/18/03 Mister Satan -- Initial implementation, submitted to erco for Fl_Table
9 // Fl_Table[1.10/LGPL] 05/17/03 Greg Ercolano -- Small mods to follow changes to Fl_Table
10 // Fl_Table[1.20/LGPL] 02/22/04 Jean-Marc Lienher -- Keyboard nav and mouse selection
11 // Fl_Table[1.21/LGPL] 02/22/04 Greg Ercolano -- Small reformatting mods, comments
12 // FLTK[1.3.0/LGPL] 10/26/10 Greg Ercolano -- Moved from Fl_Table to FLTK 1.3.x, CMP compliance
14 // Copyright 1998-2010 by Bill Spitzak and others.
16 // This library is free software; you can redistribute it and/or
17 // modify it under the terms of the GNU Library General Public
18 // License as published by the Free Software Foundation; either
19 // version 2 of the License, or (at your option) any later version.
21 // This library is distributed in the hope that it will be useful,
22 // but WITHOUT ANY WARRANTY; without even the implied warranty of
23 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 // Library General Public License for more details.
26 // You should have received a copy of the GNU Library General Public
27 // License along with this library; if not, write to the Free Software
28 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
31 // Please report all bugs and problems on the following page:
33 // http://www.fltk.org/str.php
38 #include <FL/Fl_Double_Window.H>
39 #include <FL/Fl_Table.H>
40 #include <FL/Fl_Int_Input.H>
41 #include <FL/Fl_Value_Slider.H>
42 #include <FL/fl_draw.H>
44 const int MAX_COLS
= 26;
45 const int MAX_ROWS
= 500;
47 class Spreadsheet
: public Fl_Table
{
48 Fl_Int_Input
*input
; // single instance of Fl_Int_Input widget
49 int values
[MAX_ROWS
][MAX_COLS
]; // array of data for cells
50 int row_edit
, col_edit
; // row/col being modified
51 int s_left
, s_top
, s_right
, s_bottom
; // kb nav + mouse selection
54 void draw_cell(TableContext context
,int=0,int=0,int=0,int=0,int=0,int=0);
55 void event_callback2(); // table's event callback (instance)
56 static void event_callback(Fl_Widget
*, void *v
) { // table's event callback (static)
57 ((Spreadsheet
*)v
)->event_callback2();
59 static void input_cb(Fl_Widget
*, void* v
) { // input widget's callback
60 ((Spreadsheet
*)v
)->set_value_hide();
64 Spreadsheet(int X
,int Y
,int W
,int H
,const char* L
=0) : Fl_Table(X
,Y
,W
,H
,L
) {
65 callback(&event_callback
, (void*)this);
66 when(FL_WHEN_NOT_CHANGED
|when());
67 // Create input widget that we'll use whenever user clicks on a cell
68 input
= new Fl_Int_Input(W
/2,H
/2,0,0);
70 input
->callback(input_cb
, (void*)this);
71 input
->when(FL_WHEN_ENTER_KEY_ALWAYS
); // callback triggered when user hits Enter
72 input
->maximum_size(5);
73 row_edit
= col_edit
= 0;
74 s_left
= s_top
= s_right
= s_bottom
= 0;
75 for (int c
= 0; c
< MAX_COLS
; c
++)
76 for (int r
= 0; r
< MAX_ROWS
; r
++)
77 values
[r
][c
] = (r
+ 2) * (c
+ 3); // initialize cells
82 // Apply value from input widget to values[row][col] array and hide (done editing)
83 void set_value_hide() {
84 values
[row_edit
][col_edit
] = atoi(input
->value());
86 window()->cursor(FL_CURSOR_DEFAULT
); // XXX: if we don't do this, cursor can disappear!
88 // Change number of rows
92 // Change number of columns
98 return Fl_Table::rows();
100 // Get number of columns
102 return Fl_Table::cols();
104 // Start editing a new cell: move the Fl_Int_Input widget to specified row/column
105 // Preload the widget with the cell's current value,
106 // and make the widget 'appear' at the cell's location.
108 void start_editing(int R
, int C
) {
109 row_edit
= R
; // Now editing this row/col
112 find_cell(CONTEXT_CELL
, R
,C
, X
,Y
,W
,H
); // Find X/Y/W/H of cell
113 input
->resize(X
,Y
,W
,H
); // Move Fl_Input widget there
114 char s
[30]; sprintf(s
, "%d", values
[R
][C
]); // Load input widget with cell's current value
116 input
->position(0,strlen(s
)); // Select entire input field
117 input
->show(); // Show the input widget, now that we've positioned it
120 // Tell the input widget it's done editing, and to 'hide'
121 void done_editing() {
122 if (input
->visible()) { // input widget visible, ie. edit in progress?
123 set_value_hide(); // Transfer its current contents to cell and hide
126 // Return the sum of all rows in this column
127 int sum_rows(int C
) {
129 for (int r
=0; r
<rows()-1; ++r
) // -1: don't include cell data in 'totals' column
133 // Return the sum of all cols in this row
134 int sum_cols(int R
) {
136 for (int c
=0; c
<cols()-1; ++c
) // -1: don't include cell data in 'totals' column
140 // Return the sum of all cells in table
143 for (int c
=0; c
<cols()-1; ++c
) // -1: don't include cell data in 'totals' column
144 for (int r
=0; r
<rows()-1; ++r
) // -1: ""
150 // Handle drawing all cells in table
151 void Spreadsheet::draw_cell(TableContext context
, int R
,int C
, int X
,int Y
,int W
,int H
) {
154 case CONTEXT_STARTPAGE
: // table about to redraw
155 // Get kb nav + mouse 'selection region' for use below
156 get_selection(s_top
, s_left
, s_bottom
, s_right
);
159 case CONTEXT_COL_HEADER
: // table wants us to draw a column heading (C is column)
160 fl_font(FL_HELVETICA
| FL_BOLD
, 14); // set font for heading to bold
161 fl_push_clip(X
,Y
,W
,H
); // clip region for text
163 fl_draw_box(FL_THIN_UP_BOX
, X
,Y
,W
,H
, col_header_color());
165 if (C
== cols()-1) { // Last column? show 'TOTAL'
166 fl_draw("TOTAL", X
,Y
,W
,H
, FL_ALIGN_CENTER
);
167 } else { // Not last column? show column letter
168 sprintf(s
, "%c", 'A' + C
);
169 fl_draw(s
, X
,Y
,W
,H
, FL_ALIGN_CENTER
);
175 case CONTEXT_ROW_HEADER
: // table wants us to draw a row heading (R is row)
176 fl_font(FL_HELVETICA
| FL_BOLD
, 14); // set font for row heading to bold
177 fl_push_clip(X
,Y
,W
,H
);
179 fl_draw_box(FL_THIN_UP_BOX
, X
,Y
,W
,H
, row_header_color());
181 if (R
== rows()-1) { // Last row? Show 'Total'
182 fl_draw("TOTAL", X
,Y
,W
,H
, FL_ALIGN_CENTER
);
183 } else { // Not last row? show row#
184 sprintf(s
, "%d", R
+1);
185 fl_draw(s
, X
,Y
,W
,H
, FL_ALIGN_CENTER
);
191 case CONTEXT_CELL
: { // table wants us to draw a cell
192 if (R
== row_edit
&& C
== col_edit
&& input
->visible()) {
193 return; // dont draw for cell with input widget over it
196 // Keyboard nav and mouse selection highlighting
197 if (R
>= s_top
&& R
<= s_bottom
&& C
>= s_left
&& C
<= s_right
) {
198 fl_draw_box(FL_THIN_UP_BOX
, X
,Y
,W
,H
, FL_YELLOW
);
199 } else if ( C
< cols()-1 && R
< rows()-1 ) {
200 fl_draw_box(FL_THIN_UP_BOX
, X
,Y
,W
,H
, FL_WHITE
);
202 fl_draw_box(FL_THIN_UP_BOX
, X
,Y
,W
,H
, 0xbbddbb00); // money green
205 fl_push_clip(X
+3, Y
+3, W
-6, H
-6);
208 if (C
== cols()-1 || R
== rows()-1) { // Last row or col? Show total
209 fl_font(FL_HELVETICA
| FL_BOLD
, 14); // ..in bold font
210 if (C
== cols()-1 && R
== rows()-1) { // Last row+col? Total all cells
211 sprintf(s
, "%d", sum_all());
212 } else if (C
== cols()-1) { // Row subtotal
213 sprintf(s
, "%d", sum_cols(R
));
214 } else if (R
== rows()-1) { // Col subtotal
215 sprintf(s
, "%d", sum_rows(C
));
217 fl_draw(s
, X
+3,Y
+3,W
-6,H
-6, FL_ALIGN_RIGHT
);
218 } else { // Not last row or col? Show cell contents
219 fl_font(FL_HELVETICA
, 14); // ..in regular font
220 sprintf(s
, "%d", values
[R
][C
]);
221 fl_draw(s
, X
+3,Y
+3,W
-6,H
-6, FL_ALIGN_RIGHT
);
228 case CONTEXT_RC_RESIZE
: { // table resizing rows or columns
229 if (!input
->visible()) return;
230 find_cell(CONTEXT_TABLE
, row_edit
, col_edit
, X
, Y
, W
, H
);
231 if (X
==input
->x() && Y
==input
->y() && W
==input
->w() && H
==input
->h()) {
232 return; // no change? ignore
234 input
->resize(X
,Y
,W
,H
);
243 // Callback whenever someone clicks on different parts of the table
244 void Spreadsheet::event_callback2() {
245 int R
= callback_row();
246 int C
= callback_col();
247 TableContext context
= callback_context();
250 case CONTEXT_CELL
: { // A table event occurred on a cell
251 switch (Fl::event()) { // see what FLTK event caused it
252 case FL_PUSH
: // mouse click?
253 done_editing(); // finish editing previous
254 if (R
!= rows()-1 && C
!= cols()-1 ) // only edit cells not in total's columns
255 start_editing(R
,C
); // start new edit
258 case FL_KEYBOARD
: // key press in table?
259 if ( Fl::event_key() == FL_Escape
) exit(0); // ESC closes app
260 if (C
== cols()-1 || R
== rows()-1) return; // no editing of totals column
261 done_editing(); // finish any previous editing
262 set_selection(R
, C
, R
, C
); // select the current cell
263 start_editing(R
,C
); // start new edit
264 if (Fl::event() == FL_KEYBOARD
&& Fl::e_text
[0] != '\r') {
265 input
->handle(Fl::event()); // pass keypress to input widget
272 case CONTEXT_TABLE
: // A table event occurred on dead zone in table
273 case CONTEXT_ROW_HEADER
: // A table event occurred on row/column header
274 case CONTEXT_COL_HEADER
:
275 done_editing(); // done editing, hide
283 // Change number of columns
284 void setcols_cb(Fl_Widget
* w
, void* v
) {
285 Spreadsheet
* table
= (Spreadsheet
*)v
;
286 Fl_Valuator
* in
= (Fl_Valuator
*)w
;
287 int cols
= int(in
->value()) + 1;
292 // Change number of rows
293 void setrows_cb(Fl_Widget
* w
, void* v
) {
294 Spreadsheet
* table
= (Spreadsheet
*)v
;
295 Fl_Valuator
* in
= (Fl_Valuator
*)w
;
296 int rows
= int(in
->value()) + 1;
302 Fl_Double_Window
*win
= new Fl_Double_Window(922, 382, "Fl_Table Spreadsheet with Keyboard Navigation");
303 Spreadsheet
* table
= new Spreadsheet(20, 20, win
->w()-80, win
->h()-80);
305 table
->row_header(1);
306 table
->row_header_width(70);
307 table
->row_resize(1);
309 table
->row_height_all(25);
311 table
->col_header(1);
312 table
->col_header_height(25);
313 table
->col_resize(1);
315 table
->col_width_all(70);
316 table
->set_selection(0,0,0,0); // select top/left cell
318 // Add children to window
322 Fl_Value_Slider
setrows(win
->w()-40,20,20,win
->h()-80, 0);
323 setrows
.type(FL_VERT_NICE_SLIDER
);
324 setrows
.bounds(2,MAX_ROWS
);
326 setrows
.value(table
->rows()-1);
327 setrows
.callback(setrows_cb
, (void*)table
);
328 setrows
.when(FL_WHEN_CHANGED
);
329 setrows
.clear_visible_focus();
332 Fl_Value_Slider
setcols(20,win
->h()-40,win
->w()-80,20, 0);
333 setcols
.type(FL_HOR_NICE_SLIDER
);
334 setcols
.bounds(2,MAX_COLS
);
336 setcols
.value(table
->cols()-1);
337 setcols
.callback(setcols_cb
, (void*)table
);
338 setcols
.when(FL_WHEN_CHANGED
);
339 setcols
.clear_visible_focus();
342 win
->resizable(table
);
349 // End of "$Id: table-spreadsheet-with-keyboard-nav.cxx 8321 2011-01-28 01:34:22Z greg.ercolano $".