packages feed

fltkhs-0.1.0.0: c-examples/table-sort.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <Fl_C.h>
#include <Fl_Table_RowC.h>
#include <Fl_DrawC.h>
#include <Fl_Double_WindowC.h>
#include <Fl_WindowC.h>

typedef char* string;

#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
#  include <ctype.h>
#  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

void print_array(string arr[], int length){
  printf("{");
  int i = 0;
  for(;i<length;i++){
    if (strcmp(arr[i],"") == 0){
      printf(" <N> ");
    }
    else {
      printf(" %s ", arr[i]);
    }
  }
  printf("}\n");
}

void print_addresses(void* arr[], int length){
  printf("{");
  int i = 0;
  for(;i<length;i++){
    printf(" %p ", &arr[i]);
  }
  printf("}\n");
}

/**
   Doubly Linked List, can't insert or delete mid-list
 */
typedef struct Row {
  int* length;
  string* row; // a 1D array
} Row;

Row* to_row(string* arr, int length){
  int* length_cpy;
  length_cpy = malloc(sizeof(int));
  *length_cpy = length;
  Row* result = malloc(sizeof(Row));
  result->length = length_cpy;
  result->row = arr;
  return result;
}

string get_sort_index(Row* row, int sort_index){
  string* ss = row->row;
  string result;
  if (sort_index > *(row->length)-1){
    result = "";
  }
  else {
    result = ss[sort_index];
  }
  return result;
}
void print_sort_index(Row* row, int sort_index){
  string s = get_sort_index(row, sort_index);
  if (strcmp(s,"") == 0){
    printf("<N>");
  }
  else {
    printf("%s",s);
  }
}
string* extract_index(Row* arr[], int rows, int index){
  int i = 0;
  string* result = malloc(sizeof(string) * rows);
  for(;i<rows;i++){
    result[i] = get_sort_index(arr[i], index);
  }
  return result;
}
typedef struct Node {
  struct Node* prev;
  struct Node* next;
  struct Row* row;
} Node;

Node* make_node(Row* row){
  Node* empty = malloc(sizeof(Node));
  empty->prev = NULL;
  empty->next = NULL;
  empty->row = row;
  return empty;
}

void append(Node* n, Node* next){
  n->next = next;
  next->prev = n;
}

Node* from_array(Row* arr[], int length){
  int curr_index = 0;
  Node* start = NULL;
  Node* curr_node = NULL;
  while(curr_index < length){
    Row* row = arr[curr_index];
    if (curr_index == 0){
      curr_node = make_node(row);
      start = curr_node;
    }
    else {
      Node* new_node = make_node(row);
      append(curr_node, new_node);
      curr_node = new_node;
    }
    curr_index++;
  }
  return start;
}

Node* get_last(Node* start){
  Node* curr = start;
  while (curr->next != NULL){
    curr = curr->next;
  }
  return curr;
}

void traverse_forward(Node* start, void** runningState, void (*f)(void** runningState, Row* row)){
  (*f)(runningState, start->row);
  if (start->next != NULL){
    traverse_forward(start->next, runningState, f);
  }
}

void traverse_backward(Node* end, void** runningState, void (*f)(void** runningState, Row* row)){
  (*f)(runningState, end->row);
  if (end->prev != NULL){
    traverse_backward(end->prev, runningState, f);
  }
}

void update_count(void** soFar_ref, Row* ignored){
  int* soFar = (int*)(*soFar_ref);
  *soFar = *soFar + 1;
}

typedef struct {
  int* soFar;
  struct Row** row; // array of rows
} accumulator;

void into_accumulator(void** accum_ref, Row* row){
  accumulator* accum = (accumulator*)(*accum_ref);
  accum->row[*(accum->soFar)] = row;
  *(accum->soFar) = *(accum->soFar) + 1;
}

int* count_nodes(Node* start){
  int* count = malloc(sizeof(int));
  *count = 0;
  traverse_forward(start, (void**)&count, &update_count);
  return count;
}

void update_max(void** max, Row* row){
  int* maxSoFar = (int*)(*max);
  int* length = row->length;
  if (*length > *maxSoFar) {
    *maxSoFar = *maxSoFar + 1;
  }
}

int* max_cols(Node* start){
  int* max = malloc(sizeof(int));
  *max = 0;
  traverse_forward(start, (void**)&max, &update_max);
  return max;
}

Row** to_array(Node* start){
  int* count = count_nodes(start);
  Row** into;
  into = malloc(sizeof(Row) * (*count));
  int soFar = 0;
  accumulator* accum = malloc(sizeof(accumulator));
  accum->soFar = &soFar;
  accum->row = into;
  traverse_forward(start, (void**)&accum, &into_accumulator);
  return into;
}

