/*
Copyright (C) 1999-2007 id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.

This file is part of GtkRadiant.

GtkRadiant is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

GtkRadiant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

//
// Texture Window
//
// Leonardo Zide (leo@lokigames.com)
//

/*!\todo
Clean up texture menu.
- Remove all global variables and use some objects instead.
- Create an interface for a plugin to add texture menu items.
- Make sure the interface is not dependent on gtk.
*/

#ifdef _WIN32
//#include <gdk/win32/gdkwin32.h>
#include <gdk/gdkwin32.h>
#endif
#if defined (__linux__) || defined (__APPLE__)
#include <gdk/gdkx.h>
#include <dirent.h>
#endif
#include <gtk/gtk.h>
#include <assert.h>
#include <sys/stat.h>
#include "stdafx.h"
#include "texwindow.h"
#include "str.h"
#include "missing.h"
#include "texmanip.h"

#define	TYP_MIPTEX	68
static unsigned	tex_palette[256];

#define	FONT_HEIGHT	10

//int		texture_mode = GL_NEAREST;
//int		texture_mode = GL_NEAREST_MIPMAP_NEAREST;
//int		texture_mode = GL_NEAREST_MIPMAP_LINEAR;
//int		texture_mode = GL_LINEAR;
//int		texture_mode = GL_LINEAR_MIPMAP_NEAREST;
int		texture_mode = GL_LINEAR_MIPMAP_LINEAR;

int g_nTextureOffset = 0;

// current active texture directory
//++timo FIXME: I'm not sure this is used anymore
char		texture_directory[128];
// if true, the texture window will only display in-use shaders
// if false, all the shaders in memory are displayed
qboolean g_bShowAllShaders;

bool g_bFilterEnabled = false;
CString g_strFilter;

// texture layout functions
// TTimo: now based on shaders
int     nActiveShadersCount;
int     nCurrentShader;
IShader*  pCurrentShader;
qtexture_t  *current_texture = NULL;
int     current_x, current_y, current_row;

// globals for textures
int     texture_nummenus;
char    texture_menunames[MAX_TEXTUREDIRS][128];

// the list of scripts/*.shader files we need to work with
// those are listed in shaderlist file
// FIXME TTimo I get the feeling that those would need to move to the shaders module
//   for now it's still more simple to just keep it here
GSList *l_shaderfiles = NULL;

void SelectTexture (int mx, int my, bool bShift, bool bFitScale=false);

void  Texture_MouseDown (int x, int y, int buttons);
void  Texture_MouseMoved (int x, int y, int buttons);

CPtrArray g_lstSkinCache;

// TTimo: modifed to add a qtexture_t, Texture_LoadSkin loads using the shader API / QERApp_TryTexture_ForName
// m_strName is a copy of qtex->name
struct SkinInfo
{
  CString m_strName;
  int m_nTextureBind;
  qtexture_t *m_qtex;
  SkinInfo(const char *pName, int n, qtexture_t *qtex)
  {
    m_strName = pName;
    m_nTextureBind = n;
    m_qtex = qtex;
  };
  SkinInfo(){};
};

// =============================================================================
// global functions

// gets active texture extension
//
// FIXME: fix this to be generic from project file
//
int GetTextureExtensionCount()
{
  // hardcoded hack for png support
  if (g_pGameDescription->mGameFile == "sof2.game")
    return 3;
  else
    return 2;
}

const char* GetTextureExtension(int nIndex)
{
  switch(nIndex)
  {
    case 0:
      return "tga";
      break;
    case 1:
      return "jpg";
      break;
    case 2:
      return "png";
      break;
    default:
      return NULL;
  }
}

/*
==============
Texture_InitPalette
==============
*/
void Texture_InitPalette (byte *pal)
{
  int   r,g,b;
  int   i;
  int   inf;
  byte  gammatable[256];
  float gamma;

  gamma = g_qeglobals.d_savedinfo.fGamma;

  if (gamma == 1.0)
  {
    for (i=0 ; i<256 ; i++)
      gammatable[i] = i;
  } else
  {
    for (i=0 ; i<256 ; i++)
    {
      inf = (int)( 255.0f * pow( ( i + 0.5f ) / 255.5f , gamma ) + 0.5f );
      if (inf < 0)
        inf = 0;
      if (inf > 255)
        inf = 255;
      gammatable[i] = inf;
    }
  }

  for (i=0 ; i<256 ; i++)
  {
    r = gammatable[pal[0]];
    g = gammatable[pal[1]];
    b = gammatable[pal[2]];
    pal += 3;

    //v = (r<<24) + (g<<16) + (b<<8) + 255;
    //v = BigLong (v);

    //tex_palette[i] = v;
    tex_palette[i*3+0] = r;
    tex_palette[i*3+1] = g;
    tex_palette[i*3+2] = b;
  }
}

void SetTexParameters (void)
{
  qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texture_mode );

  switch ( texture_mode )
  {
  case GL_NEAREST:
  case GL_NEAREST_MIPMAP_NEAREST:
  case GL_NEAREST_MIPMAP_LINEAR:
    qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
    break;
  case GL_LINEAR:
  case GL_LINEAR_MIPMAP_NEAREST:
  case GL_LINEAR_MIPMAP_LINEAR:
    qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    break;
  }
}

/*
============
Texture_SetMode
============
*/
void Texture_SetMode(int iMenu)
{
  int iMode;
  qboolean texturing = true;
  gpointer item = NULL;

  switch (iMenu)
  {
  case ID_VIEW_NEAREST:
    item = g_object_get_data (G_OBJECT (g_pParentWnd->m_pWidget), "menu_view_nearest");
    iMode = GL_NEAREST;
    break;
  case ID_VIEW_NEARESTMIPMAP:
    item = g_object_get_data (G_OBJECT (g_pParentWnd->m_pWidget), "menu_view_nearestmipmap");
    iMode = GL_NEAREST_MIPMAP_NEAREST;
    break;
  case ID_VIEW_LINEAR:
    item = g_object_get_data (G_OBJECT (g_pParentWnd->m_pWidget), "menu_view_linear");
    iMode = GL_LINEAR;
    break;
  case ID_VIEW_BILINEAR:
    item = g_object_get_data (G_OBJECT (g_pParentWnd->m_pWidget), "menu_view_bilinear");
    iMode = GL_NEAREST_MIPMAP_LINEAR;
    break;
  case ID_VIEW_BILINEARMIPMAP:
    item = g_object_get_data (G_OBJECT (g_pParentWnd->m_pWidget), "menu_view_bilinearmipmap");
    iMode = GL_LINEAR_MIPMAP_NEAREST;
    break;
  case ID_VIEW_TRILINEAR:
    item = g_object_get_data (G_OBJECT (g_pParentWnd->m_pWidget), "menu_view_trilinear");
    iMode = GL_LINEAR_MIPMAP_LINEAR;
    break;
  case ID_TEXTURES_WIREFRAME:
    item = g_object_get_data (G_OBJECT (g_pParentWnd->m_pWidget), "menu_view_wireframe");
    iMode = -1;
    texturing = false;
    break;
  case ID_TEXTURES_FLATSHADE:
    item = g_object_get_data (G_OBJECT (g_pParentWnd->m_pWidget), "menu_view_flatshade");
    iMode = -1;
    texturing = false;
    break;
  default:
    return;
  }

  g_qeglobals.d_savedinfo.iTexMenu = iMenu;
  // NOTE: texture_mode is a GLenum used directly in glTexParameter
  if(iMode!=-1) texture_mode = iMode;

  g_bIgnoreCommands++;
  if (item != NULL)
    gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
  g_bIgnoreCommands--;

  if (texturing)
    SetTexParameters ();

  if ( !texturing && iMenu == ID_TEXTURES_WIREFRAME)
  {
    g_pParentWnd->GetCamWnd()->Camera()->draw_mode = cd_wire;
    Map_BuildBrushData();
    Sys_UpdateWindows (W_ALL);
    return;
  } else if ( !texturing && iMenu == ID_TEXTURES_FLATSHADE)
  {
    g_pParentWnd->GetCamWnd()->Camera()->draw_mode = cd_solid;
    Map_BuildBrushData();
    Sys_UpdateWindows (W_ALL);
    return;
  }

  for (qtexture_t *q = g_qeglobals.d_qtextures; q; q = q->next)
  {
    qglBindTexture (GL_TEXTURE_2D, q->texture_number);
    SetTexParameters ();
  }

  // select the default texture
  qglBindTexture( GL_TEXTURE_2D, 0 );

  qglFinish();

  if (g_pParentWnd->GetCamWnd()->Camera()->draw_mode != cd_texture)
  {
    g_pParentWnd->GetCamWnd()->Camera()->draw_mode = cd_texture;
    Map_BuildBrushData();
  }

  Sys_UpdateWindows (W_ALL);
}

