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.

292 lines
9.9KB

  1. //
  2. // "$Id: table-sort.cxx 8193 2011-01-05 17:58:16Z greg.ercolano $"
  3. //
  4. // table-sort -- An example application using a sortable Fl_Table
  5. //
  6. // Originally the 'sortapp.cxx' example program that came with
  7. // erco's Fl_Table widget. Added to FLTK in 2010.
  8. //
  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.
  12. //
  13. // Copyright 2010 Greg Ercolano.
  14. // Copyright 1998-2010 by Bill Spitzak and others.
  15. //
  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.
  20. //
  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.
  25. //
  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
  29. // USA.
  30. //
  31. // Please report all bugs and problems on the following page:
  32. //
  33. // http://www.fltk.org/str.php
  34. //
  35. #include <FL/Fl.H>
  36. #include <FL/Fl_Double_Window.H>
  37. #include <FL/fl_draw.H>
  38. #include <FL/Fl_Table_Row.H>
  39. #include <stdio.h>
  40. #include <string.h>
  41. #include <vector>
  42. #include <algorithm> // STL sort
  43. #define MARGIN 20
  44. #ifdef WIN32
  45. // WINDOWS
  46. # define DIRCMD "dir"
  47. # define DIRHEADER { "Date", "Time", "Size", "Filename", "", "", "", "", "" }
  48. # ifdef _MSC_VER
  49. # define popen _popen
  50. # endif
  51. #else /*WIN32*/
  52. // UNIX
  53. # include <ctype.h>
  54. # define DIRCMD "ls -l"
  55. # define DIRHEADER { "Perms", "#L", "Own", "Group", "Size", "Date", "", "", "Filename" }
  56. #endif /*WIN32*/
  57. // A single row of columns
  58. class Row {
  59. public:
  60. std::vector<char*> cols;
  61. };
  62. // Sort class to handle sorting column using std::sort
  63. class SortColumn {
  64. int _col, _reverse;
  65. public:
  66. SortColumn(int col, int reverse) {
  67. _col = col;
  68. _reverse = reverse;
  69. }
  70. bool operator()(const Row &a, const Row &b) {
  71. const char *ap = ( _col < (int)a.cols.size() ) ? a.cols[_col] : "",
  72. *bp = ( _col < (int)b.cols.size() ) ? b.cols[_col] : "";
  73. if ( isdigit(*ap) && isdigit(*bp) ) { // cheezy detection of numeric data
  74. // Numeric sort
  75. int av=0; sscanf(ap, "%d", &av);
  76. int bv=0; sscanf(bp, "%d", &bv);
  77. return( _reverse ? av < bv : bv < av );
  78. } else {
  79. // Alphabetic sort
  80. return( _reverse ? strcmp(ap, bp) > 0 : strcmp(ap, bp) < 0 );
  81. }
  82. }
  83. };
  84. // Derive a custom class from Fl_Table_Row
  85. class MyTable : public Fl_Table_Row {
  86. private:
  87. std::vector<Row> _rowdata; // data in each row
  88. int _sort_reverse;
  89. int _sort_lastcol;
  90. static void event_callback(Fl_Widget*, void*);
  91. void event_callback2(); // callback for table events
  92. protected:
  93. void draw_cell(TableContext context, int R=0, int C=0, // table cell drawing
  94. int X=0, int Y=0, int W=0, int H=0);
  95. void sort_column(int col, int reverse=0); // sort table by a column
  96. void draw_sort_arrow(int X,int Y,int W,int H,int sort);
  97. public:
  98. // Ctor
  99. MyTable(int x, int y, int w, int h, const char *l=0) : Fl_Table_Row(x,y,w,h,l) {
  100. _sort_reverse = 0;
  101. _sort_lastcol = -1;
  102. end();
  103. callback(event_callback, (void*)this);
  104. }
  105. ~MyTable() { } // Dtor
  106. void load_command(const char *cmd); // Load the output of a command into table
  107. void autowidth(int pad); // Automatically set column widths to data
  108. void resize_window(); // Resize parent window to size of table
  109. };
  110. // Sort a column up or down
  111. void MyTable::sort_column(int col, int reverse) {
  112. std::sort(_rowdata.begin(), _rowdata.end(), SortColumn(col, reverse));
  113. redraw();
  114. }
  115. // Draw sort arrow
  116. void MyTable::draw_sort_arrow(int X,int Y,int W,int H,int sort) {
  117. int xlft = X+(W-6)-8;
  118. int xctr = X+(W-6)-4;
  119. int xrit = X+(W-6)-0;
  120. int ytop = Y+(H/2)-4;
  121. int ybot = Y+(H/2)+4;
  122. if ( _sort_reverse ) {
  123. // Engraved down arrow
  124. fl_color(FL_WHITE);
  125. fl_line(xrit, ytop, xctr, ybot);
  126. fl_color(41); // dark gray
  127. fl_line(xlft, ytop, xrit, ytop);
  128. fl_line(xlft, ytop, xctr, ybot);
  129. } else {
  130. // Engraved up arrow
  131. fl_color(FL_WHITE);
  132. fl_line(xrit, ybot, xctr, ytop);
  133. fl_line(xrit, ybot, xlft, ybot);
  134. fl_color(41); // dark gray
  135. fl_line(xlft, ybot, xctr, ytop);
  136. }
  137. }
  138. // Handle drawing all cells in table
  139. void MyTable::draw_cell(TableContext context, int R, int C, int X, int Y, int W, int H) {
  140. const char *s = "";
  141. if ( R < (int)_rowdata.size() && C < (int)_rowdata[R].cols.size() )
  142. s = _rowdata[R].cols[C];
  143. switch ( context ) {
  144. case CONTEXT_COL_HEADER:
  145. fl_push_clip(X,Y,W,H); {
  146. static const char *head[] = DIRHEADER;
  147. fl_draw_box(FL_THIN_UP_BOX, X,Y,W,H, FL_BACKGROUND_COLOR);
  148. if ( C < 9 ) {
  149. fl_font(FL_HELVETICA_BOLD, 16);
  150. fl_color(FL_BLACK);
  151. fl_draw(head[C], X+2,Y,W,H, FL_ALIGN_LEFT, 0, 0); // +2=pad left
  152. // Draw sort arrow
  153. if ( C == _sort_lastcol ) {
  154. draw_sort_arrow(X,Y,W,H, _sort_reverse);
  155. }
  156. }
  157. }
  158. fl_pop_clip();
  159. return;
  160. case CONTEXT_CELL: {
  161. fl_push_clip(X,Y,W,H); {
  162. // Bg color
  163. Fl_Color bgcolor = row_selected(R) ? selection_color() : FL_WHITE;
  164. fl_color(bgcolor); fl_rectf(X,Y,W,H);
  165. fl_font(FL_HELVETICA, 16);
  166. fl_color(FL_BLACK); fl_draw(s, X+2,Y,W,H, FL_ALIGN_LEFT); // +2=pad left
  167. // Border
  168. fl_color(FL_LIGHT2); fl_rect(X,Y,W,H);
  169. }
  170. fl_pop_clip();
  171. return;
  172. }
  173. default:
  174. return;
  175. }
  176. }
  177. // Automatically set column widths to widest data in each column
  178. void MyTable::autowidth(int pad) {
  179. fl_font(FL_COURIER, 16);
  180. // Initialize all column widths to lowest value
  181. for ( int c=0; c<cols(); c++ ) col_width(c, pad);
  182. for ( int r=0; r<(int)_rowdata.size(); r++ ) {
  183. int w, h;
  184. for ( int c=0; c<(int)_rowdata[r].cols.size(); c++ ) {
  185. fl_measure(_rowdata[r].cols[c], w, h, 0); // get pixel width of text
  186. if ( (w + pad) > col_width(c)) col_width(c, w + pad);
  187. }
  188. }
  189. table_resized();
  190. redraw();
  191. }
  192. // Resize parent window to size of table
  193. void MyTable::resize_window() {
  194. // Determine exact outer width of table with all columns visible
  195. int width = 4; // width of table borders
  196. for ( int t=0; t<cols(); t++ ) width += col_width(t); // total width of all columns
  197. width += MARGIN*2;
  198. if ( width < 200 || width > Fl::w() ) return;
  199. window()->resize(window()->x(), window()->y(), width, window()->h()); // resize window to fit
  200. }
  201. // Load table with output of 'cmd'
  202. void MyTable::load_command(const char *cmd) {
  203. char s[512];
  204. FILE *fp = popen(cmd, "r");
  205. cols(0);
  206. for ( int r=0; fgets(s, sizeof(s)-1, fp); r++ ) {
  207. // Add a new row
  208. Row newrow; _rowdata.push_back(newrow);
  209. std::vector<char*> &rc = _rowdata[r].cols;
  210. // Break line into separate word 'columns'
  211. char *ss;
  212. const char *delim = " \t\n";
  213. for(int t=0; (t==0)?(ss=strtok(s,delim)):(ss=strtok(NULL,delim)); t++) {
  214. rc.push_back(strdup(ss));
  215. }
  216. // Keep track of max # columns
  217. if ( (int)rc.size() > cols() ) {
  218. cols((int)rc.size());
  219. }
  220. }
  221. // How many rows we loaded
  222. rows((int)_rowdata.size());
  223. // Auto-calculate widths, with 20 pixel padding
  224. autowidth(20);
  225. }
  226. // Callback whenever someone clicks on different parts of the table
  227. void MyTable::event_callback(Fl_Widget*, void *data) {
  228. MyTable *o = (MyTable*)data;
  229. o->event_callback2();
  230. }
  231. void MyTable::event_callback2() {
  232. //int ROW = callback_row(); // unused
  233. int COL = callback_col();
  234. TableContext context = callback_context();
  235. switch ( context ) {
  236. case CONTEXT_COL_HEADER: { // someone clicked on column header
  237. if ( Fl::event() == FL_RELEASE && Fl::event_button() == 1 ) {
  238. if ( _sort_lastcol == COL ) { // Click same column? Toggle sort
  239. _sort_reverse ^= 1;
  240. } else { // Click diff column? Up sort
  241. _sort_reverse = 0;
  242. }
  243. sort_column(COL, _sort_reverse);
  244. _sort_lastcol = COL;
  245. }
  246. break;
  247. }
  248. default:
  249. return;
  250. }
  251. }
  252. int main() {
  253. Fl_Double_Window win(900,500,"Table Sorting");
  254. MyTable table(MARGIN, MARGIN, win.w()-MARGIN*2, win.h()-MARGIN*2);
  255. table.selection_color(FL_YELLOW);
  256. table.col_header(1);
  257. table.col_resize(1);
  258. table.when(FL_WHEN_RELEASE); // handle table events on release
  259. table.load_command(DIRCMD); // load table with a directory listing
  260. table.row_height_all(18); // height of all rows
  261. table.tooltip("Click on column headings to toggle column sorting");
  262. table.color(FL_WHITE);
  263. win.end();
  264. win.resizable(table);
  265. table.resize_window();
  266. win.show();
  267. return(Fl::run());
  268. }
  269. //
  270. // End of "$Id: table-sort.cxx 8193 2011-01-05 17:58:16Z greg.ercolano $".
  271. //