Logo Search packages:      
Sourcecode: vertex version File versions

texbrowsercb.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <GL/gl.h>
#include <GL/glu.h>

#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <gtkgl/gtkglarea.h>

#include "../include/string.h"
#include "../include/strexp.h"
#include "../include/disk.h"

#include "v3dtex.h"
#include "v3dmh.h"
#include "v3dmp.h"
#include "v3dmodel.h"

#include "guiutils.h"
#include "cdialog.h"
#include "pdialog.h"
#include "fb.h"

#include "editor.h"
#include "editorcb.h"
#include "editorviewcb.h"
#include "editorp.h"
#include "editorselect.h"
#include "editorlist.h"
#include "editortexture.h"

#include "texbrowser.h"
#include "texbrowsercb.h"
#include "texbrowserpv.h"

#include "vmacfg.h"
#include "vmacfglist.h"
#include "vma.h"
#include "config.h"

#ifdef MEMWATCH
# include "memwatch.h"
#endif


gbool TexBrowserMatchParmFromLine(const char *line, const char *parm);
void TexBrowserListFetch(
      ma_texture_browser_struct *tb, void *editor
);
int TexBrowserListDoFind(
        ma_texture_browser_struct *tb,
        const char *string,
        const char *type,
        gbool find_exact,
      gbool find_casesensitive
);
void TexBrowserListDeleteAll(ma_texture_browser_struct *tb);

void TexBrowserRecordPositionsCB(ma_texture_browser_struct *tb);

gint TexBrowserEventCB(
      GtkWidget *widget, gpointer event, gpointer data
);
gint TexBrowserPreviewEventCB(
      GtkWidget *widget, gpointer event, gpointer data
);
void TexBrowserPreviewScrollCB(GtkAdjustment *adj, gpointer data);
void TexBrowserZoomInPressedCB(GtkWidget *widget, gpointer data);
void TexBrowserZoomInReleasedCB(GtkWidget *widget, gpointer data);
void TexBrowserZoomOutPressedCB(GtkWidget *widget, gpointer data);
void TexBrowserZoomOutReleasedCB(GtkWidget *widget, gpointer data);
gint TexBrowserZoomInTimeOutCB(gpointer data);
gint TexBrowserZoomOutTimeOutCB(gpointer data);
void TexBrowserZoomOneToOneCB(GtkWidget *widget, gpointer data);

static gchar *TexBrowserPromptBrowseCB(
      gpointer prompt, gpointer client_data, gint value_num 
);

void TexBrowserDestroyCB(GtkObject *object, gpointer data);
gint TexBrowserCloseCB(GtkWidget *widget, GdkEvent *event, gpointer data);
void TexBrowserCloseMCB(GtkWidget *widget, gpointer data);
void TexBrowserBrowseBaseDirCB(GtkWidget *widget, gpointer data);
void TexBrowserOverrideCheckCB(GtkWidget *widget, gpointer data);
void TexBrowserReloadCB(GtkWidget *widget, gpointer data);
void TexBrowserEnterCB(GtkWidget *widget, gpointer data);
void TexBrowserSortMCB(GtkWidget *widget, gpointer data);

void TexBrowserSelectAllCB(GtkWidget *widget, gpointer data);
void TexBrowserUnselectAllCB(GtkWidget *widget, gpointer data);
void TexBrowserListSelectCB(
      GtkWidget *widget,
      gint row, gint column,
      GdkEventButton *event, gpointer data
);
void TexBrowserListUnselectCB(
      GtkWidget *widget,
      gint row, gint column,
      GdkEventButton *event, gpointer data
);
void TexBrowserListColumClickCB(
      GtkWidget *widget, gint column, gpointer data
);
gint TexBrowserMenuMapCB(
      GtkWidget *widget, gpointer event, gpointer data
);
void TexBrowserCloseOnSelectCheckCB(GtkWidget *widget, gpointer data);

void TexBrowserAddTextureCB(GtkWidget *widget, gpointer data);
void TexBrowserRemoveTextureCB(GtkWidget *widget, gpointer data);
void TexBrowserEditPropertiesCB(GtkWidget *widget, gpointer data);
void TexBrowserSelectTextureCB(GtkWidget *widget, gpointer data);


#ifndef GDK_BUTTON1
# define GDK_BUTTON1    1
#endif
#ifndef GDK_BUTTON2
# define GDK_BUTTON2    2
#endif
#ifndef GDK_BUTTON3
# define GDK_BUTTON3    3
#endif


#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))

#define DEGTORAD(d)     ((d) * PI / 180)
#define RADTODEG(r)     ((r) * 180 / PI)


/* To keep TexBrowserSortMCB() and TexBrowserListColumClickCB() from
 * recusing.
 */
static gbool tb_sorting_list = FALSE;


/*
 *    Used in TexBrowserListFetch() to get listing from editor's
 *    model data lines that contain texture_load parameters.
 */
typedef struct {
      char *name;
      char *path;
      double priority;
} tb_tex_ref_struct;


/* Timeout callback tags for zooming. */
static guint      zoom_in_toid = (guint)(-1),
            zoom_out_toid = (guint)(-1);



/*
 *    Matches by checking if the parm is the parm on the configuration
 *    line. For instance if line is "  texture_load something something"
 *    and parm is "texture_load" then this would return TRUE.
 */
gbool TexBrowserMatchParmFromLine(const char *line, const char *parm)
{
      const char *strptr = line;

      if((strptr == NULL) ||
           (parm == NULL)
      )
          return(FALSE);

      while(ISBLANK(*strptr))
          strptr++;

      return((strcasepfx(strptr, parm)) ? TRUE : FALSE);
}


/*
 *    Fetches all texture referance names, paths to texture files,
 *    and texture index number from the model header items on the
 *    editor and stores them into the texture browser's list.
 *
 *    Does not clear or modify any textures actually loaded on the
 *    editor.
 */
void TexBrowserListFetch(
      ma_texture_browser_struct *tb, void *editor
)
{
      GtkCList *clist;
        char *val[4];
      const char *cstrptr;
      int i, n, hi, ptype;
      void *p;
      ma_editor_struct *e = (ma_editor_struct *)editor;

      /* Local texture refs list (used for sorting). */
      tb_tex_ref_struct *tb_tex_ref_ptr, **tb_tex_ref = NULL;
      int tb_tex_ref_num, total_tb_tex_refs = 0;

      char **strv;
      int strc;

      char numstr[256];


      if((tb == NULL) ||
           (e == NULL)
      )
          return;

      /* Get pointer to textures list widget. */
      clist = (GtkCList *)tb->textures_list;
      if(clist == NULL)
          return;

      gtk_clist_freeze(clist);

      /* Go through each header item on the editor. */
      for(hi = 0; hi < e->total_mh_items; hi++)
      {
          p = e->mh_item[hi];
          if(p == NULL)
            continue;
          else
            ptype = (*(int *)p);

          /* Model header item a texture load? */
          if(ptype == V3DMH_TYPE_TEXTURE_LOAD)
          {
            mh_texture_load_struct *mh_texture_load = p;

            if((mh_texture_load->name == NULL) ||
                   (mh_texture_load->path == NULL)
            )
                break;

            /* Allocate more pointers. */
            tb_tex_ref_num = total_tb_tex_refs;
            total_tb_tex_refs++;
            tb_tex_ref = (tb_tex_ref_struct **)realloc(
                tb_tex_ref,
                total_tb_tex_refs * sizeof(tb_tex_ref_struct *)
            );
            if(tb_tex_ref == NULL)
            {
                total_tb_tex_refs = 0;
                gtk_clist_thaw(clist);
                return;       /* Give up. */
            }
            else
            {
                tb_tex_ref_ptr = (tb_tex_ref_struct *)calloc(
                  1,
                  sizeof(tb_tex_ref_struct)
                );
                tb_tex_ref[tb_tex_ref_num] = tb_tex_ref_ptr;
                if(tb_tex_ref_ptr == NULL)
                {
                  total_tb_tex_refs--;
                  break;
                }
                else
                {
                  tb_tex_ref_ptr->name = strdup(mh_texture_load->name);
                  tb_tex_ref_ptr->path = strdup(mh_texture_load->path);
                  tb_tex_ref_ptr->priority = mh_texture_load->priority;
/*
printf("Get(%i): %i %s\n", tb->list_sort_code, tb_tex_ref_num, 
tb_tex_ref_ptr->name);
 */
                }
            }
          } /* Texture load? */
      }     /* Go through each header item on the editor. */


      /* Allocate string array (strv and strc) for sorting. */
      strc = total_tb_tex_refs;
      strv = (char **)malloc(strc * sizeof(char *));
      if(strv == NULL)
      {
          strc = 0;
          gtk_clist_thaw(clist);
          return;
      }
      /* Sort by type. */
      switch(tb->list_sort_code)
      {
        case TEXBROWSER_LIST_SORT_NAME:
            /* Add referance names to string list. */
            for(i = 0; i < strc; i++)
            {
            tb_tex_ref_ptr = tb_tex_ref[i];
            strv[i] = StringCopyAlloc(
                ((tb_tex_ref_ptr == NULL) ? "(null)" : tb_tex_ref_ptr->name)
            );
                if(strv[i] == NULL)
                    strv[i] = StringCopyAlloc("(null)");
          }
          /* Sort referance names alphabetically. */
          strv = StringQSort(strv, strc);
          break;

          case TEXBROWSER_LIST_SORT_PATH:
            /* Add referance paths to string list. */
            for(i = 0; i < strc; i++)
            {
                tb_tex_ref_ptr = tb_tex_ref[i];
                strv[i] = StringCopyAlloc(
                    ((tb_tex_ref_ptr == NULL) ? "(null)" : tb_tex_ref_ptr->path)
                );
                if(strv[i] == NULL)
                    strv[i] = StringCopyAlloc("(null)");
            }
            /* Sort paths alphabetically. */
            strv = StringQSort(strv, strc);
            break;

          case TEXBROWSER_LIST_SORT_PRIORITY:
            /* Add priority to string list. */
            for(i = 0; i < strc; i++)
            {
                tb_tex_ref_ptr = tb_tex_ref[i];
                strv[i] = (char *)malloc(256 * sizeof(char));
            if(strv[i] == NULL)
                strv[i] = StringCopyAlloc("(null)");
            else
                sprintf(strv[i], "%f", tb_tex_ref_ptr->priority);
            }
            /* Sort priority alphabetically. */
            strv = StringQSort(strv, strc);
            break;

        default:  /* TEXBROWSER_LIST_SORT_ADDED */
            /* Add index numbers to string list. */
            for(i = 0; i < strc; i++)
            {
                tb_tex_ref_ptr = tb_tex_ref[i];
            sprintf(numstr, "%i", i);
            strv[i] = StringCopyAlloc(numstr);
            }
            /* Sort index alphabetically. */
            strv = StringQSort(strv, strc);
          break;
      }


      /* Add to textures list widget. */
      for(i = 0; i < strc; i++)
      {
          cstrptr = strv[i];
          if(cstrptr == NULL)
            cstrptr = "(null)";

          /* Match. */
          switch(tb->list_sort_code)
            {
              case TEXBROWSER_LIST_SORT_PRIORITY:
                for(n = 0; n < total_tb_tex_refs; n++)
                {
                    tb_tex_ref_ptr = tb_tex_ref[n];
                    if(tb_tex_ref_ptr == NULL)
                        continue;

                sprintf(numstr, "%f", tb_tex_ref_ptr->priority);
                    if(!strcasecmp(numstr, cstrptr))
                        break;
                }
                break;

            case TEXBROWSER_LIST_SORT_PATH:
                for(n = 0; n < total_tb_tex_refs; n++)
                {
                    tb_tex_ref_ptr = tb_tex_ref[n];
                    if(tb_tex_ref_ptr == NULL)
                        continue;

                    if(tb_tex_ref_ptr->path == NULL)
                        continue;

                    if(!strcasecmp(tb_tex_ref_ptr->path, cstrptr))
                        break;
                }
            break;

            case TEXBROWSER_LIST_SORT_NAME:
            for(n = 0; n < total_tb_tex_refs; n++)
                {
                    tb_tex_ref_ptr = tb_tex_ref[n];
                    if(tb_tex_ref_ptr == NULL)
                        continue;
                
                    if(tb_tex_ref_ptr->name == NULL)
                        continue;
                    
                    if(!strcasecmp(tb_tex_ref_ptr->name, cstrptr))
                        break;
                }
            break;

              default:  /* TEXBROWSER_LIST_SORT_ADDED */
              for(n = 0; n < total_tb_tex_refs; n++)
              {
                tb_tex_ref_ptr = tb_tex_ref[n];
                if(tb_tex_ref_ptr == NULL)
                    continue;

                sprintf(numstr, "%i", n);
                if(!strcasecmp(numstr, cstrptr))
                    break;
              }
            break;
          }
          /* No match? */
          if(n >= total_tb_tex_refs)
          {
            /* No match. */
                val[0] = strdup("#-1");
                val[1] = strdup(cstrptr);
                val[2] = strdup("*no match*");
            val[3] = strdup("0.0");

                gtk_clist_append(clist, val);
                gtk_clist_set_row_data(
                    clist,
                    i,              /* Row. */
                    (gpointer)-1    /* Set data as actual texture index. */
                );

            free(val[0]);
            free(val[1]);
            free(val[2]);
            free(val[3]);
          }
          else
          {
            /* Got match. */
            char text[80];

            sprintf(text, "#%i", n + 1);
                val[0] = strdup(text);
                val[1] = strdup((tb_tex_ref_ptr->name == NULL) ?
                "(null)" : tb_tex_ref_ptr->name
            );
            val[2] = strdup((tb_tex_ref_ptr->path == NULL) ?
                "(null)" : tb_tex_ref_ptr->path
            );
            sprintf(numstr, "%f", tb_tex_ref_ptr->priority);
                val[3] = strdup(numstr);

            gtk_clist_append(clist, val);
            gtk_clist_set_row_data(
                clist,
                i,            /* Row. */
                (gpointer)n   /* Set data as actual texture index. */
            );
/*
printf("Set row %i: %s %s\n", i, val[0], val[1]);
 */
                free(val[0]);
                free(val[1]);
                free(val[2]); 
                free(val[3]);

            /* Remove local matched texbrowser texture referance
             * struct from the list. This is to avoid multiple
             * matches of the same structs on sorts like `by priority'.
             */
            free(tb_tex_ref_ptr->name);
            free(tb_tex_ref_ptr->path);
            free(tb_tex_ref_ptr);
            tb_tex_ref[n] = NULL;

            /* Do not reduce and shift pointers, that will misalign
             * n which corresponds to actual rows and texture indexs.
             */
          }
      }

      /* Deallocate string list. */
      StringFreeArray(strv, strc);

      /* Deallocate local texbrowser texture referance structs (if any). */
      for(i = 0; i < total_tb_tex_refs; i++)
      {
            tb_tex_ref_ptr = tb_tex_ref[i];
            if(tb_tex_ref_ptr == NULL)
                continue;

            fprintf(
                stderr,
"TexBrowserListFetch(): Deallocating extraneous unsorted tex ref `%s'\n",
                tb_tex_ref_ptr->name
            );

          free(tb_tex_ref_ptr->name);
          free(tb_tex_ref_ptr->path);
          free(tb_tex_ref_ptr);
      }
      free(tb_tex_ref);
      tb_tex_ref = NULL;
      total_tb_tex_refs = 0;

      gtk_clist_thaw(clist);

      return;
}