void print_node(void** ignored, Row* row){
  if (row != NULL){
    print_array(row->row, *(row->length));
  }
  else {
    puts("Row: NULL");
  }
}

void unlink_dll(Node** start){
  Node* next = (*start)->next;
  (*start)->next = NULL;
  (*start)->prev = NULL;
  (*start)->row = NULL;
  if (next != NULL){
    unlink_dll(&next);
  }
}

void free_row(Row* r){
  free(r->length);
  free(r);
  r->row = NULL;
  r->length = NULL;
  r = NULL;
}

void free_rows(Row* arr[], int length){
  int i = 0;
  for(;i<length;i++){
    free_row(arr[i]);
  }
  arr = NULL;
}

void print_rows(Row* rows[], int num_rows){
  printf("{");
  int i = 0;
  for (;i<num_rows;i++){
    print_array(rows[i]->row, *(rows[i]->length));
  }
  printf("}\n");
}

int find_pivot(int i, int j, int sort_index, Row* A[], int (*cmp)(string,string)){
  string firstKey = get_sort_index(A[i],sort_index);
  int k = i + 1;
  for(;k<=j;k++){
    int comparison = (*cmp)(get_sort_index(A[k], sort_index), firstKey);
    if (comparison == 1) {
      return k;
    }
    else if (comparison == -1) {
      return i;
    }
  }
  return -1;
}

void swap(void** a, void** b){
  void* tmp = *a;
  *a = *b;
  *b = tmp;
}

int numcmp(string a, string b){
  int a_int;
  int b_int;
  (strcmp(a,"") == 0) ? a_int = 0 : sscanf(a, "%d", &a_int);
  (strcmp(b,"") == 0) ? b_int = 0 : sscanf(b, "%d", &b_int);
  if (a_int < b_int) {
    return -1;
  }
  else if (a_int > b_int) {
    return 1;
  }
  else {
    return 0;
  }
}

int strcmp_wrapper(string a, string b){
  return strcmp(a,b);
}

int partition(int i, int j, int sort_index,string pivot, Row* A[], int (*cmp)(string,string)){
  int l = i;
  int r = j;
  while(l <= r){
    swap((void**)&A[l], (void**)&A[r]);
    while((*cmp)(get_sort_index(A[l],sort_index),pivot) < 0){
      l++;
    }
    while(((*cmp)(get_sort_index(A[r],sort_index),pivot) >= 0)){
      r--;
    }
  }
  return l;
}

void quicksort(int i, int j, int sort_index, Row* arr[], int (*cmp)(string,string)){
  int pivot_index = find_pivot(i,j,sort_index,arr,cmp);
  string pivot;
  int k;
  if(pivot_index != -1){
    pivot = get_sort_index(arr[pivot_index], sort_index);
    k = partition(i,j,sort_index,pivot,arr,cmp);
    quicksort(i, k-1, sort_index,arr,cmp);
    quicksort(k, j,sort_index, arr,cmp);
  }
}

void reverse(void* arr[], int l, int r){
  int last = r - 1;
  while(l <= last){
    swap(&arr[l],&arr[last]);
    l++;last--;
  }
}

int numeric(string candidate){
  string endptr;
  long int parse = strtol(candidate, &endptr, 0);
  int result = ((parse != 0L) && (*endptr == '\0'));
  return result;
}

int get_first_non_null_index(string arr[], int length){
  int index = -1;
  int curr = 0;
  while(index == -1 && curr < length){
    if (strcmp(arr[curr],"") != 0){
      index = curr;
    }
    curr++;
  }
  return index;
}

int (*(determine_comparator(Row* arr[], int rows, int index)))(string,string){
  int (*cmp)(char*, char*) = NULL;
  string* col  = extract_index(arr,rows,index);
  int test_index = get_first_non_null_index(col, rows);
  if (test_index != -1){
    if (numeric(col[test_index])){
      cmp = &numcmp;
    }
    else {
      cmp = &strcmp_wrapper;
    }
  }
  return cmp;
}

typedef struct {
  Node* _rowData;
  int* _sort_reverse;
  int* _sort_lastcol;
} other_data;

