/* list.c */


/*
 * List Widget - a scrollable window of text strings in which one or more
 * can be selected by clicking on them with the mouse.
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lui.h"
#include "list.h"
#include "newlist.h"



/*
 * Redraw a single entry in the list
 */
static void draw_entry( LUI_NEWLIST *list, int entry )
{
   char *str;
   int len;
   GC back_gc, text_gc;
   int row;
   int x, y;

   row = entry - list->topstring;
   if (row>=0 && row<list->rows) {

      x = LUI_Border;
      y = LUI_Border + row * FONT_HEIGHT;

      if (entry < list->numstrings) {
         if (list->select_flags[entry]) {
            /* draw as highlighted */
            back_gc = LUI_GC_black;
            text_gc = LUI_GC_white;
         }
         else {
            /* draw normal */
            back_gc = LUI_GC_gray;
            text_gc = LUI_GC_black;
         }

         XFillRectangle( LUI_Display, list->window, back_gc,
                         x, y, list->width-2*LUI_Border, FONT_HEIGHT );

         /* Don't worry about clipping text, X will take care of that */
         str = list->strings[ entry ];
         len = strlen( str );
         XDrawString( LUI_Display, list->window, text_gc,
                      x-list->leftoffset, y+LUI_Font_yoff, str, len );
      }
      else {
         /* just redraw background */
         XFillRectangle( LUI_Display, list->window, LUI_GC_gray,
                         x, y, list->width-2*LUI_Border, FONT_HEIGHT );
      }
   }
}


/*
 * Redraw whole list.
 */
static void draw_list( LUI_NEWLIST *list )
{
   int i;

   for (i=0;i<list->rows;i++) {
      draw_entry( list, list->topstring+i );
   }

   /* draw the frame */
   LUI_DrawFrame( list->window, 0, 0, list->width, list->height, LUI_Border,1);
}




/*
 * Load an array of strings into the list widget.  Then, redraw the widget.
 * Input:  list - the list widget;
 *         num - number of strings
 *         strings - array of pointers to character arrays (like main's argv)
 *         free_flag - 1=free strings when finished, 0=never free strings
 */
void LUI_NEWListLoad( LUI_NEWLIST *list, int num, char **strings, int free_flag )
{
   float size;
   int i;

   if (!list) {
      printf("Error in LUI_NEWListLoad:  NULL list argument\n");
      return;
   }

   /* deallocate old list */
   if (list->free_flag && list->strings) {
      for (i=0;i<list->numstrings;i++) {
         free( list->strings[i] );
      }
      free( list->strings );
   }
   if (list->select_flags) {
      free( list->select_flags );
      list->select_flags = NULL;
   }


   /* store new strings */
   list->free_flag = free_flag;
   list->strings = strings;
   list->numstrings = num;
   if (num>0) {
      list->select_flags = (int *) calloc( num, sizeof(int) );
   }
   list->topstring = 0;

   /* find width, in pixels, of longest line */
   list->maxwidth = 0;
   for (i=0;i<num;i++) {
      int dir, ascent, descent;
      XCharStruct overall;
      XTextExtents( LUI_Font, strings[i], strlen(strings[i]),
                    &dir, &ascent, &descent, &overall );
      if (overall.width > list->maxwidth) {
         list->maxwidth = overall.width;
      }
   }

   draw_list( list );

   /* vertical scrollbar */
   if (num==0) {
      size = 100.0;
   }
   else {
      size = 100.0 * (float) list->rows / (float) num;
      if (size>100.0) {
         size = 100.0;
      }
   }
   LUI_ScrollBarSet( list->vsb, size, 0.0 );

   /* horizontal scrollbar */
   if (list->hsb) {
      if (list->maxwidth==0) {
         size = 100.0;
      }
      else {
         size = 100.0 * (float) list->columns / (float) list->maxwidth;
         if (size>100.0) {
            size = 100.0;
         }
      }
      LUI_ScrollBarSet( list->hsb, size, 0.0 );
   }
}



/*
 * Deallocate the strings in a list widget.  The strings *must* have been
 * originally been allocated with malloc()!
 */
void LUI_NEWListUnload( LUI_NEWLIST *list )
{
   if (list->free_flag && list->strings) {
      int i;
      for (i=0;i<list->numstrings;i++) {
         free( list->strings[i] );
      }
      free( list->strings );
      free( list->select_flags );
      list->strings = NULL;
      list->select_flags = NULL;
      list->numstrings = 0;

      draw_list( list );
      LUI_ScrollBarSet( list->vsb, 100.0, 0.0 );
   }
}





/*
 * Return the selection status of an entry in the list.
 * Input:  list - which list widget
 *         entry - which entry, 0 being the first
 * Return:  1 = selected, 0 = unselected.
 */