/*!
gamma correction stuff
took out of QERApp_LoadTextureRGBA for clarity
*/
byte g_gammatable[256];
void ResampleGamma(float fGamma)
{
  int i,inf;
  if (fGamma == 1.0)
  {
    for (i = 0; i < 256; i++)
      g_gammatable[i] = i;
  } else
  {
    for (i = 0; i < 256; i++)
    {
      inf = (int)( 255.0f * pow( (i + 0.5f) / 255.5f , fGamma ) + 0.5f );
      if (inf < 0)
        inf = 0;
      if (inf > 255)
        inf = 255;
      g_gammatable[i] = inf;
    }
  }
}

/*!
this function does the actual processing of raw RGBA data into a GL texture
it will also generate the mipmaps
it looks like pPixels nWidth nHeight are the only relevant parameters
*/
qtexture_t *QERApp_LoadTextureRGBA(unsigned char* pPixels, int nWidth, int nHeight)
{
  static float fGamma = -1;
  float total[3];
  byte  *outpixels = NULL;
  int   i, j, resampled, width2, height2, width3, height3;
  int   max_tex_size = 0, mip = 0;
  int   nCount = nWidth * nHeight;

  if (fGamma != g_qeglobals.d_savedinfo.fGamma)
  {
    fGamma = g_qeglobals.d_savedinfo.fGamma;
    ResampleGamma(fGamma);
  }

  qglGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size);
  if (!max_tex_size)
    max_tex_size = 1024;

  qtexture_t *q = (qtexture_t*)g_malloc(sizeof(*q));
  q->width = nWidth;
  q->height = nHeight;

  total[0] = total[1] = total[2] = 0.0f;

  // resample texture gamma according to user settings
  for (i = 0; i < (nCount * 4); i += 4)
  {
    for (j = 0; j < 3; j++)
    {
      total[j] += (pPixels + i)[j];
      byte b = (pPixels + i)[j];
      (pPixels + i)[j] = g_gammatable[b];
    }
  }

  q->color[0] = total[0] / (nCount * 255);
  q->color[1] = total[1] / (nCount * 255);
  q->color[2] = total[2] / (nCount * 255);

  qglGenTextures (1, &q->texture_number);

  qglBindTexture( GL_TEXTURE_2D, q->texture_number );

  SetTexParameters();

  width2 = 1; while (width2 < nWidth) width2 <<= 1;
  height2 = 1; while (height2 < nHeight) height2 <<= 1;

  width3 = width2;
  height3 = height2;
  while (width3 > max_tex_size) width3 >>= 1;
  while (height3 > max_tex_size) height3 >>= 1;
  if (width3 < 1) width3 = 1;
  if (height3 < 1) height3 = 1;

  if (!(width2 == nWidth && height2 == nHeight)) {
    resampled = 1;
    outpixels = (byte *)malloc(width2 * height2 * 4);
    R_ResampleTexture(pPixels, nWidth, nHeight, outpixels, width2, height2, 4);
  } else {
    resampled = 0;
    outpixels = pPixels;
  }

  while (width2 > width3 || height2 > height3)
  {
    GL_MipReduce(outpixels, outpixels, width2, height2, width3, height3);

    if (width2 > width3)
      width2 >>= 1;
    if (height2 > height3)
      height2 >>= 1;
  }

  qglTexImage2D(GL_TEXTURE_2D, mip++, g_qeglobals.texture_components, width2, height2, 0, GL_RGBA, GL_UNSIGNED_BYTE, outpixels);
  while (width2 > 1 || height2 > 1)
  {
    GL_MipReduce(outpixels, outpixels, width2, height2, 1, 1);

    if (width2 > 1)
      width2 >>= 1;
    if (height2 > 1)
      height2 >>= 1;

    qglTexImage2D(GL_TEXTURE_2D, mip++, g_qeglobals.texture_components, width2, height2, 0, GL_RGBA, GL_UNSIGNED_BYTE, outpixels);
  }

  qglBindTexture(GL_TEXTURE_2D, 0);
  if (resampled)
    free(outpixels);

  return q;
}

/*
==================
DumpUnreferencedShaders
usefull function: dumps the list of .shader files that are not referenced to the console
==================
*/
void DumpUnreferencedShaders()
{
  GSList *lst, *sh, *files;
  bool bFound = false;

  files = vfsGetFileList ("scripts", "shader");
  for (lst = files; lst; lst = lst->next)
  {
    bool listed = false;

    for (sh = l_shaderfiles; sh != NULL; sh = g_slist_next (sh))
      if (!strcmp ((char*)sh->data, (char*)lst->data))
      {
        listed = true;
        break;
      }

    if (!listed)
    {
      if (!bFound)
      {
        bFound = true;
        Sys_FPrintf (SYS_WRN, "Following shader files are not referenced in shaderlist.txt:\n");
      }
      Sys_FPrintf (SYS_WRN, "%s\n", (char*)lst->data);
    }
  }

  vfsClearFileDirList (&files);
}

/*
==================
BuildShaderList
build a CStringList of shader names
==================
*/
void BuildShaderList()
{
  int count;
  char filename[1024];
  char *pBuff;
  char dirstring[NAME_MAX];
  int nLen;
  if (l_shaderfiles!=NULL)
  {
    g_slist_free(l_shaderfiles);
    l_shaderfiles = NULL;
  }

  if (g_pGameDescription->mGameFile != "hl.game")
  {
    strcpy(filename, g_pGameDescription->mShaderlist.GetBuffer());
    count = vfsGetFileCount(filename, 0 );
    if (count==0)
    {
      Sys_FPrintf(SYS_ERR, "Couldn't find '%s'\n", g_pGameDescription->mShaderlist.GetBuffer());
      return;
    }
    // NOTE TTimo we use vfsGetFullPath solely to get the full path of the shader list we are gonna load
    //   but we actually send the relative path to vfsLoadFile
    //   so let's hope there is no disparity between the two functions
    if (!vfsGetFullPath(filename, 0, 0))
    {
      Sys_FPrintf(SYS_ERR, "Couldn't find full path for '%s'\n", g_pGameDescription->mShaderlist.GetBuffer());
      return;
    }
    Sys_Printf("Parsing shader files from %s\n", vfsGetFullPath(filename, 0, 0));
    nLen = vfsLoadFile (filename, reinterpret_cast<void**>(&pBuff), 0);
    if (nLen > 0)
    {
      StartTokenParsing(pBuff);
      nLen = 0;
      while (GetToken(true))
      {
        GSList *tmp;
        bool found = false;

        // each token should be a shader filename
        sprintf(dirstring, "%s.shader", token);

        for (tmp = l_shaderfiles; tmp != NULL; tmp = tmp->next)
        {
          if (!strcmp (dirstring, (char*)tmp->data))
          {
            found = true;
            Sys_FPrintf(SYS_WRN, "duplicate entry \"%s\" in shaderlist.txt\n", (char*)tmp->data);
            break;
          }
        }

        if (!found)
        {
          l_shaderfiles = g_slist_append (l_shaderfiles, strdup (dirstring));
          nLen++;
        }
      }
      g_free(pBuff);
    }
  }
}

/*
==================
FillTextureMenu

==================
*/
void ClearGSList (GSList* lst)
{
  GSList *p = lst;
  while (p)
  {
    free (p->data);
    p = g_slist_remove (p, p->data);
  }
}