void sort_column(fl_Table_Row table, int col, int should_reverse){
  other_data* o = (other_data*)Fl_Table_Row_other_data(table);
  Node* _rowData = o->_rowData;
  int num_rows = *count_nodes(_rowData);
  Row** rows = to_array(_rowData);

  int (*cmp)(string,string) = NULL;
  cmp = determine_comparator(rows,num_rows,col);
  quicksort(0,num_rows - 1, col, rows, cmp);
  if (should_reverse) {
    reverse((void**)rows, 0, num_rows);
  }
  unlink_dll(&_rowData);
  _rowData = from_array(rows, num_rows);
  o->_rowData = _rowData;
  Fl_Table_Row_set_other_data(table,(void*)o);
  Fl_Table_Row_redraw(table);
}

void event_callback(fl_Widget widget, void* data){
  fl_Table_Row table = (fl_Table_Row) widget; // does nothing but aid readability
  int COL = Fl_Table_Row_callback_col(table);
  TableContextC context = Fl_Table_Row_callback_context(table);
  other_data* o = (other_data*)Fl_Table_Row_other_data(table);
  int* _sort_lastcol = o->_sort_lastcol;
  int* _sort_reverse = o->_sort_reverse;
  switch ( context ) {
  case CONTEXT_COL_HEADERC: {              // someone clicked on column header
    if ( Fl_event() == FL_RELEASE && Fl_event_button() == 1 ) {
      if ( *_sort_lastcol == COL ) {   // Click same column? Toggle sort
	*(o->_sort_reverse) ^= 1;
      } else {                        // Click diff column? Up sort
	*(o->_sort_reverse) ^= 0;
      }
      sort_column(table, COL,*_sort_reverse);
      *(o->_sort_lastcol) = COL;
      Fl_Table_Row_set_other_data(table,(void*)o);
    }
    break;
  }
  default:
    break;
  }
}

void initializeTable(fl_Table_Row table){
  other_data* d = malloc(sizeof(other_data));
  d->_sort_reverse = malloc(sizeof(int));;
  *(d->_sort_reverse) = 0;
  d->_sort_lastcol = malloc(sizeof(int));
  *(d->_sort_lastcol) = -1;
  d->_rowData = malloc(sizeof(Node));
  d->_rowData = NULL;
  Fl_Table_Row_set_other_data(table,(void*) d);
  Fl_Table_Row_set_callback(table,(fl_Callback*)event_callback);
}

void autowidth(fl_Table_Row table, int pad){
  int w, h;
  other_data* o = (other_data*)Fl_Table_Row_other_data(table);
  int num_rows = *(count_nodes(o->_rowData));
  int num_cols = *(max_cols(o->_rowData));
  Row** rows = to_array(o->_rowData);
  // Initialize all column widths to header width
  flc_set_font(HEADER_FONTFACE, HEADER_FONTSIZE);
  int c = 0;
  for (; G_header[c]; c++ ) {
    w=0;
    flc_measure_with_draw_symbols(G_header[c], &w, &h, 0);
    Fl_Table_Row_set_col_width(table, c, w+pad);
  }
  flc_set_font(ROW_FONTFACE, ROW_FONTSIZE);
  int r = 0;
  for (;r<num_rows; r++ ) {
    int c = 0;
    for (; c<num_cols; c++ ) {
      string s = get_sort_index(rows[r], c);
      w=0;
      flc_measure_with_draw_symbols(s, &w, &h, 0);
      if ( (w + pad) > Fl_Table_Row_col_width(table,c)) Fl_Table_Row_set_col_width(table, c, w + pad);
    }
  }
  // need to do { table_resized(); redraw(); }
  // but table_resized() is unexposed.
  // setting the row_header flag induces this.
  int row_header = Fl_Table_Row_row_header(table);
  Fl_Table_Row_set_row_header(table, row_header);
}

void load_command(fl_Table_Row table, const string cmd){
  char s[512];
  FILE *fp = popen(cmd,"r");
  Fl_Table_Row_set_cols(table,0);
  Node* first_row;
  Node* curr_row;
  int r = 0;
  for (;fgets(s, sizeof(s)-1, fp); r++){
    string ss;
    const string delim = " \t\n";
    int t = 0;
    Node* first_col;
    Node* curr_col;
    for(;(t==0)?(ss=strtok(s,delim)):(ss=strtok(NULL, delim));t++) {
      Row* r = malloc(sizeof(Row));
      int* length = malloc(sizeof(int));
      *length = 1;
      string* contents = malloc(sizeof(char*));
      contents[0] = strdup(ss);
      r->length = length;
      r->row = contents;
      Node* new_col = make_node(r);
      if(t == 0){
	first_col = curr_col = new_col;
      }
      else {
	append(curr_col, new_col);
	curr_col = curr_col->next;
      }
    }
    int* num_cols = count_nodes(first_col);
    Row** fragmented = to_array(first_col);
    string* cols = extract_index(fragmented, *num_cols, 0);
    Row* row = malloc(sizeof(Row));
    row->length = num_cols;
    row->row = cols;
    Node* new_row = make_node(row);
    if (r == 0){
      first_row = curr_row = new_row;
    }
    else{
      append(curr_row, new_row);
      curr_row = curr_row->next;
    }
    if (*num_cols > Fl_Table_Row_cols(table)){
      Fl_Table_Row_set_cols(table,*num_cols);
    }
  }
  other_data* o = (other_data*)Fl_Table_Row_other_data(table);
  o->_rowData = first_row;
  Fl_Table_Row_set_other_data(table, (void*)o);
  int num_rows = *count_nodes(first_row);
  Fl_Table_Row_set_rows(table,num_rows);
  autowidth(table, 20);
}

