2 // "$Id: table-sort.cxx 8193 2011-01-05 17:58:16Z greg.ercolano $"
4 // table-sort -- An example application using a sortable Fl_Table
6 // Originally the 'sortapp.cxx' example program that came with
7 // erco's Fl_Table widget. Added to FLTK in 2010.
9 // Example of a non-trivial application that uses Fl_Table
10 // with sortable columns. This example is not trying to be simple,
11 // but to demonstrate the complexities of an actual app.
13 // Copyright 2010 Greg Ercolano.
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
37 #include <FL/Fl_Double_Window.H>
38 #include <FL/fl_draw.H>
39 #include <FL/Fl_Table_Row.H>
44 #include <algorithm> // STL sort
51 # define DIRHEADER { "Date", "Time", "Size", "Filename", "", "", "", "", "" }
58 # define DIRCMD "ls -l"
59 # define DIRHEADER { "Perms", "#L", "Own", "Group", "Size", "Date", "", "", "Filename" }
62 // A single row of columns
65 std::vector
<char*> cols
;
68 // Sort class to handle sorting column using std::sort
72 SortColumn(int col
, int reverse
) {
76 bool operator()(const Row
&a
, const Row
&b
) {
77 const char *ap
= ( _col
< (int)a
.cols
.size() ) ? a
.cols
[_col
] : "",
78 *bp
= ( _col
< (int)b
.cols
.size() ) ? b
.cols
[_col
] : "";
79 if ( isdigit(*ap
) && isdigit(*bp
) ) { // cheezy detection of numeric data
81 int av
=0; sscanf(ap
, "%d", &av
);
82 int bv
=0; sscanf(bp
, "%d", &bv
);
83 return( _reverse
? av
< bv
: bv
< av
);
86 return( _reverse
? strcmp(ap
, bp
) > 0 : strcmp(ap
, bp
) < 0 );
91 // Derive a custom class from Fl_Table_Row
92 class MyTable
: public Fl_Table_Row
{
94 std::vector
<Row
> _rowdata
; // data in each row
98 static void event_callback(Fl_Widget
*, void*);
99 void event_callback2(); // callback for table events
102 void draw_cell(TableContext context
, int R
=0, int C
=0, // table cell drawing
103 int X
=0, int Y
=0, int W
=0, int H
=0);
104 void sort_column(int col
, int reverse
=0); // sort table by a column
105 void draw_sort_arrow(int X
,int Y
,int W
,int H
,int sort
);
109 MyTable(int x
, int y
, int w
, int h
, const char *l
=0) : Fl_Table_Row(x
,y
,w
,h
,l
) {
113 callback(event_callback
, (void*)this);
115 ~MyTable() { } // Dtor
116 void load_command(const char *cmd
); // Load the output of a command into table
117 void autowidth(int pad
); // Automatically set column widths to data
118 void resize_window(); // Resize parent window to size of table
121 // Sort a column up or down
122 void MyTable::sort_column(int col
, int reverse
) {
123 std::sort(_rowdata
.begin(), _rowdata
.end(), SortColumn(col
, reverse
));
128 void MyTable::draw_sort_arrow(int X
,int Y
,int W
,int H
,int sort
) {
129 int xlft
= X
+(W
-6)-8;
130 int xctr
= X
+(W
-6)-4;
131 int xrit
= X
+(W
-6)-0;
132 int ytop
= Y
+(H
/2)-4;
133 int ybot
= Y
+(H
/2)+4;
134 if ( _sort_reverse
) {
135 // Engraved down arrow
137 fl_line(xrit
, ytop
, xctr
, ybot
);
138 fl_color(41); // dark gray
139 fl_line(xlft
, ytop
, xrit
, ytop
);
140 fl_line(xlft
, ytop
, xctr
, ybot
);
144 fl_line(xrit
, ybot
, xctr
, ytop
);
145 fl_line(xrit
, ybot
, xlft
, ybot
);
146 fl_color(41); // dark gray
147 fl_line(xlft
, ybot
, xctr
, ytop
);
151 // Handle drawing all cells in table
152 void MyTable::draw_cell(TableContext context
, int R
, int C
, int X
, int Y
, int W
, int H
) {
154 if ( R
< (int)_rowdata
.size() && C
< (int)_rowdata
[R
].cols
.size() )
155 s
= _rowdata
[R
].cols
[C
];
157 case CONTEXT_COL_HEADER
:
158 fl_push_clip(X
,Y
,W
,H
); {
159 static const char *head
[] = DIRHEADER
;
160 fl_draw_box(FL_THIN_UP_BOX
, X
,Y
,W
,H
, FL_BACKGROUND_COLOR
);
162 fl_font(FL_HELVETICA_BOLD
, 16);
164 fl_draw(head
[C
], X
+2,Y
,W
,H
, FL_ALIGN_LEFT
, 0, 0); // +2=pad left
166 if ( C
== _sort_lastcol
) {
167 draw_sort_arrow(X
,Y
,W
,H
, _sort_reverse
);
174 fl_push_clip(X
,Y
,W
,H
); {
176 Fl_Color bgcolor
= row_selected(R
) ? selection_color() : FL_WHITE
;
177 fl_color(bgcolor
); fl_rectf(X
,Y
,W
,H
);
178 fl_font(FL_HELVETICA
, 16);
179 fl_color(FL_BLACK
); fl_draw(s
, X
+2,Y
,W
,H
, FL_ALIGN_LEFT
); // +2=pad left
181 fl_color(FL_LIGHT2
); fl_rect(X
,Y
,W
,H
);
191 // Automatically set column widths to widest data in each column
192 void MyTable::autowidth(int pad
) {
193 fl_font(FL_COURIER
, 16);
194 // Initialize all column widths to lowest value
195 for ( int c
=0; c
<cols(); c
++ ) col_width(c
, pad
);
196 for ( int r
=0; r
<(int)_rowdata
.size(); r
++ ) {
198 for ( int c
=0; c
<(int)_rowdata
[r
].cols
.size(); c
++ ) {
199 fl_measure(_rowdata
[r
].cols
[c
], w
, h
, 0); // get pixel width of text
200 if ( (w
+ pad
) > col_width(c
)) col_width(c
, w
+ pad
);
207 // Resize parent window to size of table
208 void MyTable::resize_window() {
209 // Determine exact outer width of table with all columns visible
210 int width
= 4; // width of table borders
211 for ( int t
=0; t
<cols(); t
++ ) width
+= col_width(t
); // total width of all columns
213 if ( width
< 200 || width
> Fl::w() ) return;
214 window()->resize(window()->x(), window()->y(), width
, window()->h()); // resize window to fit
217 // Load table with output of 'cmd'
218 void MyTable::load_command(const char *cmd
) {
220 FILE *fp
= popen(cmd
, "r");
222 for ( int r
=0; fgets(s
, sizeof(s
)-1, fp
); r
++ ) {
224 Row newrow
; _rowdata
.push_back(newrow
);
225 std::vector
<char*> &rc
= _rowdata
[r
].cols
;
226 // Break line into separate word 'columns'
228 const char *delim
= " \t\n";
229 for(int t
=0; (t
==0)?(ss
=strtok(s
,delim
)):(ss
=strtok(NULL
,delim
)); t
++) {
230 rc
.push_back(strdup(ss
));
232 // Keep track of max # columns
233 if ( (int)rc
.size() > cols() ) {
234 cols((int)rc
.size());
237 // How many rows we loaded
238 rows((int)_rowdata
.size());
239 // Auto-calculate widths, with 20 pixel padding
243 // Callback whenever someone clicks on different parts of the table
244 void MyTable::event_callback(Fl_Widget
*, void *data
) {
245 MyTable
*o
= (MyTable
*)data
;
246 o
->event_callback2();
249 void MyTable::event_callback2() {
250 //int ROW = callback_row(); // unused
251 int COL
= callback_col();
252 TableContext context
= callback_context();
254 case CONTEXT_COL_HEADER
: { // someone clicked on column header
255 if ( Fl::event() == FL_RELEASE
&& Fl::event_button() == 1 ) {
256 if ( _sort_lastcol
== COL
) { // Click same column? Toggle sort
258 } else { // Click diff column? Up sort
261 sort_column(COL
, _sort_reverse
);
272 Fl_Double_Window
win(900,500,"Table Sorting");
273 MyTable
table(MARGIN
, MARGIN
, win
.w()-MARGIN
*2, win
.h()-MARGIN
*2);
274 table
.selection_color(FL_YELLOW
);
277 table
.when(FL_WHEN_RELEASE
); // handle table events on release
278 table
.load_command(DIRCMD
); // load table with a directory listing
279 table
.row_height_all(18); // height of all rows
280 table
.tooltip("Click on column headings to toggle column sorting");
281 table
.color(FL_WHITE
);
283 win
.resizable(table
);
284 table
.resize_window();
290 // End of "$Id: table-sort.cxx 8193 2011-01-05 17:58:16Z greg.ercolano $".