void FillTextureMenu (GSList** pArray)
{
  GtkWidget *menu, *sep, *item; // point to the Textures GtkMenu and to the last separator
  GList *lst;
  GSList *texdirs = NULL;
  GSList *texdirs_tmp = NULL;
  GSList *p;
  char dirRoot[NAME_MAX];

  // delete everything
  menu = GTK_WIDGET (g_object_get_data (G_OBJECT (g_qeglobals_gui.d_main_window), "menu_textures"));
  sep = GTK_WIDGET (g_object_get_data (G_OBJECT (g_qeglobals_gui.d_main_window), "menu_textures_separator"));
  lst = g_list_find (gtk_container_children (GTK_CONTAINER (menu)), sep);
  while (lst->next)
  {
    // these delete functions are recursive, it's gonna free all submenus
    gtk_widget_destroy (GTK_WIDGET (lst->next->data));
    // lst is no longer relevant, need to get it again
    lst = g_list_find (gtk_container_children (GTK_CONTAINER (menu)), sep);
  }

  texture_nummenus = 0;

  // add everything
  if (!g_qeglobals.d_project_entity)
    return;

  // scan texture dirs and pak files only if not restricting to shaderlist
  if (!g_PrefsDlg.m_bTexturesShaderlistOnly)
  {
    texdirs_tmp = vfsGetDirList ("textures/");
    for (p=texdirs_tmp; p; p=g_slist_next(p))
    {
      // Hydra: erm, this didn't used to do anything except leak memory...
      // For Halflife support this is required to work however.
      // g_slist_append(texdirs, p->data);
      texdirs = g_slist_append(texdirs, strdup((char *)p->data));
    }
    vfsClearFileDirList (&texdirs_tmp);
  }

  // scan the shaders in shaderlist.txt
  BuildShaderList ();
  PreloadShaders ();
  DumpUnreferencedShaders ();
  while (l_shaderfiles != NULL)
  {
    char shaderfile[PATH_MAX];
    gboolean found = FALSE;

    ExtractFileName ((char*)l_shaderfiles->data, shaderfile);
    StripExtension (shaderfile);
    g_strdown (shaderfile);

    for (GSList *tmp = texdirs; tmp; tmp = g_slist_next (tmp))
      if (!strcasecmp ((char*)tmp->data, shaderfile))
      {
	      found = TRUE;
	      break;
      }

    if (!found)
      texdirs = g_slist_prepend (texdirs, strdup (shaderfile));

    free (l_shaderfiles->data);
    l_shaderfiles = g_slist_remove (l_shaderfiles, l_shaderfiles->data);
  }

  // sort the list
  texdirs = g_slist_sort (texdirs, (GCompareFunc)strcmp);

  GSList *temp = texdirs;
  while (temp)
  {
    char* ptr = strchr ((char*)temp->data, '_');

    // do we shrink the menus?
    if (ptr != NULL)
    {
      // extract the root
      strcpy (dirRoot, (char*)temp->data);
      dirRoot[ptr - (char*)temp->data + 1] = 0;

      // we shrink only if we have at least two things to shrink :-)
      if (temp->next && (strstr ((char*)temp->next->data, dirRoot) == (char*)temp->next->data))
      {
	      GtkWidget *pSubMenu = gtk_menu_new ();
        GtkWidget *pSubMenuRef = pSubMenu;
	      // keep going...
	      do
	      {
	        item = gtk_menu_item_new_with_label ((char*)temp->data);
	        gtk_widget_show (item);
          CheckMenuSplitting (pSubMenu);
	        gtk_container_add (GTK_CONTAINER (pSubMenu), item);
	        gtk_signal_connect (GTK_OBJECT (item), "activate", GTK_SIGNAL_FUNC (HandleCommand),
			            GINT_TO_POINTER (CMD_TEXTUREWAD+texture_nummenus));

	        strcpy (texture_menunames[texture_nummenus], (char*)temp->data);
	        strcat (texture_menunames[texture_nummenus], "/");
	        if (pArray)
	          *pArray = g_slist_append (*pArray, strdup ((char*)temp->data));
	        if (++texture_nummenus == MAX_TEXTUREDIRS)
	        {
	          Sys_Printf("WARNING: max texture directories count has been reached!\n");
	          // push submenu and get out
	          item = gtk_menu_item_new_with_label (dirRoot);
	          gtk_widget_show (item);
	          gtk_container_add (GTK_CONTAINER (menu), item);
	          gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), pSubMenu);
	          ClearGSList (texdirs);
	          return;
	        }
	        temp = temp->next;
	      }
        while (temp && (strstr((char*)temp->data, dirRoot)==temp->data));

        ptr = strchr (dirRoot, '_');
        *ptr = 0;
	      item = gtk_menu_item_new_with_label (dirRoot);
	      gtk_widget_show (item);
	      CheckMenuSplitting (menu);
	      gtk_container_add (GTK_CONTAINER (menu), item);
        gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), pSubMenuRef);
	      continue;
      }
    }

    item = gtk_menu_item_new_with_label ((char*)temp->data);
    gtk_widget_show (item);
    CheckMenuSplitting (menu);
    gtk_container_add (GTK_CONTAINER (menu), item);
    gtk_signal_connect (GTK_OBJECT (item), "activate", GTK_SIGNAL_FUNC (HandleCommand),
                        GINT_TO_POINTER (CMD_TEXTUREWAD+texture_nummenus));

    strcpy (texture_menunames[texture_nummenus], (char*)temp->data);
    strcat (texture_menunames[texture_nummenus], "/");
    if (pArray)
      *pArray = g_slist_append (*pArray, strdup ((char*)temp->data));
    if (++texture_nummenus == MAX_TEXTUREDIRS)
    {
      Sys_Printf("WARNING: max texture directories count has been reached!\n");
      ClearGSList (texdirs);
      return;
    }

    temp = temp->next;
  }
  ClearGSList (texdirs);
}

/*
==============
Texture_ShowDirectory
relies on texture_directory global for the directory to use
called by
  void Texture_ShowDirectory (int menunum, bool bLinked)
  void	Texture_ShowDirectory (char* pPath, bool bLinked)
1) Load the shaders for the given directory
2) Scan the remaining texture, load them and assign them a default shader (the "noshader" shader)
NOTE: when writing a texture plugin, or some texture extensions, this function may need to be overriden, and made
  available through the IShaders interface
NOTE: for texture window layout:
  all shaders are stored with alphabetical order after load
  previously loaded and displayed stuff is hidden, only in-use and newly loaded is shown
  ( the GL textures are not flushed though)
==============
*/
void Texture_ShowDirectory ()
{
  char  name[1024];
  char  dirstring[1024];
  CString strTemp;
  int shaders_count = 0;
  int textures_count = 0;
  GSList *files = NULL, *temp;

  g_bScreenUpdates = false;

  // refresh the in-use textures: that will clear the IsDisplayed flag on unused stuff
  // and leave it on in-use so they'll still be displayed
  Texture_ShowInuse();
  // and textures loaded in the following lines will be displayed as well...
  // NOTE: shaders that are not in use but have been loaded previously are still in memory. But they don't get displayed.

  g_qeglobals.d_texturewin.originy = 0;
  // load texture_directory.shader
  // NOTE: because of above call to Texture_ClearInuse, g_ActiveShaders will have the newly loaded shaders only
  // we'll use that later to check if textures have a shader associated or not
  // NOTE: all shaders loaded through QERApp_LoadShadersFromDir will get their InUse flag to True, we'll need a call to Texture_ShowInUse for later cleanup/adjustment
  // NOTE: QERApp_LoadShadersFromDir has two criterions for loading a shader:
  //   the shaderfile is texture_directory (like "museum" will load everything in museum.shader)
  //   the shader name contains texture_directory (like "base_floor" will load museum.shader::base_floor/concfloor_rain)
  shaders_count = QERApp_LoadShadersFromDir(texture_directory);
  // load remaining texture files
  // if a texture is already in use to represent a shader, ignore it

  // need this function "GSList *lst SynapseServer::GetMinorList(char *major_name);"

  sprintf (dirstring, "textures/%s", texture_directory);
  g_ImageManager.BeginExtensionsScan();
  const char* ext;
  while((ext=g_ImageManager.GetNextExtension()) != NULL)
  {
    files = g_slist_concat(files, vfsGetFileList (dirstring, ext));
  }

  for (temp = files; temp; temp = temp->next)
  {
    sprintf(name, "%s%s", texture_directory, (char*)temp->data);

      StripExtension (name);
      strTemp = name;
      strTemp.MakeLower();

    if (strTemp.Find(".specular") >= 0 ||
         strTemp.Find(".glow") >= 0 ||
         strTemp.Find(".bump") >= 0 ||
         strTemp.Find(".diffuse") >= 0 ||
         strTemp.Find(".blend") >= 0 ||
	       strTemp.Find(".alpha") >= 0)
         continue;

    // avoid ever loading a texture name with spaces
    if (strTemp.Find(" ") >= 0)
    {
      Sys_FPrintf(SYS_WRN, "WARNING: Skipping texture name with spaces [%s]\n", strTemp.GetBuffer());
      continue;
    }

    // build a texture name that fits the conventions for qtexture_t::name
    char stdName[1024];
    sprintf( stdName, "textures/%s", name );
    // check if this texture doesn't have a shader
    if (!QERApp_ActiveShader_ForTextureName( stdName ))
    {
      QERApp_CreateShader_ForTextureName (stdName);
      textures_count++;
    }
  }

  Sys_Printf("Loaded %d shaders and created default shader for %d orphan textures.\n",
	     shaders_count, textures_count );

  vfsClearFileDirList (&files);

  // sort for displaying
  QERApp_SortActiveShaders();

  sprintf (name, "Textures: %s", texture_directory);
  gtk_window_set_title (GTK_WINDOW (g_qeglobals_gui.d_entity), name);

  // select the first texture in the list
  if (!g_qeglobals.d_texturewin.texdef.GetName()[0])
    SelectTexture (16, g_qeglobals.d_texturewin.height -16, false);

  g_bScreenUpdates = true;

  Sys_UpdateWindows (W_TEXTURE);
}