int LUI_NEWListGetStatus( LUI_NEWLIST *list, int entry )
{
   if (entry>=0 && entry<list->numstrings) {
      return list->select_flags[entry];
   }
   else {
      return 0;
   }
}



/*
 * Set the selection status of an entry in the list.
 * Input:  list - which list widget
 *         entry - which entry, 0 being the first
 *         status - 1 = selected, 0 = unselected
 */
void LUI_NEWListSetStatus( LUI_NEWLIST *list, int entry, int status )
{
   if (entry>=0 && entry<list->numstrings) {
      list->select_flags[entry] = status;
      if (entry>=list->topstring && entry<list->topstring+list->rows) {
         draw_entry( list, entry );
      }
   }
}



/*
 * Set the selection status of all entries in the list.
 * Input:  list - which list widget
 *         status - 1 = selected, 0 = unselected
 */
void LUI_NEWListSetStatusAll( LUI_NEWLIST *list, int status )
{
   int i;

   for (i=0;i<list->numstrings;i++) {
      list->select_flags[i] = status;
      if (i>=list->topstring && i<list->topstring+list->rows) {
         draw_entry( list, i );
      }
   }
}



/*
 * Register a callback function to call when the user (un)selects an
 * entry in the list.
 * Input:  list - which list
 *         callback - the callback function.  It should be declared as:
 *                      int callback( LUI_NEWLIST *list, int entry, int state )
 */
void LUI_NEWListCallback( LUI_NEWLIST *list, int (*callback)() )
{
   list->callback = callback;
   list->context_index = context_index;
}



/*
 * When an X event occurs in the list window, this function will be called.
 * Input:  list - which list widget
 *         event - the X event
 */
static int list_process( LUI_NEWLIST *list, XEvent *event )
{
   static int prev_entry = -1;
   static int first_entry = -1;

   XSynchronize(LUI_Display, 1);
   switch (event->type) {
      case Expose:
         draw_list( list );
         break;

      case ButtonPress:
         {
            /* main window */
            int row, i;
            row = (event->xbutton.y - LUI_Border) / FONT_HEIGHT;
            i = list->topstring + row;
            if (i<list->numstrings) {
               list->button_down = 1;
               /* toggle status */
               list->select_flags[i] = !list->select_flags[i];
               prev_entry = i;
               first_entry = i;
               draw_entry( list, i );
               XSync( LUI_Display, 0 );
               if (list->callback) {
                  (*list->callback)( list, i, list->select_flags[i] );
               }
            }
         }
         break;

      case MotionNotify:
         {
            /* main window */
            int row, i;
            XEvent ev;
            row = (event->xmotion.y - LUI_Border) / FONT_HEIGHT;
            if ((row <0 || row >= list->rows) && list->button_down){
               while( list->button_down && (row >= list->rows || row < 0)){

                  /* move it first */
                  if (row<0 && list->topstring-1 >= 0){
                     list->topstring--;
                     list->select_flags[list->topstring] = list->select_flags[first_entry];
                     if (list->callback) {
                        (*list->callback)( list, list->topstring,
                                             list->select_flags[list->topstring]);
                     }
                     draw_list(list);
                     LUI_ScrollBarSetPos( list->vsb,
                        100.0 * list->topstring / (list->numstrings-list->rows) );
                     }
                  else if (row>=list->rows && list->topstring < list->numstrings-list->rows){
                     list->topstring++;
                     list->select_flags[list->topstring+list->rows-1] =
                                          list->select_flags[first_entry];
                     if (list->callback) {
                        (*list->callback)( list, list->topstring+list->rows-1,
                                           list->select_flags[list->topstring+list->rows-1]);
                     }
                     draw_list(list);                  
                     LUI_ScrollBarSetPos( list->vsb,                  
                        100.0 * list->topstring / (list->numstrings-list->rows) );
                  }

                  /*do some checking */
                  if (XPending(LUI_Display)) {
                     XNextEvent(LUI_Display, &ev);
                     if (ev.type == ButtonRelease || ev.type == LeaveNotify){
                        list->button_down = 0;
                     }
                     else if (ev.type == MotionNotify){
                        row = (ev.xmotion.y - LUI_Border) / FONT_HEIGHT;
                     }
                  }
               }                  
            }
            else if (row>=0 && row<list->rows) {
               i = list->topstring + row;
               if (i>=0 && i<list->numstrings && i!=prev_entry) {
                  list->select_flags[i] = list->select_flags[first_entry];
                  draw_entry( list, i );
                  if (list->callback) {
                     (*list->callback)( list, i, list->select_flags[i] );
                  }
                  prev_entry = i;
               }
            }
/*
                  int j;
                  if (i>first_entry){
                     for (j=first_entry+1; j<=i; j++){

                        list->select_flags[j] = list->select_flags[first_entry];
                        draw_entry( list, j );
                        if (list->callback) { 
                           (*list->callback)( list, j, list->select_flags[j] );
                        }
                     }
                  }
                  else {
                     for (j=first_entry-1;j>=i;j--) {
                        list->select_flags[j] = list->select_flags[first_entry];
                        draw_entry( list, j );
                        if (list->callback) {
                           (*list->callback)( list, j, list->select_flags[j] );
                        }
                     }
                  }
                  prev_entry = i;
               }
            }
*/

         }
 
         break;

      case ButtonRelease:
         prev_entry = -1;
         list->button_down = 0;
         break;

      case EnterNotify:
         break;
      case LeaveNotify:
         break;

      default:
         printf("Error in list_process:  unexpected event (%d)\n",
                (int) event->type );
   }
   return 1;
}