/*
 *    Performs a find operation, starting from the last selected
 *    item (or the beginning if none selected).
 *
 *    The type string specifies what catagory (ie: name, path) to
 *    find.
 *
 *    Returns the item number that was matched or -1 on error or no
 *    match.
 */
int TexBrowserListDoFind(
        ma_texture_browser_struct *tb, 
        const char *string,
        const char *type,
      gbool find_exact,
      gbool find_casesensitive
)
{
      GtkCList *clist;
      gbool matched;
      gbool find_loop_wrapped = FALSE;
      int find_start_pos, i, matched_item = -1;
      gchar *cell_text;
      int items_found = 0;
      int total_textures;
      ma_editor_struct *editor;


      if((tb == NULL) ||
           (string == NULL)
      )
          return(-1);

      if((*string) == '\0')
          return(-1);

      editor = (ma_editor_struct *)tb->editor_ptr;
      if(editor == NULL)
          return(-1);

      clist = (GtkCList *)tb->textures_list;
      if(clist == NULL)
          return(-1);


      /* Get total number of textures. */
      total_textures = editor->total_textures;


      /* Find starting position to begin search, remember that
       * all positions are relative to the texture browser's
       * list (not the actually loaded textures list on the editor).
       *
       * If find_start_pos is -1 then it indicates no item was
       * selected and to start find at the beginning.
       */
      if(tb->total_selected_textures == 1)
          find_start_pos = tb->selected_texture[0];
      else
          find_start_pos = -1;

/* Performs match, uses variables string, i, matched, and cell_text. */
#define DO_MATCH  \
{ \
 matched = FALSE; \
\
 if(type == NULL) \
 { \
  if(!gtk_clist_get_text(clist, i, 1, &cell_text)) \
   cell_text = NULL; \
 } \
 else if(!strcasecmp(type, "path")) \
 { \
  if(!gtk_clist_get_text(clist, i, 2, &cell_text)) \
   cell_text = NULL; \
 } \
 else \
 { \
  cell_text = NULL; \
 } \
\
 if(cell_text != NULL) \
 { \
  if(find_exact) \
  { \
   if(find_casesensitive) \
   { \
    if(!strcmp(cell_text, string)) \
     matched = TRUE; \
   } else { \
    if(!strcasecmp(cell_text, string)) \
     matched = TRUE; \
   } \
  } \
  else \
  { \
   char *haystack = strdup(cell_text); \
   char *needle = strdup(string); \
\
   if((haystack != NULL) && (needle != NULL)) \
   { \
    if(!find_casesensitive) \
    { \
     strtoupper(haystack); \
     strtoupper(needle); \
    } \
    if(strstr(haystack, needle)) \
     matched = TRUE; \
   } \
   free(haystack); \
   free(needle); \
  } \
 } \
}

      /* Begin from start position + 1. */
      for(i = MAX(find_start_pos + 1, 0);
          i < total_textures;
          i++
      )
      {
          DO_MATCH
          if(matched)
          {
            items_found++;
            matched_item = i;
            break;
          }
      }
      /* No match and find_start_pos was not starting
       * from the beginning?
       */
      if((items_found <= 0) && (find_start_pos >= 0))
      {
          /* Begin find from beginning to either total_textures
           * or the original find_start_pos + 1.
           */
            for(i = 0;
                i < MIN(total_textures, find_start_pos + 1);
                i++
            )
            {
                DO_MATCH
                if(matched)
                {
                    items_found++;
                    matched_item = i;
                    break;
                }
            }
          find_loop_wrapped = TRUE;
      }

      /* Did we match an item? */
      if((matched_item < 0) || (matched_item >= total_textures))
      {
          /* No match, print notice but do not unselect anything. */
            CDialogGetResponse(
"No match",
"Search string not found.",
"The specified search string did not match any data\n\
that was searched through.",
                CDIALOG_ICON_WARNING,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK 
            );
          return(-1);
      }

      /* Unselect all previously selected rows and select new
       * row being matched_item.
       */
      gtk_clist_unselect_all(clist);
      gtk_clist_select_row(
          clist,
          matched_item, 0     /* Row, column. */
      );

      /* If find loop wrapped, print notice about search being
       * wrapped.
       */
      if(find_loop_wrapped)
      {
            CDialogGetResponse(
"Search wrapped",
"Search wrapped.",
"The search has wraped, meaning the item(s) found are\n\
located before the index number of the initial position\n\
the search began. This may indicate there are no more\n\
items to be found if you have continued the search.",
                CDIALOG_ICON_WARNING,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
      }

#undef DO_MATCH

      return(matched_item);
}


/*
 *    Deletes all entries in the list on the texture browser and
 *    unselects all.
 */
void TexBrowserListDeleteAll(ma_texture_browser_struct *tb)
{
        GtkWidget *w;

        if(tb == NULL)
            return;

        /* Clear textures list. */
        w = tb->textures_list;
        if(w != NULL)
            gtk_clist_clear(GTK_CLIST(w));

      /* Unselect all textures. */
        EditorUnselectAll(
            &tb->selected_texture, &tb->total_selected_textures
        );

      /* Unload preview data. */
      TexBrowserPreviewUnload(tb);
      TexBrowserPreviewDraw(tb);

      return;
}


/*
 *      Updates position values to the global configuration options
 *      sequential list for the given texture browser.
 *
 *      If global configuration option
 *      VMA_CFG_PARM_RECORD_LAST_POS_AND_SIZE is disabled then
 *      no operation will be performed.
 */
void TexBrowserRecordPositionsCB(ma_texture_browser_struct *tb)
{
        GtkWidget *w;
        vma_cfg_item_struct *opt = option;
      u_int8_t ui8;
        u_int32_t ui32;
      char *strptr;
      ma_texbrowser_preview_struct *pv;
      int toplevel_width = 100, toplevel_height = 100;


        /* Check if we are suppose to record positions and sizes. */
        if(!VMACFGItemListGetValueI(
            option, VMA_CFG_PARM_RECORD_LAST_POS_AND_SIZE
        ))
            return;


        if(tb == NULL)
            return;

      pv = &tb->preview;

        /* Record size of toplevel window. */
        w = tb->toplevel;
        if(w != NULL)
        {
            ui32 = w->allocation.width;
          toplevel_width = (int)ui32;
            VMACFGItemListMatchSetValue(
                opt, VMA_CFG_PARM_TEXBROWSER_WIDTH,
                &ui32, 0
            );

            ui32 = w->allocation.height;
            toplevel_height = (int)ui32;
            VMACFGItemListMatchSetValue(
                opt, VMA_CFG_PARM_TEXBROWSER_HEIGHT,
                &ui32, 0
            );
        }

      /* Size of preview window, this is really the width of
       * texture browser toplevel - preview_label_vbox.
       */
      w = tb->preview_label_vbox;
      if(w != NULL)
      {
            ui32 = (int)toplevel_width - (int)w->allocation.width;
            VMACFGItemListMatchSetValue(
                opt, VMA_CFG_PARM_TEXBROWSER_PREVIEW_WIDTH,
                &ui32, 0
            );

          ui32 = w->allocation.height;
          VMACFGItemListMatchSetValue(
                opt, VMA_CFG_PARM_TEXBROWSER_PREVIEW_HEIGHT,
            &ui32, 0
            );
      }

      /* Textures base dir override state. */
        w = tb->base_dir_override_check;
        if(w != NULL)
        {
          ui8 = tb->base_dir_override;

            VMACFGItemListMatchSetValue(
                opt, VMA_CFG_PARM_TEXTURE_BASE_DIR_OVERRIDE,
                &ui8, 0
          );
      }

      /* Last set textures base dir. */
      w = tb->base_dir_entry;
      if(w != NULL)
      {
          strptr = gtk_entry_get_text(GTK_ENTRY(w));
          if(strptr != NULL)
          {
            char tmp_path[PATH_MAX + NAME_MAX];
            struct stat stat_buf;

            strncpy(tmp_path, strptr, PATH_MAX + NAME_MAX);
            tmp_path[PATH_MAX + NAME_MAX - 1] = '\0';

            /* Make sure it is a full path and exists. */
            if(ISPATHABSOLUTE(tmp_path))
            {
                if(!stat(tmp_path, &stat_buf))
                  VMACFGItemListMatchSetValue(
                      opt, VMA_CFG_PARM_TEXTURE_BASE_DIR,
                      tmp_path, 0
                  );
            }
          }
      }

      /* Textures clist. */
      w = tb->textures_list;
        if(w != NULL)
        {
          int i;
          GtkCList *clist = GTK_CLIST(w);
          GtkCListColumn *column_heading;


          for(i = 0; i < clist->columns; i++)
          {
            column_heading = &clist->column[i];

            switch(i)
            {
              case 0:
                ui32 = column_heading->width;
                VMACFGItemListMatchSetValue(
                  opt, VMA_CFG_PARM_TEXBROWSER_INDEX_CHWIDTH,
                  &ui32, 0
                );
                break;

                  case 1:
                    ui32 = column_heading->width;
                    VMACFGItemListMatchSetValue(
                        opt, VMA_CFG_PARM_TEXBROWSER_NAME_CHWIDTH,
                        &ui32, 0
                    );
                    break;

                  case 2:
                    ui32 = column_heading->width;
                    VMACFGItemListMatchSetValue(
                        opt, VMA_CFG_PARM_TEXBROWSER_PATH_CHWIDTH,
                        &ui32, 0
                    );
                    break;

                  case 3:
                    ui32 = column_heading->width;
                    VMACFGItemListMatchSetValue(
                        opt, VMA_CFG_PARM_TEXBROWSER_PRIORITY_CHWIDTH,
                        &ui32, 0
                    );
                    break;
            }

          }
      }

      return;
}



/*
 *    Texture browser toplevel event callback.
 */
gint TexBrowserEventCB(GtkWidget *widget, gpointer event, gpointer data)
{
      gbool status = FALSE;
        GtkWidget *w;
        GdkEventConfigure *configure;
        GdkEventKey *key;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        ma_texbrowser_preview_struct *pv;


        if((widget == NULL) ||
           (event == NULL) ||
           (tb == NULL)
        )
            return(status);

        if(!tb->initialized)
            return(status);

      pv = &tb->preview;

        switch(*(gint *)event)
        {
          case GDK_CONFIGURE:
            w = tb->toplevel;
            configure = (GdkEventConfigure *)event;
            if(w == widget)
            {


                status = TRUE;
            }
            break;

          case GDK_KEY_PRESS: case GDK_KEY_RELEASE:
            key = event;
            switch(key->keyval)
            {
              case GDK_Alt_L: case GDK_Alt_R:
              case GDK_Control_L: case GDK_Control_R:
              case GDK_Shift_L: case GDK_Shift_R:
                /* Pass modifier to preview event management. */
            if(TexBrowserPreviewEventCB(pv->preview, event, tb))
                status = TRUE;
                break;
          }
          break;
      }


      return(status);
}

/*
 *    Handles preview GtkGLArea widget events.
 */
gint TexBrowserPreviewEventCB(
        GtkWidget *widget, gpointer event, gpointer data
)
{
      gbool status = FALSE;
        GtkWidget *w, *w2;
      GtkAdjustment *adj;
      GdkCursor *cur;
      GdkEventConfigure *configure;
        GdkEventKey *key;
        GdkEventButton *button;
        GdkEventMotion *motion;
        gint etype, x, y;
        GdkModifierType mask;
      ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
      ma_texbrowser_preview_struct *pv;


      if((tb == NULL) ||
           (event == NULL) ||
           (widget == NULL)
      )
          return(status);

      pv = &tb->preview;

        /* Get pointer to preview widget, event widget must match it. */
        w = pv->preview;
        if(w != widget)
            return(status);

        /* Handle by event type. */
        etype = (*(gint *)event);
        switch(etype)
        {   
          case GDK_EXPOSE:
          TexBrowserPreviewDraw(tb);
          status = TRUE;
            break;

          case GDK_CONFIGURE:
            configure = (GdkEventConfigure *)event;
            if(!pv->realized)
                pv->realized = TRUE;

          pv->ww = configure->width;
          pv->hw = configure->height;

            if(!TexBrowserPreviewEnableContext(tb))
                glViewport(
                    0, 0,
                    configure->width, configure->height
                );
        
            /* Update adjustments. */
            adj = pv->preview_vadj;
            w2 = pv->preview_vsb;
            if((adj != NULL) && (w2 != NULL))
            {
                adj->lower = 0;
                adj->upper = pv->tex_height * pv->zoom_coeff_wtod;
                adj->page_size = pv->hw;
                if((gint)(adj->upper - adj->lower) <= (gint)(adj->page_size))
                    gtk_widget_hide(w2);
                else
                    gtk_widget_show(w2);
                gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
            }
            adj = pv->preview_hadj;
            w2 = pv->preview_hsb;
            if((adj != NULL) && (w2 != NULL))
            {  
                adj->lower = 0;
                adj->upper = pv->tex_width * pv->zoom_coeff_wtod;
                adj->page_size = pv->ww;
                if((gint)(adj->upper - adj->lower) <= (gint)(adj->page_size))
                    gtk_widget_hide(w2);
                else
                    gtk_widget_show(w2);
                gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
            }
            status = TRUE;
            break;

          case GDK_KEY_PRESS: case GDK_KEY_RELEASE:
            key = (GdkEventKey *)event;
            if((key->keyval == GDK_Alt_L) ||
               (key->keyval == GDK_Alt_R)
            )
          {
                if(etype == GDK_KEY_PRESS)
                pv->flags |= MA_TEXBROWSER_FLAG_KEY_ZOOM;
            else
                pv->flags &= ~MA_TEXBROWSER_FLAG_KEY_ZOOM;
            status = TRUE;
          }
            else if((key->keyval == GDK_Control_L) ||
                    (key->keyval == GDK_Control_R)
            )
          {
                if(etype == GDK_KEY_PRESS)
                    pv->flags |= MA_TEXBROWSER_FLAG_KEY_TRANSLATE;
                else
                    pv->flags &= ~MA_TEXBROWSER_FLAG_KEY_TRANSLATE;
                status = TRUE;
          }
            else if((key->keyval == GDK_Shift_L) ||
                    (key->keyval == GDK_Shift_R)
            )
            {
                if(etype == GDK_KEY_PRESS)
                    pv->flags |= MA_TEXBROWSER_FLAG_KEY_ZOOM_RECT;
                else
                    pv->flags &= ~MA_TEXBROWSER_FLAG_KEY_ZOOM_RECT;
                status = TRUE;
            }
            /* Change drag state depending on modifier key states. */
          if(pv->flags & MA_TEXBROWSER_FLAG_KEY_ZOOM)
          {
            pv->drag_state = MA_TEXBROWSER_DRAG_ZOOM;
            cur = pv->zoom_cur;
          }
          else if(pv->flags & MA_TEXBROWSER_FLAG_KEY_TRANSLATE)
          {
            pv->drag_state = MA_TEXBROWSER_DRAG_TRANSLATE;
            cur = pv->translate_cur;
          }
            else if(pv->flags & MA_TEXBROWSER_FLAG_KEY_ZOOM_RECT)
            {
                pv->drag_state = MA_TEXBROWSER_DRAG_ZOOM_RECT;
                cur = pv->zoom_rect_cur;
            }
          else
          {
            pv->drag_state = MA_TEXBROWSER_DRAG_NONE;
            cur = NULL;
          }
            if(!GTK_WIDGET_NO_WINDOW(w) && status)
                gdk_window_set_cursor(w->window, cur);
            break;

          case GDK_BUTTON_PRESS:
            button = (GdkEventButton *)event;
            switch(button->button)
            {
              case GDK_BUTTON1:
                pv->flags |= MA_TEXBROWSER_FLAG_BUTTON1;
            status = TRUE;
                break;

              case GDK_BUTTON2:
                pv->flags |= MA_TEXBROWSER_FLAG_BUTTON2;
            status = TRUE;
                break;

              case GDK_BUTTON3:
/*
                pv->flags |= MA_TEXBROWSER_FLAG_BUTTON3;
 */
            status = TRUE;
            TexBrowserMenuMapCB(widget, event, tb);
            return(status);
                break;
            }
            /* Not dragging and button 1 just pressed? */
            if((pv->drag_state == MA_TEXBROWSER_DRAG_NONE) &&
               (pv->flags & (MA_TEXBROWSER_FLAG_BUTTON1))
            )
            {
                pv->drag_state = MA_TEXBROWSER_DRAG_TRANSLATE;
                cur = pv->translate_cur;
                if(!GTK_WIDGET_NO_WINDOW(w))
                    gdk_window_set_cursor(w->window, cur);
            pv->last_xw = button->x;
            pv->last_yw = button->y;
            }
            /* Not dragging and button 2 just pressed? */
            if((pv->drag_state == MA_TEXBROWSER_DRAG_NONE) &&
               (pv->flags & (MA_TEXBROWSER_FLAG_BUTTON2))
            )
            {
                pv->drag_state = MA_TEXBROWSER_DRAG_ZOOM;
                cur = pv->zoom_cur;
                if(!GTK_WIDGET_NO_WINDOW(w))
                    gdk_window_set_cursor(w->window, cur);
                pv->last_xw = button->x;
                pv->last_yw = button->y;
            }
            /* Dragging translate? */
            else if(pv->drag_state == MA_TEXBROWSER_DRAG_TRANSLATE)
            {
                pv->last_xw = button->x;
                pv->last_yw = button->y;
          }
            /* Dragging zoom? */
            else if(pv->drag_state == MA_TEXBROWSER_DRAG_ZOOM)
            {
                pv->last_xw = button->x;
                pv->last_yw = button->y;
          }
            /* Dragging zoom rectangular? */
            else if(pv->drag_state == MA_TEXBROWSER_DRAG_ZOOM_RECT)
            {
            pv->zoom_rect_start_xw = button->x;
                pv->zoom_rect_start_yw = button->y;
                pv->last_xw = button->x;
                pv->last_yw = button->y;
            }

            /* Grab pointer so it confines to view. */
            if(!GTK_WIDGET_NO_WINDOW(w))
                gdk_pointer_grab(
                    w->window,
                    TRUE,
                    GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
                    w->window,
                    GDK_NONE,
                    GDK_CURRENT_TIME
                );
            break;

          case GDK_BUTTON_RELEASE:
            button = (GdkEventButton *)event;
            switch(button->button)
            {
              case GDK_BUTTON1:
                pv->flags &= ~MA_TEXBROWSER_FLAG_BUTTON1;
                status = TRUE;
                break;
             
              case GDK_BUTTON2:
                pv->flags &= ~MA_TEXBROWSER_FLAG_BUTTON2;
                status = TRUE;
                break;

              case GDK_BUTTON3:
                pv->flags &= ~MA_TEXBROWSER_FLAG_BUTTON3;
                status = TRUE;
                break;
            }
            /* Was dragging translate? */
            if(pv->drag_state == MA_TEXBROWSER_DRAG_TRANSLATE)
            {
            if(!(pv->flags & MA_TEXBROWSER_FLAG_KEY_TRANSLATE))
            {
                pv->drag_state = MA_TEXBROWSER_DRAG_NONE;
                if(!GTK_WIDGET_NO_WINDOW(w) && status)
                        gdk_window_set_cursor(w->window, NULL);
            }
            }
          /* Was dragging zoom? */
            else if(pv->drag_state == MA_TEXBROWSER_DRAG_ZOOM)
            {
                if(!(pv->flags & MA_TEXBROWSER_FLAG_KEY_ZOOM))
                {
                    pv->drag_state = MA_TEXBROWSER_DRAG_NONE;
                    if(!GTK_WIDGET_NO_WINDOW(w) && status)
                        gdk_window_set_cursor(w->window, NULL);
                }
            }
          /* Was dragging zoom rect? */
            else if(pv->drag_state == MA_TEXBROWSER_DRAG_ZOOM_RECT)
            {
            /* Set new rectangular zoom and redraw. */
            TexBrowserPreviewZoomRect(
                tb, pv->zoom_rect_start_xw, pv->zoom_rect_start_yw,
                pv->last_xw, pv->last_yw
            );

                if(!(pv->flags & MA_TEXBROWSER_FLAG_KEY_ZOOM_RECT))
                {
                    pv->drag_state = MA_TEXBROWSER_DRAG_NONE;
                    if(!GTK_WIDGET_NO_WINDOW(w) && status)
                        gdk_window_set_cursor(w->window, NULL);
            }
            }
            /* Ungrab pointer. */ 
            gdk_pointer_ungrab(GDK_CURRENT_TIME);
          break;

          case GDK_MOTION_NOTIFY:
            motion = (GdkEventMotion *)event;
            if(motion->is_hint)
            {
                if((pv->drag_state != MA_TEXBROWSER_DRAG_NONE) &&
                   (pv->flags & (MA_TEXBROWSER_FLAG_BUTTON1 |
                                MA_TEXBROWSER_FLAG_BUTTON2 |
                                MA_TEXBROWSER_FLAG_BUTTON3)
                   )
            )
                    gdk_window_get_pointer(
                        motion->window, &x, &y, &mask
                    );
            }
            else
            {
                x = motion->x;
                y = motion->y;
                mask = motion->state;
            }
          /* Handle by drag mode. */
          switch(pv->drag_state)
          {
            case MA_TEXBROWSER_DRAG_TRANSLATE:
            /* Any of the buttons pressed? */
            if(pv->flags & (MA_TEXBROWSER_FLAG_BUTTON1 |
                                MA_TEXBROWSER_FLAG_BUTTON2 |
                                MA_TEXBROWSER_FLAG_BUTTON3)
                )
            {
                gint dw;
                gdouble coeff_wtod = pv->zoom_coeff_wtod;


                adj = pv->preview_hadj;
                    w2 = pv->preview_hsb;
                if((adj != NULL) && (w2 != NULL) && (coeff_wtod > 0.0))
                {
                  dw = (gint)(pv->last_xw - x);
                  adj->value += (gdouble)dw;

                        if((gint)adj->value >
                        (gint)(adj->upper - adj->page_size)
                  )
                      adj->value = adj->upper - adj->page_size;
                  if((gint)adj->value < 0)
                      adj->value = 0.0;
                  pv->xw = (gint)(adj->value / coeff_wtod);

                        if((gint)(adj->upper - adj->lower) <=
                            (gint)(adj->page_size)
                        )
                            gtk_widget_hide(w2);
                        else
                            gtk_widget_show(w2);
                        gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
                }

                    adj = pv->preview_vadj;
                    w2 = pv->preview_vsb;
                    if((adj != NULL) && (w2 != NULL) && (coeff_wtod > 0.0))  
                    {
                        dw = (gint)(pv->last_yw - y);
                        adj->value += (gdouble)dw;

                        if((gint)adj->value >
                                (gint)(adj->upper - adj->page_size)
                        )
                            adj->value = adj->upper - adj->page_size;
                        if((gint)adj->value < 0)
                            adj->value = 0.0;
                        pv->yw = (gint)(adj->value / coeff_wtod);

                        if((gint)(adj->upper - adj->lower) <=
                            (gint)(adj->page_size)
                        )   
                            gtk_widget_hide(w2);
                        else
                            gtk_widget_show(w2);
                        gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
                    }
                TexBrowserPreviewDraw(tb);

                    pv->last_xw = x;
                    pv->last_yw = y;
                status = TRUE;
            }
            break;

              case MA_TEXBROWSER_DRAG_ZOOM:
                /* Any of the buttons pressed? */
                if(pv->flags & (MA_TEXBROWSER_FLAG_BUTTON1 |
                                MA_TEXBROWSER_FLAG_BUTTON2 |
                                MA_TEXBROWSER_FLAG_BUTTON3)
                )
                {
                gint dw, old_tex_widthd, old_tex_heightd;
                gdouble coeff_wtod, old_coeff_wtod;


                /* Record previous zoom and sizes. */
                old_coeff_wtod = pv->zoom_coeff_wtod;
                dw = (gint)(y - pv->last_yw);
                old_tex_widthd = pv->tex_width * pv->zoom_coeff_wtod;
                old_tex_heightd = pv->tex_height * pv->zoom_coeff_wtod;

                pv->zoom_coeff_wtod += 0.01 * dw;
                if(pv->zoom_coeff_wtod > pv->zoom_max_wtod)
                  pv->zoom_coeff_wtod = pv->zoom_max_wtod;
                else if(pv->zoom_coeff_wtod < pv->zoom_min_wtod) 
                        pv->zoom_coeff_wtod = pv->zoom_min_wtod;

                /* Get new zoom coeff. */
                coeff_wtod = pv->zoom_coeff_wtod;

                /* Update horizontal value. */
                    adj = pv->preview_hadj;
                    w2 = pv->preview_hsb;
                    if((adj != NULL) && (w2 != NULL) && (coeff_wtod > 0.0))
                    {
                  gdouble center_coeff;
                  gdouble old_upper = adj->upper;

                  if(old_upper > 0.0)
                      center_coeff = (adj->value +
                        ((gdouble)pv->ww / 2)) / old_upper;
                  else
                      center_coeff = 0.0;

                  adj->upper = (gdouble)pv->tex_width * coeff_wtod;
                  adj->value = (adj->upper * center_coeff) -
                      (pv->ww / 2);

                        if((gint)adj->value >
                                (gint)(adj->upper - adj->page_size)   
                        )
                            adj->value = adj->upper - adj->page_size;
                        if((gint)adj->value < 0)
                            adj->value = 0.0;
                        pv->xw = (gint)(adj->value / coeff_wtod);

                  if((gint)(adj->upper - adj->lower) <=
                      (gint)(adj->page_size)
                  )
                  {
                      gtk_widget_hide(w2);
                  }
                  else
                  {
                      gtk_widget_show(w2);
                      gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
                  }
                    }

                /* Update vertical value. */
                    adj = pv->preview_vadj;
                w2 = pv->preview_vsb;
                    if((adj != NULL) && (w2 != NULL) && (coeff_wtod > 0.0))
                    {
                        gdouble center_coeff; 
                        gdouble old_upper = adj->upper;

                        if(old_upper > 0.0)
                            center_coeff = (adj->value +
                                ((gdouble)pv->hw / 2)) / old_upper;
                        else
                            center_coeff = 0.0;

                        adj->upper = (gdouble)pv->tex_height * coeff_wtod;
                        adj->value = (adj->upper * center_coeff) -
                            (pv->hw / 2);

                        if((gint)adj->value >
                                (gint)(adj->upper - adj->page_size)
                        )
                            adj->value = adj->upper - adj->page_size;
                        if((gint)adj->value < 0)
                            adj->value = 0.0;
                        pv->yw = (gint)(adj->value / coeff_wtod);

                        if((gint)(adj->upper - adj->lower) <=
                            (gint)(adj->page_size)
                        )
                  {
                            gtk_widget_hide(w2);
                  }
                        else
                  {
                            gtk_widget_show(w2);
                      gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
                  }
                }
                    TexBrowserPreviewDraw(tb);

                pv->last_xw = x;
                    pv->last_yw = y;
                    status = TRUE;
                }
                break;

             case MA_TEXBROWSER_DRAG_ZOOM_RECT:
               if(pv->flags & (MA_TEXBROWSER_FLAG_BUTTON1 |
                               MA_TEXBROWSER_FLAG_BUTTON2 |
                               MA_TEXBROWSER_FLAG_BUTTON3)
                )
                {
                /* Update last positions before drawing. */
                    pv->last_xw = x;
                    pv->last_yw = y;
         
                    TexBrowserPreviewDraw(tb);

                    status = TRUE;
                }   
            break;
          }
          break;


          default:
            /* Unknown event type but for this widget, can't
             * handle it though so need to return FALSE since
             * we did not handle the event.
             */
            break;
      }

      return(status);
}


/*
 *    Texture browser preview scroll callback.
 */
void TexBrowserPreviewScrollCB(GtkAdjustment *adj, gpointer data)
{
      static gbool reenterant = FALSE;
      ma_texbrowser_preview_struct *pv;
      ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
      if(tb == NULL)
          return;

      if(reenterant)
          return;
      else
          reenterant = TRUE;

      pv = &tb->preview;

      /* Vertical. */
      if(adj == pv->preview_vadj)
      {
          if(pv->zoom_coeff_wtod > 0)
          {
            if((int)(adj->value) > (int)(adj->upper - adj->page_size))
                pv->yw = (int)((adj->upper - adj->page_size) /
                  pv->zoom_coeff_wtod);
            else
                pv->yw = (int)(adj->value / pv->zoom_coeff_wtod);
          }
          else
            pv->yw = 0;
      }
      /* Horizontal. */
      else if(adj == pv->preview_hadj)
      {
            if(pv->zoom_coeff_wtod > 0)
            {
                if((int)(adj->value) > (int)(adj->upper - adj->page_size)) 
                    pv->xw = (int)((adj->upper - adj->page_size) /
                        pv->zoom_coeff_wtod);
                else
                    pv->xw = (int)(adj->value / pv->zoom_coeff_wtod);
            }
            else
                pv->xw = 0;
        }

      TexBrowserPreviewDraw(tb);

      reenterant = FALSE;

      return;
}

/*
 *    Zoom in button press and release callbacks
 */
void TexBrowserZoomInPressedCB(GtkWidget *widget, gpointer data)
{
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return; 

      if(zoom_in_toid == (guint)(-1))
            zoom_in_toid = gtk_timeout_add(
                (guint32)100,
                (GtkFunction)TexBrowserZoomInTimeOutCB,
                (gpointer)tb
            );

        TexBrowserZoomInTimeOutCB(tb);

        return;
}
void TexBrowserZoomInReleasedCB(GtkWidget *widget, gpointer data)
{
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)   
            return;

      if(zoom_in_toid != (guint)(-1))
      {
            gtk_timeout_remove(zoom_in_toid);
            zoom_in_toid = (guint)(-1);
      }

        return;
}