/*
==============
Texture_ShowDirectory
1) Load the shaders for the given directory
2) Scan the remaining texture, load them and assign them a default shader (the "noshader" shader)
NOTE: when writing a texture plugin, or some texture extensions, this function may need to be overriden, and made
  available through the IShaders interface
==============
*/
void Texture_ShowDirectory (int menunum)
{
  strcpy (texture_directory, texture_menunames[menunum-CMD_TEXTUREWAD]);
  Texture_ShowDirectory();
}

// scroll origin so the current texture is completely on screen
// if current texture is not displayed, nothing is changed
void Texture_ResetPosition()
{
  qtexture_t  *q;
  int  x,y;

  //this shouldn't ever happen, we startup with notex
  if (!g_qeglobals.d_texturewin.texdef.GetName()[0]) {
    return;
  }

  // otherwise position with current texture shown
  // this used to be in Texture_SetTexture
  Texture_StartPos ();
  while (1)
  {
    // NOTE: return value is == pCurrentShader and pCurrentShader->getTexture == current_texture
    Texture_NextPos (&x, &y);
    q = current_texture;
    // if the current texture never found (because // 'show shaders' is off,
    // for example), do nothing
    if (!q)
      break;

    int nHeight = (int)(q->height * ((float)g_PrefsDlg.m_nTextureScale / 100));
    // we have found when texdef->name and the shader name match
    // NOTE: as everywhere else for our comparisons, we are not case sensitive
    if (!strcmpi( g_qeglobals.d_texturewin.texdef.GetName(), pCurrentShader->getName() ))
    {
      // take care of calls before initialized
      if ( !g_qeglobals.d_texturewin.height) {
        g_qeglobals.d_texturewin.originy = 0;
        break;
      }
      // if the bottom of our selected texture will fit with origin 0, use that
      // to prevent scrolling uglyness (stuff scrolled off screen when
      // everything would fit)
      if ( -(y -nHeight-2*FONT_HEIGHT) <  g_qeglobals.d_texturewin.height) {
        g_qeglobals.d_texturewin.originy = 0;
        break;
      }
      // if current is off the top of the window, move it to the top
      if (y > g_qeglobals.d_texturewin.originy)
      {
        g_qeglobals.d_texturewin.originy = y;
        break;
      }

      // if current is off the bottom, put it on the bottom
      if (y-nHeight-2*FONT_HEIGHT < g_qeglobals.d_texturewin.originy-g_qeglobals.d_texturewin.height)
      {
        g_qeglobals.d_texturewin.originy = y-nHeight-2*FONT_HEIGHT+g_qeglobals.d_texturewin.height;
        break;
      }
      // if we made it here, it should already be in view
      break;
    }
  }
  Sys_UpdateWindows (W_TEXTURE);
}

/*
==============
Texture_ShowAll
will set the IsDisplayed flag on all the active shaders, so we see everything that's currently in memory
==============
*/
void Texture_ShowAll()
{
  char name[1024];

#ifdef _DEBUG
  if (g_bShowAllShaders)
    Sys_Printf("WARNING: already showing all shaders\n");
#endif
  QERApp_ActiveShaders_SetDisplayed(true);
  g_bShowAllShaders = true;
  // put some information in the texture window title?
  sprintf (name, "Textures: in use");
  gtk_window_set_title (GTK_WINDOW (g_qeglobals_gui.d_entity), name);
  Sys_UpdateWindows (W_TEXTURE);
}

/*
==============
Texture_ShowInuse
clear all IsDisplayed flags
scan the map, set IsInUse (will set IsDisplayed on the way)
NOTE: don't sort the textures, don't update the windows (it's used in several contexts, not always necessary to do either)
==============
*/
void WINAPI Texture_ShowInuse (void)
{
  face_t  *f;
  brush_t *b;
  char  name[1024];

  g_qeglobals.d_texturewin.originy = 0;

  // purge
  QERApp_ActiveShaders_SetDisplayed(false);
  // scan and only display in-use stuff
  Sys_Status("Selecting active textures", 0);

  for (b=active_brushes.next ; b != NULL && b != &active_brushes ; b=b->next)
  {
    if (b->patchBrush)
    {
      b->pPatch->pShader->SetInUse(true);
    } else
    {
      for (f=b->brush_faces ; f ; f=f->next)
      {
        f->pShader->SetInUse(true);
      }
    }
  }
  for (b=selected_brushes.next ; b != NULL && b != &selected_brushes ; b=b->next)
  {
    if (b->patchBrush)
    {
      b->pPatch->pShader->SetInUse(true);
    } else
    {
      for (f=b->brush_faces ; f ; f=f->next)
      {
        f->pShader->SetInUse(true);
      }
    }
  }

  // we are no longer showing everything
  g_bShowAllShaders = false;
  // put some information in the texture window title?
  sprintf (name, "Textures: in use");
  gtk_window_set_title (GTK_WINDOW (g_qeglobals_gui.d_entity), name);


  // select the first texture in the list
  if (!g_qeglobals.d_texturewin.texdef.GetName()[0])
  {
    SelectTexture (16, g_qeglobals.d_texturewin.height -16, false);
  }
}

void Texture_ShowStartupShaders()
{
  if (g_PrefsDlg.m_nShader == PrefsDlg::SHADER_COMMON)
  {
    // RIANT
    // HACK FOR JK2 SUPPORT
    if (g_pGameDescription->mGameFile == "jk2.game" || g_pGameDescription->mGameFile == "ja.game")
    {
      strcpy (texture_directory, "system/");
    }
    // RIANT
    // HACK FOR SOF2 SUPPORT
    else if (g_pGameDescription->mGameFile == "sof2.game")
    {
      strcpy (texture_directory, "tools/");
    }
    else strcpy (texture_directory, "common/");
    Texture_ShowDirectory ();
  }

  if (g_PrefsDlg.m_nShader == PrefsDlg::SHADER_ALL) {
    int    count;
    char   filename[1024];
    char   *pBuff;
    char   dirstring[NAME_MAX];
    int    nLen;
    GSList *shaderfiles = NULL;

    strcpy(filename, g_pGameDescription->mShaderlist.GetBuffer());
    count = vfsGetFileCount(filename, 0);
    if (count == 0)
    {
      Sys_FPrintf(SYS_ERR, "Couldn't find '%s'\n", g_pGameDescription->mShaderlist.GetBuffer());
      return;
    }

    if (!vfsGetFullPath(filename, 0, 0))
    {
      Sys_FPrintf(SYS_ERR, "Couldn't find full path for '%s'\n", g_pGameDescription->mShaderlist.GetBuffer());
      return;
    }

    Sys_Printf("Parsing shader files from %s\n", vfsGetFullPath(filename, 0, 0));
    nLen = vfsLoadFile (filename, reinterpret_cast<void**>(&pBuff), 0);
    if (nLen > 0)
    {
      StartTokenParsing(pBuff);
      nLen = 0;
      while (GetToken(true))
      {
        GSList *tmp;
        bool found = false;

        // each token should be a shader filename
        sprintf(dirstring, "%s.shader", token);

        for (tmp = shaderfiles; tmp != NULL; tmp = tmp->next)
        {
          if (!strcmp (dirstring, (char*)tmp->data))
          {
            found = true;
            Sys_FPrintf(SYS_WRN, "duplicate entry \"%s\" in shaderlist.txt\n", (char*)tmp->data);
            break;
          }
        }

        if (!found)
        {
          shaderfiles = g_slist_append (l_shaderfiles, strdup (dirstring));
          strcpy (texture_directory, dirstring);
          Texture_ShowDirectory ();
          nLen++;
        }
      }
      g_free(pBuff);
    }
  }
}

