text: //
  //      table-sort -- An example application using a sortable Fl_Table
  //
  //      Originally the 'sortapp.cxx' example program that came with
  //      erco's Fl_Table widget. Added to FLTK in 2010.
  //
  //      Example of a non-trivial application that uses Fl_Table
  //      with sortable columns. This example is not trying to be simple,
  //      but to demonstrate the complexities of an actual app.
  //
  // Copyright 2010 Greg Ercolano.
  // Copyright 1998-2010 by Bill Spitzak and others.
  //
  // This library is free software. Distribution and use rights are outlined in
  // the file "COPYING" which should have been included with this file.  If this
  // file is missing or damaged, see the license at:
  //
  //     https://www.fltk.org/COPYING.php
  //
  // Please see the following page on how to report bugs and issues:
  //
  //     https://www.fltk.org/bugs.php
  //
  
  #include <FL/Fl.H>
  #include <FL/Fl_Double_Window.H>
  #include <FL/fl_draw.H>
  #include <FL/Fl_Table_Row.H>
  
  #include <stdio.h>
  #include <string.h>
  #include <ctype.h>
  #include <vector>
  #include <algorithm>            // STL sort
  
  #define MARGIN 20
  
  #ifdef _WIN32
  // WINDOWS
  #  define DIRCMD          "dir"
  static const char *G_header[] = { "Date", "Time", "Size", "Filename", "", "", "", "", "", 0 };
  #  ifdef _MSC_VER
  #    define popen           _popen
  #  endif
  #else /* _WIN32 */
  // UNIX
  #  define DIRCMD          "ls -l"
  static const char *G_header[] = { "Perms", "#L", "Own", "Group", "Size", "Date", "", "", "Filename", 0 };
  #endif /* _WIN32 */
  
  // Font face/sizes for header and rows
  #define HEADER_FONTFACE FL_HELVETICA_BOLD
  #define HEADER_FONTSIZE 16
  #define ROW_FONTFACE    FL_HELVETICA
  #define ROW_FONTSIZE    16
  
  // A single row of columns
  class Row {
  public:
      std::vector<char*> cols;
  };
  
  // Sort class to handle sorting column using std::sort
  class SortColumn {
      int _col, _reverse;
  public:
      SortColumn(int col, int reverse) {
          _col = col;
          _reverse = reverse;
      }
      bool operator()(const Row &a, const Row &b) {
          const char *ap = ( _col < (int)a.cols.size() ) ? a.cols[_col] : "",
                     *bp = ( _col < (int)b.cols.size() ) ? b.cols[_col] : "";
          if ( isdigit(*ap) && isdigit(*bp) ) {           // cheezy detection of numeric data
              // Numeric sort
              int av=0; sscanf(ap, "%d", &av);
              int bv=0; sscanf(bp, "%d", &bv);
              return( _reverse ? av < bv : bv < av );
          } else {
              // Alphabetic sort
              return( _reverse ? strcmp(ap, bp) > 0 : strcmp(ap, bp) < 0 );
          }
      }
  };
  
  // Derive a custom class from Fl_Table_Row
  class MyTable : public Fl_Table_Row {
  private:
      std::vector<Row> _rowdata;                                  // data in each row
      int _sort_reverse;
      int _sort_lastcol;
  
      static void event_callback(Fl_Widget*, void*);
      void event_callback2();                                     // callback for table events
  
  protected:
      void draw_cell(TableContext context, int R=0, int C=0,      // table cell drawing
                     int X=0, int Y=0, int W=0, int H=0);
      void sort_column(int col, int reverse=0);                   // sort table by a column
      void draw_sort_arrow(int X,int Y,int W,int H);
  
  public:
      // Ctor
      MyTable(int x, int y, int w, int h, const char *l=0) : Fl_Table_Row(x,y,w,h,l) {
          _sort_reverse = 0;
          _sort_lastcol = -1;
          end();
          callback(event_callback, (void*)this);
      }
      ~MyTable() { }                              // Dtor
      void load_command(const char *cmd);         // Load the output of a command into table
      void autowidth(int pad);                    // Automatically set column widths to data
      void resize_window();                       // Resize parent window to size of table
  };
  
  // Sort a column up or down
  void MyTable::sort_column(int col, int reverse) {
      std::sort(_rowdata.begin(), _rowdata.end(), SortColumn(col, reverse));
      redraw();
  }
  
  // Draw sort arrow
  void MyTable::draw_sort_arrow(int X,int Y,int W,int H) {
      int xlft = X+(W-6)-8;
      int xctr = X+(W-6)-4;
      int xrit = X+(W-6)-0;
      int ytop = Y+(H/2)-4;
      int ybot = Y+(H/2)+4;
      if ( _sort_reverse ) {
          // Engraved down arrow
          fl_color(FL_WHITE);
          fl_line(xrit, ytop, xctr, ybot);
          fl_color(41);                   // dark gray
          fl_line(xlft, ytop, xrit, ytop);
          fl_line(xlft, ytop, xctr, ybot);
      } else {
          // Engraved up arrow
          fl_color(FL_WHITE);
          fl_line(xrit, ybot, xctr, ytop);
          fl_line(xrit, ybot, xlft, ybot);
          fl_color(41);                   // dark gray
          fl_line(xlft, ybot, xctr, ytop);
      }
  }
  
  // Handle drawing all cells in table
  void MyTable::draw_cell(TableContext context, int R, int C, int X, int Y, int W, int H) {
      const char *s = "";
      if ( R < (int)_rowdata.size() && C < (int)_rowdata[R].cols.size() )
          s = _rowdata[R].cols[C];
      switch ( context ) {
          case CONTEXT_COL_HEADER:
              fl_push_clip(X,Y,W,H); {
                  fl_draw_box(FL_THIN_UP_BOX, X,Y,W,H, FL_BACKGROUND_COLOR);
                  if ( C < 9 ) {
                      fl_font(HEADER_FONTFACE, HEADER_FONTSIZE);
                      fl_color(FL_BLACK);
                      fl_draw(G_header[C], X+2,Y,W,H, FL_ALIGN_LEFT, 0, 0);         // +2=pad left
                      // Draw sort arrow
                      if ( C == _sort_lastcol ) {
                          draw_sort_arrow(X,Y,W,H);
                      }
                  }
              }
              fl_pop_clip();
              return;
          case CONTEXT_CELL: {
              fl_push_clip(X,Y,W,H); {
                  // Bg color
                  Fl_Color bgcolor = row_selected(R) ? selection_color() : FL_WHITE;
                  fl_color(bgcolor); fl_rectf(X,Y,W,H);
                  fl_font(ROW_FONTFACE, ROW_FONTSIZE);
                  fl_color(FL_BLACK); fl_draw(s, X+2,Y,W,H, FL_ALIGN_LEFT);     // +2=pad left
                  // Border
                  fl_color(FL_LIGHT2); fl_rect(X,Y,W,H);
              }
              fl_pop_clip();
              return;
          }
          default:
              return;
      }
  }
  
  // Automatically set column widths to widest data in each column
  void MyTable::autowidth(int pad) {
      int w, h;
      // Initialize all column widths to header width
      fl_font(HEADER_FONTFACE, HEADER_FONTSIZE);
      for ( int c=0; G_header[c]; c++ ) {
          w=0; fl_measure(G_header[c], w, h, 0);                   // pixel width of header text
          col_width(c, w+pad);
      }
      fl_font(ROW_FONTFACE, ROW_FONTSIZE);
      for ( int r=0; r<(int)_rowdata.size(); r++ ) {
          for ( int c=0; c<(int)_rowdata[r].cols.size(); c++ ) {
              w=0; fl_measure(_rowdata[r].cols[c], w, h, 0);       // pixel width of row text
              if ( (w + pad) > col_width(c)) col_width(c, w + pad);
          }
      }
      table_resized();
      redraw();
  }
  
  // Resize parent window to size of table
  void MyTable::resize_window() {
      // Determine exact outer width of table with all columns visible
      int width = 4;                                          // width of table borders
      for ( int t=0; t<cols(); t++ ) width += col_width(t);   // total width of all columns
      width += MARGIN*2;
      if ( width < 200 || width > Fl::w() ) return;
      window()->resize(window()->x(), window()->y(), width, window()->h());  // resize window to fit
  }
  
  // Load table with output of 'cmd'
  void MyTable::load_command(const char *cmd) {
      char s[512];
      FILE *fp = popen(cmd, "r");
      cols(0);
      for ( int r=0; fgets(s, sizeof(s)-1, fp); r++ ) {
          // Add a new row
          Row newrow; _rowdata.push_back(newrow);
          std::vector<char*> &rc = _rowdata[r].cols;
          // Break line into separate word 'columns'
          char *ss;
          const char *delim = " \t\n";
          for(int t=0; (t==0)?(ss=strtok(s,delim)):(ss=strtok(NULL,delim)); t++) {
              rc.push_back(strdup(ss));
          }
          // Keep track of max # columns
          if ( (int)rc.size() > cols() ) {
              cols((int)rc.size());
          }
      }
      // How many rows we loaded
      rows((int)_rowdata.size());
      // Auto-calculate widths, with 20 pixel padding
      autowidth(20);
  }
  
  // Callback whenever someone clicks on different parts of the table
  void MyTable::event_callback(Fl_Widget*, void *data) {
      MyTable *o = (MyTable*)data;
      o->event_callback2();
  }
  
  void MyTable::event_callback2() {
      //int ROW = callback_row();                 // unused
      int COL = callback_col();
      TableContext context = callback_context();
      switch ( context ) {
          case CONTEXT_COL_HEADER: {              // someone clicked on column header
              if ( Fl::event() == FL_RELEASE && Fl::event_button() == 1 ) {
                  if ( _sort_lastcol == COL ) {   // Click same column? Toggle sort
                      _sort_reverse ^= 1;
                  } else {                        // Click diff column? Up sort
                      _sort_reverse = 0;
                  }
                  sort_column(COL, _sort_reverse);
                  _sort_lastcol = COL;
              }
              break;
          }
          default:
              return;
      }
  }
  
  int main() {
      Fl_Double_Window win(900,500,"Table Sorting");
          MyTable table(MARGIN, MARGIN, win.w()-MARGIN*2, win.h()-MARGIN*2);
          table.selection_color(FL_YELLOW);
          table.col_header(1);
          table.col_resize(1);
          table.when(FL_WHEN_RELEASE);            // handle table events on release
          table.load_command(DIRCMD);             // load table with a directory listing
          table.row_height_all(18);               // height of all rows
          table.tooltip("Click on column headings to toggle column sorting");
          table.color(FL_WHITE);
      win.end();
      win.resizable(table);
      table.resize_window();
      win.show();
      return(Fl::run());
  }
  