/*
 *      Zoom out button press and release callbacks
 */
void TexBrowserZoomOutPressedCB(GtkWidget *widget, gpointer data)
{
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return;   

      if(zoom_out_toid == (guint)(-1))
            zoom_out_toid = gtk_timeout_add(
                (guint32)100,
                (GtkFunction)TexBrowserZoomOutTimeOutCB,
                (gpointer)tb
            );

      TexBrowserZoomOutTimeOutCB(tb);

        return;
}
void TexBrowserZoomOutReleasedCB(GtkWidget *widget, gpointer data)
{
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return;

      if(zoom_out_toid != (guint)(-1))
      {
          gtk_timeout_remove(zoom_out_toid);
          zoom_out_toid = (guint)(-1);
      }

      return;
}


/*
 *    Preview zoom in timeout callback.
 */
gint TexBrowserZoomInTimeOutCB(gpointer data)
{
      GtkWidget *w2;
      double old_tex_widthd, old_tex_heightd;
      double zoom_max_wtod, coeff_wtod;
      GtkAdjustment *adj;
        ma_texbrowser_preview_struct *pv;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return(FALSE);

        pv = &tb->preview;
      zoom_max_wtod = pv->zoom_max_wtod;

      old_tex_widthd = pv->tex_width * pv->zoom_coeff_wtod;
        old_tex_heightd = pv->tex_height * pv->zoom_coeff_wtod;

      pv->zoom_coeff_wtod += 0.05;
      if(pv->zoom_coeff_wtod > zoom_max_wtod)
          pv->zoom_coeff_wtod = zoom_max_wtod;

        /* Update adjustments. */
      coeff_wtod = pv->zoom_coeff_wtod;
      adj = pv->preview_hadj;
        w2 = pv->preview_hsb;
        if((adj != NULL) && (w2 != NULL) && (coeff_wtod > 0.0))
        {
            double center_coeff;   
            double old_upper = adj->upper;

            if(old_upper > 0.0)
                center_coeff = (adj->value +
                    ((double)pv->ww / 2)) / old_upper;
            else
                center_coeff = 0.0;

            adj->upper = (double)pv->tex_width * coeff_wtod;
            adj->value = (adj->upper * center_coeff) - (pv->ww / 2);

            if((int)adj->value > (int)(adj->upper - adj->page_size))
                adj->value = adj->upper - adj->page_size;
            if((int)adj->value < 0)
                adj->value = 0.0;
            pv->xw = (int)(adj->value / coeff_wtod);
            if((int)(adj->upper - adj->lower) <= (int)(adj->page_size))
                gtk_widget_hide(w2);
            else
                gtk_widget_show(w2);
            gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
        }

        adj = pv->preview_vadj;
        w2 = pv->preview_vsb;
        if((adj != NULL) && (w2 != NULL) && (coeff_wtod > 0.0))
        {
            double center_coeff;
            double old_upper = adj->upper;

            if(old_upper > 0.0)
                center_coeff = (adj->value +
                    ((double)pv->hw / 2)) / old_upper;
            else
                center_coeff = 0.0;

            adj->upper = (double)pv->tex_height * coeff_wtod;
            adj->value = (adj->upper * center_coeff) - (pv->hw / 2);

            if((int)adj->value > (int)(adj->upper - adj->page_size))
                adj->value = adj->upper - adj->page_size;
            if((int)adj->value < 0)
                adj->value = 0.0;
            pv->yw = (int)(adj->value / coeff_wtod);
            if((int)(adj->upper - adj->lower) <= (int)(adj->page_size))
                gtk_widget_hide(w2);
            else
                gtk_widget_show(w2);
            gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
        }

        /* Redraw preview. */
        TexBrowserPreviewDraw(tb);

      return(TRUE);
}