/*
============================================================================

TEXTURE LAYOUT

TTimo: now based on a rundown through all the shaders
nActiveShadersCount: number of shader that have a qtexture_t and may be displayed in the tex window
nCurrentShader: index of active shader that has the current_texture
pCurrentShader: IShader* for current shader
NOTE: we expect the Active shaders count doesn't change during a Texture_StartPos .. Texture_NextPos cycle
  otherwise we may need to rely on a list instead of an array storage
============================================================================
*/

void Texture_StartPos (void)
{
  //++timo TODO: check use of current_texture and current_row?
  current_x = 8;
  current_y = -8;
  current_row = 0;
  nActiveShadersCount = QERApp_GetActiveShaderCount();
  nCurrentShader = -1;
  current_texture = NULL;
  pCurrentShader = NULL;
}

// if texture_showinuse jump over non in-use textures
// it's not very clear what should be done here and what in Texture_Draw .. maybe merging the two would do good
IShader* Texture_NextPos (int *x, int *y)
{
  qtexture_t* q;
  while (1)
  {
    if (nCurrentShader >= nActiveShadersCount - 1)
    {
      // no more shaders
      current_texture = NULL;
      pCurrentShader = NULL;
      return NULL;
    }
    nCurrentShader++;
    pCurrentShader = QERApp_ActiveShader_ForIndex(nCurrentShader);
    if (pCurrentShader == NULL)
    {
      Sys_Printf("ERROR: unexpected pCurrentShader == NULL in Texture_NextPos\n");
      return NULL;
    }
    current_texture = pCurrentShader->getTexture();
    q = current_texture;

    if (!q)
    {
      Sys_Printf("WARNING: found an IShader without qtexture_t in Texture_NextPos\n");
      return NULL;
    }

    /*
    Never show anything other than "textures/" path,
    This is for q1/q2/q3 .map format, which expects "textures/" path on everything we apply
    */
    if(strncmp(pCurrentShader->getName(), "textures/", 9) != 0)
      continue;

    // don't show shaders?
    if (!(g_PrefsDlg.m_bShowShaders || pCurrentShader->IsDefault()))
      continue;

    if (g_PrefsDlg.m_bTextureWindow)
    {
      // some basic filtering
      if (!g_pParentWnd->GetTexWnd()->CheckFilter( pCurrentShader->getName() ))
        continue;
    }

    //++timo FIXME: texture_showinuse is useless? with the menu and reload we just refresh the IsDisplayed flag
    // but the IsInUse is only relevant to draw the green outline
    if (pCurrentShader->IsDisplayed())
      break;

    continue;
  }

  int nWidth = (int)(q->width * ((float)g_PrefsDlg.m_nTextureScale / 100));
  int nHeight = (int)(q->height * ((float)g_PrefsDlg.m_nTextureScale / 100));
  if (current_x + nWidth > g_qeglobals.d_texturewin.width-8 && current_row)
  { // go to the next row unless the texture is the first on the row
    current_x = 8;
    current_y -= current_row + FONT_HEIGHT + 4;
    current_row = 0;
  }

  *x = current_x;
  *y = current_y;

  // Is our texture larger than the row? If so, grow the
  // row height to match it

  if (current_row < nHeight)
    current_row = nHeight;

  // never go less than 64, or the names get all crunched up
  current_x += nWidth < 64 ? 64 : nWidth;
  current_x += 8;

  return pCurrentShader;
}

/*
============================================================================

  MOUSE ACTIONS

============================================================================
*/

static  int textures_cursorx, textures_cursory;

/*
============
Texture_SetTexture

brushprimit_texdef must be understood as a qtexture_t with width=2 height=2 ( the default one )
============
*/

//++timo NOTE: this is a mix of Shader module stuff and texture explorer
// it might need to be split in parts or moved out .. dunno
void WINAPI Texture_SetTexture (texdef_t *texdef, brushprimit_texdef_t *brushprimit_texdef, bool bFitScale, IPluginTexdef *pTexdef, bool bSetSelection )
{
  if (texdef->GetName()[0] == '(')
  {
    Sys_Status("Can't select an entity texture", 0);
    return;
  }
  g_qeglobals.d_texturewin.texdef = *texdef;
  g_qeglobals.d_texturewin.texdef.flags &= ~SURF_KEEP;
  g_qeglobals.d_texturewin.texdef.contents &= ~CONTENTS_KEEP;
  // store the shader pointer
  // NOTE: maybe passing the shader pointer would help?
  g_qeglobals.d_texturewin.pShader->DecRef();
  g_qeglobals.d_texturewin.pShader = QERApp_Shader_ForName(texdef->GetName());
  g_qeglobals.d_texturewin.pShader->IncRef();
  // set this shader as in use
  g_qeglobals.d_texturewin.pShader->SetInUse( true );
  // store the texture coordinates for new brush primitive mode
  // be sure that all the callers are using the default 2x2 texture
  if (g_qeglobals.m_bBrushPrimitMode)
  {
    g_qeglobals.d_texturewin.brushprimit_texdef = *brushprimit_texdef;
  }

  g_dlgFind.updateTextures(texdef->GetName());
  if (!g_dlgFind.isOpen() && bSetSelection)
  {
    Select_SetTexture(texdef,brushprimit_texdef,bFitScale);
  }

  //plugins: send a message telling that the selected texture may have changed
  DispatchRadiantMsg( RADIANT_TEXTURE );

  // scroll origin so the texture is completely on screen
  // takes texdef from g_qeglobals.d_texturewin.texdef, set above
  Texture_ResetPosition();
}

void ViewShader(const char *pFile, const char *pName)
{
  //  ask the vfs to build the full path to the file
  // (i.e. the first one found)
  char *fullName = vfsGetFullPath(pFile,0,0);
  if (fullName == NULL)
  {
    Sys_FPrintf (SYS_ERR, "Couldn't get a full path to the shader file: %s\n", pFile);
    return;
  }

  char* pBuff = NULL;
  int nSize = vfsLoadFullPathFile(fullName, reinterpret_cast<void**>(&pBuff));
  if (nSize <= 0)
  {
    Sys_FPrintf (SYS_ERR, "Failed to load shader file %s\n", fullName);
    return;
  }
  // look for the shader declaration
  int nStart;
  CString strFind = pName;
  CString strLook = pBuff;
  strLook.MakeLower();
  strFind.MakeLower();
  // offset used when jumping over commented out definitions
  int nOffset = 0;
  while (true)
  {
    nStart = strLook.Find(strFind, nOffset);
    if (nStart == -1)
      break;
    // we have found something, maybe it's a commented out shader name?
    char *strCheck = new char[strLook.GetLength()+1];
    strcpy( strCheck, strLook.GetBuffer() );
    strCheck[nStart] = 0;
    char *pCheck = strrchr( strCheck, '\n' );
    // if there's a commentary sign in-between we'll continue
    if (pCheck && strstr( pCheck, "//" ))
    {
      delete[] strCheck;
      nOffset = nStart + 1;
      continue;
    }
    delete[] strCheck;
    nOffset = nStart;
    break;
  }
  // now close the file
  g_free(pBuff);

  DoTextEditor (fullName, nOffset);
}