void draw_sort_arrow(fl_Table_Row table,int X,int Y,int W,int H){
  other_data* o = (other_data*)Fl_Table_Row_other_data(table);
  int sort_reverse = *(o->_sort_reverse);
  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
    flc_set_color(FL_WHITE);
    flc_line(xrit, ytop, xctr, ybot);
    flc_set_color(41);                   // dark gray
    flc_line(xlft, ytop, xrit, ytop);
    flc_line(xlft, ytop, xctr, ybot);
  } else {
    // Engraved up arrow
    flc_set_color(FL_WHITE);
    flc_line(xrit, ybot, xctr, ytop);
    flc_line(xrit, ybot, xlft, ybot);
    flc_set_color(41);                   // dark gray
    flc_line(xlft, ybot, xctr, ytop);
  }
}

void draw_cell(fl_Table_Row table, TableContextC context, int R, int C, int X, int Y, int W, int H) {
  const char *s = "";
  other_data* o = (other_data*)Fl_Table_Row_other_data(table);
  Node* _rowdata = o->_rowData;
  if (_rowdata != NULL){
    Row** rows = to_array(_rowdata);
    int num_rows = *count_nodes(_rowdata);
    if ( R < num_rows && C < *(rows[R]->length)){
      s = rows[R]->row[C];
    }
  }
  switch ( context ) {
  case CONTEXT_COL_HEADERC:
    flc_push_clip(X,Y,W,H);
    {
      flc_draw_box(FL_THIN_UP_BOX, X,Y,W,H, FL_BACKGROUND_COLOR);
      if ( C < 9 ) {
	flc_set_font(HEADER_FONTFACE, HEADER_FONTSIZE);
	flc_set_color(FL_BLACK);
	flc_draw_with_img_draw_symbols(G_header[C], X+2,Y,W,H, FL_ALIGN_LEFT, 0, 0);         // +2=pad left
	// Draw sort arrow
	if ( C == *(o->_sort_lastcol)) {
	  draw_sort_arrow(table,X,Y,W,H);
	}
      }
    }
    flc_pop_clip();
    return;
  case CONTEXT_CELLC: {
    flc_push_clip(X,Y,W,H);
    {
      // Bg color
      Fl_Color bgcolor;
      if (Fl_Table_Row_row_selected(table,R)){
	bgcolor = Fl_Table_Row_selection_color(table);
      }
      else {
	bgcolor = FL_WHITE;
      }
      flc_set_color(bgcolor);
      flc_rectf(X,Y,W,H);
      flc_set_font(ROW_FONTFACE, ROW_FONTSIZE);
      flc_set_color(FL_BLACK);
      flc_draw_with_align(s, X+2,Y,W,H, FL_ALIGN_LEFT);
      // Border
      flc_set_color(FL_LIGHT2);
      flc_rect(X,Y,W,H);
    }
    flc_pop_clip();
    return;
  }
  default:
    return;
  }
}

// Resize parent window to size of table
void resize_window(fl_Table table) {
  int width = 4;                                          // width of table borders
  int t = 0;
  for (; t<Fl_Table_Row_cols(table); t++ ){
    width += Fl_Table_Row_col_width(table,t);
  }
  width += MARGIN*2;
  if (width < 200 || width > Fl_w()) return;
  fl_Window window = (fl_Window)Fl_Table_Row_window(table);
  Fl_Window_resize(window, Fl_Window_x(window), Fl_Window_y(window), width, Fl_Window_h(window));
}