/*
 *    Preview zoom out timeout callback.
 */
gint TexBrowserZoomOutTimeOutCB(gpointer data)
{
      GtkWidget *w2;
        double old_tex_widthd, old_tex_heightd;
        double zoom_min_wtod, coeff_wtod;
        GtkAdjustment *adj;
        ma_texbrowser_preview_struct *pv;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return(FALSE);

        pv = &tb->preview;
      zoom_min_wtod = pv->zoom_min_wtod;

        old_tex_widthd = pv->tex_width * pv->zoom_coeff_wtod;
        old_tex_heightd = pv->tex_height * pv->zoom_coeff_wtod;

        pv->zoom_coeff_wtod -= 0.05;
        if(pv->zoom_coeff_wtod < zoom_min_wtod)
            pv->zoom_coeff_wtod = zoom_min_wtod;

      /* Update adjustments. */
      coeff_wtod = pv->zoom_coeff_wtod;
      adj = pv->preview_hadj;
        w2 = pv->preview_hsb;
        if((adj != NULL) && (w2 != NULL) && (coeff_wtod > 0.0))
        {
            double center_coeff;   
            double old_upper = adj->upper;

            if(old_upper > 0.0)
                center_coeff = (adj->value +
                    ((double)pv->ww / 2)) / old_upper;
            else
                center_coeff = 0.0;

            adj->upper = (double)pv->tex_width * coeff_wtod;
            adj->value = (adj->upper * center_coeff) - (pv->ww / 2);

            if((int)adj->value > (int)(adj->upper - adj->page_size))
                adj->value = adj->upper - adj->page_size;
            if((int)adj->value < 0)
                adj->value = 0.0;
            pv->xw = (int)(adj->value / coeff_wtod);
            if((int)(adj->upper - adj->lower) <= (int)(adj->page_size))
                gtk_widget_hide(w2);
            else
                gtk_widget_show(w2);
            gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
        }

        adj = pv->preview_vadj;
        w2 = pv->preview_vsb;
        if((adj != NULL) && (w2 != NULL) && (coeff_wtod > 0.0))
        {
            double center_coeff;
            double old_upper = adj->upper;

            if(old_upper > 0.0)
                center_coeff = (adj->value +
                    ((double)pv->hw / 2)) / old_upper;
            else
                center_coeff = 0.0;

            adj->upper = (double)pv->tex_height * coeff_wtod;
            adj->value = (adj->upper * center_coeff) - (pv->hw / 2);

            if((int)adj->value > (int)(adj->upper - adj->page_size))
                adj->value = adj->upper - adj->page_size;
            if((int)adj->value < 0)
                adj->value = 0.0;
            pv->yw = (int)(adj->value / coeff_wtod);
            if((int)(adj->upper - adj->lower) <= (int)(adj->page_size))
                gtk_widget_hide(w2);
            else
                gtk_widget_show(w2);
            gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
        }

        /* Redraw preview. */
        TexBrowserPreviewDraw(tb);

        return(TRUE);
}

/*
 *    Zoom one to one callback.
 */
void TexBrowserZoomOneToOneCB(GtkWidget *widget, gpointer data)
{
      GtkWidget *w;
      GtkAdjustment *adj;
        ma_texbrowser_preview_struct *pv;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return;

        pv = &tb->preview;

        pv->zoom_coeff_wtod = 1.0;

        /* Update adjustments. */
        adj = pv->preview_vadj;
        w = pv->preview_vsb;
        if((adj != NULL) && (w != NULL))
        {
          adj->value = 0;
            adj->lower = 0;
            adj->upper = pv->tex_height * pv->zoom_coeff_wtod;
            adj->page_size = pv->hw;
            if(pv->zoom_coeff_wtod > 0.0)
                pv->yw = adj->value / pv->zoom_coeff_wtod;

            if((int)(adj->upper - adj->lower) <= (int)(adj->page_size))
                gtk_widget_hide(w);
            else
                gtk_widget_show(w);
            gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
        }

        adj = pv->preview_hadj;   
        w = pv->preview_hsb;
        if((adj != NULL) && (w != NULL))
        {
            adj->value = 0;
            adj->lower = 0;
            adj->upper = pv->tex_width * pv->zoom_coeff_wtod;
            adj->page_size = pv->ww;
          if(pv->zoom_coeff_wtod > 0.0)
            pv->xw = adj->value / pv->zoom_coeff_wtod;

            if((int)(adj->upper - adj->lower) <= (int)(adj->page_size))
                gtk_widget_hide(w);
            else
                gtk_widget_show(w);
            gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
        }

        /* Redraw preview. */
        TexBrowserPreviewDraw(tb);

      return;
}

/*
 *    Zoom to fit callback.
 */
void TexBrowserZoomToFitCB(GtkWidget *widget, gpointer data)
{
        GtkWidget *w;
        GtkAdjustment *adj;
        ma_texbrowser_preview_struct *pv;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return;

        pv = &tb->preview;

      /* Calculate zoom so that longer dimension fits snugly
       * in preview window.
       */
      if(pv->tex_width > pv->tex_height)
      {
          if(pv->tex_width > 0)
            pv->zoom_coeff_wtod = (double)pv->ww /
                (double)pv->tex_width;
          else
            pv->zoom_coeff_wtod = 1.0;
      }
      else
      {
            if(pv->tex_height > 0)
                pv->zoom_coeff_wtod = (double)pv->hw /
                    (double)pv->tex_height;
            else
                pv->zoom_coeff_wtod = 1.0;
      }


        /* Update adjustments. */
        adj = pv->preview_vadj;
        w = pv->preview_vsb; 
        if((adj != NULL) && (w != NULL))
        {   
            adj->value = 0;
            adj->lower = 0;
            adj->upper = pv->tex_height * pv->zoom_coeff_wtod;
            adj->page_size = pv->hw;
            if(pv->zoom_coeff_wtod > 0.0)
                pv->yw = adj->value / pv->zoom_coeff_wtod;

            if((int)(adj->upper - adj->lower) <= (int)(adj->page_size))
                gtk_widget_hide(w);
            else
                gtk_widget_show(w);
            gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
        }

        adj = pv->preview_hadj;
        w = pv->preview_hsb;
        if((adj != NULL) && (w != NULL))
        {
            adj->value = 0;
            adj->lower = 0;
            adj->upper = pv->tex_width * pv->zoom_coeff_wtod;
            adj->page_size = pv->ww;
            if(pv->zoom_coeff_wtod > 0.0)
                pv->xw = adj->value / pv->zoom_coeff_wtod;

            if((int)(adj->upper - adj->lower) <= (int)(adj->page_size))
                gtk_widget_hide(w);
            else
                gtk_widget_show(w);
            gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
        }


        /* Redraw preview. */
        TexBrowserPreviewDraw(tb);

        return;
}


/*
 *    Prompt dialog browse button callback. Need to return a statically
 *    allocated value (or NULL to indicate abort). Inputs are as
 *    follows:
 *
 *    1     Pointer to the prompt dialog structure
 *    2     Pointer to client data (the texture browser struct)
 *    3     Prompt number on prompt dialog
 */