/*
==============
SelectTexture

  By mouse click
==============
*/
void SelectTexture (int mx, int my, bool bShift, bool bFitScale)
{
  int   x, y;
  qtexture_t  *q;
  texdef_t  tex;
  brushprimit_texdef_t brushprimit_tex;

  my += g_qeglobals.d_texturewin.originy-g_qeglobals.d_texturewin.height;

  Texture_StartPos ();
  while (1)
  {
    // NOTE: return value is == pCurrentShader and pCurrentShader->getTexture == current_texture
    Texture_NextPos (&x, &y);
    q = current_texture;
    if (!q)
      break;
    int nWidth = (int)(q->width * ((float)g_PrefsDlg.m_nTextureScale / 100));
    int nHeight = (int)(q->height * ((float)g_PrefsDlg.m_nTextureScale / 100));
    if (mx > x && mx - x < nWidth
      && my < y && y - my < nHeight + FONT_HEIGHT)
    {
      if (bShift)
      {
        if (pCurrentShader->IsDefault())
          Sys_Printf("ERROR: %s is not a shader, it's a texture.\n", pCurrentShader->getName() );
        else
          ViewShader( pCurrentShader->getShaderFileName(), pCurrentShader->getName() );
      }
      else
      {
        memset (&tex, 0, sizeof(tex));
        memset (&brushprimit_tex, 0, sizeof(brushprimit_tex));
        if (g_qeglobals.m_bBrushPrimitMode)
        {
          // brushprimit fitted to a 2x2 texture
          brushprimit_tex.coords[0][0] = 1.0f;
          brushprimit_tex.coords[1][1] = 1.0f;
        }
        else
        {
          tex.scale[0] = g_PrefsDlg.m_fDefTextureScale;
          tex.scale[1] = g_PrefsDlg.m_fDefTextureScale;
        }
        tex.flags = pCurrentShader->getFlags();
        // TTimo - shader code cleanup
        // texdef.name is the name of the shader, not the name of the actual texture file
        tex.SetName(pCurrentShader->getName());
        // NOTE WARNING: Texture_SetTexture uses Texture_NextPos stuff to move the window position on to the texture
        // if there's some kind of fuckup in Texture_SetTexture you may end up with different pCurrentShader or even pCurrentShader == NULL
        // so we just consider pCurrentShader and current_texture are not valid after this point
        IShader *pAuxShader = pCurrentShader;
        Texture_SetTexture ( &tex, &brushprimit_tex, bFitScale, NULL); // Nurail
        CString strTex;
        CString strName;
        // if shader, print shader name, otherwise texture name
        //++timo FIXME: maybe CShader needs some properties between color / default / actual shader
#ifdef _DEBUG
        // this one is never supposed to be set as current one
        if (pAuxShader->IsColor())
          Sys_Printf("ERROR: unexpected pCurrentShader->IsColor() in SelectTexture\n");
#endif
        // NOTE: IsColor is false, IsDefault the only remaining property
        if (pAuxShader->IsDefault())
        {
          strName = q->name;
          // remove the "textures/" if needed
          if (strName.Find("textures/")!=-1)
            strName = strName.Mid(9);
        }
        else
        {
          strName = pAuxShader->getName();
        }
        strTex.Format("%s W: %i H: %i", strName.GetBuffer(), q->width, q->height);
        g_pParentWnd->SetStatusText(3, strTex);
      }
      return;
    }
  }

  Sys_Status("Did not select a texture", 0);
}

/*
==============
Texture_MouseDown
==============
*/
void Texture_MouseDown (int x, int y, int buttons)
{
  Sys_GetCursorPos (&textures_cursorx, &textures_cursory);

  // lbutton = select texture
  if (buttons == MK_LBUTTON || buttons == (MK_LBUTTON | MK_SHIFT) || buttons == (MK_LBUTTON | MK_CONTROL))
  {
    SelectTexture (x, g_qeglobals.d_texturewin.height - 1 - y, buttons & MK_SHIFT, buttons & MK_CONTROL);
    UpdateSurfaceDialog();
    UpdatePatchInspector();
  }
}

/*
==============
Texture_MouseMoved
==============
*/

void Texture_MouseMoved (int x, int y, int buttons)
{
  int scale = 1;

  if ( buttons & MK_SHIFT )
    scale = 4;

  // rbutton = drag texture origin
  if (buttons & MK_RBUTTON)
  {
    Sys_GetCursorPos (&x, &y);
    if ( y != textures_cursory)
    {
      g_qeglobals.d_texturewin.originy += ( y-textures_cursory) * scale;
      if (g_qeglobals.d_texturewin.originy > 0)
        g_qeglobals.d_texturewin.originy = 0;
      Sys_SetCursorPos (textures_cursorx, textures_cursory);

      // (g_PrefsDlg.m_bTextureScrollbar && g_qeglobals_gui.d_texture_scroll != NULL)
      // fixes broken texture scrolling when scrollbar is disabled
      GtkAdjustment *vadjustment = gtk_range_get_adjustment (GTK_RANGE (g_qeglobals_gui.d_texture_scroll));
      gtk_adjustment_set_value (vadjustment, abs(g_qeglobals.d_texturewin.originy));
      //
    }
    return;
  }
}

/*
============================================================================

DRAWING

============================================================================
*/

int imax(int iFloor, int i) { if (i>iFloor) return iFloor;return i;}

/*
============
Texture_Draw
TTimo: relying on the shaders list to display the textures
we must query all qtexture_t* to manage and display through the IShaders interface
this allows a plugin to completely override the texture system
============
*/
void Texture_Draw (int width, int height)
{
  int x, y, last_y = 0, last_height = 0, nWidth, nHeight;
  qtexture_t *q;
  char *name;

  qglClearColor (g_qeglobals.d_savedinfo.colors[COLOR_TEXTUREBACK][0],
                 g_qeglobals.d_savedinfo.colors[COLOR_TEXTUREBACK][1],
                 g_qeglobals.d_savedinfo.colors[COLOR_TEXTUREBACK][2], 0);
  qglViewport (0,0,width,height);
  qglMatrixMode(GL_PROJECTION);
  qglLoadIdentity ();

  qglClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  qglDisable (GL_DEPTH_TEST);
  qglDisable(GL_BLEND);
  qglOrtho (0, width, g_qeglobals.d_texturewin.originy-height, g_qeglobals.d_texturewin.originy, -100, 100);
  qglEnable (GL_TEXTURE_2D);

  qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
  g_qeglobals.d_texturewin.width = width;
  g_qeglobals.d_texturewin.height = height;

  Texture_StartPos();
  for (;;)
  {
    // NOTE: return value is == pCurrentShader and pCurrentShader->getTexture == current_texture
    Texture_NextPos (&x, &y);
    q = current_texture;
    if (!q)
      break;

    nWidth = (int)(q->width * ((float)g_PrefsDlg.m_nTextureScale / 100));
    nHeight = (int)(q->height * ((float)g_PrefsDlg.m_nTextureScale / 100));

    if (y != last_y)
    {
      last_y = y;
      last_height = 0;
    }
    last_height = MAX (nHeight, last_height);

    // Is this texture visible?
    if ((y-nHeight-FONT_HEIGHT < g_qeglobals.d_texturewin.originy)
        && (y > g_qeglobals.d_texturewin.originy - height))
    {
      // borders rules:
      // if it's the current texture, draw a thick red line, else:
      // shaders have a white border, simple textures don't
      // if !texture_showinuse: (some textures displayed may not be in use)
      // draw an additional square around with 0.5 1 0.5 color
      if (!strcmpi(g_qeglobals.d_texturewin.texdef.GetName(), pCurrentShader->getName()))
      {
	      qglLineWidth (3);
	      qglColor3f (1,0,0);
	      qglDisable (GL_TEXTURE_2D);

	      qglBegin (GL_LINE_LOOP);
	      qglVertex2f (x-4,y-FONT_HEIGHT+4);
	      qglVertex2f (x-4,y-FONT_HEIGHT-nHeight-4);
	      qglVertex2f (x+4+nWidth,y-FONT_HEIGHT-nHeight-4);
	      qglVertex2f (x+4+nWidth,y-FONT_HEIGHT+4);
	      qglEnd ();

	      qglEnable (GL_TEXTURE_2D);
	      qglLineWidth (1);
      }
      else
      {
	      qglLineWidth (1);
	      // shader border:
	      if (!pCurrentShader->IsDefault())
	      {
	        qglColor3f (1,1,1);
	        qglDisable (GL_TEXTURE_2D);

	        qglBegin (GL_LINE_LOOP);
	        qglVertex2f (x-1,y+1-FONT_HEIGHT);
	        qglVertex2f (x-1,y-nHeight-1-FONT_HEIGHT);
	        qglVertex2f (x+1+nWidth,y-nHeight-1-FONT_HEIGHT);
	        qglVertex2f (x+1+nWidth,y+1-FONT_HEIGHT);
	        qglEnd ();
	        qglEnable (GL_TEXTURE_2D);
	      }

	      // highlight in-use textures
	      if (pCurrentShader->IsInUse())
	      {
	        qglColor3f (0.5,1,0.5);
	        qglDisable (GL_TEXTURE_2D);
	        qglBegin (GL_LINE_LOOP);
	        qglVertex2f (x-3,y+3-FONT_HEIGHT);
	        qglVertex2f (x-3,y-nHeight-3-FONT_HEIGHT);
	        qglVertex2f (x+3+nWidth,y-nHeight-3-FONT_HEIGHT);
	        qglVertex2f (x+3+nWidth,y+3-FONT_HEIGHT);
	        qglEnd ();
	        qglEnable (GL_TEXTURE_2D);
	      }
      }

      // Draw the texture
      qglBindTexture (GL_TEXTURE_2D, q->texture_number);
      QE_CheckOpenGLForErrors();
      qglColor3f (1,1,1);
      qglBegin (GL_QUADS);
      qglTexCoord2f (0,0);
      qglVertex2f (x,y-FONT_HEIGHT);
      qglTexCoord2f (1,0);
      qglVertex2f (x+nWidth,y-FONT_HEIGHT);
      qglTexCoord2f (1,1);
      qglVertex2f (x+nWidth,y-FONT_HEIGHT-nHeight);
      qglTexCoord2f (0,1);
      qglVertex2f (x,y-FONT_HEIGHT-nHeight);
      qglEnd ();

      // draw the texture name
      qglDisable (GL_TEXTURE_2D);
      qglColor3f (1,1,1);

      qglRasterPos2f (x, y-FONT_HEIGHT+2);

      // don't draw the directory name
      name = (char*)pCurrentShader->getName();
      name += strlen(name);
      while(name != (char*)pCurrentShader->getName() && *(name-1) != '/' && *(name-1) != '\\')
        name--;

      gtk_glwidget_print_string(name);
      qglEnable (GL_TEXTURE_2D);
    }
  }

  g_qeglobals.d_texturewin.m_nTotalHeight = abs(y) + last_height + FONT_HEIGHT + 4;

  // reset the current texture
  qglBindTexture(GL_TEXTURE_2D, 0);
  qglFinish();
}