void test(){
  int arr_length = 11;
  string a[] = {"a","4"};
  Row* a_row = to_row(a,2);
  string b[] = {"b","3","row_B"};
  Row* b_row = to_row(b,3);
  string c[] = {"c","2","row_C"};
  Row* c_row = to_row(c,3);
  string d[] = {"d","1","row_D"};
  Row* d_row = to_row(d,3);
  string e[] = {"e","1","row_E"};
  Row* e_row = to_row(e,3);
  string f[] = {"f","1","row_F"};
  Row* f_row = to_row(f,3);
  string g[] = {"g","1","row_G"};
  Row* g_row = to_row(g,3);
  string h[] = {"h","1","row_H"};
  Row* h_row = to_row(h,3);
  string i[] = {"i","1","row_I"};
  Row* i_row = to_row(i,3);
  string j[] = {"j","1","row_J"};
  Row* j_row = to_row(j,3);
  string k[] = {"k","1","row_K"};
  Row* k_row = to_row(k,3);
  int sort_index = 0;
  Row** rows;
  rows = malloc(sizeof(Row*) * arr_length);
  rows[0] = a_row;
  rows[1] = b_row;
  rows[2] = c_row;
  rows[3] = d_row;
  rows[4] = e_row;
  rows[5] = f_row;
  rows[6] = g_row;
  rows[7] = h_row;
  rows[8] = i_row;
  rows[9] = j_row;
  rows[10] = k_row;

  // Reversing a reversed list should be idempotent
  reverse((void**)rows, 0, arr_length);
  print_rows(rows, arr_length);
  reverse((void**)rows, 0, arr_length);
  print_rows(rows, arr_length);
  puts("Row array -> doubly linked list.");
  Node* start = from_array(rows, arr_length);
  unlink_dll(&start);
  start = NULL;

  puts("Doubly linked list -> row array");
  int (*cmp)(string,string) = NULL;
  cmp = determine_comparator(rows,arr_length,sort_index);
  printf("Sort by column : %d\n", sort_index);
  quicksort(0,arr_length - 1,sort_index,rows,cmp);
  puts("Row array -> doubly linked list.");
  puts("Print doubly linked list");
  start = from_array(rows, arr_length);
  traverse_forward(start, NULL, &print_node);

  puts("Print it backwards");
  Node* last = get_last(start);
  traverse_backward(last, NULL, &print_node);

  unlink_dll(&start);
  start = NULL;

  puts("Change sort index to the numbers");
  sort_index = 1;
  cmp =  determine_comparator(rows,arr_length,sort_index);
  puts("Re-sort");
  quicksort(0,arr_length - 1,sort_index,rows,cmp);
  start = from_array(rows, arr_length);
  traverse_forward(start, NULL, &print_node);
  reverse((void**)rows, 0, arr_length);
  unlink_dll(&start);
  start = NULL;

  puts("Change sort index to uppercase letters");
  sort_index = 2;
  cmp =  determine_comparator(rows,arr_length,sort_index);
  puts("Re-sort");
  quicksort(0,arr_length - 1,sort_index,rows,cmp);
  start = from_array(rows, arr_length);
  traverse_forward(start, NULL, &print_node);
  unlink_dll(&start);
  start = NULL;

  puts("Count nodes");
  start = from_array(rows, arr_length);
  int* count = count_nodes(start);
  printf("Number of nodes: %d\n", *count);

  puts("Test turning a dll back into an array");
  Row** from_node = to_array(start);
  print_rows(from_node, arr_length);
  free_rows(rows, arr_length);
}

int main(int argc, string* argv){
  fl_Table_Row_Virtual_Funcs* funcs = (fl_Table_Row_Virtual_Funcs*)Fl_Table_Row_default_virtual_funcs();
  funcs->draw_cell = &draw_cell;
  fl_Double_Window window = (fl_Double_Window)Fl_Double_Window_New_WithLabel(900,500,"Table Sorting");
  Fl_Double_Window_begin(window);
  fl_Table_Row table = (fl_Table_Row)Fl_OverriddenTable_Row_New(MARGIN, MARGIN, Fl_Double_Window_w(window)-MARGIN*2, Fl_Double_Window_h(window)-MARGIN*2,funcs);
  initializeTable(table);
  Fl_Table_Row_set_selection_color(table,FL_YELLOW);
  Fl_Table_Row_set_col_header(table,1);
  Fl_Table_Row_set_col_resize(table,1);
  Fl_Table_Row_set_when(table,FL_WHEN_RELEASE);
  load_command(table,DIRCMD);
  Fl_Table_Row_set_row_height_all(table,18);
  Fl_Table_Row_set_tooltip(table,"Click on column headings to toggle column sorting");
  Fl_Table_Row_set_color(table,FL_WHITE);
  Fl_Double_Window_end(window);
  Fl_Double_Window_set_resizable(window,table);
  resize_window(table);
  Fl_Double_Window_show(window);
  return (Fl_run());
}