static gchar *TexBrowserPromptBrowseCB(
        gpointer prompt, gpointer client_data, gint value_num  
)
{
      gbool status;
      GtkWidget *w;
      gchar **fb_path_rtn, *textures_base_dir;
      gint fb_path_total_rtns;
      fb_type_struct *fb_type_rtn;
      static gchar rtn_path[PATH_MAX + NAME_MAX];
      ma_texture_browser_struct *tb = (ma_texture_browser_struct *)client_data;
      if(tb == NULL)
          return(NULL);

      /* Get base dir if available. */
      w = tb->base_dir_entry;
      if(w != NULL)
          textures_base_dir = gtk_entry_get_text(GTK_ENTRY(w));
      else
          textures_base_dir = NULL;

      /* Handle by prompt number. */
      switch(value_num)
      {
        /* Texture file path prompt. */
        case 1:
          FileBrowserSetTransientFor(tb->toplevel);
          status = FileBrowserGetResponse(
            "Select Texture",
            "Select", "Cancel",
            dname.fb_last_textures, /* Last path. */
                ftype.load_texture, ftype.load_texture_total,
            &fb_path_rtn, &fb_path_total_rtns,
            &fb_type_rtn
          );
          FileBrowserSetTransientFor(NULL);

          if(status)
          {
            if((fb_path_rtn == NULL) || (textures_base_dir == NULL))
            {
                return(NULL);
            }
            else
            {
                int len = strlen(textures_base_dir);
                char    *strrtn = NULL,
                        *strptr, *path_ptr;

                /* Get pointer to last path return. */
                path_ptr = ((fb_path_total_rtns > 0) ?
                  fb_path_rtn[fb_path_total_rtns - 1] : NULL
                );

                /* Record file browser return path. */
                if(path_ptr != NULL)
                {
                  VMARecordFBPath(
                      path_ptr,
                      dname.fb_last_textures,
                      1
                  );

                  /* Use relative path if possable. */
#ifdef __MSW__
                  if(strcasepfx(path_ptr, textures_base_dir))
#else
                  if(strpfx(path_ptr, textures_base_dir))
#endif
                    {
                      /* Parse as relative path. */
                        strncpy(
                          rtn_path,
                          path_ptr + len,
                          PATH_MAX + NAME_MAX
                      );
                            rtn_path[PATH_MAX + NAME_MAX - 1] = '\0';
                      strptr = rtn_path;
                      while((*strptr) == DIR_DELIMINATOR)
                          strptr++;
                    }
                  else
                  {
                      /* Use absolute path. */
                      strncpy(
                                rtn_path,
                                path_ptr,
                                PATH_MAX + NAME_MAX
                            );
                      rtn_path[PATH_MAX + NAME_MAX - 1] = '\0';
                      strptr = rtn_path;
                  }
                  strrtn = strptr;
                    if(strrtn == NULL)
                      return(NULL);

                    /* Set prompt 0 to value of texture name if
                     * if it contains no data.
                     */
                    strptr = PDialogGetPromptValue(0);
                    if((strptr == NULL) ? 1 : ((*strptr) == '\0'))
                    {
                      /* Prompt 0 contains no text, set some. */
                      char tmp_name[256];

                      strptr = strrchr(strrtn, DIR_DELIMINATOR);
                      strncpy(
                          tmp_name,
                          ((strptr == NULL) ? strrtn : strptr + 1),
                          256
                      );
                      tmp_name[256 - 1] = '\0';

                      /* Substitute '.' to '_'. */
                      strptr = strchr(tmp_name, '.');
                      while(strptr != NULL)
                      {
                          *strptr = '_';
                          strptr = strchr(strptr, '.');
                      }

                      PDialogSetPromptValue(
                          0, NULL, NULL, tmp_name
                      );
                  }
                }

                /* Return path. */
                return(strrtn);
            }
          }
          else
          {
            /* Cancel. */
            return(NULL);
          }
          break;

        default:
          /* Some other prompt number, not suppose to happen, ignore. */
          break;
      }

      return(NULL);
}


/*
 *    Destroy callback.
 */
void TexBrowserDestroyCB(GtkObject *object, gpointer data)
{
      return;
}

/*
 *    Close callback, will also record positions.
 */
gint TexBrowserCloseCB(GtkWidget *widget, GdkEvent *event, gpointer data)
{
      ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return(FALSE);

      if(tb->toplevel == NULL)
          return(FALSE);

      /* Record last texture browser positions. */
      TexBrowserRecordPositionsCB(data);

      /* Unmap the texture browser. */
      TexBrowserUnmap(data);

      return(TRUE);
}

/*
 *    Close callback.
 */
void TexBrowserCloseMCB(GtkWidget *widget, gpointer data)
{
      TexBrowserCloseCB(widget, NULL, data);

      return;
}


/*
 *    Maps file browser to query for new textures base directory
 *    for the given texture browser.
 */