//++timo seems we only know hard inits now..
//void Texture_Init (bool bHardInit)
void Texture_Init()
{
  g_qeglobals.d_qtextures = NULL;
  // initialize the qtexture map
  if (g_qeglobals.d_qtexmap)
  {
    Sys_FPrintf(SYS_ERR, "TODO: delete g_qeglobals.d_qtexmap in Texture_Init\n");
  }
  g_qeglobals.d_qtexmap = g_hash_table_new (g_str_hash, g_str_equal);
  // initialize .. in some cases if no default texture / project loaded it crashes
  memset( &g_qeglobals.d_texturewin.texdef, 0, sizeof(g_qeglobals.d_texturewin.texdef) );
  g_qeglobals.d_texturewin.texdef.SetName(SHADER_NOT_FOUND);
  g_qeglobals.d_texturewin.pShader = QERApp_Shader_ForName(SHADER_NOT_FOUND);
}

// FIXME TTimo this needs to move to the shader module along with l_shaderlist move
// preload shader files that have been listed in shaderlist.txt
void PreloadShaders()
{
  GSList *lst = l_shaderfiles;
  Str shadername;
  while (lst)
  {
    shadername = g_pGameDescription->mShaderPath;
    shadername += (char*)lst->data;
    QERApp_LoadShaderFile(shadername.GetBuffer());
    lst = lst->next;
  }
}

// TTimo: modified to expect the reletive path to the skin as input
// will look into pak files if necessary
// uses the shader code to load the texture Try_Texture_ForName
// modified SkinInfo accordingly to store the qtexture_t and shader name (reletive version)
// the .md3 have bundled filetype extension, but they don't fit with the actual data
//   ex: models/mapobjects/gargoyle.tga doesn't exist, but models/mapobjects/gargoyle.jpg can be used instead
//   so we remove the extension before load attempt
int WINAPI Texture_LoadSkin(char *pName, int *pnWidth, int *pnHeight)
{
  //  byte *pic = NULL;
  //  byte *pic32 = NULL;
  int nTex = -1;
  qtexture_t *qtex;
  SkinInfo *pInfo;
  const char *pCleanName;

  int nSize = g_lstSkinCache.GetSize();
  pCleanName = QERApp_CleanTextureName( pName, false );
  for (int i = 0; i < nSize; i++)
  {
    SkinInfo *pInfo = reinterpret_cast<SkinInfo*>(g_lstSkinCache.GetAt(i));
    if (pInfo)
    {
      if (stricmp(pCleanName, pInfo->m_strName) == 0)
      {
        return pInfo->m_nTextureBind;
      }
    }
  }

  // if the load is successfull, we get back a qtexture_t
  // we don't need to free it, it's in g_qeglobals.d_qtextures
  // NOTE: we need to free the SkinInfo though..
  qtex = QERApp_Try_Texture_ForName( pCleanName );
  if (qtex)
  {
    nTex = qtex->texture_number;
    pInfo = new SkinInfo(qtex->name, nTex, qtex);
  } else
  {
    pInfo = new SkinInfo(pCleanName, -1, NULL);
  }
  g_lstSkinCache.Add(pInfo);

  return nTex;
}

bool TexWnd::CheckFilter( const char* name )
{
  const char* buf = gtk_entry_get_text (GTK_ENTRY (m_pFilter));
  if (strstr( name, buf ) != 0)
    return true;
  return false;
}

// =============================================================================
// static functions

static void vertical_scroll (GtkWidget *widget, gpointer data)
{
  ((TexWnd*)data)->OnVScroll ();
}

static void filter_changed (GtkWidget *widget, gpointer data)
{
  CString str;
  str = gtk_entry_get_text (GTK_ENTRY (widget));
  ((TexWnd*)data)->UpdateFilter (str);
}

// =============================================================================
// TexWnd class

TexWnd::TexWnd()
: GLWindow (FALSE)
{
  m_pFilter = NULL;
  m_bNeedRange = true;
}

TexWnd::~TexWnd()
{
}

void TexWnd::OnCreate ()
{
  if (!MakeCurrent ())
    Error ("glMakeCurrent in TexWnd::OnCreate failed");

  g_qeglobals_gui.d_texture = m_pWidget;
  g_nTextureOffset = 0;

  GtkAdjustment *vadjustment = gtk_range_get_adjustment (GTK_RANGE (g_qeglobals_gui.d_texture_scroll));
  gtk_signal_connect (GTK_OBJECT (vadjustment), "value_changed", GTK_SIGNAL_FUNC (vertical_scroll), this);

  if (g_PrefsDlg.m_bTextureScrollbar)
    gtk_widget_show (g_qeglobals_gui.d_texture_scroll);
  else
    gtk_widget_hide (g_qeglobals_gui.d_texture_scroll);
  m_bNeedRange = true;

  gtk_signal_connect (GTK_OBJECT (m_pFilter), "changed", GTK_SIGNAL_FUNC (filter_changed), this);
  if (g_PrefsDlg.m_bTextureWindow)
    gtk_widget_show (m_pFilter);
}

void TexWnd::UpdateFilter(const char* pFilter)
{
  g_bFilterEnabled = false;
  if (pFilter)
  {
    g_strFilter = pFilter;
    if (g_strFilter.GetLength() > 0)
      g_bFilterEnabled = true;
    QERApp_SortActiveShaders();
  }
  Sys_UpdateWindows (W_TEXTURE);
}

void TexWnd::OnSize (int cx, int cy)
{
  m_bNeedRange = true;
}