/*
 * Callback for the vertical slider, when it moves this function is called.
 */
static int vscroll_cb( LUI_SCROLLBAR *sb, float pos )
{
   int newtop;
   LUI_NEWLIST *list;

   list = (LUI_NEWLIST *) sb->userdata;

   newtop = (list->numstrings - list->rows) * pos / 100.0;
   if (newtop != list->topstring) {
      list->topstring = newtop;
      draw_list( list );
   }
   return 1;
}


/*
 * Callback for the horizontal slider, when it moves this function is called.
 */
static int hscroll_cb( LUI_SCROLLBAR *sb, float pos )
{
   int newleft;
   LUI_NEWLIST *list;

   list = (LUI_NEWLIST *) sb->userdata;

   newleft = (list->maxwidth - list->columns) * pos / 100.0;
   if (newleft != list->leftoffset) {
      list->leftoffset = newleft;
      draw_list( list );
   }
   return 1;
}



/*
 * Create a new list widget.
 * Input:  parent - parent window ID
 *         x, y, location of list widget with respect to parent in pixels
 *         width, height - outside dimensions of list widget in pixels
 *                         including the scroll bar.
 *         hscroll - include a horizontal scrollbar?
 * Return:  LUI_NEWLIST pointer or NULL if error.
 */
LUI_NEWLIST *LUI_NEWListCreate( Window parent, int x, int y, int width, int height,
                          int hscroll )
{
   LUI_NEWLIST *list;

   LUI_LayoutCheck( &x, &y, &width, &height );

   list = (LUI_NEWLIST *) malloc( sizeof(LUI_NEWLIST) );
   if (!list) {
      return NULL;
   }

   width -= (SLIDER_WIDTH + 2 * LUI_Border);
   if (hscroll) {
      height -= (SLIDER_WIDTH + 2 * LUI_Border);
   }

   list->x = x;
   list->y = y;
   list->width = width;
   list->height = height - 2;
   list->window = XCreateSimpleWindow( LUI_Display, parent,
                                       x, y, width, height-2,
                                       1, LUI_Color_black, LUI_Color_gray );
   LUI_EventAdd2( list->window, 
                  ExposureMask | ButtonPressMask | ButtonMotionMask
                  | ButtonReleaseMask,
                  (LUI_FNCP) list_process, list );
   XMapWindow( LUI_Display, list->window );

   list->vsb = LUI_ScrollBarCreate( parent, x + width + 2*LUI_Border, y,
                                    SLIDER_WIDTH, height-2, 1 );
   LUI_ScrollBarData( list->vsb, list );
   LUI_ScrollBarCallback( list->vsb, vscroll_cb );

   if (hscroll) {
      list->hsb = LUI_ScrollBarCreate( parent, x, y + height + 2*LUI_Border,
                                       width, 20, 0 );
      LUI_ScrollBarData( list->hsb, list );
      LUI_ScrollBarCallback( list->hsb, hscroll_cb );
   }
   else {
      list->hsb = NULL;
   }
   list->leftoffset = 0;

   list->strings = NULL;
   list->select_flags = NULL;
   list->numstrings = 0;
   list->rows = (list->height-2*LUI_Border) / FONT_HEIGHT;
   list->columns = list->width-2*LUI_Border;
   list->topstring = 0;

   return list;
}



/*
 * Destroy the given list widget.
 */
void LUI_NEWListDestroy( LUI_NEWLIST *list )
{
   if (!list) {
      printf("Error in LUI_NEWListDestroy:  NULL list argument\n");
      return;
   }

   if (list->select_flags) {
      free( list->select_flags );
   }
   if (list->window) {
      LUI_EventRemove( list->window );
      XDestroyWindow( LUI_Display, list->window );
   }
   free( list );
}





syntax highlighted by Code2HTML, v. 0.9.1