void TexBrowserBrowseBaseDirCB(GtkWidget *widget, gpointer data)
{
      gint status;
      gbool fb_status;
      gchar *strptr;
        gchar **fb_path_rtn;
      gint fb_path_total_rtns;
        fb_type_struct *fb_type_rtn;
      gchar tmp_path[PATH_MAX + NAME_MAX];
      GtkWidget *w;
      struct stat stat_buf;
      ma_editor_struct *editor;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return;

      editor = (ma_editor_struct *)tb->editor_ptr;
      if(editor == NULL)
          return;

      if(!tb->base_dir_override)
      {
          /* Not overriding texture base dir, so check if model
           * header items define a base directory.
           */
          if(EditorListHeaderGetTextureBaseDir(editor) != NULL)
            status = 1;
          else
            status = 0;

          /* Model header items defined texture base directory? */
          if(status)
          {
            CDialogGetResponse(
"Model specifies value",
"Override texture base directory is turned off, and the\n\
model file explicitly sets the texture base directory.",
"The option override texture base directory is turned off,\n\
which means the model file's specified texture base\n\
directory will be used. If you want to specify your own\n\
texture base directory, then turn on override next to\n\
the texture base directory prompt.",
                CDIALOG_ICON_WARNING,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            return;
          }
      }


      w = tb->base_dir_entry;
      if(w == NULL)
          return;

      strptr = gtk_entry_get_text(GTK_ENTRY(w));
      if(strptr == NULL)
          strncpy(tmp_path, dname.home, PATH_MAX + NAME_MAX);
      else
          strncpy(tmp_path, strptr, PATH_MAX + NAME_MAX);
      tmp_path[PATH_MAX + NAME_MAX - 1] = '\0';

      /* Query new path from user. */
      FileBrowserSetTransientFor(tb->toplevel);
      fb_status = FileBrowserGetResponse(
          "Set Textures Base Directory",
          "Select", "Cancel",
          tmp_path,
            ftype.load_texture, ftype.load_texture_total,
          &fb_path_rtn, &fb_path_total_rtns,
          &fb_type_rtn
      );
      FileBrowserSetTransientFor(NULL);

      if(!fb_status)
          return;

      /* Got path response? */
      if(fb_path_rtn == NULL)
          return;

      strptr = ((fb_path_total_rtns > 0) ?
          fb_path_rtn[fb_path_total_rtns - 1] : NULL
      );
      if(strptr == NULL)
          return;

      strncpy(tmp_path, strptr, PATH_MAX + NAME_MAX);
      tmp_path[PATH_MAX + NAME_MAX - 1] = '\0';


      /* Check if the directory exists. */
        if(stat(tmp_path, &stat_buf))
      {
          gchar text[256 + PATH_MAX + NAME_MAX];
          sprintf(text, "No such directory:\n    %s", tmp_path);

          CDialogGetResponse(
"No such directory!",
text,
"The specified directory could not be found, please verify\n\
that it exists.",
            CDIALOG_ICON_ERROR,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
          return;
      }

      /* Check if it actually is a directory. */
      if(!S_ISDIR(stat_buf.st_mode))
      {
          /* Not a dir and must be a dir, so get its parent. */
            int len = strlen(tmp_path);
            char *strptr;

            strptr = tmp_path + len - 1;
            while(strptr > tmp_path)
            {
                if(*strptr == DIR_DELIMINATOR)
                {
                    (*(strptr + 1)) = '\0';
                    break;
                }
                strptr--;
            }
        }

      /* Add tailing deliminator as needed. */
      if(1)
      {
            gint len = strlen(tmp_path);
            gchar *strptr;

          if((len > 1) && (len < (PATH_MAX + NAME_MAX - 1)))
          {
            strptr = &tmp_path[len - 1];
            if(*strptr != DIR_DELIMINATOR)
            {
                (*(strptr + 1)) = DIR_DELIMINATOR;
                (*(strptr + 2)) = '\0';
            }
          }
      }


      /* Update new path to entry. */
        gtk_entry_set_text(GTK_ENTRY(w), tmp_path);

      /* Update editor model header items about change, */
      EditorListHeaderSetTextureBaseDir(editor, tmp_path);

      /* Reload textures. */
      TexBrowserReloadCB(tb->reload_btn, tb);
}

/*
 *    Override texture base directory check button callback.
 */
void TexBrowserOverrideCheckCB(GtkWidget *widget, gpointer data)
{
        static gbool reenterant = FALSE;
        GtkWidget *w;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if((tb == NULL) || 
           (widget == NULL)
        )
            return;

        if(reenterant)
            return;
        else
            reenterant = TRUE;


        w = tb->base_dir_override_check;
        if(w == widget)
        {
            tb->base_dir_override = GTK_TOGGLE_BUTTON(w)->active;
        }

        /* Update menus. */  
        TexBrowserUpdateMenus(tb);

        reenterant = FALSE;

        return;
}

/*
 *    Callback to unload all textures on the editor structure
 *    then reload them and reupdate the texture browser list.
 */
void TexBrowserReloadCB(GtkWidget *widget, gpointer data)
{
      int i;
      ma_editor_struct *editor;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return;

      editor = (ma_editor_struct *)tb->editor_ptr;
      if(editor == NULL)
          return;

        TexBrowserSetBusy(tb);

      /* Delete all textures loaded on the editor structure. */
      EditorTextureListDeleteAll(editor);

      /* Reload textures on editor structure. */
      EditorTextureLoadAll(editor);


      /* Rerealize all primitives on each model on the editor. */
      for(i = 0; i < editor->total_models; i++)
            EditorPrimitiveRealizeAllType(
                editor, i, V3DMP_TYPE_TEXTURE_SELECT, TRUE  
            );

      /* Refresh textures list on texture browser. */
      TexBrowserListDeleteAll(tb);
      TexBrowserListFetch(tb, editor);

      /* Update menus. */
      TexBrowserUpdateMenus(tb);
        TexBrowserSetReady(tb);

      /* Need to update editor. */
        EditorUpdateMenus(editor);
        EditorUpdateAllViewMenus(editor);
        EditorRedrawAllViews(editor);

      return;
}

/*
 *    Text entry enter callback.
 */
void TexBrowserEnterCB(GtkWidget *widget, gpointer data)
{
      GtkWidget *w;
      ma_editor_struct *editor;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if((tb == NULL) ||
           (widget == NULL)
      )
            return;

      editor = tb->editor_ptr;

      /* Texture base dir text entry? */
      if(widget == tb->base_dir_entry)
      {
          char *new_dir = (char *)gtk_entry_get_text(GTK_ENTRY(widget));

          /* Copy string. */
          if(new_dir != NULL)
            new_dir = strdup(new_dir);

          /* Override, set new texture base directory? */
          if((tb->base_dir_override) && (editor != NULL) &&
               (new_dir != NULL)
          )
          {
            gtk_entry_set_position(GTK_ENTRY(widget), 0);

            /* Update editor's model header items list, set new
             * texture base directory.
             */
            EditorListHeaderSetTextureBaseDir(editor, new_dir);
          }
          else if(!tb->base_dir_override)
          {
                CDialogGetResponse(
"Model specifies value",
"Override texture base directory is turned off, and the\n\
model file explicitly sets the texture base directory.",
"The option override texture base directory is turned off,\n\
which means the model file's specified texture base\n\
directory will be used. If you want to specify your own\n\
texture base directory, then turn on override next to\n\
the texture base directory prompt.",
                    CDIALOG_ICON_WARNING,
                    CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                    CDIALOG_BTNFLAG_OK
                );
          }

          /* Reload actual textures on editor and update texture
           * browser's texture list.
           */
          TexBrowserReloadCB(widget, tb);

          /* Free new directory. */
          free(new_dir);
      }

      /* Search combo entry? */
      w = tb->search_combo;
      if(widget == w)
      {
          GtkCombo *combo = GTK_COMBO(w);
          char *new_value, *search_string;


          /* Get new value from combo's entry widget. */
          new_value = gtk_entry_get_text(
            GTK_ENTRY(combo->entry)
          );

          /* Allocate search string. */
          search_string = strdup(
            (new_value == NULL) ? "" : new_value
          );

          /* Do search. */
          TexBrowserListDoFind(
            tb,
            search_string,    /* Search string. */
            NULL,       /* Type string. */
            FALSE,            /* Find exact? */
            FALSE       /* Find case sensitive? */
          );

          /* Free search string which we duplicated. */
          free(search_string);
      }

      return;
}


/*
 *    Handles any of the `sort by' menu item widgets.
 */
void TexBrowserSortMCB(GtkWidget *widget, gpointer data)
{
      GtkWidget *w;
      static gbool reenterant = FALSE;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;

      if(reenterant)
          return;
      else
          reenterant = TRUE;

        if((tb == NULL) ||
           (widget == NULL) 
        )
      {
          reenterant = FALSE;
            return;
      }

#define UPDATE_CHECK_MENU_ITEM_STATE    \
{ \
 if(w != NULL) \
  gtk_check_menu_item_set_active( \
   GTK_CHECK_MENU_ITEM(w), state \
  ); \
}

      if(tb_sorting_list)
      {
          reenterant = FALSE;
          return;
      }
      else
      {
          tb_sorting_list = TRUE;
      }

        /* By index. */
        if(widget == tb->sort_by_index_micheck)
        {
          tb->list_sort_code = TEXBROWSER_LIST_SORT_ADDED;

            w = tb->textures_list;
            if(w != NULL)
                gtk_clist_set_sort_column(GTK_CLIST(w), 0);
      }
      /* By name. */
      else if(widget == tb->sort_by_name_micheck)
      {
            tb->list_sort_code = TEXBROWSER_LIST_SORT_NAME;

            w = tb->textures_list;
            if(w != NULL)
                gtk_clist_set_sort_column(GTK_CLIST(w), 1);
      }
      /* By path. */
      else if(widget == tb->sort_by_path_micheck)
      {
            tb->list_sort_code = TEXBROWSER_LIST_SORT_PATH;

            w = tb->textures_list;
            if(w != NULL)
                gtk_clist_set_sort_column(GTK_CLIST(w), 2);
      }
      /* By priority. */
      else if(widget == tb->sort_by_priority_micheck)
      {
            tb->list_sort_code = TEXBROWSER_LIST_SORT_PRIORITY;

            w = tb->textures_list;
            if(w != NULL)
                gtk_clist_set_sort_column(GTK_CLIST(w), 3);
        }
#undef UPDATE_CHECK_MENU_ITEM_STATE

        /* Clear and refetch textures listing, this will make it
         * re-sort. 
         */
        TexBrowserListDeleteAll(tb);
        TexBrowserListFetch(tb, tb->editor_ptr);

        /* Update menus. */
        TexBrowserUpdateMenus(tb);

      tb_sorting_list = FALSE;
      reenterant = FALSE;
      return;
}

/*
 *    Select all callback.
 */
void TexBrowserSelectAllCB(GtkWidget *widget, gpointer data)
{
        static gbool reenterant = FALSE;
      GtkWidget *w;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if((tb == NULL) ||
           (widget == NULL)
        )
            return;
        
        if(reenterant)
            return;
        else
            reenterant = TRUE;

      w = tb->textures_list;
      if(w != NULL)
          gtk_clist_select_all(GTK_CLIST(w));

        /* Update menus. */
        TexBrowserUpdateMenus(tb);

      reenterant = FALSE;

      return;
}

/*
 *    Unselect all callback.
 */
void TexBrowserUnselectAllCB(GtkWidget *widget, gpointer data)
{
        static gbool reenterant = FALSE;
      GtkWidget *w;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if((tb == NULL) ||
           (widget == NULL)
        )
            return;

        if(reenterant)
            return;
        else
            reenterant = TRUE;

        w = tb->textures_list;
        if(w != NULL)
            gtk_clist_unselect_all(GTK_CLIST(w));

        /* Update menus. */
        TexBrowserUpdateMenus(tb);

        reenterant = FALSE;

        return;
}

/*
 *    Textures list select callback.
 */
void TexBrowserListSelectCB(
        GtkWidget *widget,
        gint row, gint column,
        GdkEventButton *event,
        gpointer data
)
{
      static gbool reenterant = FALSE;
      ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if((tb == NULL) ||
         (widget == NULL)
      )
            return;

        if(reenterant)
            return;
        else
            reenterant = TRUE;

        /* Scroll to row if not visable. */
        if(gtk_clist_row_is_visible(GTK_CLIST(widget), row) !=
            GTK_VISIBILITY_FULL
        )
            gtk_clist_moveto(
                GTK_CLIST(widget),
                row, 0,       /* Row, column. */
                0.5, 0.0      /* Row, column. */
            );


        /* Select row on textures list selected record. */
        EditorSelect(
            &tb->selected_texture,
            &tb->total_selected_textures,
            row
        );

      /* Exactly one row selected? */
      if(tb->total_selected_textures == 1)
      {
          /* Update preview. */
          TexBrowserPreviewLoad(
            tb,
            tb->selected_texture[0]
          );
          TexBrowserPreviewDraw(tb);
      }
      else
      {
          /* Update preview. */
          TexBrowserPreviewUnload(tb);
          TexBrowserPreviewDraw(tb);
      }

      /* Update menus. */
      TexBrowserUpdateMenus(tb);


      /* Double clicked? */
      if(event != NULL)
      {
          if(event->type == GDK_2BUTTON_PRESS)
          {
            /* Select this texture. */
            TexBrowserSelectTextureCB(tb->texture_select_btn, tb);
          }
      }

      reenterant = FALSE;

      return;
}

/*
 *    Texture list unselect callback.
 */
void TexBrowserListUnselectCB(
        GtkWidget *widget,
        gint row, gint column,
        GdkEventButton *event,
        gpointer data
)
{       
        static gbool reenterant = FALSE;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if((tb == NULL) ||
           (widget == NULL)
      )
            return;

      if(reenterant)
          return;
      else
          reenterant = TRUE;

        /* Unselect row on textures list selected record. */
        EditorUnselect(
            &tb->selected_texture,
            &tb->total_selected_textures,
            row  
        );

        /* Exactly one row selected? */
        if(tb->total_selected_textures == 1)
        {
            /* Update preview. */
            TexBrowserPreviewLoad(
                tb,
                tb->selected_texture[0]
            );
            TexBrowserPreviewDraw(tb);
        }
        else
        {
            /* Update preview. */
            TexBrowserPreviewUnload(tb);
            TexBrowserPreviewDraw(tb);
        }

        /* Update menus. */
        TexBrowserUpdateMenus(tb);

        reenterant = FALSE;

        return;
}

/*
 *    Colum click callback.
 */
void TexBrowserListColumClickCB(
        GtkWidget *widget,
        gint column,
        gpointer data
)
{
      static gbool reenterant = FALSE;
      GtkWidget *w;
      ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if((tb == NULL) ||
           (widget == NULL)
      )
            return;

      if(reenterant)
          return;
      else
          reenterant = TRUE;

      w = tb->textures_list;
      if(w != widget)
      {
          reenterant = FALSE;
          return;
      }

        if(tb_sorting_list)
        {
            reenterant = FALSE;
            return;
        }
        else
        {
            tb_sorting_list = TRUE;
        }

      /* Change sort order code by the colum that was clicked. */
      switch(column)
      {
        /* Index column. */
        case 0:
          tb->list_sort_code = TEXBROWSER_LIST_SORT_ADDED;
          break;

        /* Referance name column. */
        case 1:
          tb->list_sort_code = TEXBROWSER_LIST_SORT_NAME;
          break;

        /* Texture image file path column. */
        case 2:
          tb->list_sort_code = TEXBROWSER_LIST_SORT_PATH;
            break;

          /* Texture priority column. */
          case 3:
            tb->list_sort_code = TEXBROWSER_LIST_SORT_PRIORITY;
            break;
      }

      if(GTK_CLIST(w)->sort_column != column)
          GTK_CLIST(w)->sort_column = column;

      /* Clear and refetch textures listing, this will make it
       * re-sort.
       */
      TexBrowserListDeleteAll(tb);
      TexBrowserListFetch(tb, tb->editor_ptr);

        /* Update menus. */
        TexBrowserUpdateMenus(tb);

      tb_sorting_list = FALSE;
      reenterant = FALSE;
      return;
}

/*
 *    Textures list menu map callback.
 */
gint TexBrowserMenuMapCB(
      GtkWidget *widget, gpointer event, gpointer data
)
{
      GtkWidget *w;
        GdkEventButton *button = NULL;
      ma_texbrowser_preview_struct *pv;
      ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if((tb == NULL) ||
           (event == NULL) ||
           (widget == NULL)
        )
            return(FALSE);

      pv = &tb->preview;


      /* Is it a GDK_BUTTON_PRESS event? */
        if((*(gint *)event) == GDK_BUTTON_PRESS)
            button = (GdkEventButton *)event;

        /* Textures list? */
        if(widget == tb->textures_list)
        {
          w = tb->textures_list_menu;
            if((button != NULL) && (w != NULL))
            {
                if(button->button == 3)
                {
                    gtk_menu_popup(
                        GTK_MENU(w),
                        NULL, NULL, NULL, NULL,
                        button->button, button->time
                    );
                    return(TRUE);
                }
            }
        }
        /* Preview glarea? */
        else if(widget == pv->preview)
        {
            w = pv->preview_menu;
            if((button != NULL) && (w != NULL))
            {
                if(button->button == 3)
                {
                    gtk_menu_popup(
                        GTK_MENU(w),
                        NULL, NULL, NULL, NULL,
                        button->button, button->time
                    );
                    return(TRUE);
                }
            }
        }

      return(FALSE);
}



/*
 *    Close on select check toggle callback.
 */
void TexBrowserCloseOnSelectCheckCB(GtkWidget *widget, gpointer data)
{
      static gbool reenterant = FALSE;
      GtkWidget *w;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if((tb == NULL) ||
           (widget == NULL)
        )
            return;

      if(reenterant)
          return;
      else
          reenterant = TRUE;


      w = tb->close_on_select_check;
      if(w == widget)
      {
          tb->close_on_select = GTK_TOGGLE_BUTTON(w)->active;
      }


      /* Update menus. */
        TexBrowserUpdateMenus(tb);

      reenterant = FALSE;

      return;
}


/*
 *    Add new texture callback.
 */
void TexBrowserAddTextureCB(GtkWidget *widget, gpointer data)
{
      int i, n, append_texture = 0;
      char **strv; int strc;
      int new_tex_num;  /* On editor's textures list. */
      char *strptr, *texture_base_dir = NULL;
      GtkWidget *w, *clist;
      char *new_name = NULL, *new_path = NULL, *new_path_full = NULL;
      double priority = 0.0;
        v3d_texture_ref_struct *t;
        ma_editor_struct *editor;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return;
        
        editor = tb->editor_ptr;
        if(editor == NULL)
            return;

        clist = tb->textures_list;
        if(clist == NULL)
          return;

        TexBrowserSetBusy(tb);


      /* ********************************************************** */
      /* Query user input for new texture information. */
        PDialogDeleteAllPrompts(); 
        PDialogAddPrompt(NULL, "Name:", NULL);
        PDialogAddPromptWithBrowse(
            NULL, "Path:", NULL,
            tb, TexBrowserPromptBrowseCB
        );
        PDialogAddPrompt(NULL, "Priority:", "0.5");
        strv = PDialogGetResponse(
            "Add Texture",
            NULL, NULL,
            PDIALOG_ICON_QUESTION,
            "Add", "Cancel",
            PDIALOG_BTNFLAG_SUBMIT | PDIALOG_BTNFLAG_CANCEL,
            PDIALOG_BTNFLAG_SUBMIT,
            &strc
        );
        if((strv == NULL) || (strc <= 0))
        {
            /* User selected cancel. */
            TexBrowserSetReady(tb);
            return;
        }

        /* Update editor to has changes. */
        if(!editor->has_changes)
            editor->has_changes = TRUE;

        /* Get new values (coppied if possable). */
        strptr = ((strc > 0) ? strv[0] : NULL);
        new_name = ((strptr == NULL) ? NULL : strdup(strptr));

        strptr = ((strc > 1) ? strv[1] : NULL);
        new_path = ((strptr == NULL) ? NULL : strdup(strptr));

      strptr = ((strc > 2) ? strv[2] : NULL);
      if(strptr != NULL)
          priority = atof(strptr);

        /* strc and strv will be considered garbage from this point on. */


/* From here on, call this just before return;. */
#define DO_FREE_RETURNS \
{ \
 /* Free coppied new values. */ \
 free(new_name); new_name = NULL; \
 free(new_path); new_path = NULL; \
 free(new_path_full); new_path_full = NULL; \
}

        /* Check if another texture already has this ref name. */
        if(new_name != NULL)
        {
            for(i = 0; i < editor->total_textures; i++)  
            {
                t = editor->texture[i];
                if(t == NULL)
                    continue;

                if(t->name == NULL)
                    continue;

                /* Same referance name? */
                if(!strcasecmp(t->name, new_name))
                {
                    CDialogGetResponse(
"Referance name conflict!",
"Another texture referance is using the same referance\n\
name. Please try a different name.",
"If another texture referance is using the same name\n\
as this one, then whichever texture referance has\n\
a lower index value will get used and the other will\n\
never get used.",
                        CDIALOG_ICON_ERROR,
                        CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                        CDIALOG_BTNFLAG_OK
                    );
                    DO_FREE_RETURNS
                    TexBrowserSetReady(tb);
                    return;
                }

            }
        }

        /* Get textures base dir. */
        w = tb->base_dir_entry;
        if(w != NULL)
            texture_base_dir = gtk_entry_get_text(GTK_ENTRY(w));

        /* Check if the specified path exists and create new_path_full
         * if possable.
         */
        if((texture_base_dir != NULL) && (new_path != NULL))
        {
            /* Make sure new_path is an absolute path, append base
             * dir as needed.
             */
            if(ISPATHABSOLUTE(new_path))
            {
                new_path_full = strdup(new_path);
            }
            else 
            {
                strptr = PrefixPaths(texture_base_dir, new_path);
                if(strptr != NULL)
                    new_path_full = strdup(strptr);
            }
          /* Allocated full path from above? */
            if(new_path_full != NULL)
            {
                struct stat stat_buf;
                int status = 0;

                if(stat(new_path_full, &stat_buf))
                    status = 1;
                else if(S_ISDIR(stat_buf.st_mode))
                    status = 1;

                if(status)
                {
                    /* Path is invalid, non-existant, or is a dir. */
                    char tmp_path[256];
                    char text[128 + 256]; 
                      
                    /* File does not exist, query to continue add? */
                    strncpy(tmp_path, new_path_full, 256);
                    tmp_path[256 - 1] = '\0';  
                    sprintf(
                       text,
"No such file: %s\n\
Do you still want to accept the specified path?",
                        tmp_path
                    );
                    status = CDialogGetResponse(
"File not found!",
text,
"If you specify a path to a non-existant texture, then\n\
that texture will not be loaded and not displayed when selected.",
                        CDIALOG_ICON_WARNING,
                        CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO |
                  CDIALOG_BTNFLAG_HELP,
                        CDIALOG_BTNFLAG_NO
                    );
                    switch(status)
                    {
                      case CDIALOG_RESPONSE_NO:
                      case CDIALOG_RESPONSE_CANCEL:
                  case CDIALOG_RESPONSE_NOT_AVAILABLE:
                        DO_FREE_RETURNS
                        TexBrowserSetReady(tb);
                        return;
                        break;
                    }
                }
            }
        }

      /* Get current selected texture item on the texture browser
       * list.
       */
      if(tb->total_selected_textures == 1)
          i = tb->selected_texture[0];
      else
          i = -1;

        /* Calculate position to insert new texture on the
         * editor structure.
         */
      if(i >= 0)
      {
            new_tex_num = (int)gtk_clist_get_row_data(
                GTK_CLIST(clist), i
            );
      }
      else
      {
          new_tex_num = -1;   /* Append. */
          append_texture = 1;
      }

      /* Sanitize new_tex_num. */
      if((new_tex_num < 0) || (new_tex_num > editor->total_textures))
          new_tex_num = MAX(editor->total_textures, 0);

      /* Allocate one more pointer for textures on editor structure. */
      editor->total_textures++;
      editor->texture = (v3d_texture_ref_struct **)realloc(
          editor->texture,
          editor->total_textures * sizeof(v3d_texture_ref_struct *)
      );
      if(editor->texture == NULL)
      {
          editor->total_textures = 0;
            TexBrowserSetReady(tb);
          return;
      }
        /* Sanitize new_tex_num (again). */
        if((new_tex_num < 0) || (new_tex_num >= editor->total_textures))
            new_tex_num = MAX(editor->total_textures - 1, 0);

      /* Shift pointers. */
      for(i = editor->total_textures - 1; i > new_tex_num; i--)
          editor->texture[i] = editor->texture[i - 1];      
      /* Set newly allocated pointer to NULL, so when loading new
       * texture, this spot will recieve it.
       */
      editor->texture[new_tex_num] = NULL;

      /* Add new texture. */
        EditorTextureLoad(
            editor,
            new_name,
            new_path,
          priority
        );

      /* Add a new model header item to specify the loading of
       * this texture.
       */
        if((new_name != NULL) && (new_path != NULL))
        {
          int j;
          void *p; int ptype;
          mh_texture_load_struct *mh_texture_load;


          if(append_texture)
          {
            /* Appending texture, seek from last model header item
             * to last texture load model header item if any.
             */
                for(i = editor->total_mh_items - 1; i >= 0; i--)
                {
                    p = editor->mh_item[i];
                    if(p == NULL)
                        continue;
                    else
                        ptype = (*(int *)p);

                    if(ptype == V3DMH_TYPE_TEXTURE_LOAD)
                        break;
            }
            if(i < 0)
            {
                /* No texture loads, seek from last to first
                 * looking for a V3DMH_TYPE_TEXTURE_BASE_DIRECTORY
                 * if any.
                 */
                for(i = editor->total_mh_items - 1; i >= 0; i--)
                {
                  p = editor->mh_item[i];
                        if(p == NULL)
                            continue;
                        else
                            ptype = (*(int *)p);

                        if(ptype == V3DMH_TYPE_TEXTURE_BASE_DIRECTORY)
                            break;
                }
                if(i < 0)
                {
                  i = editor->total_mh_items;
                }
                else
                {
                  i++;
                }
            }
            else
            {
                i++;
            }
          }
          else
          {
            /* Inserting texture, so insert texture load model
             * header item.
             */
              for(i = 0, j = 0; i < editor->total_mh_items; i++)
              {
                p = editor->mh_item[i];
                if(p == NULL)
                    continue;
                else
                    ptype = (*(int *)p);

                if(ptype == V3DMH_TYPE_TEXTURE_LOAD)
                    j++;

                    if((j > new_tex_num) && (j > 0))
                        break;
            }
          }
          if(i < editor->total_mh_items)
          {
            /* Found insert at index. */
            n = i;
          }
          else
          {
            /* Beyond last texture referance, append. */
            n = -1;
          }

          /* Add a new model header item at position n + 1 to specify
           * to load new texture.
           */
          mh_texture_load = V3DMHListInsert(
            &editor->mh_item, &editor->total_mh_items,
            n, V3DMH_TYPE_TEXTURE_LOAD
          );
          if(mh_texture_load != NULL)
          {
            free(mh_texture_load->name);
            mh_texture_load->name = strdup(new_name);

            free(mh_texture_load->path);
            mh_texture_load->path = strdup(new_path);

            mh_texture_load->priority = priority;
          }
      }

      /* Check if editor's model header items specify a texture
       * base directory. If not then insert one just before the
       * first texture load header item.
       */
      if(texture_base_dir != NULL)
      {
            void *p; int ptype;
            mh_texture_base_directory_struct *mh_tbd;


            for(i = 0; i < editor->total_mh_items; i++)
            {
                p = editor->mh_item[i];
                if(p == NULL)
                    continue;
                else
                    ptype = (*(int *)p);

                if(ptype == V3DMH_TYPE_TEXTURE_BASE_DIRECTORY)
                    break;
            }
          if(i >= editor->total_mh_items)
          {
            /* Did not find a texture base directory model header
             * item, seek i to first texture load header item.
             */
            for(i = 0; i < editor->total_mh_items; i++)
                {
                    p = editor->mh_item[i];
                    if(p == NULL)
                        continue;
                    else
                        ptype = (*(int *)p);
                    if(ptype == V3DMH_TYPE_TEXTURE_LOAD)
                        break; 
                }
            if(i < editor->total_mh_items)
            {
                mh_tbd = V3DMHListInsert(
                  &editor->mh_item, &editor->total_mh_items,
                  i, V3DMH_TYPE_TEXTURE_BASE_DIRECTORY
                );
                if(mh_tbd != NULL)
                {
                  free(mh_tbd->path);
                  mh_tbd->path = strdup(texture_base_dir);
                }
            }
          }
      }


        /* Rerealize all primitives on each model on the editor. */
        for(i = 0; i < editor->total_models; i++)
            EditorPrimitiveRealizeAllType(
                editor, i, V3DMP_TYPE_TEXTURE_SELECT, TRUE  
            );

        /* Update values to GUI list, this will update values on
         * the list and resort it.
         */
        TexBrowserListDeleteAll(tb);
        TexBrowserListFetch(tb, tb->editor_ptr);

        /* Update menus. */
        TexBrowserUpdateMenus(tb);

        EditorUpdateMenus(editor);
        EditorUpdateAllViewMenus(editor);

        EditorRedrawAllViews(editor);


        DO_FREE_RETURNS
#undef DO_FREE_RETURNS

        TexBrowserSetReady(tb);

      return;
}

/*
 *    Remove selected texture(s) callback.
 */
void TexBrowserRemoveTextureCB(GtkWidget *widget, gpointer data)
{
      gbool yes_to_all = FALSE;
      gbool need_break, need_continue;
      int i, n, sel_tex, tex_num, textures_deleted;
      void *p; int ptype;
      char *tex_name = NULL;
      GtkWidget *clist;
        ma_editor_struct *editor;
      v3d_texture_ref_struct *t;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return;

        editor = tb->editor_ptr;
        if(editor == NULL)
            return;

      /* No textures selected to be removed? */
      if(tb->total_selected_textures < 1)
      {
            CDialogGetResponse(
"No selected texture",
"No textures selected to be removed.",
"You must select atleast one texture if you want to\n\
remove it.",
                CDIALOG_ICON_ERROR,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            return;
      }

      clist = tb->textures_list;
      if(clist == NULL)
          return;

        TexBrowserSetBusy(tb);


      /* Go through each selected texture on the texture browser. */
      for(i = 0, textures_deleted = 0; i < tb->total_selected_textures; i++)
      {
          /* Get selected texture (relative to texture browser list. */
          sel_tex = tb->selected_texture[i];
          if((sel_tex < 0) || (sel_tex >= editor->total_textures))
            continue;

          /* Get number of actual loaded texture on editor structure. */
          tex_num = (int)gtk_clist_get_row_data(
            GTK_CLIST(clist), sel_tex
            );

          /* Get pointer to actual loaded texture on editor. */
          if((tex_num >= 0) && (tex_num < editor->total_textures))
            t = editor->texture[tex_num];
          else
            t = NULL;
          if(t == NULL)
            continue;


          /* Comferm removeal. */
          need_break = FALSE;
          need_continue = FALSE;
          if(!yes_to_all)
          {
            int len = ((t->name == NULL) ? 0 : strlen(t->name)) + 256;
            char *text;

            /* Allocate message. */
            text = (char *)malloc(len * sizeof(char *));
            if(text != NULL)
            {
                int status;

                if(t->name == NULL)
                  sprintf(text, "Remove unnamed texture #%i?",
                      (int)(tex_num + 1)
                  );
                else
                  sprintf(text, "Remove texture `%s'?",
                      t->name
                  );
                /* Ask comfermation. */
                    status = CDialogGetResponse(
"Comferm texture removal",
text,
"Are you sure you want to remove the specified texture\n\
referance? If you say yes then the referance to the\n\
texture will be removed from the model. Note that removing\n\
a texture referance does not delete the actual texture\n\
file.",
                  CDIALOG_ICON_WARNING,
                  CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_YES_TO_ALL |
                  CDIALOG_BTNFLAG_NO | CDIALOG_BTNFLAG_CANCEL |
                  CDIALOG_BTNFLAG_HELP,
                  CDIALOG_BTNFLAG_NO
                );
                /* Deallocate tempory message. */
                free(text);
                text = NULL;
                /* Handle user response. */
                switch(status)
                {
                  case CDIALOG_RESPONSE_YES_TO_ALL:
                  yes_to_all = TRUE;
                  case CDIALOG_RESPONSE_YES:
                  case CDIALOG_RESPONSE_OK:
                  break;

                  case CDIALOG_RESPONSE_NO:
                  need_continue = TRUE;
                  break;

                  case CDIALOG_RESPONSE_CANCEL:
                  case CDIALOG_RESPONSE_NOT_AVAILABLE:
                  need_break = TRUE;
                  break;
                }
            }
          } /* Comferm removal. */
            /* Got a `cancel' response? */
            if(need_break)
                break;
          /* Got a `no' response? */
          if(need_continue)
            continue;

          /* Mark editor as having changes. */
          if(!editor->has_changes)
            editor->has_changes = TRUE;


          /* Record texture referance name if available. */
          if(t->name != NULL)
            tex_name = strdup(t->name);
          else
            tex_name = NULL;
          if(tex_name == NULL)
            tex_name = strdup("");
          /* Memory allocation error. */
          if(tex_name == NULL)
            break;


          /* Delete actual texture referance on editor and set
           * the pointer index to NULL.
           */
            if(VMA_MAX_3D_VIEWS_PER_EDITOR > 0)
                View3DGLEnableContext(editor->view3d[0]);
          V3DTextureDestroy(editor->texture[tex_num]);
          editor->texture[tex_num] = NULL;
          textures_deleted++;

          /* Do not shift pointers yet, they will be shifted after
           * all textures selected for removeal have been removed.
           */


          /* Remove any corresponding model header items of type
           * V3DMH_TYPE_TEXTURE_LOAD specifying to load this texture
           * from the editor.
           */
          for(n = 0; n < editor->total_mh_items; n++)
          {
            p = editor->mh_item[n];
            if(p == NULL)
                continue;
            else
                ptype = (*(int *)p);

            if(ptype == V3DMH_TYPE_TEXTURE_LOAD)
            {
                mh_texture_load_struct *mh_texture_load = p;

                if(!strcasecmp(mh_texture_load->name, tex_name))
                {
                  V3DMHDestroy(p);
                  editor->mh_item[n] = NULL;

                        /* Do not shift pointers yet, they will be shifted
                   * after all textures selected for removeal have
                   * been removed.
                   */
                }
            }
          }

          /* Deallocate recorded current texture name. */
          free(tex_name);
          tex_name = NULL;

      }     /* Go through each selected texture on the texture browser. */


      /* Reallocate actual loaded textures list on editor. */
      for(i = 0; i < editor->total_textures;)
      {
          if(editor->texture[i] != NULL)
          {
            i++;
            continue;
          }

          /* Shift pointers and reallocate. */
          editor->total_textures--;
            for(n = i; n < editor->total_textures; n++)
                editor->texture[n] = editor->texture[n + 1];

            if(editor->total_textures > 0)
            {
                editor->texture = (v3d_texture_ref_struct **)realloc(
                    editor->texture,
                    editor->total_textures * sizeof(v3d_texture_ref_struct *)
                );
                if(editor->texture == NULL)
                {
                    editor->total_textures = 0;
                }
            }
            else
            {
                free(editor->texture);
                editor->texture = NULL;
                editor->total_textures = 0;
            }   
      }

        /* Reallocate model header items list on editor. */
        for(i = 0; i < editor->total_mh_items;)
        {
            if(editor->mh_item[i] != NULL)
            {
                i++;
                continue;
            }

            /* Shift pointers and reallocate. */
            editor->total_mh_items--;
            for(n = i; n < editor->total_mh_items; n++)
                editor->mh_item[n] = editor->mh_item[n + 1];

            if(editor->total_mh_items > 0)
            {
                editor->mh_item = (void **)realloc(
                    editor->mh_item,
                    editor->total_mh_items * sizeof(void *)
                );
                if(editor->mh_item == NULL)
                {
                    editor->total_mh_items = 0;
                }
            }
            else
            {
                free(editor->mh_item);
                editor->mh_item = NULL;
                editor->total_mh_items = 0;
            }
      }


        /* Rerealize all primitives on each model on the editor. */
        for(i = 0; i < editor->total_models; i++)
            EditorPrimitiveRealizeAllType(
                editor, i, V3DMP_TYPE_TEXTURE_SELECT, TRUE  
            );

        /* Update values to GUI list, this will update values on
         * the list and resort it.
         */
        TexBrowserListDeleteAll(tb);
        TexBrowserListFetch(tb, tb->editor_ptr);


        /* Update menus. */   
        TexBrowserUpdateMenus(tb);

        EditorUpdateMenus(editor);
        EditorUpdateAllViewMenus(editor);
      EditorRedrawAllViews(editor);

        TexBrowserSetReady(tb);

      return;
}

/*
 *    Editor texture referance properties callback.
 */
void TexBrowserEditPropertiesCB(GtkWidget *widget, gpointer data)
{
      GtkWidget *w, *clist;
      gint i, strc;
      gchar **strv, *strptr;
      gchar *texture_base_dir = NULL;
      gchar *cur_name = NULL, *cur_path = NULL;
      gchar *new_name = NULL, *new_path = NULL, *new_path_full = NULL;
      gdouble priority = 0.0;
      gint  selected_tex_item,      /* On GUI list. */
            tex_num;          /* On editor. */
      v3d_texture_ref_struct *tex_ptr, *t;
      ma_editor_struct *editor;
      v3d_model_struct *model;
      gchar numstr[256];
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return;

      editor = tb->editor_ptr;
      if(editor == NULL)
          return;

      /* Is exactly one texture selected. */
      if(tb->total_selected_textures == 1)
      {
          selected_tex_item = tb->selected_texture[0];
      }
      else
      {
          CDialogGetResponse(
"Invalid selection!",
"You must select exactly one texture referance.",
"Exactly one texture referance's properties may be edited at\n\
one time. Select exactly one texture referance to edit.",
            CDIALOG_ICON_ERROR,
            CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
            CDIALOG_BTNFLAG_OK
          );
          return;
      }


      TexBrowserSetBusy(tb);

      /* Get tex_num referance to actual textures list on editor
       * from the selected texture item on the list.
       */
      clist = tb->textures_list;
      if(clist == NULL)
      {
          TexBrowserSetReady(tb);
          return;
      }

      tex_num = (int)gtk_clist_get_row_data(
          GTK_CLIST(clist), selected_tex_item
      );


      /* Get pointer to actual texture on editor (may be NULL). */
      if((tex_num < 0) || (tex_num >= editor->total_textures))
          tex_ptr = NULL;
      else
          tex_ptr = editor->texture[tex_num];

      /* Get textures base dir. */
      w = tb->base_dir_entry;
      if(w != NULL)
          texture_base_dir = gtk_entry_get_text(GTK_ENTRY(w));

      /* Get current name, path, and priority from the actual texture
       * structure.
       */
      if(tex_ptr != NULL)
      {
          cur_name = tex_ptr->name;
          cur_path = tex_ptr->filename;
          priority = tex_ptr->priority;

          /* Strip texture base dir from cur path. */
          if((texture_base_dir != NULL) && (cur_path != NULL))
          {
            /* Is texture base directory a prefix of the cur path? */
#ifdef __MSW__
            if(strcasepfx(cur_path, texture_base_dir))
#else
            if(strpfx(cur_path, texture_base_dir))
#endif
            {
                int len = strlen(texture_base_dir);

                cur_path = cur_path + len;
                while(*cur_path == DIR_DELIMINATOR)
                  cur_path++;
            }
          }
      }


      /* ********************************************************** */
      /* Query new properties from user. */
      PDialogDeleteAllPrompts();
      PDialogAddPrompt(NULL, "Name:", cur_name);
        PDialogAddPromptWithBrowse(
          NULL, "Path:", cur_path,
          tb, TexBrowserPromptBrowseCB
      );
      sprintf(numstr, "%f", priority);
      PDialogAddPrompt(NULL, "Priority:", numstr);
      strv = PDialogGetResponse(
            "Texture Referance Properties",
          NULL, NULL,
            PDIALOG_ICON_QUESTION,
            "OK", "Cancel",
            PDIALOG_BTNFLAG_SUBMIT | PDIALOG_BTNFLAG_CANCEL,
            PDIALOG_BTNFLAG_SUBMIT,
            &strc
      );
      if((strv == NULL) || (strc <= 0))
      {
          /* User selected cancel. */
          TexBrowserSetReady(tb);
          return;
      }

      /* Update editor to has changes. */
      if(!editor->has_changes)
          editor->has_changes = TRUE;


      /* Get new values (coppied if possable). */
      strptr = ((strc > 0) ? strv[0] : NULL);
      new_name = ((strptr == NULL) ? NULL : strdup(strptr));

        strptr = ((strc > 1) ? strv[1] : NULL);
        new_path = ((strptr == NULL) ? NULL : strdup(strptr));

      strptr = ((strc > 2) ? strv[2] : NULL);
      if(strptr != NULL)
          priority = atof(strptr);

      /* strc and strv will be considered garbage from this point on. */


/* From here on, call this just before return;. */
#define DO_FREE_RETURNS \
{ \
 /* Free coppied new values. */ \
 free(new_name); new_name = NULL; \
 free(new_path); new_path = NULL; \
 free(new_path_full); new_path_full = NULL; \
}

      /* Check if another texture already has this ref name. */
      if(new_name != NULL)
      {
          for(i = 0; i < editor->total_textures; i++)
          {
            t = editor->texture[i];
            if(t == NULL)
                continue;

              /* Skip the one we're currently editing. */
              if(i == tex_num)
                continue;

              if(t->name == NULL)
                continue;

            /* Same referance name? */
            if(!strcasecmp(t->name, new_name))
            {
                CDialogGetResponse(
"Referance name conflict!",
"Another texture referance is using the same referance\n\
name. Please try a different name.",
"If another texture referance is using the same name\n\
as this one, then whichever texture referance has\n\
a lower index value will get used and the other will\n\
never get used.",
                        CDIALOG_ICON_ERROR,
                        CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                        CDIALOG_BTNFLAG_OK
                    );
                DO_FREE_RETURNS
                TexBrowserSetReady(tb);
                return;
            }
          }
      }


      /* Check if the specified path exists and create new_path_full
       * if possable.
       */
      if((texture_base_dir != NULL) && (new_path != NULL))
      {
          /* Make sure new_path is an absolute path, append base
           * dir as needed.
           */
          if(ISPATHABSOLUTE(new_path))
          {
            new_path_full = strdup(new_path);
          }
          else
          {
            strptr = PrefixPaths(texture_base_dir, new_path);
            if(strptr != NULL)
                new_path_full = strdup(strptr);
          }
          /* Allocated full path from above? */
          if(new_path_full != NULL)
          {
            struct stat stat_buf;
            int status = 0;

            if(stat(new_path_full, &stat_buf))
                status = 1;
            else if(S_ISDIR(stat_buf.st_mode))
                status = 1;

            if(status)
            {
                /* Path is invalid, non-existant, or is a dir. */
                char tmp_path[256];
                char text[128 + 256];

                /* File does not exist, query to continue change? */
                strncpy(tmp_path, new_path_full, 256);
                tmp_path[256 - 1] = '\0';
                sprintf(
                  text,
"No such file: %s\n\
Do you still want to accept the specified path?",
                  tmp_path
                );
                    status = CDialogGetResponse(
"File not found!",
text,
"If you specify a path to a non-existant texture, then\n\
that texture will not be loaded and not displayed when selected.",
                        CDIALOG_ICON_WARNING,
                        CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO |
                  CDIALOG_BTNFLAG_HELP,
                        CDIALOG_BTNFLAG_NO
                    );
                    switch(status)
                    {
                      case CDIALOG_RESPONSE_NO:
                      case CDIALOG_RESPONSE_CANCEL:
                  case CDIALOG_RESPONSE_NOT_AVAILABLE:
                        DO_FREE_RETURNS
                  TexBrowserSetReady(tb);
                        return;
                        break; 
                    }
            }
          }
      }


      /* Update model header items and texture select primitives
       * on each model.
       */
      if((new_name != NULL) && (new_path != NULL) &&
           (cur_name != NULL)
      )
      {
          void *p; int ptype;
          mh_texture_load_struct *mh_texture_load;

          /* Go through each model header item. */
          for(i = 0; i < editor->total_mh_items; i++)
            {
            p = editor->mh_item[i];
            if(p == NULL)
                continue;
            else
                ptype = (*(int *)p);

            if(ptype == V3DMH_TYPE_TEXTURE_LOAD)
            {
                mh_texture_load = p;

                /* Is this the model header item we are looking for? */
                if(!strcasecmp(mh_texture_load->name, cur_name))
                {
                  /* Update model header item values. */
                    free(mh_texture_load->name);
                    mh_texture_load->name = strdup(new_name);

                        free(mh_texture_load->path);
                        mh_texture_load->path = strdup(new_path);

                  mh_texture_load->priority = priority;
                }
            }
          }

            /* Go through each model and update its primitives. */
            for(i = 0; i < editor->total_models; i++)
            {
                model = editor->model[i];
                if(model == NULL)
                    continue;

            /* Update all texture select primitive name values
             * but not client_data values, it will be updated
             * farther below.
             */
                EditorTextureChangeName(
                editor, model,
                cur_name, new_name
            );
          }
      }


      /* Update actual loaded texture on editor list? */
      if(tex_ptr == NULL)
      {
          /* GUI list item never reffered to a valid texture loaded
           * on the editor structure. So load a new one now.
           */
            EditorTextureLoad(
                editor,
                new_name,
                new_path,
            priority
            );
      }
      else
      {
          /* Replace texture on editor. */

          /* Deallocate existing texture. */
            if(VMA_MAX_3D_VIEWS_PER_EDITOR > 0)
                View3DGLEnableContext(editor->view3d[0]);
          V3DTextureDestroy(tex_ptr);

          /* Set pointer position NULL so EditorTextureLoad() will
           * load it to that position.
           */
          editor->texture[tex_num] = NULL;

          /* Load the texture. */
          EditorTextureLoad(
            editor,
            new_name,
            new_path,
            priority
          );
      }

      /* cur_name and cur_path are invalid now. */


        /* Rerealize all primitives on each model on the editor
       * and update texture select primitives on the primitives
       * list.
       */
        for(i = 0; i < editor->total_models; i++)
      {
          model = editor->model[i];
          if(model == NULL)
            continue;

            EditorPrimitiveRealizeAllType(
            editor, i, V3DMP_TYPE_TEXTURE_SELECT, TRUE
          );

          /* This the selected model on the editor? */
          if(i == EditorSelectedModelIndex(editor))
          {
            gint pn;
            gpointer p;

            /* Update primitives list. */
            for(pn = 0; pn < model->total_primitives; pn++)
            {
                p = model->primitive[pn];
                if(p == NULL)
                  continue;

                if((*(int *)p) == V3DMP_TYPE_TEXTURE_SELECT)
                {
                  EditorListPrimitivesSetTexture(
                      editor->primitives_list, pn,
                      p, TRUE
                  );

                  /* This primitive selected? */
                  if(pn == ((editor->total_selected_primitives == 1) ?
                      editor->selected_primitive[0] : -1
                  ))
                  {
                      EditorListDeleteValuesG(editor);
                      EditorListAddValuesRG(editor, p);
                  }
                }
            }

          }
      }


        /* Update values to GUI list, this will update values on
       * the list and resort it.
       */
        TexBrowserListDeleteAll(tb);
        TexBrowserListFetch(tb, tb->editor_ptr);


        /* Update menus. */
        TexBrowserUpdateMenus(tb);

        EditorUpdateMenus(editor);
        EditorUpdateAllViewMenus(editor);

        EditorRedrawAllViews(editor);

      DO_FREE_RETURNS

#undef DO_FREE_RETURNS

      TexBrowserSetReady(tb);

      return;
}


/*
 *    Applies the selected texture on the texture browser
 *    to the editor if the editor is currently has selected
 *    a texture_select primitive.
 */
void TexBrowserSelectTextureCB(GtkWidget *widget, gpointer data)
{
      gint i;
      gpointer p;
      mp_texture_select_struct *mp_texture_select;
        ma_editor_struct *editor;
        v3d_model_struct *model;
      GtkWidget *w;
      gchar *new_name;
        ma_texture_browser_struct *tb = (ma_texture_browser_struct *)data;
        if(tb == NULL)
            return;

        editor = tb->editor_ptr;  
        if(editor == NULL)
            return;

      /* Get new name from selected textures list item on texture
       * browser.
       */
      w = tb->textures_list;
      if(w == NULL)
          return;

      if(tb->total_selected_textures == 1)
          i = tb->selected_texture[0];
      else
          return;

      if(!gtk_clist_get_text(GTK_CLIST(w), i, 1, &new_name))
          new_name = NULL;
      if(new_name == NULL)
          return;


      /* Get selected model on editor. */
      i = EditorSelectedModelIndex(editor);
      model = V3DModelListGetPtr(
          editor->model, editor->total_models, i
      );
      if(model == NULL)
          return;

      /* Get selected primitive. */
      if(editor->total_selected_primitives == 1)
          i = editor->selected_primitive[0];
      else
          return;

      p = V3DMPListGetPtr(
          model->primitive, model->total_primitives, i
      );
      if(p == NULL)
          return;

      /* Handle by primitive type. */
      switch(*(int *)p)
      {
        case V3DMP_TYPE_TEXTURE_SELECT:
          mp_texture_select = p;

          /* Set new name on primitive. */
          free(mp_texture_select->name);
          mp_texture_select->name = strdup(new_name);

          /* Rerealize primitive, this will update its client_data
           * number to reffer to the new texture on the editor.
           */
          EditorTexturePrimitiveRealize(
            editor, mp_texture_select, TRUE
          );

          /* Check if editor has this primitive selected and if so then
           * we need to update the lists as needed.
           */
          w = editor->primitives_list;
          if(w != NULL)
          {
            EditorListPrimitivesSetTexture(
                w, i,
                p, TRUE
            );
          }
          EditorListDeleteValuesG(editor);
          EditorListAddValuesRG(editor, p);

          /* Reselect first item on editor's values list. */
          w = editor->values_list;
          if(w != NULL)
          {
            gtk_clist_select_row(
                GTK_CLIST(w), 0, 0
            );
          }
          break;
      }
        /* Update editor to has changes. */
        if(!editor->has_changes)
            editor->has_changes = TRUE;

        /* Update menus. */
        TexBrowserUpdateMenus(tb);

        EditorUpdateMenus(editor);
      EditorUpdateAllViewMenus(editor);
      EditorRedrawAllViews(editor);

        /* Close on select? */
        if(tb->close_on_select)
            TexBrowserUnmap(tb);


      return;
}

Generated by  Doxygen 1.6.0   Back to index