void TexWnd::OnExpose ()
{
  int nOld = g_qeglobals.d_texturewin.m_nTotalHeight;
  if (!MakeCurrent ())
  {
    Sys_Printf("ERROR: glXMakeCurrent failed..\n ");
    Sys_Printf("Please restart Radiant if the Texture view is not working\n");
  } else
  {
    QE_CheckOpenGLForErrors();
    Texture_Draw (m_pWidget->allocation.width, m_pWidget->allocation.height - g_nTextureOffset);
    QE_CheckOpenGLForErrors();
    SwapBuffers ();
  }
  if (g_PrefsDlg.m_bTextureScrollbar && (m_bNeedRange || g_qeglobals.d_texturewin.m_nTotalHeight != nOld))
  {
    GtkAdjustment *vadjustment = gtk_range_get_adjustment (GTK_RANGE (g_qeglobals_gui.d_texture_scroll));

    vadjustment->value = -g_qeglobals.d_texturewin.originy;
    vadjustment->page_size = m_pWidget->allocation.height;
    vadjustment->page_increment = m_pWidget->allocation.height/2;
    vadjustment->step_increment = 20;
    vadjustment->lower = 0;
    vadjustment->upper = g_qeglobals.d_texturewin.m_nTotalHeight;

    gtk_signal_emit_by_name (GTK_OBJECT (vadjustment), "changed");

    m_bNeedRange = false;
  }
}

void TexWnd::OnLButtonDown (guint32 flags, int pointx, int pointy)
{
  SetCapture ();
  Texture_MouseDown (pointx, pointy - g_nTextureOffset, flags);
}

void TexWnd::OnRButtonDown (guint32 flags, int pointx, int pointy)
{
  SetCapture ();
  Texture_MouseDown (pointx, pointy - g_nTextureOffset, flags);
}

void TexWnd::OnMButtonDown (guint32 flags, int pointx, int pointy)
{
  SetCapture ();
  Texture_MouseDown (pointx, pointy - g_nTextureOffset, flags);
}

void TexWnd::OnLButtonUp (guint32 flags, int pointx, int pointy)
{
  ReleaseCapture ();
  DragDropTexture (flags, pointx, pointy);
}

void TexWnd::OnRButtonUp (guint32 flags, int pointx, int pointy)
{
  ReleaseCapture ();
}

void TexWnd::OnMButtonUp (guint32 flags, int pointx, int pointy)
{
  ReleaseCapture ();
}

void TexWnd::OnMouseMove (guint32 flags, int pointx, int pointy)
{
  Texture_MouseMoved (pointx, pointy - g_nTextureOffset, flags);
  // if scrollbar is hidden, we don't seem to get an update
  if( !g_PrefsDlg.m_bTextureScrollbar )
    RedrawWindow ();
}

void TexWnd::OnVScroll ()
{
  GtkAdjustment *vadjustment = gtk_range_get_adjustment (GTK_RANGE (g_qeglobals_gui.d_texture_scroll));

  g_qeglobals.d_texturewin.originy = - (int)vadjustment->value;
  RedrawWindow ();
}

void TexWnd::UpdatePrefs()
{
  if (g_PrefsDlg.m_bTextureWindow)
    gtk_widget_show (m_pFilter);
  else
    gtk_widget_hide (m_pFilter);

  if (g_PrefsDlg.m_bTextureScrollbar)
    gtk_widget_show (g_qeglobals_gui.d_texture_scroll);
  else
    gtk_widget_hide (g_qeglobals_gui.d_texture_scroll);
  m_bNeedRange = true;
  RedrawWindow ();
}

void TexWnd::FocusEdit()
{
  if (GTK_WIDGET_VISIBLE (m_pFilter))
    gtk_window_set_focus (GTK_WINDOW (g_pParentWnd->m_pWidget), m_pFilter);
}

void TexWnd::OnMouseWheel(bool bUp)
{
  if (bUp)
  {
    if(g_qeglobals.d_texturewin.originy < 0) {
      g_qeglobals.d_texturewin.originy += g_PrefsDlg.m_nWheelInc;
      // clamp so we don't get jiggle if moved by less than scrollwheel increment
      if(g_qeglobals.d_texturewin.originy > 0) {
        g_qeglobals.d_texturewin.originy = 0;
      }
    }
  }
  else
  {
    if(g_qeglobals.d_texturewin.originy > (-g_qeglobals.d_texturewin.m_nTotalHeight + g_qeglobals.d_texturewin.height))
      g_qeglobals.d_texturewin.originy -= g_PrefsDlg.m_nWheelInc;
  }
  GtkAdjustment *vadjustment = gtk_range_get_adjustment (GTK_RANGE (g_qeglobals_gui.d_texture_scroll));
  gtk_adjustment_set_value (vadjustment, abs(g_qeglobals.d_texturewin.originy));

  RedrawWindow();
}

void TexWnd::DragDropTexture (guint32 flags, int pointx, int pointy)
{
  // This gets called from leftmouse up event. We see if the mouseup is above
  // the camwindow. If this is the case do a trace for a surface. If we hit a
  // surface, texture it with the current texture.

  int     m_ptXcheck, m_ptYcheck;
  int     m_ptX, m_ptY;
  GtkWidget *widget;
  gint x, y;
  vec3_t  dir;
  float	  f, r, u;
  int     i;

  // we only want to catch a plain mouseevent
  if (flags)
    return;

  // see if we are above the camwindow
  Sys_GetCursorPos (&m_ptX, &m_ptY);

  if (g_pParentWnd->CurrentStyle() == MainFrame::eFloating)
    widget = g_pParentWnd->GetCamWnd()->m_pParent;
  else
    widget = g_pParentWnd->GetCamWnd()->GetWidget();

  get_window_pos (widget, &x, &y);

  if (m_ptX < x || m_ptY < y ||
      m_ptX > x + widget->allocation.width ||
      m_ptY > y + widget->allocation.height)
    return;

  // check if the camwindow isn't being partially hidden by another window at this point
  m_ptXcheck = m_ptX;
  m_ptYcheck = m_ptY;

  if( g_pParentWnd->GetCamWnd()->GetWidget()->window != gdk_window_at_pointer( &m_ptXcheck, &m_ptYcheck ) )
    return;

  // calc ray direction
  x = m_ptX - x;
  y = g_pParentWnd->GetCamWnd()->Camera()->height - 1 - (m_ptY - y);
  u = (float)( y - ( g_pParentWnd->GetCamWnd()->Camera()->height * .5f ) ) / ( g_pParentWnd->GetCamWnd()->Camera()->height * .5f );
  r = (float)( x - ( g_pParentWnd->GetCamWnd()->Camera()->width * .5f ) ) / ( g_pParentWnd->GetCamWnd()->Camera()->width * .5f );
  f = 1;

  for (i=0 ; i<3 ; i++)
    dir[i] = g_pParentWnd->GetCamWnd()->Camera()->vpn[i] * f +
        g_pParentWnd->GetCamWnd()->Camera()->vright[i] * r +
        g_pParentWnd->GetCamWnd()->Camera()->vup[i] * u;
  VectorNormalize (dir, dir);

  // do a trace for a surface
	trace_t	t;

	t = Test_Ray (g_pParentWnd->GetCamWnd()->Camera()->origin, dir, SF_SINGLEFACE);

  if (t.brush)
  {
    texdef_t  tex;
    brushprimit_texdef_t brushprimit_tex;

    memset (&tex, 0, sizeof(tex));
    memset (&brushprimit_tex, 0, sizeof(brushprimit_tex));
    if (g_qeglobals.m_bBrushPrimitMode)
    {
      // brushprimit fitted to a 2x2 texture
      brushprimit_tex.coords[0][0] = 1.0f;
      brushprimit_tex.coords[1][1] = 1.0f;
    } else
    {
      tex.scale[0] = g_PrefsDlg.m_fDefTextureScale;
      tex.scale[1] = g_PrefsDlg.m_fDefTextureScale;
    }
    tex.flags = g_qeglobals.d_texturewin.texdef.flags;
    tex.value = g_qeglobals.d_texturewin.texdef.value;
    tex.contents = g_qeglobals.d_texturewin.texdef.contents;
    // TTimo - shader code cleanup
    // texdef.name is the name of the shader, not the name of the actual texture file
    tex.SetName(g_qeglobals.d_texturewin.texdef.GetName());

    Undo_Start("set face textures");
	  Undo_AddBrush(t.brush);
	  SetFaceTexdef (t.face, &tex, &brushprimit_tex, false, NULL );
	  Brush_Build(t.brush, false);
	  Undo_EndBrush(t.brush);
	  Undo_End();

    Sys_UpdateWindows (W_CAMERA);
    g_pParentWnd->OnTimer ();
  }
}
