]> git.xonotic.org Git - xonotic/netradiant.git/commitdiff
Merge commit '3a78d902017a780e65f21f12c709aa746dfcab84' into garux-merge
authorThomas Debesse <dev@illwieckz.net>
Tue, 26 May 2020 14:00:22 +0000 (16:00 +0200)
committerThomas Debesse <dev@illwieckz.net>
Tue, 26 May 2020 14:00:22 +0000 (16:00 +0200)
15 files changed:
1  2 
libs/gtkutil/xorrectangle.cpp
libs/gtkutil/xorrectangle.h.orig
radiant/autosave.cpp
radiant/brush.h
radiant/brushmanip.cpp
radiant/brushmanip.cpp.orig
radiant/camwindow.cpp
radiant/camwindow.cpp.orig
radiant/select.cpp
radiant/select.cpp.orig
radiant/selection.cpp
radiant/selection.cpp.orig
radiant/texwindow.cpp
radiant/texwindow.cpp.orig
radiant/xywindow.cpp

index b9b15faf8c688afdec684a66a73e32152594f3fa,e4f46c6b8b171a838841c651b4ce216b125e89b9..13b9ba2bc8f826d1c508dbef7aa66af4d0c5e549
 -/*
 -   Copyright (C) 2001-2006, William Joseph.
 -   All Rights Reserved.
 +#include "xorrectangle.h"
  
 -   This file is part of GtkRadiant.
 +#include <gtk/gtk.h>
  
 -   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.
 +#include "gtkutil/glwidget.h"
 +#include "igl.h"
  
 -   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.
 +#include <gtk/gtkglwidget.h>
  
 -   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
 - */
 +//#include "stream/stringstream.h"
  
 -#include "xorrectangle.h"
 +bool XORRectangle::initialised() const
 +{
 +    return !!cr;
 +}
 +
 +void XORRectangle::lazy_init()
 +{
 +    if (!initialised()) {
 +        cr = gdk_cairo_create(gtk_widget_get_window(m_widget));
 +    }
 +}
 +
 +void XORRectangle::draw() const
 +{
 +    const int x = float_to_integer(m_rectangle.x);
 +    const int y = float_to_integer(m_rectangle.y);
 +    const int w = float_to_integer(m_rectangle.w);
 +    const int h = float_to_integer(m_rectangle.h);
 +    GtkAllocation allocation;
 +    gtk_widget_get_allocation(m_widget, &allocation);
 +    cairo_rectangle(cr, x, -(h) - (y - allocation.height), w, h);
 +    cairo_set_source_rgb(cr, 1, 1, 1);
 +    cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);
 +    cairo_stroke(cr);
 +}
 +
 +XORRectangle::XORRectangle(ui::GLArea widget) : m_widget(widget), cr(0)
 +{
 +}
 +
 +XORRectangle::~XORRectangle()
 +{
 +    if (initialised()) {
 +        cairo_destroy(cr);
 +    }
 +}
 +
 +void XORRectangle::set(rectangle_t rectangle)
 +{
 +    if (gtk_widget_get_realized(m_widget)) {
 +              if( m_rectangle.w != rectangle.w || m_rectangle.h != rectangle.h ){
 +              //if( !(m_rectangle.w == 0 && m_rectangle.h == 0 && rectangle.w == 0 && rectangle.h == 0) ){
 +              //globalOutputStream() << "m_x" << m_rectangle.x << " m_y" << m_rectangle.y << " m_w" << m_rectangle.w << " m_h" << m_rectangle.h << "\n";
 +              //globalOutputStream() << "__x" << rectangle.x << " __y" << rectangle.y << " __w" << rectangle.w << " __h" << rectangle.h << "\n";
 +                      if ( glwidget_make_current( m_widget ) != FALSE ) {
 +                              GlobalOpenGL_debugAssertNoErrors();
 +
 +                              gint width, height;
 +                              gdk_gl_drawable_get_size( gtk_widget_get_gl_drawable( m_widget ), &width, &height );
 +
 +                              glViewport( 0, 0, width, height );
 +                              glMatrixMode( GL_PROJECTION );
 +                              glLoadIdentity();
 +                              glOrtho( 0, width, 0, height, -100, 100 );
 +
 +                              glMatrixMode( GL_MODELVIEW );
 +                              glLoadIdentity();
 +
 +                              glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
 +                              glDisable( GL_DEPTH_TEST );
 +
 +                              glDrawBuffer( GL_FRONT );
 +
 +                              glEnable( GL_BLEND );
 +                              glBlendFunc( GL_ONE_MINUS_DST_COLOR, GL_ZERO );
 +
 +                              glLineWidth( 2 );
 +                              glColor3f( 1, 1, 1 );
 +                              glDisable( GL_TEXTURE_2D );
 +                              glBegin( GL_LINE_LOOP );
 +                              glVertex2f( m_rectangle.x, m_rectangle.y + m_rectangle.h );
 +                              glVertex2f( m_rectangle.x + m_rectangle.w, m_rectangle.y + m_rectangle.h );
 +                              glVertex2f( m_rectangle.x + m_rectangle.w, m_rectangle.y );
 +                              glVertex2f( m_rectangle.x, m_rectangle.y );
 +                              glEnd();
 +
 +                              glBegin( GL_LINE_LOOP );
 +                              glVertex2f( rectangle.x, rectangle.y + rectangle.h );
 +                              glVertex2f( rectangle.x + rectangle.w, rectangle.y + rectangle.h );
 +                              glVertex2f( rectangle.x + rectangle.w, rectangle.y );
 +                              glVertex2f( rectangle.x, rectangle.y );
 +                              glEnd();
 +
++                              glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
 +                              glDrawBuffer( GL_BACK );
 +                              GlobalOpenGL_debugAssertNoErrors();
 +                              //glwidget_swap_buffers( m_widget );
 +                              glwidget_make_current( m_widget );
 +                      }
 +              }
 +              m_rectangle = rectangle;
 +    }
 +}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e1043d76ec8ab06c029744a8fc1535e9c19b9a30
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,143 @@@
++/*
++   Copyright (C) 2001-2006, William Joseph.
++   All Rights Reserved.
++
++   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
++ */
++
++#if !defined ( INCLUDED_XORRECTANGLE_H )
++#define INCLUDED_XORRECTANGLE_H
++
++#include <cairo.h>
++#include <uilib/uilib.h>
++#include "math/vector.h"
++
++class rectangle_t
++{
++public:
++rectangle_t()
++      : x( 0 ), y( 0 ), w( 0 ), h( 0 )
++{}
++rectangle_t( float _x, float _y, float _w, float _h )
++      : x( _x ), y( _y ), w( _w ), h( _h )
++{}
++float x;
++float y;
++float w;
++float h;
++};
++
++struct Coord2D
++{
++      float x, y;
++      Coord2D( float _x, float _y )
++              : x( _x ), y( _y ){
++      }
++};
++
++inline Coord2D coord2d_device2screen( const Coord2D& coord, unsigned int width, unsigned int height ){
++      return Coord2D( ( ( coord.x + 1.0f ) * 0.5f ) * width, ( ( coord.y + 1.0f ) * 0.5f ) * height );
++}
++
++inline rectangle_t rectangle_from_area( const float min[2], const float max[2], unsigned int width, unsigned int height ){
++      Coord2D botleft( coord2d_device2screen( Coord2D( min[0], min[1] ), width, height ) );
++      Coord2D topright( coord2d_device2screen( Coord2D( max[0], max[1] ), width, height ) );
++      return rectangle_t( botleft.x, botleft.y, topright.x - botleft.x, topright.y - botleft.y );
++}
++
++class XORRectangle
++{
++
++rectangle_t m_rectangle;
++
++ui::GLArea m_widget;
++cairo_t *cr;
++
++bool initialised() const;
++void lazy_init();
++void draw() const;
++
++public:
++<<<<<<< HEAD
++XORRectangle( ui::GLArea widget );
++~XORRectangle();
++void set( rectangle_t rectangle );
++=======
++XORRectangle( GtkWidget* widget ) : m_widget( widget ){
++}
++~XORRectangle(){
++}
++void set( rectangle_t rectangle ){
++      if ( GTK_WIDGET_REALIZED( m_widget ) ) {
++              if( m_rectangle.w != rectangle.w || m_rectangle.h != rectangle.h ){
++              //if( !(m_rectangle.w == 0 && m_rectangle.h == 0 && rectangle.w == 0 && rectangle.h == 0) ){
++              //globalOutputStream() << "m_x" << m_rectangle.x << " m_y" << m_rectangle.y << " m_w" << m_rectangle.w << " m_h" << m_rectangle.h << "\n";
++              //globalOutputStream() << "__x" << rectangle.x << " __y" << rectangle.y << " __w" << rectangle.w << " __h" << rectangle.h << "\n";
++                      if ( glwidget_make_current( m_widget ) != FALSE ) {
++                              GlobalOpenGL_debugAssertNoErrors();
++
++                              gint width, height;
++                              gdk_gl_drawable_get_size( gtk_widget_get_gl_drawable( m_widget ), &width, &height );
++
++                              glViewport( 0, 0, width, height );
++                              glMatrixMode( GL_PROJECTION );
++                              glLoadIdentity();
++                              glOrtho( 0, width, 0, height, -100, 100 );
++
++                              glMatrixMode( GL_MODELVIEW );
++                              glLoadIdentity();
++
++                              glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
++                              glDisable( GL_DEPTH_TEST );
++
++                              glDrawBuffer( GL_FRONT );
++
++                              glEnable( GL_BLEND );
++                              glBlendFunc( GL_ONE_MINUS_DST_COLOR, GL_ZERO );
++
++                              glLineWidth( 2 );
++                              glColor3f( 1, 1, 1 );
++                              glDisable( GL_TEXTURE_2D );
++                              glBegin( GL_LINE_LOOP );
++                              glVertex2f( m_rectangle.x, m_rectangle.y + m_rectangle.h );
++                              glVertex2f( m_rectangle.x + m_rectangle.w, m_rectangle.y + m_rectangle.h );
++                              glVertex2f( m_rectangle.x + m_rectangle.w, m_rectangle.y );
++                              glVertex2f( m_rectangle.x, m_rectangle.y );
++                              glEnd();
++
++                              glBegin( GL_LINE_LOOP );
++                              glVertex2f( rectangle.x, rectangle.y + rectangle.h );
++                              glVertex2f( rectangle.x + rectangle.w, rectangle.y + rectangle.h );
++                              glVertex2f( rectangle.x + rectangle.w, rectangle.y );
++                              glVertex2f( rectangle.x, rectangle.y );
++                              glEnd();
++
++                              glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
++                              glDrawBuffer( GL_BACK );
++                              GlobalOpenGL_debugAssertNoErrors();
++                              //glwidget_swap_buffers( m_widget );
++                              glwidget_make_current( m_widget );
++                      }
++              }
++              m_rectangle = rectangle;
++      }
++}
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++};
++
++
++#endif
Simple merge
diff --cc radiant/brush.h
Simple merge
Simple merge
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ab329b85c415dc646dfd7aca639bee85e88ad82a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1332 @@@
++/*
++   Copyright (C) 1999-2006 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
++ */
++
++#include "brushmanip.h"
++
++
++#include "gtkutil/widget.h"
++#include "gtkutil/menu.h"
++#include "gtkmisc.h"
++#include "brushnode.h"
++#include "map.h"
++#include "texwindow.h"
++#include "gtkdlgs.h"
++#include "commands.h"
++#include "mainframe.h"
++#include "dialog.h"
++#include "xywindow.h"
++#include "preferences.h"
++
++#include <list>
++#include <gdk/gdkkeysyms.h>
++
++void Brush_ConstructCuboid( Brush& brush, const AABB& bounds, const char* shader, const TextureProjection& projection ){
++      const unsigned char box[3][2] = { { 0, 1 }, { 2, 0 }, { 1, 2 } };
++      Vector3 mins( vector3_subtracted( bounds.origin, bounds.extents ) );
++      Vector3 maxs( vector3_added( bounds.origin, bounds.extents ) );
++
++      brush.clear();
++      brush.reserve( 6 );
++
++      {
++              for ( int i = 0; i < 3; ++i )
++              {
++                      Vector3 planepts1( maxs );
++                      Vector3 planepts2( maxs );
++                      planepts2[box[i][0]] = mins[box[i][0]];
++                      planepts1[box[i][1]] = mins[box[i][1]];
++
++                      brush.addPlane( maxs, planepts1, planepts2, shader, projection );
++              }
++      }
++      {
++              for ( int i = 0; i < 3; ++i )
++              {
++                      Vector3 planepts1( mins );
++                      Vector3 planepts2( mins );
++                      planepts1[box[i][0]] = maxs[box[i][0]];
++                      planepts2[box[i][1]] = maxs[box[i][1]];
++
++                      brush.addPlane( mins, planepts1, planepts2, shader, projection );
++              }
++      }
++}
++
++inline float max_extent( const Vector3& extents ){
++      return std::max( std::max( extents[0], extents[1] ), extents[2] );
++}
++
++inline float max_extent_2d( const Vector3& extents, int axis ){
++      switch ( axis )
++      {
++      case 0:
++              return std::max( extents[1], extents[2] );
++      case 1:
++              return std::max( extents[0], extents[2] );
++      default:
++              return std::max( extents[0], extents[1] );
++      }
++}
++
++const std::size_t c_brushPrism_minSides = 3;
++const std::size_t c_brushPrism_maxSides = c_brush_maxFaces - 2;
++const char* const c_brushPrism_name = "brushPrism";
++
++void Brush_ConstructPrism( Brush& brush, const AABB& bounds, std::size_t sides, int axis, const char* shader, const TextureProjection& projection ){
++      if ( sides < c_brushPrism_minSides ) {
++              globalErrorStream() << c_brushPrism_name << ": sides " << Unsigned( sides ) << ": too few sides, minimum is " << Unsigned( c_brushPrism_minSides ) << "\n";
++              return;
++      }
++      if ( sides > c_brushPrism_maxSides ) {
++              globalErrorStream() << c_brushPrism_name << ": sides " << Unsigned( sides ) << ": too many sides, maximum is " << Unsigned( c_brushPrism_maxSides ) << "\n";
++              return;
++      }
++
++      brush.clear();
++      brush.reserve( sides + 2 );
++
++      Vector3 mins( vector3_subtracted( bounds.origin, bounds.extents ) );
++      Vector3 maxs( vector3_added( bounds.origin, bounds.extents ) );
++
++      float radius = max_extent_2d( bounds.extents, axis );
++      const Vector3& mid = bounds.origin;
++      Vector3 planepts[3];
++
++      planepts[2][( axis + 1 ) % 3] = mins[( axis + 1 ) % 3];
++      planepts[2][( axis + 2 ) % 3] = mins[( axis + 2 ) % 3];
++      planepts[2][axis] = maxs[axis];
++      planepts[1][( axis + 1 ) % 3] = maxs[( axis + 1 ) % 3];
++      planepts[1][( axis + 2 ) % 3] = mins[( axis + 2 ) % 3];
++      planepts[1][axis] = maxs[axis];
++      planepts[0][( axis + 1 ) % 3] = maxs[( axis + 1 ) % 3];
++      planepts[0][( axis + 2 ) % 3] = maxs[( axis + 2 ) % 3];
++      planepts[0][axis] = maxs[axis];
++
++      brush.addPlane( planepts[0], planepts[1], planepts[2], shader, projection );
++
++      planepts[0][( axis + 1 ) % 3] = mins[( axis + 1 ) % 3];
++      planepts[0][( axis + 2 ) % 3] = mins[( axis + 2 ) % 3];
++      planepts[0][axis] = mins[axis];
++      planepts[1][( axis + 1 ) % 3] = maxs[( axis + 1 ) % 3];
++      planepts[1][( axis + 2 ) % 3] = mins[( axis + 2 ) % 3];
++      planepts[1][axis] = mins[axis];
++      planepts[2][( axis + 1 ) % 3] = maxs[( axis + 1 ) % 3];
++      planepts[2][( axis + 2 ) % 3] = maxs[( axis + 2 ) % 3];
++      planepts[2][axis] = mins[axis];
++
++      brush.addPlane( planepts[0], planepts[1], planepts[2], shader, projection );
++
++      for ( std::size_t i = 0 ; i < sides ; ++i )
++      {
++              double sv = sin( i * 3.14159265 * 2 / sides );
++              double cv = cos( i * 3.14159265 * 2 / sides );
++
++//            planepts[0][( axis + 1 ) % 3] = static_cast<float>( floor( mid[( axis + 1 ) % 3] + radius * cv + 0.5 ) );
++//            planepts[0][( axis + 2 ) % 3] = static_cast<float>( floor( mid[( axis + 2 ) % 3] + radius * sv + 0.5 ) );
++              planepts[0][( axis + 1 ) % 3] = static_cast<float>( mid[( axis + 1 ) % 3] + radius * cv );
++              planepts[0][( axis + 2 ) % 3] = static_cast<float>( mid[( axis + 2 ) % 3] + radius * sv );
++              planepts[0][axis] = mins[axis];
++
++              planepts[1][( axis + 1 ) % 3] = planepts[0][( axis + 1 ) % 3];
++              planepts[1][( axis + 2 ) % 3] = planepts[0][( axis + 2 ) % 3];
++              planepts[1][axis] = maxs[axis];
++
++//            planepts[2][( axis + 1 ) % 3] = static_cast<float>( floor( planepts[0][( axis + 1 ) % 3] - radius * sv + 0.5 ) );
++//            planepts[2][( axis + 2 ) % 3] = static_cast<float>( floor( planepts[0][( axis + 2 ) % 3] + radius * cv + 0.5 ) );
++              planepts[2][( axis + 1 ) % 3] = static_cast<float>( planepts[0][( axis + 1 ) % 3] - radius * sv );
++              planepts[2][( axis + 2 ) % 3] = static_cast<float>( planepts[0][( axis + 2 ) % 3] + radius * cv );
++              planepts[2][axis] = maxs[axis];
++              //globalOutputStream() << planepts[0] << "   " << planepts[2] << "  #" << i << "   sin " << sv << "  cos " << cv << "\n";
++
++              brush.addPlane( planepts[0], planepts[1], planepts[2], shader, projection );
++      }
++}
++
++const std::size_t c_brushCone_minSides = 3;
++const std::size_t c_brushCone_maxSides = c_brush_maxFaces - 1;
++const char* const c_brushCone_name = "brushCone";
++
++void Brush_ConstructCone( Brush& brush, const AABB& bounds, std::size_t sides, const char* shader, const TextureProjection& projection ){
++      if ( sides < c_brushCone_minSides ) {
++              globalErrorStream() << c_brushCone_name << ": sides " << Unsigned( sides ) << ": too few sides, minimum is " << Unsigned( c_brushCone_minSides ) << "\n";
++              return;
++      }
++      if ( sides > c_brushCone_maxSides ) {
++              globalErrorStream() << c_brushCone_name << ": sides " << Unsigned( sides ) << ": too many sides, maximum is " << Unsigned( c_brushCone_maxSides ) << "\n";
++              return;
++      }
++
++      brush.clear();
++      brush.reserve( sides + 1 );
++
++      Vector3 mins( vector3_subtracted( bounds.origin, bounds.extents ) );
++      Vector3 maxs( vector3_added( bounds.origin, bounds.extents ) );
++
++      float radius = max_extent( bounds.extents );
++      const Vector3& mid = bounds.origin;
++      Vector3 planepts[3];
++
++      planepts[0][0] = mins[0]; planepts[0][1] = mins[1]; planepts[0][2] = mins[2];
++      planepts[1][0] = maxs[0]; planepts[1][1] = mins[1]; planepts[1][2] = mins[2];
++      planepts[2][0] = maxs[0]; planepts[2][1] = maxs[1]; planepts[2][2] = mins[2];
++
++      brush.addPlane( planepts[0], planepts[1], planepts[2], shader, projection );
++
++      for ( std::size_t i = 0 ; i < sides ; ++i )
++      {
++              double sv = sin( i * 3.14159265 * 2 / sides );
++              double cv = cos( i * 3.14159265 * 2 / sides );
++
++              planepts[0][0] = static_cast<float>( mid[0] + radius * cv );
++              planepts[0][1] = static_cast<float>( mid[1] + radius * sv );
++//            planepts[0][0] = static_cast<float>( floor( mid[0] + radius * cv + 0.5 ) );
++//            planepts[0][1] = static_cast<float>( floor( mid[1] + radius * sv + 0.5 ) );
++              planepts[0][2] = mins[2];
++
++              planepts[1][0] = mid[0];
++              planepts[1][1] = mid[1];
++              planepts[1][2] = maxs[2];
++
++              planepts[2][0] = static_cast<float>( planepts[0][0] - radius * sv );
++              planepts[2][1] = static_cast<float>( planepts[0][1] + radius * cv );
++//            planepts[2][0] = static_cast<float>( floor( planepts[0][0] - radius * sv + 0.5 ) );
++//            planepts[2][1] = static_cast<float>( floor( planepts[0][1] + radius * cv + 0.5 ) );
++              planepts[2][2] = maxs[2];
++
++              brush.addPlane( planepts[0], planepts[1], planepts[2], shader, projection );
++      }
++}
++
++const std::size_t c_brushSphere_minSides = 3;
++const std::size_t c_brushSphere_maxSides = 31;
++const char* const c_brushSphere_name = "brushSphere";
++
++void Brush_ConstructSphere( Brush& brush, const AABB& bounds, std::size_t sides, const char* shader, const TextureProjection& projection ){
++      if ( sides < c_brushSphere_minSides ) {
++              globalErrorStream() << c_brushSphere_name << ": sides " << Unsigned( sides ) << ": too few sides, minimum is " << Unsigned( c_brushSphere_minSides ) << "\n";
++              return;
++      }
++      if ( sides > c_brushSphere_maxSides ) {
++              globalErrorStream() << c_brushSphere_name << ": sides " << Unsigned( sides ) << ": too many sides, maximum is " << Unsigned( c_brushSphere_maxSides ) << "\n";
++              return;
++      }
++
++      brush.clear();
++      brush.reserve( sides * sides );
++
++      float radius = max_extent( bounds.extents );
++      const Vector3& mid = bounds.origin;
++      Vector3 planepts[3];
++
++      double dt = 2 * c_pi / sides;
++      double dp = c_pi / sides;
++      for ( std::size_t i = 0; i < sides; i++ )
++      {
++              for ( std::size_t j = 0; j < sides - 1; j++ )
++              {
++                      double t = i * dt;
++                      double p = float(j * dp - c_pi / 2);
++
++                      planepts[0] = vector3_added( mid, vector3_scaled( vector3_for_spherical( t, p ), radius ) );
++                      planepts[1] = vector3_added( mid, vector3_scaled( vector3_for_spherical( t, p + dp ), radius ) );
++                      planepts[2] = vector3_added( mid, vector3_scaled( vector3_for_spherical( t + dt, p + dp ), radius ) );
++
++                      brush.addPlane( planepts[0], planepts[1], planepts[2], shader, projection );
++              }
++      }
++
++      {
++              double p = ( sides - 1 ) * dp - c_pi / 2;
++              for ( std::size_t i = 0; i < sides; i++ )
++              {
++                      double t = i * dt;
++
++                      planepts[0] = vector3_added( mid, vector3_scaled( vector3_for_spherical( t, p ), radius ) );
++                      planepts[1] = vector3_added( mid, vector3_scaled( vector3_for_spherical( t + dt, p + dp ), radius ) );
++                      planepts[2] = vector3_added( mid, vector3_scaled( vector3_for_spherical( t + dt, p ), radius ) );
++
++                      brush.addPlane( planepts[0], planepts[1], planepts[2], shader, projection );
++              }
++      }
++}
++
++const std::size_t c_brushRock_minSides = 10;
++const std::size_t c_brushRock_maxSides = 1000;
++const char* const c_brushRock_name = "brushRock";
++
++void Brush_ConstructRock( Brush& brush, const AABB& bounds, std::size_t sides, const char* shader, const TextureProjection& projection ){
++      if ( sides < c_brushRock_minSides ) {
++              globalErrorStream() << c_brushRock_name << ": sides " << Unsigned( sides ) << ": too few sides, minimum is " << Unsigned( c_brushRock_minSides ) << "\n";
++              return;
++      }
++      if ( sides > c_brushRock_maxSides ) {
++              globalErrorStream() << c_brushRock_name << ": sides " << Unsigned( sides ) << ": too many sides, maximum is " << Unsigned( c_brushRock_maxSides ) << "\n";
++              return;
++      }
++
++      brush.clear();
++      brush.reserve( sides * sides );
++
++      float radius = max_extent( bounds.extents );
++      const Vector3& mid = bounds.origin;
++      Vector3 planepts[3];
++
++      for ( std::size_t j = 0; j < sides; j++ )
++      {
++              planepts[0][0] = rand() - ( RAND_MAX / 2 );
++              planepts[0][1] = rand() - ( RAND_MAX / 2 );
++              planepts[0][2] = rand() - ( RAND_MAX / 2 );
++              vector3_normalise( planepts[0] );
++
++              // find two vectors that are perpendicular to planepts[0]
++              ComputeAxisBase( planepts[0], planepts[1], planepts[2] );
++
++              planepts[0] = vector3_added( mid, vector3_scaled( planepts[0], radius ) );
++              planepts[1] = vector3_added( planepts[0], vector3_scaled( planepts[1], radius ) );
++              planepts[2] = vector3_added( planepts[0], vector3_scaled( planepts[2], radius ) );
++
++#if 0
++              // make sure the orientation is right
++              if ( vector3_dot( vector3_subtracted( planepts[0], mid ), vector3_cross( vector3_subtracted( planepts[1], mid ), vector3_subtracted( planepts[2], mid ) ) ) > 0 ) {
++                      Vector3 h;
++                      h = planepts[1];
++                      planepts[1] = planepts[2];
++                      planepts[2] = h;
++                      globalOutputStream() << "flip\n";
++              }
++              else{
++                      globalOutputStream() << "no flip\n";
++              }
++#endif
++
++              brush.addPlane( planepts[0], planepts[1], planepts[2], shader, projection );
++      }
++}
++
++int GetViewAxis(){
++      switch ( GlobalXYWnd_getCurrentViewType() )
++      {
++      case XY:
++              return 2;
++      case XZ:
++              return 1;
++      case YZ:
++              return 0;
++      }
++      return 2;
++}
++
++void Brush_ConstructPrefab( Brush& brush, EBrushPrefab type, const AABB& bounds, std::size_t sides, const char* shader, const TextureProjection& projection ){
++      switch ( type )
++      {
++      case eBrushCuboid:
++      {
++              UndoableCommand undo( "brushCuboid" );
++
++              Brush_ConstructCuboid( brush, bounds, shader, projection );
++      }
++      break;
++      case eBrushPrism:
++      {
++              int axis = GetViewAxis();
++              StringOutputStream command;
++              command << c_brushPrism_name << " -sides " << Unsigned( sides ) << " -axis " << axis;
++              UndoableCommand undo( command.c_str() );
++
++              Brush_ConstructPrism( brush, bounds, sides, axis, shader, projection );
++      }
++      break;
++      case eBrushCone:
++      {
++              StringOutputStream command;
++              command << c_brushCone_name << " -sides " << Unsigned( sides );
++              UndoableCommand undo( command.c_str() );
++
++              Brush_ConstructCone( brush, bounds, sides, shader, projection );
++      }
++      break;
++      case eBrushSphere:
++      {
++              StringOutputStream command;
++              command << c_brushSphere_name << " -sides " << Unsigned( sides );
++              UndoableCommand undo( command.c_str() );
++
++              Brush_ConstructSphere( brush, bounds, sides, shader, projection );
++      }
++      break;
++      case eBrushRock:
++      {
++              StringOutputStream command;
++              command << c_brushRock_name << " -sides " << Unsigned( sides );
++              UndoableCommand undo( command.c_str() );
++
++              Brush_ConstructRock( brush, bounds, sides, shader, projection );
++      }
++      break;
++      }
++}
++
++
++void ConstructRegionBrushes( scene::Node* brushes[6], const Vector3& region_mins, const Vector3& region_maxs ){
++      {
++              // set mins
++              Vector3 mins( region_mins[0] - 32, region_mins[1] - 32, region_mins[2] - 32 );
++
++              // vary maxs
++              for ( std::size_t i = 0; i < 3; i++ )
++              {
++                      Vector3 maxs( region_maxs[0] + 32, region_maxs[1] + 32, region_maxs[2] + 32 );
++                      maxs[i] = region_mins[i];
++                      Brush_ConstructCuboid( *Node_getBrush( *brushes[i] ), aabb_for_minmax( mins, maxs ), texdef_name_default(), TextureProjection() );
++              }
++      }
++
++      {
++              // set maxs
++              Vector3 maxs( region_maxs[0] + 32, region_maxs[1] + 32, region_maxs[2] + 32 );
++
++              // vary mins
++              for ( std::size_t i = 0; i < 3; i++ )
++              {
++                      Vector3 mins( region_mins[0] - 32, region_mins[1] - 32, region_mins[2] - 32 );
++                      mins[i] = region_maxs[i];
++                      Brush_ConstructCuboid( *Node_getBrush( *brushes[i + 3] ), aabb_for_minmax( mins, maxs ), texdef_name_default(), TextureProjection() );
++              }
++      }
++}
++
++
++void Scene_BrushSetTexdef_Selected( scene::Graph& graph, const TextureProjection& projection ){
++      Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) {
++              face.SetTexdef(projection);
++      });
++      SceneChangeNotify();
++}
++
++void Scene_BrushSetTexdef_Component_Selected( scene::Graph& graph, const TextureProjection& projection ){
++      Scene_ForEachSelectedBrushFace(graph, [&](Face &face) {
++              face.SetTexdef(projection);
++      });
++      SceneChangeNotify();
++}
++
++
++<<<<<<< HEAD
++=======
++class FaceSetFlags
++{
++const ContentsFlagsValue& m_flags;
++public:
++FaceSetFlags( const ContentsFlagsValue& flags ) : m_flags( flags ){
++}
++void operator()( Face& face ) const {
++      face.SetFlags( m_flags );
++}
++};
++
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++void Scene_BrushSetFlags_Selected( scene::Graph& graph, const ContentsFlagsValue& flags ){
++      Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) {
++              face.SetFlags(flags);
++      });
++      SceneChangeNotify();
++}
++
++void Scene_BrushSetFlags_Component_Selected( scene::Graph& graph, const ContentsFlagsValue& flags ){
++      Scene_ForEachSelectedBrushFace(graph, [&](Face &face) {
++              face.SetFlags(flags);
++      });
++      SceneChangeNotify();
++}
++
++void Scene_BrushShiftTexdef_Selected( scene::Graph& graph, float s, float t ){
++      Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) {
++              face.ShiftTexdef(s, t);
++      });
++      SceneChangeNotify();
++}
++
++void Scene_BrushShiftTexdef_Component_Selected( scene::Graph& graph, float s, float t ){
++      Scene_ForEachSelectedBrushFace(graph, [&](Face &face) {
++              face.ShiftTexdef(s, t);
++      });
++      SceneChangeNotify();
++}
++
++void Scene_BrushScaleTexdef_Selected( scene::Graph& graph, float s, float t ){
++      Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) {
++              face.ScaleTexdef(s, t);
++      });
++      SceneChangeNotify();
++}
++
++void Scene_BrushScaleTexdef_Component_Selected( scene::Graph& graph, float s, float t ){
++      Scene_ForEachSelectedBrushFace(graph, [&](Face &face) {
++              face.ScaleTexdef(s, t);
++      });
++      SceneChangeNotify();
++}
++
++void Scene_BrushRotateTexdef_Selected( scene::Graph& graph, float angle ){
++      Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) {
++              face.RotateTexdef(angle);
++      });
++      SceneChangeNotify();
++}
++
++void Scene_BrushRotateTexdef_Component_Selected( scene::Graph& graph, float angle ){
++      Scene_ForEachSelectedBrushFace(graph, [&](Face &face) {
++              face.RotateTexdef(angle);
++      });
++      SceneChangeNotify();
++}
++
++
++void Scene_BrushSetShader_Selected( scene::Graph& graph, const char* name ){
++      Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) {
++              face.SetShader(name);
++      });
++      SceneChangeNotify();
++}
++
++void Scene_BrushSetShader_Component_Selected( scene::Graph& graph, const char* name ){
++      Scene_ForEachSelectedBrushFace(graph, [&](Face &face) {
++              face.SetShader(name);
++      });
++      SceneChangeNotify();
++}
++
++void Scene_BrushSetDetail_Selected( scene::Graph& graph, bool detail ){
++      Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) {
++              face.setDetail(detail);
++      });
++      SceneChangeNotify();
++}
++
++bool Face_FindReplaceShader( Face& face, const char* find, const char* replace ){
++      if ( shader_equal( face.GetShader(), find ) ) {
++              face.SetShader( replace );
++              return true;
++      }
++      return false;
++}
++
++bool DoingSearch( const char *repl ){
++      return ( repl == NULL || ( strcmp( "textures/", repl ) == 0 ) );
++}
++
++void Scene_BrushFindReplaceShader( scene::Graph& graph, const char* find, const char* replace ){
++      if ( DoingSearch( replace ) ) {
++              Scene_ForEachBrush_ForEachFaceInstance(graph, [&](FaceInstance &faceinst) {
++                      if (shader_equal(faceinst.getFace().GetShader(), find)) {
++                              faceinst.setSelected(SelectionSystem::eFace, true);
++                      }
++              });
++      }
++      else
++      {
++              Scene_ForEachBrush_ForEachFace(graph, [&](Face &face) { Face_FindReplaceShader(face, find, replace); });
++      }
++}
++
++void Scene_BrushFindReplaceShader_Selected( scene::Graph& graph, const char* find, const char* replace ){
++      if ( DoingSearch( replace ) ) {
++              Scene_ForEachSelectedBrush_ForEachFaceInstance(graph, [&](FaceInstance &faceinst) {
++                      if (shader_equal(faceinst.getFace().GetShader(), find)) {
++                              faceinst.setSelected(SelectionSystem::eFace, true);
++                      }
++              });
++      }
++      else
++      {
++              Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) {
++                      Face_FindReplaceShader(face, find, replace);
++              });
++      }
++}
++
++// TODO: find for components
++// d1223m: dont even know what they are...
++void Scene_BrushFindReplaceShader_Component_Selected( scene::Graph& graph, const char* find, const char* replace ){
++      if ( DoingSearch( replace ) ) {
++
++      }
++      else
++      {
++              Scene_ForEachSelectedBrushFace(graph, [&](Face &face) {
++                      Face_FindReplaceShader(face, find, replace);
++              });
++      }
++}
++
++
++void Scene_BrushFitTexture_Selected( scene::Graph& graph, float s_repeat, float t_repeat ){
++      Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) {
++              face.FitTexture(s_repeat, t_repeat);
++      });
++      SceneChangeNotify();
++}
++
++void Scene_BrushFitTexture_Component_Selected( scene::Graph& graph, float s_repeat, float t_repeat ){
++      Scene_ForEachSelectedBrushFace(graph, [&](Face &face) {
++              face.FitTexture(s_repeat, t_repeat);
++      });
++      SceneChangeNotify();
++}
++
++TextureProjection g_defaultTextureProjection;
++
++const TextureProjection& TextureTransform_getDefault(){
++      TexDef_Construct_Default( g_defaultTextureProjection );
++      return g_defaultTextureProjection;
++}
++
++void Scene_BrushConstructPrefab( scene::Graph& graph, EBrushPrefab type, std::size_t sides, const char* shader ){
++      if ( GlobalSelectionSystem().countSelected() != 0 ) {
++              const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
++
++              Brush* brush = Node_getBrush( path.top() );
++              if ( brush != 0 ) {
++                      AABB bounds = brush->localAABB(); // copy bounds because the brush will be modified
++                      Brush_ConstructPrefab( *brush, type, bounds, sides, shader, TextureTransform_getDefault() );
++                      SceneChangeNotify();
++              }
++      }
++}
++
++void Scene_BrushResize_Selected( scene::Graph& graph, const AABB& bounds, const char* shader ){
++      if ( GlobalSelectionSystem().countSelected() != 0 ) {
++              const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
++
++              Brush* brush = Node_getBrush( path.top() );
++              if ( brush != 0 ) {
++                      Brush_ConstructCuboid( *brush, bounds, shader, TextureTransform_getDefault() );
++                      SceneChangeNotify();
++              }
++      }
++}
++
++bool Brush_hasShader( const Brush& brush, const char* name ){
++      for ( Brush::const_iterator i = brush.begin(); i != brush.end(); ++i )
++      {
++              if ( shader_equal( ( *i )->GetShader(), name ) ) {
++                      return true;
++              }
++      }
++      return false;
++}
++
++class BrushSelectByShaderWalker : public scene::Graph::Walker
++{
++const char* m_name;
++public:
++BrushSelectByShaderWalker( const char* name )
++      : m_name( name ){
++}
++
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      if ( path.top().get().visible() ) {
++              Brush* brush = Node_getBrush( path.top() );
++              if ( brush != 0 && Brush_hasShader( *brush, m_name ) ) {
++                      Instance_getSelectable( instance )->setSelected( true );
++              }
++      }
++      else{
++              return false;
++      }
++      return true;
++}
++};
++
++void Scene_BrushSelectByShader( scene::Graph& graph, const char* name ){
++      graph.traverse( BrushSelectByShaderWalker( name ) );
++}
++
++void Scene_BrushSelectByShader_Component( scene::Graph& graph, const char* name ){
++      Scene_ForEachSelectedBrush_ForEachFaceInstance(graph, [&](FaceInstance &face) {
++              printf("checking %s = %s\n", face.getFace().GetShader(), name);
++              if (shader_equal(face.getFace().GetShader(), name)) {
++              face.setSelected( SelectionSystem::eFace, true );
++      }
++      });
++}
++
++void Scene_BrushGetTexdef_Selected( scene::Graph& graph, TextureProjection& projection ){
++      bool done = false;
++      Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) {
++              if (!done) {
++                      done = true;
++                      face.GetTexdef(projection);
++}
++      });
++}
++
++void Scene_BrushGetTexdef_Component_Selected( scene::Graph& graph, TextureProjection& projection ){
++#if 1
++      if ( !g_SelectedFaceInstances.empty() ) {
++              FaceInstance& faceInstance = g_SelectedFaceInstances.last();
++              faceInstance.getFace().GetTexdef( projection );
++      }
++#else
++      FaceGetTexdef visitor( projection );
++      Scene_ForEachSelectedBrushFace( graph, visitor );
++#endif
++}
++
++void Scene_BrushGetShaderSize_Component_Selected( scene::Graph& graph, size_t& width, size_t& height ){
++      if ( !g_SelectedFaceInstances.empty() ) {
++              FaceInstance& faceInstance = g_SelectedFaceInstances.last();
++              width = faceInstance.getFace().getShader().width();
++              height = faceInstance.getFace().getShader().height();
++      }
++}
++
++
++void Scene_BrushGetFlags_Selected( scene::Graph& graph, ContentsFlagsValue& flags ){
++#if 1
++      if ( GlobalSelectionSystem().countSelected() != 0 ) {
++              BrushInstance* brush = Instance_getBrush( GlobalSelectionSystem().ultimateSelected() );
++              if ( brush != 0 ) {
++                      bool done = false;
++                      Brush_forEachFace(*brush, [&](Face &face) {
++                              if (!done) {
++                                      done = true;
++                                      face.GetFlags(flags);
++                              }
++                      });
++              }
++      }
++#else
++      Scene_ForEachSelectedBrush_ForEachFace( graph, FaceGetFlags( flags ) );
++#endif
++}
++
++void Scene_BrushGetFlags_Component_Selected( scene::Graph& graph, ContentsFlagsValue& flags ){
++#if 1
++      if ( !g_SelectedFaceInstances.empty() ) {
++              FaceInstance& faceInstance = g_SelectedFaceInstances.last();
++              faceInstance.getFace().GetFlags( flags );
++      }
++#else
++      Scene_ForEachSelectedBrushFace( graph, FaceGetFlags( flags ) );
++#endif
++}
++
++
++void Scene_BrushGetShader_Selected( scene::Graph& graph, CopiedString& shader ){
++#if 1
++      if ( GlobalSelectionSystem().countSelected() != 0 ) {
++              BrushInstance* brush = Instance_getBrush( GlobalSelectionSystem().ultimateSelected() );
++              if ( brush != 0 ) {
++                      bool done = false;
++                      Brush_forEachFace(*brush, [&](Face &face) {
++                              if (!done) {
++                                      done = true;
++                                      shader = face.GetShader();
++                              }
++                      });
++              }
++      }
++#else
++      Scene_ForEachSelectedBrush_ForEachFace( graph, FaceGetShader( shader ) );
++#endif
++}
++
++void Scene_BrushGetShader_Component_Selected( scene::Graph& graph, CopiedString& shader ){
++#if 1
++      if ( !g_SelectedFaceInstances.empty() ) {
++              FaceInstance& faceInstance = g_SelectedFaceInstances.last();
++              shader = faceInstance.getFace().GetShader();
++      }
++#else
++      FaceGetShader visitor( shader );
++      Scene_ForEachSelectedBrushFace( graph, visitor );
++#endif
++}
++
++
++class filter_face_shader : public FaceFilter
++{
++const char* m_shader;
++public:
++filter_face_shader( const char* shader ) : m_shader( shader ){
++}
++
++bool filter( const Face& face ) const {
++      return shader_equal( face.GetShader(), m_shader );
++}
++};
++
++class filter_face_shader_prefix : public FaceFilter
++{
++const char* m_prefix;
++public:
++filter_face_shader_prefix( const char* prefix ) : m_prefix( prefix ){
++}
++
++bool filter( const Face& face ) const {
++      return shader_equal_n( face.GetShader(), m_prefix, strlen( m_prefix ) );
++}
++};
++
++class filter_face_flags : public FaceFilter
++{
++int m_flags;
++public:
++filter_face_flags( int flags ) : m_flags( flags ){
++}
++
++bool filter( const Face& face ) const {
++      return ( face.getShader().shaderFlags() & m_flags ) != 0;
++}
++};
++
++class filter_face_contents : public FaceFilter
++{
++int m_contents;
++public:
++filter_face_contents( int contents ) : m_contents( contents ){
++}
++
++bool filter( const Face& face ) const {
++      return ( face.getShader().m_flags.m_contentFlags & m_contents ) != 0;
++}
++};
++
++
++
++class filter_brush_any_face : public BrushFilter
++{
++FaceFilter* m_filter;
++public:
++filter_brush_any_face( FaceFilter* filter ) : m_filter( filter ){
++}
++
++bool filter( const Brush& brush ) const {
++      bool filtered = false;
++      Brush_forEachFace(brush, [&](Face &face) {
++              if (m_filter->filter(face)) {
++                      filtered = true;
++              }
++      });
++      return filtered;
++}
++};
++
++class filter_brush_all_faces : public BrushFilter
++{
++FaceFilter* m_filter;
++public:
++filter_brush_all_faces( FaceFilter* filter ) : m_filter( filter ){
++}
++bool filter( const Brush& brush ) const {
++      bool filtered = true;
++      Brush_forEachFace(brush, [&](Face &face) {
++              if (!m_filter->filter(face)) {
++                      filtered = false;
++              }
++      });
++      return filtered;
++}
++};
++
++
++filter_face_flags g_filter_face_clip( QER_CLIP );
++filter_brush_all_faces g_filter_brush_clip( &g_filter_face_clip );
++
++filter_face_shader g_filter_face_clip_q2( "textures/clip" );
++filter_brush_all_faces g_filter_brush_clip_q2( &g_filter_face_clip_q2 );
++
++filter_face_shader g_filter_face_weapclip( "textures/common/weapclip" );
++filter_brush_all_faces g_filter_brush_weapclip( &g_filter_face_weapclip );
++
++filter_face_shader g_filter_face_commonclip( "textures/common/clip" );
++filter_brush_all_faces g_filter_brush_commonclip( &g_filter_face_commonclip );
++
++filter_face_shader g_filter_face_fullclip( "textures/common/fullclip" );
++filter_brush_all_faces g_filter_brush_fullclip( &g_filter_face_fullclip );
++
++filter_face_shader g_filter_face_botclip( "textures/common/botclip" );
++filter_brush_all_faces g_filter_brush_botclip( &g_filter_face_botclip );
++
++filter_face_shader_prefix g_filter_face_caulk( "textures/common/caulk" );
++filter_brush_all_faces g_filter_brush_caulk( &g_filter_face_caulk );
++
++filter_face_shader_prefix g_filter_face_caulk_ja( "textures/system/caulk" );
++filter_brush_all_faces g_filter_brush_caulk_ja( &g_filter_face_caulk_ja );
++
++filter_face_flags g_filter_face_liquids( QER_LIQUID );
++filter_brush_any_face g_filter_brush_liquids( &g_filter_face_liquids );
++
++filter_face_shader_prefix g_filter_face_liquidsdir( "textures/liquids/" );
++filter_brush_any_face g_filter_brush_liquidsdir( &g_filter_face_liquidsdir );
++
++filter_face_shader g_filter_face_hint( "textures/common/hint" );
++filter_brush_any_face g_filter_brush_hint( &g_filter_face_hint );
++
++filter_face_shader g_filter_face_hintlocal( "textures/common/hintlocal" );
++filter_brush_any_face g_filter_brush_hintlocal( &g_filter_face_hintlocal );
++
++filter_face_shader g_filter_face_hint_q2( "textures/hint" );
++filter_brush_any_face g_filter_brush_hint_q2( &g_filter_face_hint_q2 );
++
++filter_face_shader g_filter_face_hint_ja( "textures/system/hint" );
++filter_brush_any_face g_filter_brush_hint_ja( &g_filter_face_hint_ja );
++
++filter_face_shader g_filter_face_areaportal( "textures/common/areaportal" );
++filter_brush_any_face g_filter_brush_areaportal( &g_filter_face_areaportal );
++
++filter_face_shader g_filter_face_visportal( "textures/editor/visportal" );
++filter_brush_any_face g_filter_brush_visportal( &g_filter_face_visportal );
++
++filter_face_shader g_filter_face_clusterportal( "textures/common/clusterportal" );
++filter_brush_all_faces g_filter_brush_clusterportal( &g_filter_face_clusterportal );
++
++filter_face_shader g_filter_face_lightgrid( "textures/common/lightgrid" );
++filter_brush_all_faces g_filter_brush_lightgrid( &g_filter_face_lightgrid );
++
++filter_face_flags g_filter_face_translucent( QER_TRANS | QER_ALPHATEST );
++filter_brush_any_face g_filter_brush_translucent( &g_filter_face_translucent );
++
++filter_face_contents g_filter_face_detail( BRUSH_DETAIL_MASK );
++filter_brush_all_faces g_filter_brush_detail( &g_filter_face_detail );
++
++filter_face_shader_prefix g_filter_face_decals( "textures/decals/" );
++filter_brush_any_face g_filter_brush_decals( &g_filter_face_decals );
++
++
++void BrushFilters_construct(){
++      add_brush_filter( g_filter_brush_clip, EXCLUDE_CLIP );
++      add_brush_filter( g_filter_brush_clip_q2, EXCLUDE_CLIP );
++      add_brush_filter( g_filter_brush_weapclip, EXCLUDE_CLIP );
++      add_brush_filter( g_filter_brush_fullclip, EXCLUDE_CLIP );
++      add_brush_filter( g_filter_brush_commonclip, EXCLUDE_CLIP );
++      add_brush_filter( g_filter_brush_botclip, EXCLUDE_BOTCLIP );
++      add_brush_filter( g_filter_brush_caulk, EXCLUDE_CAULK );
++      add_brush_filter( g_filter_brush_caulk_ja, EXCLUDE_CAULK );
++      add_face_filter( g_filter_face_caulk, EXCLUDE_CAULK );
++      add_face_filter( g_filter_face_caulk_ja, EXCLUDE_CAULK );
++      add_brush_filter( g_filter_brush_liquids, EXCLUDE_LIQUIDS );
++      add_brush_filter( g_filter_brush_liquidsdir, EXCLUDE_LIQUIDS );
++      add_brush_filter( g_filter_brush_hint, EXCLUDE_HINTSSKIPS );
++      add_brush_filter( g_filter_brush_hintlocal, EXCLUDE_HINTSSKIPS );
++      add_brush_filter( g_filter_brush_hint_q2, EXCLUDE_HINTSSKIPS );
++      add_brush_filter( g_filter_brush_hint_ja, EXCLUDE_HINTSSKIPS );
++      add_brush_filter( g_filter_brush_clusterportal, EXCLUDE_CLUSTERPORTALS );
++      add_brush_filter( g_filter_brush_visportal, EXCLUDE_VISPORTALS );
++      add_brush_filter( g_filter_brush_areaportal, EXCLUDE_AREAPORTALS );
++      add_brush_filter( g_filter_brush_translucent, EXCLUDE_TRANSLUCENT );
++      add_brush_filter( g_filter_brush_detail, EXCLUDE_DETAILS );
++      add_brush_filter( g_filter_brush_detail, EXCLUDE_STRUCTURAL, true );
++      add_brush_filter( g_filter_brush_lightgrid, EXCLUDE_LIGHTGRID );
++      add_brush_filter( g_filter_brush_decals, EXCLUDE_DECALS );
++}
++
++#if 0
++
++void normalquantisation_draw(){
++      glPointSize( 1 );
++      glBegin( GL_POINTS );
++      for ( std::size_t i = 0; i <= c_quantise_normal; ++i )
++      {
++              for ( std::size_t j = 0; j <= c_quantise_normal; ++j )
++              {
++                      Normal3f vertex( normal3f_normalised( Normal3f(
++                                                                                                        static_cast<float>( c_quantise_normal - j - i ),
++                                                                                                        static_cast<float>( i ),
++                                                                                                        static_cast<float>( j )
++                                                                                                        ) ) );
++                      VectorScale( normal3f_to_array( vertex ), 64.f, normal3f_to_array( vertex ) );
++                      glVertex3fv( normal3f_to_array( vertex ) );
++                      vertex.x = -vertex.x;
++                      glVertex3fv( normal3f_to_array( vertex ) );
++              }
++      }
++      glEnd();
++}
++
++class RenderableNormalQuantisation : public OpenGLRenderable
++{
++public:
++void render( RenderStateFlags state ) const {
++      normalquantisation_draw();
++}
++};
++
++const float g_test_quantise_normal = 1.f / static_cast<float>( 1 << 3 );
++
++class TestNormalQuantisation
++{
++void check_normal( const Normal3f& normal, const Normal3f& other ){
++      spherical_t spherical = spherical_from_normal3f( normal );
++      double longditude = RAD2DEG( spherical.longditude );
++      double latitude = RAD2DEG( spherical.latitude );
++      double x = cos( spherical.longditude ) * sin( spherical.latitude );
++      double y = sin( spherical.longditude ) * sin( spherical.latitude );
++      double z = cos( spherical.latitude );
++
++      ASSERT_MESSAGE( normal3f_dot( normal, other ) > 0.99, "bleh" );
++}
++
++void test_normal( const Normal3f& normal ){
++      Normal3f test = normal3f_from_spherical( spherical_from_normal3f( normal ) );
++      check_normal( normal, test );
++
++      EOctant octant = normal3f_classify_octant( normal );
++      Normal3f folded = normal3f_fold_octant( normal, octant );
++      ESextant sextant = normal3f_classify_sextant( folded );
++      folded = normal3f_fold_sextant( folded, sextant );
++
++      double scale = static_cast<float>( c_quantise_normal ) / ( folded.x + folded.y + folded.z );
++
++      double zbits = folded.z * scale;
++      double ybits = folded.y * scale;
++
++      std::size_t zbits_q = static_cast<std::size_t>( zbits );
++      std::size_t ybits_q = static_cast<std::size_t>( ybits );
++
++      ASSERT_MESSAGE( zbits_q <= ( c_quantise_normal / 8 ) * 3, "bleh" );
++      ASSERT_MESSAGE( ybits_q <= ( c_quantise_normal / 2 ), "bleh" );
++      ASSERT_MESSAGE( zbits_q + ( ( c_quantise_normal / 2 ) - ybits_q ) <= ( c_quantise_normal / 2 ), "bleh" );
++
++      std::size_t y_t = ( zbits_q < ( c_quantise_normal / 4 ) ) ? ybits_q : ( c_quantise_normal / 2 ) - ybits_q;
++      std::size_t z_t = ( zbits_q < ( c_quantise_normal / 4 ) ) ? zbits_q : ( c_quantise_normal / 2 ) - zbits_q;
++      std::size_t index = ( c_quantise_normal / 4 ) * y_t + z_t;
++      ASSERT_MESSAGE( index <= ( c_quantise_normal / 4 ) * ( c_quantise_normal / 2 ), "bleh" );
++
++      Normal3f tmp( c_quantise_normal - zbits_q - ybits_q, ybits_q, zbits_q );
++      tmp = normal3f_normalised( tmp );
++
++      Normal3f unfolded = normal3f_unfold_octant( normal3f_unfold_sextant( tmp, sextant ), octant );
++
++      check_normal( normal, unfolded );
++
++      double dot = normal3f_dot( normal, unfolded );
++      float length = VectorLength( normal3f_to_array( unfolded ) );
++      float inv_length = 1 / length;
++
++      Normal3f quantised = normal3f_quantised( normal );
++      check_normal( normal, quantised );
++}
++void test2( const Normal3f& normal, const Normal3f& other ){
++      if ( normal3f_quantised( normal ) != normal3f_quantised( other ) ) {
++              int bleh = 0;
++      }
++}
++
++static Normal3f normalise( float x, float y, float z ){
++      return normal3f_normalised( Normal3f( x, y, z ) );
++}
++
++float vec_rand(){
++      return static_cast<float>( rand() - ( RAND_MAX / 2 ) );
++}
++
++Normal3f normal3f_rand(){
++      return normalise( vec_rand(), vec_rand(), vec_rand() );
++}
++
++public:
++TestNormalQuantisation(){
++      for ( int i = 4096; i > 0; --i )
++              test_normal( normal3f_rand() );
++
++      test_normal( normalise( 1, 0, 0 ) );
++      test_normal( normalise( 0, 1, 0 ) );
++      test_normal( normalise( 0, 0, 1 ) );
++      test_normal( normalise( 1, 1, 0 ) );
++      test_normal( normalise( 1, 0, 1 ) );
++      test_normal( normalise( 0, 1, 1 ) );
++
++      test_normal( normalise( 10000, 10000, 10000 ) );
++      test_normal( normalise( 10000, 10000, 10001 ) );
++      test_normal( normalise( 10000, 10000, 10002 ) );
++      test_normal( normalise( 10000, 10000, 10010 ) );
++      test_normal( normalise( 10000, 10000, 10020 ) );
++      test_normal( normalise( 10000, 10000, 10030 ) );
++      test_normal( normalise( 10000, 10000, 10100 ) );
++      test_normal( normalise( 10000, 10000, 10101 ) );
++      test_normal( normalise( 10000, 10000, 10102 ) );
++      test_normal( normalise( 10000, 10000, 10200 ) );
++      test_normal( normalise( 10000, 10000, 10201 ) );
++      test_normal( normalise( 10000, 10000, 10202 ) );
++      test_normal( normalise( 10000, 10000, 10203 ) );
++      test_normal( normalise( 10000, 10000, 10300 ) );
++
++
++      test2( normalise( 10000, 10000, 10000 ), normalise( 10000, 10000, 10001 ) );
++      test2( normalise( 10000, 10000, 10001 ), normalise( 10000, 10001, 10000 ) );
++}
++};
++
++TestNormalQuantisation g_testNormalQuantisation;
++
++
++#endif
++
++#if 0
++class TestSelectableObserver : public observer_template<const Selectable&>
++{
++public:
++void notify( const Selectable& arguments ){
++      bool bleh = arguments.isSelected();
++}
++};
++
++inline void test_bleh(){
++      TestSelectableObserver test;
++      ObservableSelectableInstance< SingleObservable< SelectionChangeCallback > > bleh;
++      bleh.attach( test );
++      bleh.setSelected( true );
++      bleh.detach( test );
++}
++
++class TestBleh
++{
++public:
++TestBleh(){
++      test_bleh();
++}
++};
++
++const TestBleh testbleh;
++#endif
++
++
++#if 0
++class TestRefcountedString
++{
++public:
++TestRefcountedString(){
++      {
++              // copy construct
++              SmartString string1( "string1" );
++              SmartString string2( string1 );
++              SmartString string3( string2 );
++      }
++      {
++              // refcounted assignment
++              SmartString string1( "string1" );
++              SmartString string2( "string2" );
++              string1 = string2;
++      }
++      {
++              // copy assignment
++              SmartString string1( "string1" );
++              SmartString string2( "string2" );
++              string1 = string2.c_str();
++      }
++      {
++              // self-assignment
++              SmartString string1( "string1" );
++              string1 = string1;
++      }
++      {
++              // self-assignment via another reference
++              SmartString string1( "string1" );
++              SmartString string2( string1 );
++              string1 = string2;
++      }
++}
++};
++
++const TestRefcountedString g_testRefcountedString;
++
++#endif
++
++void Select_MakeDetail(){
++      UndoableCommand undo( "brushSetDetail" );
++      Scene_BrushSetDetail_Selected( GlobalSceneGraph(), true );
++}
++
++void Select_MakeStructural(){
++      UndoableCommand undo( "brushClearDetail" );
++      Scene_BrushSetDetail_Selected( GlobalSceneGraph(), false );
++}
++
++class BrushMakeSided
++{
++std::size_t m_count;
++public:
++BrushMakeSided( std::size_t count )
++      : m_count( count ){
++}
++
++void set(){
++      Scene_BrushConstructPrefab( GlobalSceneGraph(), eBrushPrism, m_count, TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
++}
++
++typedef MemberCaller<BrushMakeSided, void(), &BrushMakeSided::set> SetCaller;
++};
++
++
++BrushMakeSided g_brushmakesided3( 3 );
++BrushMakeSided g_brushmakesided4( 4 );
++BrushMakeSided g_brushmakesided5( 5 );
++BrushMakeSided g_brushmakesided6( 6 );
++BrushMakeSided g_brushmakesided7( 7 );
++BrushMakeSided g_brushmakesided8( 8 );
++BrushMakeSided g_brushmakesided9( 9 );
++
++inline int axis_for_viewtype( int viewtype ){
++      switch ( viewtype )
++      {
++      case XY:
++              return 2;
++      case XZ:
++              return 1;
++      case YZ:
++              return 0;
++      }
++      return 2;
++}
++
++class BrushPrefab
++{
++EBrushPrefab m_type;
++public:
++BrushPrefab( EBrushPrefab type )
++      : m_type( type ){
++}
++
++void set(){
++      DoSides( m_type, axis_for_viewtype( GetViewAxis() ) );
++}
++
++typedef MemberCaller<BrushPrefab, void(), &BrushPrefab::set> SetCaller;
++};
++
++BrushPrefab g_brushprism( eBrushPrism );
++BrushPrefab g_brushcone( eBrushCone );
++BrushPrefab g_brushsphere( eBrushSphere );
++BrushPrefab g_brushrock( eBrushRock );
++
++
++void FlipClip();
++
++void SplitClip();
++
++void Clip();
++
++void OnClipMode( bool enable );
++
++bool ClipMode();
++
++
++void ClipSelected(){
++      if ( ClipMode() ) {
++              UndoableCommand undo( "clipperClip" );
++              Clip();
++      }
++}
++
++void SplitSelected(){
++      if ( ClipMode() ) {
++              UndoableCommand undo( "clipperSplit" );
++              SplitClip();
++      }
++}
++
++void FlipClipper(){
++      FlipClip();
++}
++
++
++Callback<void()> g_texture_lock_status_changed;
++ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_texdef_movelock_caller( g_brush_texturelock_enabled );
++ToggleItem g_texdef_movelock_item( g_texdef_movelock_caller );
++
++void Texdef_ToggleMoveLock(){
++      g_brush_texturelock_enabled = !g_brush_texturelock_enabled;
++      g_texdef_movelock_item.update();
++      g_texture_lock_status_changed();
++}
++
++
++void Brush_registerCommands(){
++      GlobalToggles_insert( "TogTexLock", makeCallbackF(Texdef_ToggleMoveLock), ToggleItem::AddCallbackCaller( g_texdef_movelock_item ), Accelerator( 'T', (GdkModifierType)GDK_SHIFT_MASK ) );
++
++      GlobalCommands_insert( "BrushPrism", BrushPrefab::SetCaller( g_brushprism ) );
++      GlobalCommands_insert( "BrushCone", BrushPrefab::SetCaller( g_brushcone ) );
++      GlobalCommands_insert( "BrushSphere", BrushPrefab::SetCaller( g_brushsphere ) );
++      GlobalCommands_insert( "BrushRock", BrushPrefab::SetCaller( g_brushrock ) );
++
++      GlobalCommands_insert( "Brush3Sided", BrushMakeSided::SetCaller( g_brushmakesided3 ), Accelerator( '3', (GdkModifierType)GDK_CONTROL_MASK ) );
++      GlobalCommands_insert( "Brush4Sided", BrushMakeSided::SetCaller( g_brushmakesided4 ), Accelerator( '4', (GdkModifierType)GDK_CONTROL_MASK ) );
++      GlobalCommands_insert( "Brush5Sided", BrushMakeSided::SetCaller( g_brushmakesided5 ), Accelerator( '5', (GdkModifierType)GDK_CONTROL_MASK ) );
++      GlobalCommands_insert( "Brush6Sided", BrushMakeSided::SetCaller( g_brushmakesided6 ), Accelerator( '6', (GdkModifierType)GDK_CONTROL_MASK ) );
++      GlobalCommands_insert( "Brush7Sided", BrushMakeSided::SetCaller( g_brushmakesided7 ), Accelerator( '7', (GdkModifierType)GDK_CONTROL_MASK ) );
++      GlobalCommands_insert( "Brush8Sided", BrushMakeSided::SetCaller( g_brushmakesided8 ), Accelerator( '8', (GdkModifierType)GDK_CONTROL_MASK ) );
++      GlobalCommands_insert( "Brush9Sided", BrushMakeSided::SetCaller( g_brushmakesided9 ), Accelerator( '9', (GdkModifierType)GDK_CONTROL_MASK ) );
++
++      GlobalCommands_insert( "ClipSelected", makeCallbackF(ClipSelected), Accelerator( GDK_KEY_Return ) );
++      GlobalCommands_insert( "SplitSelected", makeCallbackF(SplitSelected), Accelerator( GDK_KEY_Return, (GdkModifierType)GDK_SHIFT_MASK ) );
++      GlobalCommands_insert( "FlipClip", makeCallbackF(FlipClipper), Accelerator( GDK_KEY_Return, (GdkModifierType)GDK_CONTROL_MASK ) );
++
++      GlobalCommands_insert( "MakeDetail", makeCallbackF(Select_MakeDetail), Accelerator( 'M', (GdkModifierType)GDK_CONTROL_MASK ) );
++      GlobalCommands_insert( "MakeStructural", makeCallbackF(Select_MakeStructural), Accelerator( 'S', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
++}
++
++void Brush_constructMenu( ui::Menu menu ){
++      create_menu_item_with_mnemonic( menu, "Prism...", "BrushPrism" );
++      create_menu_item_with_mnemonic( menu, "Cone...", "BrushCone" );
++      create_menu_item_with_mnemonic( menu, "Sphere...", "BrushSphere" );
++      create_menu_item_with_mnemonic( menu, "Rock...", "BrushRock" );
++      menu_separator( menu );
++      {
++              auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "CSG" );
++              if ( g_Layout_enableDetachableMenus.m_value ) {
++                      menu_tearoff( menu_in_menu );
++              }
++              create_menu_item_with_mnemonic( menu_in_menu, "CSG _Subtract", "CSGSubtract" );
++              create_menu_item_with_mnemonic( menu_in_menu, "CSG _Merge", "CSGMerge" );
++              create_menu_item_with_mnemonic( menu_in_menu, "Make _Room", "CSGRoom" );
++              create_menu_item_with_mnemonic( menu_in_menu, "CSG _Tool", "CSGTool" );
++      }
++      menu_separator( menu );
++      {
++              auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "Clipper" );
++              if ( g_Layout_enableDetachableMenus.m_value ) {
++                      menu_tearoff( menu_in_menu );
++              }
++
++              create_menu_item_with_mnemonic( menu_in_menu, "Clip selection", "ClipSelected" );
++              create_menu_item_with_mnemonic( menu_in_menu, "Split selection", "SplitSelected" );
++              create_menu_item_with_mnemonic( menu_in_menu, "Flip Clip orientation", "FlipClip" );
++      }
++      menu_separator( menu );
++      create_menu_item_with_mnemonic( menu, "Make detail", "MakeDetail" );
++      create_menu_item_with_mnemonic( menu, "Make structural", "MakeStructural" );
++//    create_menu_item_with_mnemonic( menu, "Snap selection to _grid", "SnapToGrid" );
++
++      create_check_menu_item_with_mnemonic( menu, "Texture Lock", "TogTexLock" );
++      menu_separator( menu );
++      create_menu_item_with_mnemonic( menu, "Copy Face Texture", "FaceCopyTexture" );
++      create_menu_item_with_mnemonic( menu, "Paste Face Texture", "FacePasteTexture" );
++
++      command_connect_accelerator( "Brush3Sided" );
++      command_connect_accelerator( "Brush4Sided" );
++      command_connect_accelerator( "Brush5Sided" );
++      command_connect_accelerator( "Brush6Sided" );
++      command_connect_accelerator( "Brush7Sided" );
++      command_connect_accelerator( "Brush8Sided" );
++      command_connect_accelerator( "Brush9Sided" );
++}
index eafc1da093d41d8bf1044178492d1baefc792c74,01a7f491650c1210613fddbf6aa134d14aaed211..376a31fbedb791c970c297756c57a315acef4aca
@@@ -908,48 -913,48 +908,49 @@@ void KeyEvent_disconnect( const char* n
  }
  
  void CamWnd_registerCommands( CamWnd& camwnd ){
-       GlobalKeyEvents_insert( "CameraForward", Accelerator( GDK_KEY_Up ),
+       GlobalKeyEvents_insert( "CameraForward", accelerator_null(),
 -                                                      ReferenceCaller<camera_t, Camera_MoveForward_KeyDown>( camwnd.getCamera() ),
 -                                                      ReferenceCaller<camera_t, Camera_MoveForward_KeyUp>( camwnd.getCamera() )
 +                                                      ReferenceCaller<camera_t, void(), Camera_MoveForward_KeyDown>( camwnd.getCamera() ),
 +                                                      ReferenceCaller<camera_t, void(), Camera_MoveForward_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraBack", Accelerator( GDK_KEY_Down ),
+       GlobalKeyEvents_insert( "CameraBack", accelerator_null(),
 -                                                      ReferenceCaller<camera_t, Camera_MoveBack_KeyDown>( camwnd.getCamera() ),
 -                                                      ReferenceCaller<camera_t, Camera_MoveBack_KeyUp>( camwnd.getCamera() )
 +                                                      ReferenceCaller<camera_t, void(), Camera_MoveBack_KeyDown>( camwnd.getCamera() ),
 +                                                      ReferenceCaller<camera_t, void(), Camera_MoveBack_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraLeft", Accelerator( GDK_KEY_Left ),
+       GlobalKeyEvents_insert( "CameraLeft", accelerator_null(),
 -                                                      ReferenceCaller<camera_t, Camera_RotateLeft_KeyDown>( camwnd.getCamera() ),
 -                                                      ReferenceCaller<camera_t, Camera_RotateLeft_KeyUp>( camwnd.getCamera() )
 +                                                      ReferenceCaller<camera_t, void(), Camera_RotateLeft_KeyDown>( camwnd.getCamera() ),
 +                                                      ReferenceCaller<camera_t, void(), Camera_RotateLeft_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraRight", Accelerator( GDK_KEY_Right ),
+       GlobalKeyEvents_insert( "CameraRight", accelerator_null(),
 -                                                      ReferenceCaller<camera_t, Camera_RotateRight_KeyDown>( camwnd.getCamera() ),
 -                                                      ReferenceCaller<camera_t, Camera_RotateRight_KeyUp>( camwnd.getCamera() )
 +                                                      ReferenceCaller<camera_t, void(), Camera_RotateRight_KeyDown>( camwnd.getCamera() ),
 +                                                      ReferenceCaller<camera_t, void(), Camera_RotateRight_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraStrafeRight", Accelerator( GDK_KEY_period ),
+       GlobalKeyEvents_insert( "CameraStrafeRight", accelerator_null(),
 -                                                      ReferenceCaller<camera_t, Camera_MoveRight_KeyDown>( camwnd.getCamera() ),
 -                                                      ReferenceCaller<camera_t, Camera_MoveRight_KeyUp>( camwnd.getCamera() )
 +                                                      ReferenceCaller<camera_t, void(), Camera_MoveRight_KeyDown>( camwnd.getCamera() ),
 +                                                      ReferenceCaller<camera_t, void(), Camera_MoveRight_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraStrafeLeft", Accelerator( GDK_KEY_comma ),
+       GlobalKeyEvents_insert( "CameraStrafeLeft", accelerator_null(),
 -                                                      ReferenceCaller<camera_t, Camera_MoveLeft_KeyDown>( camwnd.getCamera() ),
 -                                                      ReferenceCaller<camera_t, Camera_MoveLeft_KeyUp>( camwnd.getCamera() )
 +                                                      ReferenceCaller<camera_t, void(), Camera_MoveLeft_KeyDown>( camwnd.getCamera() ),
 +                                                      ReferenceCaller<camera_t, void(), Camera_MoveLeft_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraUp", Accelerator( 'D' ),
+       GlobalKeyEvents_insert( "CameraUp", accelerator_null(),
 -                                                      ReferenceCaller<camera_t, Camera_MoveUp_KeyDown>( camwnd.getCamera() ),
 -                                                      ReferenceCaller<camera_t, Camera_MoveUp_KeyUp>( camwnd.getCamera() )
 +                                                      ReferenceCaller<camera_t, void(), Camera_MoveUp_KeyDown>( camwnd.getCamera() ),
 +                                                      ReferenceCaller<camera_t, void(), Camera_MoveUp_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraDown", Accelerator( 'C' ),
+       GlobalKeyEvents_insert( "CameraDown", accelerator_null(),
 -                                                      ReferenceCaller<camera_t, Camera_MoveDown_KeyDown>( camwnd.getCamera() ),
 -                                                      ReferenceCaller<camera_t, Camera_MoveDown_KeyUp>( camwnd.getCamera() )
 +                                                      ReferenceCaller<camera_t, void(), Camera_MoveDown_KeyDown>( camwnd.getCamera() ),
 +                                                      ReferenceCaller<camera_t, void(), Camera_MoveDown_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraAngleDown", Accelerator( 'A' ),
-                                                       ReferenceCaller<camera_t, void(), Camera_PitchDown_KeyDown>( camwnd.getCamera() ),
-                                                       ReferenceCaller<camera_t, void(), Camera_PitchDown_KeyUp>( camwnd.getCamera() )
-                                                       );
-       GlobalKeyEvents_insert( "CameraAngleUp", Accelerator( 'Z' ),
+       GlobalKeyEvents_insert( "CameraAngleUp", accelerator_null(),
 -                                                      ReferenceCaller<camera_t, Camera_PitchUp_KeyDown>( camwnd.getCamera() ),
 -                                                      ReferenceCaller<camera_t, Camera_PitchUp_KeyUp>( camwnd.getCamera() )
 +                                                      ReferenceCaller<camera_t, void(), Camera_PitchUp_KeyDown>( camwnd.getCamera() ),
 +                                                      ReferenceCaller<camera_t, void(), Camera_PitchUp_KeyUp>( camwnd.getCamera() )
                                                        );
 -                                                      ReferenceCaller<camera_t, Camera_PitchDown_KeyDown>( camwnd.getCamera() ),
 -                                                      ReferenceCaller<camera_t, Camera_PitchDown_KeyUp>( camwnd.getCamera() )
+       GlobalKeyEvents_insert( "CameraAngleDown", accelerator_null(),
++                                                      ReferenceCaller<camera_t, void(), Camera_PitchDown_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, void(), Camera_PitchDown_KeyUp>( camwnd.getCamera() )
+                                                       );
  
-       GlobalKeyEvents_insert( "CameraFreeMoveForward", Accelerator( GDK_KEY_Up ),
++
+       GlobalKeyEvents_insert( "CameraFreeMoveForward", accelerator_null(),
                                                        FreeMoveCameraMoveForwardKeyDownCaller( camwnd.getCamera() ),
                                                        FreeMoveCameraMoveForwardKeyUpCaller( camwnd.getCamera() )
                                                        );
                                                        FreeMoveCameraMoveDownKeyUpCaller( camwnd.getCamera() )
                                                        );
  
-       GlobalCommands_insert( "CameraForward", ReferenceCaller<camera_t, void(), Camera_MoveForward_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_Up ) );
-       GlobalCommands_insert( "CameraBack", ReferenceCaller<camera_t, void(), Camera_MoveBack_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_Down ) );
-       GlobalCommands_insert( "CameraLeft", ReferenceCaller<camera_t, void(), Camera_RotateLeft_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_Left ) );
-       GlobalCommands_insert( "CameraRight", ReferenceCaller<camera_t, void(), Camera_RotateRight_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_Right ) );
-       GlobalCommands_insert( "CameraStrafeRight", ReferenceCaller<camera_t, void(), Camera_MoveRight_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_period ) );
-       GlobalCommands_insert( "CameraStrafeLeft", ReferenceCaller<camera_t, void(), Camera_MoveLeft_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_comma ) );
 -      GlobalCommands_insert( "CameraForward", ReferenceCaller<camera_t, Camera_MoveForward_Discrete>( camwnd.getCamera() ) );
 -      GlobalCommands_insert( "CameraBack", ReferenceCaller<camera_t, Camera_MoveBack_Discrete>( camwnd.getCamera() ) );
 -      GlobalCommands_insert( "CameraLeft", ReferenceCaller<camera_t, Camera_RotateLeft_Discrete>( camwnd.getCamera() ) );
 -      GlobalCommands_insert( "CameraRight", ReferenceCaller<camera_t, Camera_RotateRight_Discrete>( camwnd.getCamera() ) );
 -      GlobalCommands_insert( "CameraStrafeRight", ReferenceCaller<camera_t, Camera_MoveRight_Discrete>( camwnd.getCamera() ) );
 -      GlobalCommands_insert( "CameraStrafeLeft", ReferenceCaller<camera_t, Camera_MoveLeft_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraForward", ReferenceCaller<camera_t, void(), Camera_MoveForward_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraBack", ReferenceCaller<camera_t, void(), Camera_MoveBack_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraLeft", ReferenceCaller<camera_t, void(), Camera_RotateLeft_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraRight", ReferenceCaller<camera_t, void(), Camera_RotateRight_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraStrafeRight", ReferenceCaller<camera_t, void(), Camera_MoveRight_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraStrafeLeft", ReferenceCaller<camera_t, void(), Camera_MoveLeft_Discrete>( camwnd.getCamera() ) );
  
-       GlobalCommands_insert( "CameraUp", ReferenceCaller<camera_t, void(), Camera_MoveUp_Discrete>( camwnd.getCamera() ), Accelerator( 'D' ) );
-       GlobalCommands_insert( "CameraDown", ReferenceCaller<camera_t, void(), Camera_MoveDown_Discrete>( camwnd.getCamera() ), Accelerator( 'C' ) );
-       GlobalCommands_insert( "CameraAngleUp", ReferenceCaller<camera_t, void(), Camera_PitchUp_Discrete>( camwnd.getCamera() ), Accelerator( 'A' ) );
-       GlobalCommands_insert( "CameraAngleDown", ReferenceCaller<camera_t, void(), Camera_PitchDown_Discrete>( camwnd.getCamera() ), Accelerator( 'Z' ) );
 -      GlobalCommands_insert( "CameraUp", ReferenceCaller<camera_t, Camera_MoveUp_Discrete>( camwnd.getCamera() ) );
 -      GlobalCommands_insert( "CameraDown", ReferenceCaller<camera_t, Camera_MoveDown_Discrete>( camwnd.getCamera() ) );
 -      GlobalCommands_insert( "CameraAngleUp", ReferenceCaller<camera_t, Camera_PitchUp_Discrete>( camwnd.getCamera() ) );
 -      GlobalCommands_insert( "CameraAngleDown", ReferenceCaller<camera_t, Camera_PitchDown_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraUp", ReferenceCaller<camera_t, void(), Camera_MoveUp_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraDown", ReferenceCaller<camera_t, void(), Camera_MoveDown_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraAngleUp", ReferenceCaller<camera_t, void(), Camera_PitchUp_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraAngleDown", ReferenceCaller<camera_t, void(), Camera_PitchDown_Discrete>( camwnd.getCamera() ) );
  }
  
  void CamWnd_Move_Enable( CamWnd& camwnd ){
@@@ -1998,20 -1997,23 +1999,23 @@@ void CamWnd_Construct()
        GlobalShortcuts_insert( "CameraFreeMoveLeft2", Accelerator( GDK_Left ) );
        GlobalShortcuts_insert( "CameraFreeMoveRight2", Accelerator( GDK_Right ) );
  
 -      GlobalToggles_insert( "ShowStats", FreeCaller<ShowStatsToggle>(), ToggleItem::AddCallbackCaller( g_show_stats ) );
 -
 -      GlobalPreferenceSystem().registerPreference( "ShowStats", BoolImportStringCaller( g_camwindow_globals_private.m_showStats ), BoolExportStringCaller( g_camwindow_globals_private.m_showStats ) );
 -      GlobalPreferenceSystem().registerPreference( "MoveSpeed", IntImportStringCaller( g_camwindow_globals_private.m_nMoveSpeed ), IntExportStringCaller( g_camwindow_globals_private.m_nMoveSpeed ) );
 -      GlobalPreferenceSystem().registerPreference( "CamLinkSpeed", BoolImportStringCaller( g_camwindow_globals_private.m_bCamLinkSpeed ), BoolExportStringCaller( g_camwindow_globals_private.m_bCamLinkSpeed ) );
 -      GlobalPreferenceSystem().registerPreference( "AngleSpeed", IntImportStringCaller( g_camwindow_globals_private.m_nAngleSpeed ), IntExportStringCaller( g_camwindow_globals_private.m_nAngleSpeed ) );
 -      GlobalPreferenceSystem().registerPreference( "CamInverseMouse", BoolImportStringCaller( g_camwindow_globals_private.m_bCamInverseMouse ), BoolExportStringCaller( g_camwindow_globals_private.m_bCamInverseMouse ) );
 -      GlobalPreferenceSystem().registerPreference( "CamDiscrete", makeBoolStringImportCallback( CamWndMoveDiscreteImportCaller() ), BoolExportStringCaller( g_camwindow_globals_private.m_bCamDiscrete ) );
 -      GlobalPreferenceSystem().registerPreference( "CubicClipping", BoolImportStringCaller( g_camwindow_globals_private.m_bCubicClipping ), BoolExportStringCaller( g_camwindow_globals_private.m_bCubicClipping ) );
 -      GlobalPreferenceSystem().registerPreference( "CubicScale", IntImportStringCaller( g_camwindow_globals.m_nCubicScale ), IntExportStringCaller( g_camwindow_globals.m_nCubicScale ) );
 -      GlobalPreferenceSystem().registerPreference( "SI_Colors4", Vector3ImportStringCaller( g_camwindow_globals.color_cameraback ), Vector3ExportStringCaller( g_camwindow_globals.color_cameraback ) );
 -      GlobalPreferenceSystem().registerPreference( "SI_Colors12", Vector3ImportStringCaller( g_camwindow_globals.color_selbrushes3d ), Vector3ExportStringCaller( g_camwindow_globals.color_selbrushes3d ) );
 -      GlobalPreferenceSystem().registerPreference( "CameraRenderMode", makeIntStringImportCallback( RenderModeImportCaller() ), makeIntStringExportCallback( RenderModeExportCaller() ) );
 -      GlobalPreferenceSystem().registerPreference( "StrafeMode", IntImportStringCaller( g_camwindow_globals_private.m_nStrafeMode ), IntExportStringCaller( g_camwindow_globals_private.m_nStrafeMode ) );
+       GlobalShortcuts_insert( "CameraFreeMoveUp", accelerator_null() );
+       GlobalShortcuts_insert( "CameraFreeMoveDown", accelerator_null() );
 +      GlobalToggles_insert( "ShowStats", makeCallbackF(ShowStatsToggle), ToggleItem::AddCallbackCaller( g_show_stats ) );
 +
 +      GlobalPreferenceSystem().registerPreference( "ShowStats", make_property_string( g_camwindow_globals_private.m_showStats ) );
 +      GlobalPreferenceSystem().registerPreference( "MoveSpeed", make_property_string( g_camwindow_globals_private.m_nMoveSpeed ) );
 +      GlobalPreferenceSystem().registerPreference( "CamLinkSpeed", make_property_string( g_camwindow_globals_private.m_bCamLinkSpeed ) );
 +      GlobalPreferenceSystem().registerPreference( "AngleSpeed", make_property_string( g_camwindow_globals_private.m_nAngleSpeed ) );
 +      GlobalPreferenceSystem().registerPreference( "CamInverseMouse", make_property_string( g_camwindow_globals_private.m_bCamInverseMouse ) );
 +      GlobalPreferenceSystem().registerPreference( "CamDiscrete", make_property_string<CamWnd_Move_Discrete>());
 +      GlobalPreferenceSystem().registerPreference( "CubicClipping", make_property_string( g_camwindow_globals_private.m_bCubicClipping ) );
 +      GlobalPreferenceSystem().registerPreference( "CubicScale", make_property_string( g_camwindow_globals.m_nCubicScale ) );
 +      GlobalPreferenceSystem().registerPreference( "SI_Colors4", make_property_string( g_camwindow_globals.color_cameraback ) );
 +      GlobalPreferenceSystem().registerPreference( "SI_Colors12", make_property_string( g_camwindow_globals.color_selbrushes3d ) );
 +      GlobalPreferenceSystem().registerPreference( "CameraRenderMode", make_property_string<RenderMode>() );
 +      GlobalPreferenceSystem().registerPreference( "StrafeMode", make_property_string( g_camwindow_globals_private.m_nStrafeMode ) );
  
        CamWnd_constructStatic();
  
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..f1ba3b4ccc6db879d30fde4bdd33c4d3465c2039
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2136 @@@
++/*
++   Copyright (C) 1999-2006 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
++ */
++
++//
++// Camera Window
++//
++// Leonardo Zide (leo@lokigames.com)
++//
++
++#include "camwindow.h"
++
++#include <gtk/gtk.h>
++#include <gdk/gdkkeysyms.h>
++
++#include "debugging/debugging.h"
++
++#include "iscenegraph.h"
++#include "irender.h"
++#include "igl.h"
++#include "icamera.h"
++#include "cullable.h"
++#include "renderable.h"
++#include "preferencesystem.h"
++
++#include "signal/signal.h"
++#include "container/array.h"
++#include "scenelib.h"
++#include "render.h"
++#include "cmdlib.h"
++#include "math/frustum.h"
++
++#include "gtkutil/widget.h"
++#include "gtkutil/button.h"
++#include "gtkutil/toolbar.h"
++#include "gtkutil/glwidget.h"
++#include "gtkutil/xorrectangle.h"
++#include "gtkmisc.h"
++#include "selection.h"
++#include "mainframe.h"
++#include "preferences.h"
++#include "commands.h"
++#include "xywindow.h"
++#include "windowobservers.h"
++#include "renderstate.h"
++
++#include "timer.h"
++
++Signal0 g_cameraMoved_callbacks;
++
++void AddCameraMovedCallback( const SignalHandler& handler ){
++      g_cameraMoved_callbacks.connectLast( handler );
++}
++
++void CameraMovedNotify(){
++      g_cameraMoved_callbacks();
++}
++
++
++struct camwindow_globals_private_t
++{
++      int m_nMoveSpeed;
++      bool m_bCamLinkSpeed;
++      int m_nAngleSpeed;
++      bool m_bCamInverseMouse;
++      bool m_bCamDiscrete;
++      bool m_bCubicClipping;
++      bool m_showStats;
++      int m_nStrafeMode;
++
++      camwindow_globals_private_t() :
++              m_nMoveSpeed( 100 ),
++              m_bCamLinkSpeed( true ),
++              m_nAngleSpeed( 3 ),
++              m_bCamInverseMouse( false ),
++              m_bCamDiscrete( true ),
++              m_bCubicClipping( false ),
++              m_showStats( true ),
++              m_nStrafeMode( 0 ){
++      }
++
++};
++
++camwindow_globals_private_t g_camwindow_globals_private;
++
++
++const Matrix4 g_opengl2radiant(
++      0, 0,-1, 0,
++      -1, 0, 0, 0,
++      0, 1, 0, 0,
++      0, 0, 0, 1
++      );
++
++const Matrix4 g_radiant2opengl(
++      0,-1, 0, 0,
++      0, 0, 1, 0,
++      -1, 0, 0, 0,
++      0, 0, 0, 1
++      );
++
++struct camera_t;
++void Camera_mouseMove( camera_t& camera, int x, int y );
++
++enum camera_draw_mode
++{
++      cd_wire,
++      cd_solid,
++      cd_texture,
++      cd_lighting
++};
++
++struct camera_t
++{
++      int width, height;
++
++      bool timing;
++
++      Vector3 origin;
++      Vector3 angles;
++
++      Vector3 color; // background
++
++      Vector3 forward, right; // move matrix (TTimo: used to have up but it was not updated)
++      Vector3 vup, vpn, vright; // view matrix (taken from the modelview matrix)
++
++      Matrix4 projection;
++      Matrix4 modelview;
++
++      bool m_strafe; // true when in strafemode toggled by the ctrl-key
++      bool m_strafe_forward; // true when in strafemode by ctrl-key and shift is pressed for forward strafing
++
++      unsigned int movementflags; // movement flags
++      Timer m_keycontrol_timer;
++      guint m_keymove_handler;
++
++
++      float fieldOfView;
++
++      DeferredMotionDelta m_mouseMove;
++
++      static void motionDelta( int x, int y, void* data ){
++              Camera_mouseMove( *reinterpret_cast<camera_t*>( data ), x, y );
++      }
++
++      View* m_view;
++      Callback<void()> m_update;
++
++      static camera_draw_mode draw_mode;
++
++      camera_t( View* view, const Callback<void()>& update )
++              : width( 0 ),
++              height( 0 ),
++              timing( false ),
++              origin( 0, 0, 0 ),
++              angles( 0, 0, 0 ),
++              color( 0, 0, 0 ),
++              movementflags( 0 ),
++              m_keymove_handler( 0 ),
++              fieldOfView( 110.0f ),
++              m_mouseMove( motionDelta, this ),
++              m_view( view ),
++              m_update( update ){
++      }
++};
++
++camera_draw_mode camera_t::draw_mode = cd_texture;
++
++inline Matrix4 projection_for_camera( float near_z, float far_z, float fieldOfView, int width, int height ){
++      const float half_width = static_cast<float>( near_z * tan( degrees_to_radians( fieldOfView * 0.5 ) ) );
++      const float half_height = half_width * ( static_cast<float>( height ) / static_cast<float>( width ) );
++
++      return matrix4_frustum(
++                         -half_width,
++                         half_width,
++                         -half_height,
++                         half_height,
++                         near_z,
++                         far_z
++                         );
++}
++
++float Camera_getFarClipPlane( camera_t& camera ){
++      return ( g_camwindow_globals_private.m_bCubicClipping ) ? pow( 2.0, ( g_camwindow_globals.m_nCubicScale + 7 ) / 2.0 ) : 32768.0f;
++}
++
++void Camera_updateProjection( camera_t& camera ){
++      float farClip = Camera_getFarClipPlane( camera );
++      camera.projection = projection_for_camera( farClip / 4096.0f, farClip, camera.fieldOfView, camera.width, camera.height );
++
++      camera.m_view->Construct( camera.projection, camera.modelview, camera.width, camera.height );
++}
++
++void Camera_updateVectors( camera_t& camera ){
++      for ( int i = 0 ; i < 3 ; i++ )
++      {
++              camera.vright[i] = camera.modelview[( i << 2 ) + 0];
++              camera.vup[i] = camera.modelview[( i << 2 ) + 1];
++              camera.vpn[i] = camera.modelview[( i << 2 ) + 2];
++      }
++}
++
++void Camera_updateModelview( camera_t& camera ){
++      camera.modelview = g_matrix4_identity;
++
++      // roll, pitch, yaw
++      Vector3 radiant_eulerXYZ( 0, -camera.angles[CAMERA_PITCH], camera.angles[CAMERA_YAW] );
++
++      matrix4_translate_by_vec3( camera.modelview, camera.origin );
++      matrix4_rotate_by_euler_xyz_degrees( camera.modelview, radiant_eulerXYZ );
++      matrix4_multiply_by_matrix4( camera.modelview, g_radiant2opengl );
++      matrix4_affine_invert( camera.modelview );
++
++      Camera_updateVectors( camera );
++
++      camera.m_view->Construct( camera.projection, camera.modelview, camera.width, camera.height );
++}
++
++
++void Camera_Move_updateAxes( camera_t& camera ){
++      double ya = degrees_to_radians( camera.angles[CAMERA_YAW] );
++
++      // the movement matrix is kept 2d
++      camera.forward[0] = static_cast<float>( cos( ya ) );
++      camera.forward[1] = static_cast<float>( sin( ya ) );
++      camera.forward[2] = 0;
++      camera.right[0] = camera.forward[1];
++      camera.right[1] = -camera.forward[0];
++}
++
++void Camera_Freemove_updateAxes( camera_t& camera ){
++      camera.right = camera.vright;
++      camera.forward = vector3_negated( camera.vpn );
++}
++
++const Vector3& Camera_getOrigin( camera_t& camera ){
++      return camera.origin;
++}
++
++void Camera_setOrigin( camera_t& camera, const Vector3& origin ){
++      camera.origin = origin;
++      Camera_updateModelview( camera );
++      camera.m_update();
++      CameraMovedNotify();
++}
++
++const Vector3& Camera_getAngles( camera_t& camera ){
++      return camera.angles;
++}
++
++void Camera_setAngles( camera_t& camera, const Vector3& angles ){
++      camera.angles = angles;
++      Camera_updateModelview( camera );
++      camera.m_update();
++      CameraMovedNotify();
++}
++
++
++void Camera_FreeMove( camera_t& camera, int dx, int dy ){
++      // free strafe mode, toggled by the ctrl key with optional shift for forward movement
++      if ( camera.m_strafe ) {
++              float strafespeed = 0.65f;
++
++              if ( g_camwindow_globals_private.m_bCamLinkSpeed ) {
++                      strafespeed = (float)g_camwindow_globals_private.m_nMoveSpeed / 100;
++              }
++
++              camera.origin -= camera.vright * strafespeed * dx;
++              if ( camera.m_strafe_forward ) {
++                      camera.origin -= camera.vpn * strafespeed * dy;
++              }
++              else{
++                      camera.origin += camera.vup * strafespeed * dy;
++              }
++      }
++      else // free rotation
++      {
++              const float dtime = 0.1f;
++
++              if ( g_camwindow_globals_private.m_bCamInverseMouse ) {
++                      camera.angles[CAMERA_PITCH] -= dy * dtime * g_camwindow_globals_private.m_nAngleSpeed;
++              }
++              else{
++                      camera.angles[CAMERA_PITCH] += dy * dtime * g_camwindow_globals_private.m_nAngleSpeed;
++              }
++
++              camera.angles[CAMERA_YAW] += dx * dtime * g_camwindow_globals_private.m_nAngleSpeed;
++
++              if ( camera.angles[CAMERA_PITCH] > 90 ) {
++                      camera.angles[CAMERA_PITCH] = 90;
++              }
++              else if ( camera.angles[CAMERA_PITCH] < -90 ) {
++                      camera.angles[CAMERA_PITCH] = -90;
++              }
++
++              if ( camera.angles[CAMERA_YAW] >= 360 ) {
++                      camera.angles[CAMERA_YAW] -= 360;
++              }
++              else if ( camera.angles[CAMERA_YAW] <= 0 ) {
++                      camera.angles[CAMERA_YAW] += 360;
++              }
++      }
++
++      Camera_updateModelview( camera );
++      Camera_Freemove_updateAxes( camera );
++}
++
++void Cam_MouseControl( camera_t& camera, int x, int y ){
++<<<<<<< HEAD
++      float xf = (float)( x - camera.width / 2 ) / ( camera.width / 2 );
++      float yf = (float)( y - camera.height / 2 ) / ( camera.height / 2 );
++=======
++//    int xl, xh;
++//    int yl, yh;
++      float xf, yf;
++
++      xf = (float)( x - camera.width / 2 ) / ( camera.width / 2 );
++      yf = (float)( y - camera.height / 2 ) / ( camera.height / 2 );
++
++//    xl = camera.width / 3;
++//    xh = xl * 2;
++//    yl = camera.height / 3;
++//    yh = yl * 2;
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++
++      xf *= 1.0f - fabsf( yf );
++      if ( xf < 0 ) {
++              xf += 0.1f;
++              if ( xf > 0 ) {
++                      xf = 0;
++              }
++      }
++      else
++      {
++              xf -= 0.1f;
++              if ( xf < 0 ) {
++                      xf = 0;
++              }
++      }
++
++      vector3_add( camera.origin, vector3_scaled( camera.forward, yf * 0.1f * g_camwindow_globals_private.m_nMoveSpeed ) );
++      camera.angles[CAMERA_YAW] += xf * -0.1f * g_camwindow_globals_private.m_nAngleSpeed;
++
++      Camera_updateModelview( camera );
++}
++
++void Camera_mouseMove( camera_t& camera, int x, int y ){
++      //globalOutputStream() << "mousemove... ";
++      Camera_FreeMove( camera, -x, -y );
++      camera.m_update();
++      CameraMovedNotify();
++}
++
++const unsigned int MOVE_NONE = 0;
++const unsigned int MOVE_FORWARD = 1 << 0;
++const unsigned int MOVE_BACK = 1 << 1;
++const unsigned int MOVE_ROTRIGHT = 1 << 2;
++const unsigned int MOVE_ROTLEFT = 1 << 3;
++const unsigned int MOVE_STRAFERIGHT = 1 << 4;
++const unsigned int MOVE_STRAFELEFT = 1 << 5;
++const unsigned int MOVE_UP = 1 << 6;
++const unsigned int MOVE_DOWN = 1 << 7;
++const unsigned int MOVE_PITCHUP = 1 << 8;
++const unsigned int MOVE_PITCHDOWN = 1 << 9;
++const unsigned int MOVE_ALL = MOVE_FORWARD | MOVE_BACK | MOVE_ROTRIGHT | MOVE_ROTLEFT | MOVE_STRAFERIGHT | MOVE_STRAFELEFT | MOVE_UP | MOVE_DOWN | MOVE_PITCHUP | MOVE_PITCHDOWN;
++
++void Cam_KeyControl( camera_t& camera, float dtime ){
++      // Update angles
++      if ( camera.movementflags & MOVE_ROTLEFT ) {
++              camera.angles[CAMERA_YAW] += 15 * dtime * g_camwindow_globals_private.m_nAngleSpeed;
++      }
++      if ( camera.movementflags & MOVE_ROTRIGHT ) {
++              camera.angles[CAMERA_YAW] -= 15 * dtime * g_camwindow_globals_private.m_nAngleSpeed;
++      }
++      if ( camera.movementflags & MOVE_PITCHUP ) {
++              camera.angles[CAMERA_PITCH] += 15 * dtime * g_camwindow_globals_private.m_nAngleSpeed;
++              if ( camera.angles[CAMERA_PITCH] > 90 ) {
++                      camera.angles[CAMERA_PITCH] = 90;
++              }
++      }
++      if ( camera.movementflags & MOVE_PITCHDOWN ) {
++              camera.angles[CAMERA_PITCH] -= 15 * dtime * g_camwindow_globals_private.m_nAngleSpeed;
++              if ( camera.angles[CAMERA_PITCH] < -90 ) {
++                      camera.angles[CAMERA_PITCH] = -90;
++              }
++      }
++
++      Camera_updateModelview( camera );
++      Camera_Freemove_updateAxes( camera );
++
++      // Update position
++      if ( camera.movementflags & MOVE_FORWARD ) {
++              vector3_add( camera.origin, vector3_scaled( camera.forward, dtime * g_camwindow_globals_private.m_nMoveSpeed ) );
++      }
++      if ( camera.movementflags & MOVE_BACK ) {
++              vector3_add( camera.origin, vector3_scaled( camera.forward, -dtime * g_camwindow_globals_private.m_nMoveSpeed ) );
++      }
++      if ( camera.movementflags & MOVE_STRAFELEFT ) {
++              vector3_add( camera.origin, vector3_scaled( camera.right, -dtime * g_camwindow_globals_private.m_nMoveSpeed ) );
++      }
++      if ( camera.movementflags & MOVE_STRAFERIGHT ) {
++              vector3_add( camera.origin, vector3_scaled( camera.right, dtime * g_camwindow_globals_private.m_nMoveSpeed ) );
++      }
++      if ( camera.movementflags & MOVE_UP ) {
++              vector3_add( camera.origin, vector3_scaled( g_vector3_axis_z, dtime * g_camwindow_globals_private.m_nMoveSpeed ) );
++      }
++      if ( camera.movementflags & MOVE_DOWN ) {
++              vector3_add( camera.origin, vector3_scaled( g_vector3_axis_z, -dtime * g_camwindow_globals_private.m_nMoveSpeed ) );
++      }
++
++      Camera_updateModelview( camera );
++}
++
++void Camera_keyMove( camera_t& camera ){
++      camera.m_mouseMove.flush();
++
++      //globalOutputStream() << "keymove... ";
++      float time_seconds = camera.m_keycontrol_timer.elapsed_msec() / static_cast<float>( msec_per_sec );
++      camera.m_keycontrol_timer.start();
++      if ( time_seconds > 0.05f ) {
++              time_seconds = 0.05f; // 20fps
++      }
++      Cam_KeyControl( camera, time_seconds * 5.0f );
++
++      camera.m_update();
++      CameraMovedNotify();
++}
++
++gboolean camera_keymove( gpointer data ){
++      Camera_keyMove( *reinterpret_cast<camera_t*>( data ) );
++      return TRUE;
++}
++
++void Camera_setMovementFlags( camera_t& camera, unsigned int mask ){
++      if ( ( ~camera.movementflags & mask ) != 0 && camera.movementflags == 0 ) {
++              camera.m_keymove_handler = g_idle_add( camera_keymove, &camera );
++      }
++      camera.movementflags |= mask;
++}
++void Camera_clearMovementFlags( camera_t& camera, unsigned int mask ){
++      if ( ( camera.movementflags & ~mask ) == 0 && camera.movementflags != 0 ) {
++              g_source_remove( camera.m_keymove_handler );
++              camera.m_keymove_handler = 0;
++      }
++      camera.movementflags &= ~mask;
++}
++
++void Camera_MoveForward_KeyDown( camera_t& camera ){
++      Camera_setMovementFlags( camera, MOVE_FORWARD );
++}
++void Camera_MoveForward_KeyUp( camera_t& camera ){
++      Camera_clearMovementFlags( camera, MOVE_FORWARD );
++}
++void Camera_MoveBack_KeyDown( camera_t& camera ){
++      Camera_setMovementFlags( camera, MOVE_BACK );
++}
++void Camera_MoveBack_KeyUp( camera_t& camera ){
++      Camera_clearMovementFlags( camera, MOVE_BACK );
++}
++
++void Camera_MoveLeft_KeyDown( camera_t& camera ){
++      Camera_setMovementFlags( camera, MOVE_STRAFELEFT );
++}
++void Camera_MoveLeft_KeyUp( camera_t& camera ){
++      Camera_clearMovementFlags( camera, MOVE_STRAFELEFT );
++}
++void Camera_MoveRight_KeyDown( camera_t& camera ){
++      Camera_setMovementFlags( camera, MOVE_STRAFERIGHT );
++}
++void Camera_MoveRight_KeyUp( camera_t& camera ){
++      Camera_clearMovementFlags( camera, MOVE_STRAFERIGHT );
++}
++
++void Camera_MoveUp_KeyDown( camera_t& camera ){
++      Camera_setMovementFlags( camera, MOVE_UP );
++}
++void Camera_MoveUp_KeyUp( camera_t& camera ){
++      Camera_clearMovementFlags( camera, MOVE_UP );
++}
++void Camera_MoveDown_KeyDown( camera_t& camera ){
++      Camera_setMovementFlags( camera, MOVE_DOWN );
++}
++void Camera_MoveDown_KeyUp( camera_t& camera ){
++      Camera_clearMovementFlags( camera, MOVE_DOWN );
++}
++
++void Camera_RotateLeft_KeyDown( camera_t& camera ){
++      Camera_setMovementFlags( camera, MOVE_ROTLEFT );
++}
++void Camera_RotateLeft_KeyUp( camera_t& camera ){
++      Camera_clearMovementFlags( camera, MOVE_ROTLEFT );
++}
++void Camera_RotateRight_KeyDown( camera_t& camera ){
++      Camera_setMovementFlags( camera, MOVE_ROTRIGHT );
++}
++void Camera_RotateRight_KeyUp( camera_t& camera ){
++      Camera_clearMovementFlags( camera, MOVE_ROTRIGHT );
++}
++
++void Camera_PitchUp_KeyDown( camera_t& camera ){
++      Camera_setMovementFlags( camera, MOVE_PITCHUP );
++}
++void Camera_PitchUp_KeyUp( camera_t& camera ){
++      Camera_clearMovementFlags( camera, MOVE_PITCHUP );
++}
++void Camera_PitchDown_KeyDown( camera_t& camera ){
++      Camera_setMovementFlags( camera, MOVE_PITCHDOWN );
++}
++void Camera_PitchDown_KeyUp( camera_t& camera ){
++      Camera_clearMovementFlags( camera, MOVE_PITCHDOWN );
++}
++
++
++typedef ReferenceCaller<camera_t, void(), &Camera_MoveForward_KeyDown> FreeMoveCameraMoveForwardKeyDownCaller;
++typedef ReferenceCaller<camera_t, void(), &Camera_MoveForward_KeyUp> FreeMoveCameraMoveForwardKeyUpCaller;
++typedef ReferenceCaller<camera_t, void(), &Camera_MoveBack_KeyDown> FreeMoveCameraMoveBackKeyDownCaller;
++typedef ReferenceCaller<camera_t, void(), &Camera_MoveBack_KeyUp> FreeMoveCameraMoveBackKeyUpCaller;
++typedef ReferenceCaller<camera_t, void(), &Camera_MoveLeft_KeyDown> FreeMoveCameraMoveLeftKeyDownCaller;
++typedef ReferenceCaller<camera_t, void(), &Camera_MoveLeft_KeyUp> FreeMoveCameraMoveLeftKeyUpCaller;
++typedef ReferenceCaller<camera_t, void(), &Camera_MoveRight_KeyDown> FreeMoveCameraMoveRightKeyDownCaller;
++typedef ReferenceCaller<camera_t, void(), &Camera_MoveRight_KeyUp> FreeMoveCameraMoveRightKeyUpCaller;
++typedef ReferenceCaller<camera_t, void(), &Camera_MoveUp_KeyDown> FreeMoveCameraMoveUpKeyDownCaller;
++typedef ReferenceCaller<camera_t, void(), &Camera_MoveUp_KeyUp> FreeMoveCameraMoveUpKeyUpCaller;
++typedef ReferenceCaller<camera_t, void(), &Camera_MoveDown_KeyDown> FreeMoveCameraMoveDownKeyDownCaller;
++typedef ReferenceCaller<camera_t, void(), &Camera_MoveDown_KeyUp> FreeMoveCameraMoveDownKeyUpCaller;
++
++
++const float SPEED_MOVE = 32;
++const float SPEED_TURN = 22.5;
++const float MIN_CAM_SPEED = 10;
++const float MAX_CAM_SPEED = 610;
++const float CAM_SPEED_STEP = 50;
++
++void Camera_MoveForward_Discrete( camera_t& camera ){
++      Camera_Move_updateAxes( camera );
++      Camera_setOrigin( camera, vector3_added( Camera_getOrigin( camera ), vector3_scaled( camera.forward, SPEED_MOVE ) ) );
++}
++void Camera_MoveBack_Discrete( camera_t& camera ){
++      Camera_Move_updateAxes( camera );
++      Camera_setOrigin( camera, vector3_added( Camera_getOrigin( camera ), vector3_scaled( camera.forward, -SPEED_MOVE ) ) );
++}
++
++void Camera_MoveUp_Discrete( camera_t& camera ){
++      Vector3 origin( Camera_getOrigin( camera ) );
++      origin[2] += SPEED_MOVE;
++      Camera_setOrigin( camera, origin );
++}
++void Camera_MoveDown_Discrete( camera_t& camera ){
++      Vector3 origin( Camera_getOrigin( camera ) );
++      origin[2] -= SPEED_MOVE;
++      Camera_setOrigin( camera, origin );
++}
++
++void Camera_MoveLeft_Discrete( camera_t& camera ){
++      Camera_Move_updateAxes( camera );
++      Camera_setOrigin( camera, vector3_added( Camera_getOrigin( camera ), vector3_scaled( camera.right, -SPEED_MOVE ) ) );
++}
++void Camera_MoveRight_Discrete( camera_t& camera ){
++      Camera_Move_updateAxes( camera );
++      Camera_setOrigin( camera, vector3_added( Camera_getOrigin( camera ), vector3_scaled( camera.right, SPEED_MOVE ) ) );
++}
++
++void Camera_RotateLeft_Discrete( camera_t& camera ){
++      Vector3 angles( Camera_getAngles( camera ) );
++      angles[CAMERA_YAW] += SPEED_TURN;
++      Camera_setAngles( camera, angles );
++}
++void Camera_RotateRight_Discrete( camera_t& camera ){
++      Vector3 angles( Camera_getAngles( camera ) );
++      angles[CAMERA_YAW] -= SPEED_TURN;
++      Camera_setAngles( camera, angles );
++}
++
++void Camera_PitchUp_Discrete( camera_t& camera ){
++      Vector3 angles( Camera_getAngles( camera ) );
++      angles[CAMERA_PITCH] += SPEED_TURN;
++      if ( angles[CAMERA_PITCH] > 90 ) {
++              angles[CAMERA_PITCH] = 90;
++      }
++      Camera_setAngles( camera, angles );
++}
++void Camera_PitchDown_Discrete( camera_t& camera ){
++      Vector3 angles( Camera_getAngles( camera ) );
++      angles[CAMERA_PITCH] -= SPEED_TURN;
++      if ( angles[CAMERA_PITCH] < -90 ) {
++              angles[CAMERA_PITCH] = -90;
++      }
++      Camera_setAngles( camera, angles );
++}
++
++
++class RadiantCameraView : public CameraView
++{
++camera_t& m_camera;
++View* m_view;
++Callback<void()> m_update;
++public:
++RadiantCameraView( camera_t& camera, View* view, const Callback<void()>& update ) : m_camera( camera ), m_view( view ), m_update( update ){
++}
++void update(){
++      m_view->Construct( m_camera.projection, m_camera.modelview, m_camera.width, m_camera.height );
++      m_update();
++}
++void setModelview( const Matrix4& modelview ){
++      m_camera.modelview = modelview;
++      matrix4_multiply_by_matrix4( m_camera.modelview, g_radiant2opengl );
++      matrix4_affine_invert( m_camera.modelview );
++      Camera_updateVectors( m_camera );
++      update();
++}
++void setFieldOfView( float fieldOfView ){
++      float farClip = Camera_getFarClipPlane( m_camera );
++      m_camera.projection = projection_for_camera( farClip / 4096.0f, farClip, fieldOfView, m_camera.width, m_camera.height );
++      update();
++}
++};
++
++
++void Camera_motionDelta( int x, int y, unsigned int state, void* data ){
++      camera_t* cam = reinterpret_cast<camera_t*>( data );
++
++      cam->m_mouseMove.motion_delta( x, y, state );
++
++      switch ( g_camwindow_globals_private.m_nStrafeMode )
++      {
++      case 0:
++              cam->m_strafe = ( state & GDK_CONTROL_MASK ) != 0;
++              if ( cam->m_strafe ) {
++                      cam->m_strafe_forward = ( state & GDK_SHIFT_MASK ) != 0;
++              }
++              else{
++                      cam->m_strafe_forward = false;
++              }
++              break;
++      case 1:
++              cam->m_strafe = ( state & GDK_CONTROL_MASK ) != 0 && ( state & GDK_SHIFT_MASK ) == 0;
++              cam->m_strafe_forward = false;
++              break;
++      case 2:
++              cam->m_strafe = ( state & GDK_CONTROL_MASK ) != 0 && ( state & GDK_SHIFT_MASK ) == 0;
++              cam->m_strafe_forward = cam->m_strafe;
++              break;
++      }
++}
++
++class CamWnd
++{
++View m_view;
++camera_t m_Camera;
++RadiantCameraView m_cameraview;
++#if 0
++int m_PositionDragCursorX;
++int m_PositionDragCursorY;
++#endif
++
++guint m_freemove_handle_focusout;
++
++static Shader* m_state_select1;
++static Shader* m_state_select2;
++
++FreezePointer m_freezePointer;
++
++public:
++ui::GLArea m_gl_widget;
++ui::Window m_parent{ui::null};
++
++SelectionSystemWindowObserver* m_window_observer;
++XORRectangle m_XORRectangle;
++
++DeferredDraw m_deferredDraw;
++DeferredMotion m_deferred_motion;
++
++guint m_selection_button_press_handler;
++guint m_selection_button_release_handler;
++guint m_selection_motion_handler;
++
++guint m_freelook_button_press_handler;
++
++guint m_sizeHandler;
++guint m_exposeHandler;
++
++CamWnd();
++~CamWnd();
++
++bool m_drawing;
++void queue_draw(){
++      //ASSERT_MESSAGE(!m_drawing, "CamWnd::queue_draw(): called while draw is already in progress");
++      if ( m_drawing ) {
++              return;
++      }
++      //globalOutputStream() << "queue... ";
++      m_deferredDraw.draw();
++}
++void draw();
++
++static void captureStates(){
++      m_state_select1 = GlobalShaderCache().capture( "$CAM_HIGHLIGHT" );
++      m_state_select2 = GlobalShaderCache().capture( "$CAM_OVERLAY" );
++}
++static void releaseStates(){
++      GlobalShaderCache().release( "$CAM_HIGHLIGHT" );
++      GlobalShaderCache().release( "$CAM_OVERLAY" );
++}
++
++camera_t& getCamera(){
++      return m_Camera;
++}
++
++void BenchMark();
++void Cam_ChangeFloor( bool up );
++
++void DisableFreeMove();
++void EnableFreeMove();
++bool m_bFreeMove;
++
++CameraView& getCameraView(){
++      return m_cameraview;
++}
++
++private:
++void Cam_Draw();
++};
++
++typedef MemberCaller<CamWnd, void(), &CamWnd::queue_draw> CamWndQueueDraw;
++
++Shader* CamWnd::m_state_select1 = 0;
++Shader* CamWnd::m_state_select2 = 0;
++
++CamWnd* NewCamWnd(){
++      return new CamWnd;
++}
++void DeleteCamWnd( CamWnd* camwnd ){
++      delete camwnd;
++}
++
++void CamWnd_constructStatic(){
++      CamWnd::captureStates();
++}
++
++void CamWnd_destroyStatic(){
++      CamWnd::releaseStates();
++}
++
++static CamWnd* g_camwnd = 0;
++
++void GlobalCamera_setCamWnd( CamWnd& camwnd ){
++      g_camwnd = &camwnd;
++}
++
++
++ui::GLArea CamWnd_getWidget( CamWnd& camwnd ){
++      return camwnd.m_gl_widget;
++}
++
++ui::Window CamWnd_getParent( CamWnd& camwnd ){
++      return camwnd.m_parent;
++}
++
++ToggleShown g_camera_shown( true );
++
++void CamWnd_setParent( CamWnd& camwnd, ui::Window parent ){
++      camwnd.m_parent = parent;
++      g_camera_shown.connect( camwnd.m_parent );
++}
++
++void CamWnd_Update( CamWnd& camwnd ){
++      camwnd.queue_draw();
++}
++
++
++
++camwindow_globals_t g_camwindow_globals;
++
++const Vector3& Camera_getOrigin( CamWnd& camwnd ){
++      return Camera_getOrigin( camwnd.getCamera() );
++}
++
++void Camera_setOrigin( CamWnd& camwnd, const Vector3& origin ){
++      Camera_setOrigin( camwnd.getCamera(), origin );
++}
++
++const Vector3& Camera_getAngles( CamWnd& camwnd ){
++      return Camera_getAngles( camwnd.getCamera() );
++}
++
++void Camera_setAngles( CamWnd& camwnd, const Vector3& angles ){
++      Camera_setAngles( camwnd.getCamera(), angles );
++}
++
++
++// =============================================================================
++// CamWnd class
++
++gboolean enable_freelook_button_press( ui::Widget widget, GdkEventButton* event, CamWnd* camwnd ){
++      if ( event->type == GDK_BUTTON_PRESS && event->button == 3 && modifiers_for_state( event->state ) == c_modifierNone ) {
++              camwnd->EnableFreeMove();
++              return TRUE;
++      }
++      return FALSE;
++}
++
++gboolean disable_freelook_button_press( ui::Widget widget, GdkEventButton* event, CamWnd* camwnd ){
++      if ( event->type == GDK_BUTTON_PRESS && event->button == 3 && modifiers_for_state( event->state ) == c_modifierNone ) {
++              camwnd->DisableFreeMove();
++              return TRUE;
++      }
++      return FALSE;
++}
++
++#if 0
++gboolean mousecontrol_button_press( ui::Widget widget, GdkEventButton* event, CamWnd* camwnd ){
++      if ( event->type == GDK_BUTTON_PRESS && event->button == 3 ) {
++              Cam_MouseControl( camwnd->getCamera(), event->x, widget->allocation.height - 1 - event->y );
++      }
++      return FALSE;
++}
++#endif
++
++void camwnd_update_xor_rectangle( CamWnd& self, rect_t area ){
++      if ( self.m_gl_widget.visible() ) {
++              self.m_XORRectangle.set( rectangle_from_area( area.min, area.max, self.getCamera().width, self.getCamera().height ) );
++      }
++}
++
++
++gboolean selection_button_press( ui::Widget widget, GdkEventButton* event, WindowObserver* observer ){
++      if ( event->type == GDK_BUTTON_PRESS ) {
++              observer->onMouseDown( WindowVector_forDouble( event->x, event->y ), button_for_button( event->button ), modifiers_for_state( event->state ) );
++      }
++      return FALSE;
++}
++
++gboolean selection_button_release( ui::Widget widget, GdkEventButton* event, WindowObserver* observer ){
++      if ( event->type == GDK_BUTTON_RELEASE ) {
++              observer->onMouseUp( WindowVector_forDouble( event->x, event->y ), button_for_button( event->button ), modifiers_for_state( event->state ) );
++      }
++      return FALSE;
++}
++
++void selection_motion( gdouble x, gdouble y, guint state, void* data ){
++      //globalOutputStream() << "motion... ";
++      reinterpret_cast<WindowObserver*>( data )->onMouseMotion( WindowVector_forDouble( x, y ), modifiers_for_state( state ) );
++}
++
++inline WindowVector windowvector_for_widget_centre( ui::Widget widget ){
++      auto allocation = widget.dimensions();
++      return WindowVector( static_cast<float>( allocation.width / 2 ), static_cast<float>(allocation.height / 2 ) );
++}
++
++gboolean selection_button_press_freemove( ui::Widget widget, GdkEventButton* event, WindowObserver* observer ){
++      if ( event->type == GDK_BUTTON_PRESS ) {
++              observer->onMouseDown( windowvector_for_widget_centre( widget ), button_for_button( event->button ), modifiers_for_state( event->state ) );
++      }
++      return FALSE;
++}
++
++gboolean selection_button_release_freemove( ui::Widget widget, GdkEventButton* event, WindowObserver* observer ){
++      if ( event->type == GDK_BUTTON_RELEASE ) {
++              observer->onMouseUp( windowvector_for_widget_centre( widget ), button_for_button( event->button ), modifiers_for_state( event->state ) );
++      }
++      return FALSE;
++}
++
++gboolean selection_motion_freemove( ui::Widget widget, GdkEventMotion *event, WindowObserver* observer ){
++      observer->onMouseMotion( windowvector_for_widget_centre( widget ), modifiers_for_state( event->state ) );
++      return FALSE;
++}
++
++gboolean wheelmove_scroll( ui::Widget widget, GdkEventScroll* event, CamWnd* camwnd ){
++      if ( event->direction == GDK_SCROLL_UP ) {
++              Camera_Freemove_updateAxes( camwnd->getCamera() );
++              Camera_setOrigin( *camwnd, vector3_added( Camera_getOrigin( *camwnd ), vector3_scaled( camwnd->getCamera().forward, static_cast<float>( g_camwindow_globals_private.m_nMoveSpeed ) ) ) );
++      }
++      else if ( event->direction == GDK_SCROLL_DOWN ) {
++              Camera_Freemove_updateAxes( camwnd->getCamera() );
++              Camera_setOrigin( *camwnd, vector3_added( Camera_getOrigin( *camwnd ), vector3_scaled( camwnd->getCamera().forward, -static_cast<float>( g_camwindow_globals_private.m_nMoveSpeed ) ) ) );
++      }
++
++      return FALSE;
++}
++
++gboolean camera_size_allocate( ui::Widget widget, GtkAllocation* allocation, CamWnd* camwnd ){
++      camwnd->getCamera().width = allocation->width;
++      camwnd->getCamera().height = allocation->height;
++      Camera_updateProjection( camwnd->getCamera() );
++      camwnd->m_window_observer->onSizeChanged( camwnd->getCamera().width, camwnd->getCamera().height );
++      camwnd->queue_draw();
++      return FALSE;
++}
++
++gboolean camera_expose( ui::Widget widget, GdkEventExpose* event, gpointer data ){
++      reinterpret_cast<CamWnd*>( data )->draw();
++      return FALSE;
++}
++
++void KeyEvent_connect( const char* name ){
++      const KeyEvent& keyEvent = GlobalKeyEvents_find( name );
++      keydown_accelerators_add( keyEvent.m_accelerator, keyEvent.m_keyDown );
++      keyup_accelerators_add( keyEvent.m_accelerator, keyEvent.m_keyUp );
++}
++
++void KeyEvent_disconnect( const char* name ){
++      const KeyEvent& keyEvent = GlobalKeyEvents_find( name );
++      keydown_accelerators_remove( keyEvent.m_accelerator );
++      keyup_accelerators_remove( keyEvent.m_accelerator );
++}
++
++void CamWnd_registerCommands( CamWnd& camwnd ){
++<<<<<<< HEAD
++      GlobalKeyEvents_insert( "CameraForward", Accelerator( GDK_KEY_Up ),
++                                                      ReferenceCaller<camera_t, void(), Camera_MoveForward_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, void(), Camera_MoveForward_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraBack", Accelerator( GDK_KEY_Down ),
++                                                      ReferenceCaller<camera_t, void(), Camera_MoveBack_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, void(), Camera_MoveBack_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraLeft", Accelerator( GDK_KEY_Left ),
++                                                      ReferenceCaller<camera_t, void(), Camera_RotateLeft_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, void(), Camera_RotateLeft_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraRight", Accelerator( GDK_KEY_Right ),
++                                                      ReferenceCaller<camera_t, void(), Camera_RotateRight_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, void(), Camera_RotateRight_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraStrafeRight", Accelerator( GDK_KEY_period ),
++                                                      ReferenceCaller<camera_t, void(), Camera_MoveRight_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, void(), Camera_MoveRight_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraStrafeLeft", Accelerator( GDK_KEY_comma ),
++                                                      ReferenceCaller<camera_t, void(), Camera_MoveLeft_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, void(), Camera_MoveLeft_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraUp", Accelerator( 'D' ),
++                                                      ReferenceCaller<camera_t, void(), Camera_MoveUp_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, void(), Camera_MoveUp_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraDown", Accelerator( 'C' ),
++                                                      ReferenceCaller<camera_t, void(), Camera_MoveDown_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, void(), Camera_MoveDown_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraAngleDown", Accelerator( 'A' ),
++                                                      ReferenceCaller<camera_t, void(), Camera_PitchDown_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, void(), Camera_PitchDown_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraAngleUp", Accelerator( 'Z' ),
++                                                      ReferenceCaller<camera_t, void(), Camera_PitchUp_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, void(), Camera_PitchUp_KeyUp>( camwnd.getCamera() )
++=======
++      GlobalKeyEvents_insert( "CameraForward", accelerator_null(),
++                                                      ReferenceCaller<camera_t, Camera_MoveForward_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, Camera_MoveForward_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraBack", accelerator_null(),
++                                                      ReferenceCaller<camera_t, Camera_MoveBack_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, Camera_MoveBack_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraLeft", accelerator_null(),
++                                                      ReferenceCaller<camera_t, Camera_RotateLeft_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, Camera_RotateLeft_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraRight", accelerator_null(),
++                                                      ReferenceCaller<camera_t, Camera_RotateRight_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, Camera_RotateRight_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraStrafeRight", accelerator_null(),
++                                                      ReferenceCaller<camera_t, Camera_MoveRight_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, Camera_MoveRight_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraStrafeLeft", accelerator_null(),
++                                                      ReferenceCaller<camera_t, Camera_MoveLeft_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, Camera_MoveLeft_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraUp", accelerator_null(),
++                                                      ReferenceCaller<camera_t, Camera_MoveUp_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, Camera_MoveUp_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraDown", accelerator_null(),
++                                                      ReferenceCaller<camera_t, Camera_MoveDown_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, Camera_MoveDown_KeyUp>( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraAngleUp", accelerator_null(),
++                                                      ReferenceCaller<camera_t, Camera_PitchUp_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, Camera_PitchUp_KeyUp>( camwnd.getCamera() )
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++                                                      );
++      GlobalKeyEvents_insert( "CameraAngleDown", accelerator_null(),
++                                                      ReferenceCaller<camera_t, Camera_PitchDown_KeyDown>( camwnd.getCamera() ),
++                                                      ReferenceCaller<camera_t, Camera_PitchDown_KeyUp>( camwnd.getCamera() )
++                                                      );
++
++<<<<<<< HEAD
++      GlobalKeyEvents_insert( "CameraFreeMoveForward", Accelerator( GDK_KEY_Up ),
++                                                      FreeMoveCameraMoveForwardKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveForwardKeyUpCaller( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraFreeMoveBack", Accelerator( GDK_KEY_Down ),
++                                                      FreeMoveCameraMoveBackKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveBackKeyUpCaller( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraFreeMoveLeft", Accelerator( GDK_KEY_Left ),
++                                                      FreeMoveCameraMoveLeftKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveLeftKeyUpCaller( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraFreeMoveRight", Accelerator( GDK_KEY_Right ),
++=======
++      GlobalKeyEvents_insert( "CameraFreeMoveForward", accelerator_null(),
++                                                      FreeMoveCameraMoveForwardKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveForwardKeyUpCaller( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraFreeMoveBack", accelerator_null(),
++                                                      FreeMoveCameraMoveBackKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveBackKeyUpCaller( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraFreeMoveLeft", accelerator_null(),
++                                                      FreeMoveCameraMoveLeftKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveLeftKeyUpCaller( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraFreeMoveRight", accelerator_null(),
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++                                                      FreeMoveCameraMoveRightKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveRightKeyUpCaller( camwnd.getCamera() )
++                                                      );
++
++      GlobalKeyEvents_insert( "CameraFreeMoveForward2", accelerator_null(),
++                                                      FreeMoveCameraMoveForwardKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveForwardKeyUpCaller( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraFreeMoveBack2", accelerator_null(),
++                                                      FreeMoveCameraMoveBackKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveBackKeyUpCaller( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraFreeMoveLeft2", accelerator_null(),
++                                                      FreeMoveCameraMoveLeftKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveLeftKeyUpCaller( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraFreeMoveRight2", accelerator_null(),
++                                                      FreeMoveCameraMoveRightKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveRightKeyUpCaller( camwnd.getCamera() )
++                                                      );
++
++<<<<<<< HEAD
++      GlobalKeyEvents_insert( "CameraFreeMoveUp", Accelerator( 'D' ),
++                                                      FreeMoveCameraMoveUpKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveUpKeyUpCaller( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraFreeMoveDown", Accelerator( 'C' ),
++=======
++      GlobalKeyEvents_insert( "CameraFreeMoveUp", accelerator_null(),
++                                                      FreeMoveCameraMoveUpKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveUpKeyUpCaller( camwnd.getCamera() )
++                                                      );
++      GlobalKeyEvents_insert( "CameraFreeMoveDown", accelerator_null(),
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++                                                      FreeMoveCameraMoveDownKeyDownCaller( camwnd.getCamera() ),
++                                                      FreeMoveCameraMoveDownKeyUpCaller( camwnd.getCamera() )
++                                                      );
++
++<<<<<<< HEAD
++      GlobalCommands_insert( "CameraForward", ReferenceCaller<camera_t, void(), Camera_MoveForward_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_Up ) );
++      GlobalCommands_insert( "CameraBack", ReferenceCaller<camera_t, void(), Camera_MoveBack_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_Down ) );
++      GlobalCommands_insert( "CameraLeft", ReferenceCaller<camera_t, void(), Camera_RotateLeft_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_Left ) );
++      GlobalCommands_insert( "CameraRight", ReferenceCaller<camera_t, void(), Camera_RotateRight_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_Right ) );
++      GlobalCommands_insert( "CameraStrafeRight", ReferenceCaller<camera_t, void(), Camera_MoveRight_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_period ) );
++      GlobalCommands_insert( "CameraStrafeLeft", ReferenceCaller<camera_t, void(), Camera_MoveLeft_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_comma ) );
++
++      GlobalCommands_insert( "CameraUp", ReferenceCaller<camera_t, void(), Camera_MoveUp_Discrete>( camwnd.getCamera() ), Accelerator( 'D' ) );
++      GlobalCommands_insert( "CameraDown", ReferenceCaller<camera_t, void(), Camera_MoveDown_Discrete>( camwnd.getCamera() ), Accelerator( 'C' ) );
++      GlobalCommands_insert( "CameraAngleUp", ReferenceCaller<camera_t, void(), Camera_PitchUp_Discrete>( camwnd.getCamera() ), Accelerator( 'A' ) );
++      GlobalCommands_insert( "CameraAngleDown", ReferenceCaller<camera_t, void(), Camera_PitchDown_Discrete>( camwnd.getCamera() ), Accelerator( 'Z' ) );
++=======
++      GlobalCommands_insert( "CameraForward", ReferenceCaller<camera_t, Camera_MoveForward_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraBack", ReferenceCaller<camera_t, Camera_MoveBack_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraLeft", ReferenceCaller<camera_t, Camera_RotateLeft_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraRight", ReferenceCaller<camera_t, Camera_RotateRight_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraStrafeRight", ReferenceCaller<camera_t, Camera_MoveRight_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraStrafeLeft", ReferenceCaller<camera_t, Camera_MoveLeft_Discrete>( camwnd.getCamera() ) );
++
++      GlobalCommands_insert( "CameraUp", ReferenceCaller<camera_t, Camera_MoveUp_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraDown", ReferenceCaller<camera_t, Camera_MoveDown_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraAngleUp", ReferenceCaller<camera_t, Camera_PitchUp_Discrete>( camwnd.getCamera() ) );
++      GlobalCommands_insert( "CameraAngleDown", ReferenceCaller<camera_t, Camera_PitchDown_Discrete>( camwnd.getCamera() ) );
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++}
++
++void CamWnd_Move_Enable( CamWnd& camwnd ){
++      KeyEvent_connect( "CameraForward" );
++      KeyEvent_connect( "CameraBack" );
++      KeyEvent_connect( "CameraLeft" );
++      KeyEvent_connect( "CameraRight" );
++      KeyEvent_connect( "CameraStrafeRight" );
++      KeyEvent_connect( "CameraStrafeLeft" );
++      KeyEvent_connect( "CameraUp" );
++      KeyEvent_connect( "CameraDown" );
++      KeyEvent_connect( "CameraAngleUp" );
++      KeyEvent_connect( "CameraAngleDown" );
++}
++
++void CamWnd_Move_Disable( CamWnd& camwnd ){
++      KeyEvent_disconnect( "CameraForward" );
++      KeyEvent_disconnect( "CameraBack" );
++      KeyEvent_disconnect( "CameraLeft" );
++      KeyEvent_disconnect( "CameraRight" );
++      KeyEvent_disconnect( "CameraStrafeRight" );
++      KeyEvent_disconnect( "CameraStrafeLeft" );
++      KeyEvent_disconnect( "CameraUp" );
++      KeyEvent_disconnect( "CameraDown" );
++      KeyEvent_disconnect( "CameraAngleUp" );
++      KeyEvent_disconnect( "CameraAngleDown" );
++}
++
++void CamWnd_Move_Discrete_Enable( CamWnd& camwnd ){
++      command_connect_accelerator( "CameraForward" );
++      command_connect_accelerator( "CameraBack" );
++      command_connect_accelerator( "CameraLeft" );
++      command_connect_accelerator( "CameraRight" );
++      command_connect_accelerator( "CameraStrafeRight" );
++      command_connect_accelerator( "CameraStrafeLeft" );
++      command_connect_accelerator( "CameraUp" );
++      command_connect_accelerator( "CameraDown" );
++      command_connect_accelerator( "CameraAngleUp" );
++      command_connect_accelerator( "CameraAngleDown" );
++}
++
++void CamWnd_Move_Discrete_Disable( CamWnd& camwnd ){
++      command_disconnect_accelerator( "CameraForward" );
++      command_disconnect_accelerator( "CameraBack" );
++      command_disconnect_accelerator( "CameraLeft" );
++      command_disconnect_accelerator( "CameraRight" );
++      command_disconnect_accelerator( "CameraStrafeRight" );
++      command_disconnect_accelerator( "CameraStrafeLeft" );
++      command_disconnect_accelerator( "CameraUp" );
++      command_disconnect_accelerator( "CameraDown" );
++      command_disconnect_accelerator( "CameraAngleUp" );
++      command_disconnect_accelerator( "CameraAngleDown" );
++}
++
++struct CamWnd_Move_Discrete {
++      static void Export(const Callback<void(bool)> &returnz) {
++              returnz(g_camwindow_globals_private.m_bCamDiscrete);
++      }
++
++      static void Import(bool value) {
++              if (g_camwnd) {
++                      Import_(*g_camwnd, value);
++              } else {
++                      g_camwindow_globals_private.m_bCamDiscrete = value;
++              }
++      }
++
++      static void Import_(CamWnd &camwnd, bool value) {
++      if ( g_camwindow_globals_private.m_bCamDiscrete ) {
++              CamWnd_Move_Discrete_Disable( camwnd );
++              } else {
++              CamWnd_Move_Disable( camwnd );
++      }
++
++      g_camwindow_globals_private.m_bCamDiscrete = value;
++
++      if ( g_camwindow_globals_private.m_bCamDiscrete ) {
++              CamWnd_Move_Discrete_Enable( camwnd );
++              } else {
++              CamWnd_Move_Enable( camwnd );
++      }
++}
++};
++
++
++void CamWnd_Add_Handlers_Move( CamWnd& camwnd ){
++      camwnd.m_selection_button_press_handler = camwnd.m_gl_widget.connect( "button_press_event", G_CALLBACK( selection_button_press ), camwnd.m_window_observer );
++      camwnd.m_selection_button_release_handler = camwnd.m_gl_widget.connect( "button_release_event", G_CALLBACK( selection_button_release ), camwnd.m_window_observer );
++      camwnd.m_selection_motion_handler = camwnd.m_gl_widget.connect( "motion_notify_event", G_CALLBACK( DeferredMotion::gtk_motion ), &camwnd.m_deferred_motion );
++
++      camwnd.m_freelook_button_press_handler = camwnd.m_gl_widget.connect( "button_press_event", G_CALLBACK( enable_freelook_button_press ), &camwnd );
++
++      if ( g_camwindow_globals_private.m_bCamDiscrete ) {
++              CamWnd_Move_Discrete_Enable( camwnd );
++      }
++      else
++      {
++              CamWnd_Move_Enable( camwnd );
++      }
++}
++
++void CamWnd_Remove_Handlers_Move( CamWnd& camwnd ){
++      g_signal_handler_disconnect( G_OBJECT( camwnd.m_gl_widget ), camwnd.m_selection_button_press_handler );
++      g_signal_handler_disconnect( G_OBJECT( camwnd.m_gl_widget ), camwnd.m_selection_button_release_handler );
++      g_signal_handler_disconnect( G_OBJECT( camwnd.m_gl_widget ), camwnd.m_selection_motion_handler );
++
++      g_signal_handler_disconnect( G_OBJECT( camwnd.m_gl_widget ), camwnd.m_freelook_button_press_handler );
++
++      if ( g_camwindow_globals_private.m_bCamDiscrete ) {
++              CamWnd_Move_Discrete_Disable( camwnd );
++      }
++      else
++      {
++              CamWnd_Move_Disable( camwnd );
++      }
++}
++
++void CamWnd_Add_Handlers_FreeMove( CamWnd& camwnd ){
++      camwnd.m_selection_button_press_handler = camwnd.m_gl_widget.connect( "button_press_event", G_CALLBACK( selection_button_press_freemove ), camwnd.m_window_observer );
++      camwnd.m_selection_button_release_handler = camwnd.m_gl_widget.connect( "button_release_event", G_CALLBACK( selection_button_release_freemove ), camwnd.m_window_observer );
++      camwnd.m_selection_motion_handler = camwnd.m_gl_widget.connect( "motion_notify_event", G_CALLBACK( selection_motion_freemove ), camwnd.m_window_observer );
++
++      camwnd.m_freelook_button_press_handler = camwnd.m_gl_widget.connect( "button_press_event", G_CALLBACK( disable_freelook_button_press ), &camwnd );
++
++      KeyEvent_connect( "CameraFreeMoveForward" );
++      KeyEvent_connect( "CameraFreeMoveBack" );
++      KeyEvent_connect( "CameraFreeMoveLeft" );
++      KeyEvent_connect( "CameraFreeMoveRight" );
++
++      KeyEvent_connect( "CameraFreeMoveForward2" );
++      KeyEvent_connect( "CameraFreeMoveBack2" );
++      KeyEvent_connect( "CameraFreeMoveLeft2" );
++      KeyEvent_connect( "CameraFreeMoveRight2" );
++
++      KeyEvent_connect( "CameraFreeMoveUp" );
++      KeyEvent_connect( "CameraFreeMoveDown" );
++}
++
++void CamWnd_Remove_Handlers_FreeMove( CamWnd& camwnd ){
++      KeyEvent_disconnect( "CameraFreeMoveForward" );
++      KeyEvent_disconnect( "CameraFreeMoveBack" );
++      KeyEvent_disconnect( "CameraFreeMoveLeft" );
++      KeyEvent_disconnect( "CameraFreeMoveRight" );
++
++      KeyEvent_disconnect( "CameraFreeMoveForward2" );
++      KeyEvent_disconnect( "CameraFreeMoveBack2" );
++      KeyEvent_disconnect( "CameraFreeMoveLeft2" );
++      KeyEvent_disconnect( "CameraFreeMoveRight2" );
++
++      KeyEvent_disconnect( "CameraFreeMoveUp" );
++      KeyEvent_disconnect( "CameraFreeMoveDown" );
++
++      g_signal_handler_disconnect( G_OBJECT( camwnd.m_gl_widget ), camwnd.m_selection_button_press_handler );
++      g_signal_handler_disconnect( G_OBJECT( camwnd.m_gl_widget ), camwnd.m_selection_button_release_handler );
++      g_signal_handler_disconnect( G_OBJECT( camwnd.m_gl_widget ), camwnd.m_selection_motion_handler );
++
++      g_signal_handler_disconnect( G_OBJECT( camwnd.m_gl_widget ), camwnd.m_freelook_button_press_handler );
++}
++
++CamWnd::CamWnd() :
++      m_view( true ),
++      m_Camera( &m_view, CamWndQueueDraw( *this ) ),
++      m_cameraview( m_Camera, &m_view, ReferenceCaller<CamWnd, void(), CamWnd_Update>( *this ) ),
++      m_gl_widget( glwidget_new( TRUE ) ),
++      m_window_observer( NewWindowObserver() ),
++      m_XORRectangle( m_gl_widget ),
++      m_deferredDraw( WidgetQueueDrawCaller( m_gl_widget ) ),
++      m_deferred_motion( selection_motion, m_window_observer ),
++      m_selection_button_press_handler( 0 ),
++      m_selection_button_release_handler( 0 ),
++      m_selection_motion_handler( 0 ),
++      m_freelook_button_press_handler( 0 ),
++      m_drawing( false ){
++      m_bFreeMove = false;
++
++      GlobalWindowObservers_add( m_window_observer );
++      GlobalWindowObservers_connectWidget( m_gl_widget );
++
++      m_window_observer->setRectangleDrawCallback( ReferenceCaller<CamWnd, void(rect_t), camwnd_update_xor_rectangle>( *this ) );
++      m_window_observer->setView( m_view );
++
++      g_object_ref( m_gl_widget._handle );
++
++      gtk_widget_set_events( m_gl_widget, GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK );
++      gtk_widget_set_can_focus( m_gl_widget, true );
++
++      m_sizeHandler = m_gl_widget.connect( "size_allocate", G_CALLBACK( camera_size_allocate ), this );
++      m_exposeHandler = m_gl_widget.on_render( G_CALLBACK( camera_expose ), this );
++
++      Map_addValidCallback( g_map, DeferredDrawOnMapValidChangedCaller( m_deferredDraw ) );
++
++      CamWnd_registerCommands( *this );
++
++      CamWnd_Add_Handlers_Move( *this );
++
++      m_gl_widget.connect( "scroll_event", G_CALLBACK( wheelmove_scroll ), this );
++
++      AddSceneChangeCallback( ReferenceCaller<CamWnd, void(), CamWnd_Update>( *this ) );
++
++      PressedButtons_connect( g_pressedButtons, m_gl_widget );
++}
++
++CamWnd::~CamWnd(){
++      if ( m_bFreeMove ) {
++              DisableFreeMove();
++      }
++
++      CamWnd_Remove_Handlers_Move( *this );
++
++      g_signal_handler_disconnect( G_OBJECT( m_gl_widget ), m_sizeHandler );
++      g_signal_handler_disconnect( G_OBJECT( m_gl_widget ), m_exposeHandler );
++
++      m_gl_widget.unref();
++
++      m_window_observer->release();
++}
++
++class FloorHeightWalker : public scene::Graph::Walker
++{
++float m_current;
++float& m_bestUp;
++float& m_bestDown;
++public:
++FloorHeightWalker( float current, float& bestUp, float& bestDown ) :
++      m_current( current ), m_bestUp( bestUp ), m_bestDown( bestDown ){
++      bestUp = g_MaxWorldCoord;
++      bestDown = -g_MaxWorldCoord;
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      if ( path.top().get().visible()
++               && Node_isBrush( path.top() ) ) { // this node is a floor
++              const AABB& aabb = instance.worldAABB();
++              float floorHeight = aabb.origin.z() + aabb.extents.z();
++              if ( floorHeight > m_current && floorHeight < m_bestUp ) {
++                      m_bestUp = floorHeight;
++              }
++              if ( floorHeight < m_current && floorHeight > m_bestDown ) {
++                      m_bestDown = floorHeight;
++              }
++      }
++      else if( !path.top().get().visible() ){
++              return false;
++      }
++      return true;
++}
++};
++
++void CamWnd::Cam_ChangeFloor( bool up ){
++      float current = m_Camera.origin[2] - 48;
++      float bestUp;
++      float bestDown;
++      GlobalSceneGraph().traverse( FloorHeightWalker( current, bestUp, bestDown ) );
++
++      if ( up && bestUp != g_MaxWorldCoord ) {
++              current = bestUp;
++      }
++      if ( !up && bestDown != -g_MaxWorldCoord ) {
++              current = bestDown;
++      }
++
++      m_Camera.origin[2] = current + 48;
++      Camera_updateModelview( getCamera() );
++      CamWnd_Update( *this );
++      CameraMovedNotify();
++}
++
++
++#if 0
++
++// button_press
++Sys_GetCursorPos( &m_PositionDragCursorX, &m_PositionDragCursorY );
++
++// motion
++if ( ( m_bFreeMove && ( buttons == ( RAD_CONTROL | RAD_SHIFT ) ) )
++       || ( !m_bFreeMove && ( buttons == ( RAD_RBUTTON | RAD_CONTROL ) ) ) ) {
++      Cam_PositionDrag();
++      CamWnd_Update( camwnd );
++      CameraMovedNotify();
++      return;
++}
++
++void CamWnd::Cam_PositionDrag(){
++      int x, y;
++
++      Sys_GetCursorPos( m_gl_widget, &x, &y );
++      if ( x != m_PositionDragCursorX || y != m_PositionDragCursorY ) {
++              x -= m_PositionDragCursorX;
++              vector3_add( m_Camera.origin, vector3_scaled( m_Camera.vright, x ) );
++              y -= m_PositionDragCursorY;
++              m_Camera.origin[2] -= y;
++              Camera_updateModelview();
++              CamWnd_Update( camwnd );
++              CameraMovedNotify();
++
++              Sys_SetCursorPos( m_parent, m_PositionDragCursorX, m_PositionDragCursorY );
++      }
++}
++#endif
++
++
++// NOTE TTimo if there's an OS-level focus out of the application
++//   then we can release the camera cursor grab
++static gboolean camwindow_freemove_focusout( ui::Widget widget, GdkEventFocus* event, gpointer data ){
++      reinterpret_cast<CamWnd*>( data )->DisableFreeMove();
++      return FALSE;
++}
++
++void CamWnd::EnableFreeMove(){
++      //globalOutputStream() << "EnableFreeMove\n";
++
++      ASSERT_MESSAGE( !m_bFreeMove, "EnableFreeMove: free-move was already enabled" );
++      m_bFreeMove = true;
++      Camera_clearMovementFlags( getCamera(), MOVE_ALL );
++
++      CamWnd_Remove_Handlers_Move( *this );
++      CamWnd_Add_Handlers_FreeMove( *this );
++
++      gtk_window_set_focus( m_parent, m_gl_widget );
++      m_freemove_handle_focusout = m_gl_widget.connect( "focus_out_event", G_CALLBACK( camwindow_freemove_focusout ), this );
++      m_freezePointer.freeze_pointer( m_parent, m_gl_widget, Camera_motionDelta, &m_Camera );
++
++      CamWnd_Update( *this );
++}
++
++void CamWnd::DisableFreeMove(){
++      //globalOutputStream() << "DisableFreeMove\n";
++
++      ASSERT_MESSAGE( m_bFreeMove, "DisableFreeMove: free-move was not enabled" );
++      m_bFreeMove = false;
++      Camera_clearMovementFlags( getCamera(), MOVE_ALL );
++
++      CamWnd_Remove_Handlers_FreeMove( *this );
++      CamWnd_Add_Handlers_Move( *this );
++
++      m_freezePointer.unfreeze_pointer( m_parent, true );
++      g_signal_handler_disconnect( G_OBJECT( m_gl_widget ), m_freemove_handle_focusout );
++
++      CamWnd_Update( *this );
++}
++
++
++#include "renderer.h"
++
++class CamRenderer : public Renderer
++{
++struct state_type
++{
++      state_type() : m_highlight( 0 ), m_state( 0 ), m_lights( 0 ){
++      }
++      unsigned int m_highlight;
++      Shader* m_state;
++      const LightList* m_lights;
++};
++
++std::vector<state_type> m_state_stack;
++RenderStateFlags m_globalstate;
++Shader* m_state_select0;
++Shader* m_state_select1;
++const Vector3& m_viewer;
++
++public:
++CamRenderer( RenderStateFlags globalstate, Shader* select0, Shader* select1, const Vector3& viewer ) :
++      m_globalstate( globalstate ),
++      m_state_select0( select0 ),
++      m_state_select1( select1 ),
++      m_viewer( viewer ){
++      ASSERT_NOTNULL( select0 );
++      ASSERT_NOTNULL( select1 );
++      m_state_stack.push_back( state_type() );
++}
++
++void SetState( Shader* state, EStyle style ){
++      ASSERT_NOTNULL( state );
++      if ( style == eFullMaterials ) {
++              m_state_stack.back().m_state = state;
++      }
++}
++EStyle getStyle() const {
++      return eFullMaterials;
++}
++void PushState(){
++      m_state_stack.push_back( m_state_stack.back() );
++}
++void PopState(){
++      ASSERT_MESSAGE( !m_state_stack.empty(), "popping empty stack" );
++      m_state_stack.pop_back();
++}
++void Highlight( EHighlightMode mode, bool bEnable = true ){
++      ( bEnable )
++      ? m_state_stack.back().m_highlight |= mode
++                                                                                : m_state_stack.back().m_highlight &= ~mode;
++}
++void setLights( const LightList& lights ){
++      m_state_stack.back().m_lights = &lights;
++}
++void addRenderable( const OpenGLRenderable& renderable, const Matrix4& world ){
++      if ( m_state_stack.back().m_highlight & ePrimitive ) {
++              m_state_select0->addRenderable( renderable, world, m_state_stack.back().m_lights );
++      }
++      if ( m_state_stack.back().m_highlight & eFace ) {
++              m_state_select1->addRenderable( renderable, world, m_state_stack.back().m_lights );
++      }
++
++      m_state_stack.back().m_state->addRenderable( renderable, world, m_state_stack.back().m_lights );
++}
++
++void render( const Matrix4& modelview, const Matrix4& projection ){
++      GlobalShaderCache().render( m_globalstate, modelview, projection, m_viewer );
++}
++};
++
++/*
++   ==============
++   Cam_Draw
++   ==============
++ */
++/*
++void ShowStatsToggle(){
++      g_camwindow_globals_private.m_showStats ^= 1;
++}
++
++void ShowStatsExport( const Callback<void(bool)> &importer ){
++      importer( g_camwindow_globals_private.m_showStats );
++}
++
++FreeCaller<void(const Callback<void(bool)>&), ShowStatsExport> g_show_stats_caller;
++Callback<void(const Callback<void(bool)> &)> g_show_stats_callback( g_show_stats_caller );
++ToggleItem g_show_stats( g_show_stats_callback );
++*/
++
++void ShowStatsToggle(){
++      g_camwindow_globals_private.m_showStats ^= 1;
++//    g_show_stats.update();
++      UpdateAllWindows();
++}
++typedef FreeCaller<void(), ShowStatsToggle> ShowStatsToggleCaller;
++void ShowStatsExport( const Callback<void(bool)> & importer ){
++      importer( g_camwindow_globals_private.m_showStats );
++}
++typedef FreeCaller<void(const Callback<void(bool)> &), ShowStatsExport> ShowStatsExportCaller;
++
++ShowStatsExportCaller g_show_stats_caller;
++Callback<void(const Callback<void(bool)> &)> g_show_stats_callback( g_show_stats_caller );
++ToggleItem g_show_stats( g_show_stats_callback );
++
++void CamWnd::Cam_Draw(){
++      glViewport( 0, 0, m_Camera.width, m_Camera.height );
++#if 0
++      GLint viewprt[4];
++      glGetIntegerv( GL_VIEWPORT, viewprt );
++#endif
++
++      // enable depth buffer writes
++      glDepthMask( GL_TRUE );
++      glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
++
++      Vector3 clearColour( 0, 0, 0 );
++      if ( m_Camera.draw_mode != cd_lighting ) {
++              clearColour = g_camwindow_globals.color_cameraback;
++      }
++
++      glClearColor( clearColour[0], clearColour[1], clearColour[2], 0 );
++      glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
++
++      extern void Renderer_ResetStats();
++      Renderer_ResetStats();
++      extern void Cull_ResetStats();
++      Cull_ResetStats();
++
++      glMatrixMode( GL_PROJECTION );
++      glLoadMatrixf( reinterpret_cast<const float*>( &m_Camera.projection ) );
++
++      glMatrixMode( GL_MODELVIEW );
++      glLoadMatrixf( reinterpret_cast<const float*>( &m_Camera.modelview ) );
++
++
++      // one directional light source directly behind the viewer
++      {
++              GLfloat inverse_cam_dir[4], ambient[4], diffuse[4]; //, material[4];
++
++              ambient[0] = ambient[1] = ambient[2] = 0.4f;
++              ambient[3] = 1.0f;
++              diffuse[0] = diffuse[1] = diffuse[2] = 0.4f;
++              diffuse[3] = 1.0f;
++              //material[0] = material[1] = material[2] = 0.8f;
++              //material[3] = 1.0f;
++
++              inverse_cam_dir[0] = m_Camera.vpn[0];
++              inverse_cam_dir[1] = m_Camera.vpn[1];
++              inverse_cam_dir[2] = m_Camera.vpn[2];
++              inverse_cam_dir[3] = 0;
++
++              glLightfv( GL_LIGHT0, GL_POSITION, inverse_cam_dir );
++
++              glLightfv( GL_LIGHT0, GL_AMBIENT, ambient );
++              glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse );
++
++              glEnable( GL_LIGHT0 );
++      }
++
++
++      unsigned int globalstate = RENDER_DEPTHTEST | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_ALPHATEST | RENDER_BLEND | RENDER_CULLFACE | RENDER_COLOURARRAY | RENDER_OFFSETLINE | RENDER_POLYGONSMOOTH | RENDER_LINESMOOTH | RENDER_FOG | RENDER_COLOURCHANGE;
++      switch ( m_Camera.draw_mode )
++      {
++      case cd_wire:
++              break;
++      case cd_solid:
++              globalstate |= RENDER_FILL
++                                         | RENDER_LIGHTING
++                                         | RENDER_SMOOTH
++                                         | RENDER_SCALED;
++              break;
++      case cd_texture:
++              globalstate |= RENDER_FILL
++                                         | RENDER_LIGHTING
++                                         | RENDER_TEXTURE
++                                         | RENDER_SMOOTH
++                                         | RENDER_SCALED;
++              break;
++      case cd_lighting:
++              globalstate |= RENDER_FILL
++                                         | RENDER_LIGHTING
++                                         | RENDER_TEXTURE
++                                         | RENDER_SMOOTH
++                                         | RENDER_SCALED
++                                         | RENDER_BUMP
++                                         | RENDER_PROGRAM
++                                         | RENDER_SCREEN;
++              break;
++      default:
++              globalstate = 0;
++              break;
++      }
++
++//    if ( !g_xywindow_globals.m_bNoStipple ) {
++              globalstate |= RENDER_LINESTIPPLE | RENDER_POLYGONSTIPPLE;
++//    }
++
++      {
++              CamRenderer renderer( globalstate, m_state_select2, m_state_select1, m_view.getViewer() );
++
++              Scene_Render( renderer, m_view );
++
++              renderer.render( m_Camera.modelview, m_Camera.projection );
++      }
++
++      // prepare for 2d stuff
++      glColor4f( 1, 1, 1, 1 );
++      glDisable( GL_BLEND );
++      glMatrixMode( GL_PROJECTION );
++      glLoadIdentity();
++      glOrtho( 0, (float)m_Camera.width, 0, (float)m_Camera.height, -100, 100 );
++      glScalef( 1, -1, 1 );
++      glTranslatef( 0, -(float)m_Camera.height, 0 );
++      glMatrixMode( GL_MODELVIEW );
++      glLoadIdentity();
++
++      if ( GlobalOpenGL().GL_1_3() ) {
++              glClientActiveTexture( GL_TEXTURE0 );
++              glActiveTexture( GL_TEXTURE0 );
++      }
++
++      glDisableClientState( GL_TEXTURE_COORD_ARRAY );
++      glDisableClientState( GL_NORMAL_ARRAY );
++      glDisableClientState( GL_COLOR_ARRAY );
++
++      glDisable( GL_TEXTURE_2D );
++      glDisable( GL_LIGHTING );
++      glDisable( GL_COLOR_MATERIAL );
++      glDisable( GL_DEPTH_TEST );
++      glColor3f( 1.f, 1.f, 1.f );
++      glLineWidth( 1 );
++
++      // draw the crosshair
++      if ( m_bFreeMove ) {
++              glBegin( GL_LINES );
++              glVertex2f( (float)m_Camera.width / 2.f, (float)m_Camera.height / 2.f + 6 );
++              glVertex2f( (float)m_Camera.width / 2.f, (float)m_Camera.height / 2.f + 2 );
++              glVertex2f( (float)m_Camera.width / 2.f, (float)m_Camera.height / 2.f - 6 );
++              glVertex2f( (float)m_Camera.width / 2.f, (float)m_Camera.height / 2.f - 2 );
++              glVertex2f( (float)m_Camera.width / 2.f + 6, (float)m_Camera.height / 2.f );
++              glVertex2f( (float)m_Camera.width / 2.f + 2, (float)m_Camera.height / 2.f );
++              glVertex2f( (float)m_Camera.width / 2.f - 6, (float)m_Camera.height / 2.f );
++              glVertex2f( (float)m_Camera.width / 2.f - 2, (float)m_Camera.height / 2.f );
++              glEnd();
++      }
++
++      if ( g_camwindow_globals_private.m_showStats ) {
++              glRasterPos3f( 1.0f, static_cast<float>( m_Camera.height ) - GlobalOpenGL().m_font->getPixelDescent(), 0.0f );
++              extern const char* Renderer_GetStats();
++              GlobalOpenGL().drawString( Renderer_GetStats() );
++
++              glRasterPos3f( 1.0f, static_cast<float>( m_Camera.height ) - GlobalOpenGL().m_font->getPixelDescent() - GlobalOpenGL().m_font->getPixelHeight(), 0.0f );
++              extern const char* Cull_GetStats();
++              GlobalOpenGL().drawString( Cull_GetStats() );
++      }
++
++      // bind back to the default texture so that we don't have problems
++      // elsewhere using/modifying texture maps between contexts
++      glBindTexture( GL_TEXTURE_2D, 0 );
++}
++
++void CamWnd::draw(){
++      m_drawing = true;
++
++      //globalOutputStream() << "draw...\n";
++      if ( glwidget_make_current( m_gl_widget ) != FALSE ) {
++              if ( Map_Valid( g_map ) && ScreenUpdates_Enabled() ) {
++                      GlobalOpenGL_debugAssertNoErrors();
++                      Cam_Draw();
++                      GlobalOpenGL_debugAssertNoErrors();
++                      //qglFinish();
++
++                      m_XORRectangle.set( rectangle_t() );
++              }
++
++              glwidget_swap_buffers( m_gl_widget );
++      }
++
++      m_drawing = false;
++}
++
++void CamWnd::BenchMark(){
++      double dStart = Sys_DoubleTime();
++      for ( int i = 0 ; i < 100 ; i++ )
++      {
++              Vector3 angles;
++              angles[CAMERA_ROLL] = 0;
++              angles[CAMERA_PITCH] = 0;
++              angles[CAMERA_YAW] = static_cast<float>( i * ( 360.0 / 100.0 ) );
++              Camera_setAngles( *this, angles );
++      }
++      double dEnd = Sys_DoubleTime();
++      globalOutputStream() << FloatFormat( dEnd - dStart, 5, 2 ) << " seconds\n";
++}
++
++
++void GlobalCamera_ResetAngles(){
++      CamWnd& camwnd = *g_camwnd;
++      Vector3 angles;
++      angles[CAMERA_ROLL] = angles[CAMERA_PITCH] = 0;
++      angles[CAMERA_YAW] = static_cast<float>( 22.5 * floor( ( Camera_getAngles( camwnd )[CAMERA_YAW] + 11 ) / 22.5 ) );
++      Camera_setAngles( camwnd, angles );
++}
++
++#include "select.h"
++
++void GlobalCamera_FocusOnSelected(){
++      CamWnd& camwnd = *g_camwnd;
++
++      Vector3 angles( Camera_getAngles( camwnd ) );
++      Vector3 radangles( degrees_to_radians( angles[0] ), degrees_to_radians( angles[1] ), degrees_to_radians( angles[2] ) );
++      Vector3 viewvector;
++      viewvector[0] = cos( radangles[1] ) * cos( radangles[0] );
++      viewvector[1] = sin( radangles[1] ) * cos( radangles[0] );
++      viewvector[2] = sin( radangles[0] );
++
++      Vector3 camorigin( Camera_getOrigin( camwnd ) );
++
++      AABB aabb( aabb_for_minmax( Select_getWorkZone().d_work_min, Select_getWorkZone().d_work_max ) );
++
++      View& view = *( camwnd.getCamera().m_view );
++
++      Plane3 frustumPlanes[4];
++      frustumPlanes[0] = plane3_translated( view.getFrustum().left, camorigin - aabb.origin );
++      frustumPlanes[1] = plane3_translated( view.getFrustum().right, camorigin - aabb.origin );
++      frustumPlanes[2] = plane3_translated( view.getFrustum().top, camorigin - aabb.origin );
++      frustumPlanes[3] = plane3_translated( view.getFrustum().bottom, camorigin - aabb.origin );
++
++      float offset = 64.0f;
++
++      Vector3 corners[8];
++      aabb_corners( aabb, corners );
++
++      for ( size_t i = 0; i < 4; ++i ){
++              for ( size_t j = 0; j < 8; ++j ){
++                      Ray ray( aabb.origin, -viewvector );
++                      //Plane3 newplane( frustumPlanes[i].normal(), vector3_dot( frustumPlanes[i].normal(), corners[j] - frustumPlanes[i].normal() * 16.0f ) );
++                      Plane3 newplane( frustumPlanes[i].normal(), vector3_dot( frustumPlanes[i].normal(), corners[j] ) );
++                      float d = vector3_dot( ray.direction, newplane.normal() );
++                      if( d != 0 ){
++                              float s = vector3_dot( newplane.normal() * newplane.dist() - ray.origin, newplane.normal() ) / d;
++                              offset = std::max( offset, s );
++                      }
++              }
++      }
++      Camera_setOrigin( camwnd, aabb.origin - viewvector * offset );
++}
++
++void Camera_ChangeFloorUp(){
++      CamWnd& camwnd = *g_camwnd;
++      camwnd.Cam_ChangeFloor( true );
++}
++
++void Camera_ChangeFloorDown(){
++      CamWnd& camwnd = *g_camwnd;
++      camwnd.Cam_ChangeFloor( false );
++}
++
++void Camera_CubeIn(){
++      CamWnd& camwnd = *g_camwnd;
++      g_camwindow_globals.m_nCubicScale--;
++      if ( g_camwindow_globals.m_nCubicScale < 1 ) {
++              g_camwindow_globals.m_nCubicScale = 1;
++      }
++      Camera_updateProjection( camwnd.getCamera() );
++      CamWnd_Update( camwnd );
++      g_pParentWnd->SetGridStatus();
++}
++
++void Camera_CubeOut(){
++      CamWnd& camwnd = *g_camwnd;
++      g_camwindow_globals.m_nCubicScale++;
++      if ( g_camwindow_globals.m_nCubicScale > 23 ) {
++              g_camwindow_globals.m_nCubicScale = 23;
++      }
++      Camera_updateProjection( camwnd.getCamera() );
++      CamWnd_Update( camwnd );
++      g_pParentWnd->SetGridStatus();
++}
++
++bool Camera_GetFarClip(){
++      return g_camwindow_globals_private.m_bCubicClipping;
++}
++
++ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_getfarclip_caller( g_camwindow_globals_private.m_bCubicClipping );
++ToggleItem g_getfarclip_item( g_getfarclip_caller );
++
++void Camera_SetFarClip( bool value ){
++      CamWnd& camwnd = *g_camwnd;
++      g_camwindow_globals_private.m_bCubicClipping = value;
++      g_getfarclip_item.update();
++      Camera_updateProjection( camwnd.getCamera() );
++      CamWnd_Update( camwnd );
++}
++
++struct Camera_FarClip {
++      static void Export(const Callback<void(bool)> &returnz) {
++              returnz(g_camwindow_globals_private.m_bCubicClipping);
++      }
++
++      static void Import(bool value) {
++              Camera_SetFarClip(value);
++      }
++};
++
++void Camera_ToggleFarClip(){
++      Camera_SetFarClip( !Camera_GetFarClip() );
++}
++
++
++void CamWnd_constructToolbar( ui::Toolbar toolbar ){
++      toolbar_append_toggle_button( toolbar, "Cubic clip the camera view (Ctrl + \\)", "view_cubicclipping.png", "ToggleCubicClip" );
++}
++
++void CamWnd_registerShortcuts(){
++      toggle_add_accelerator( "ToggleCubicClip" );
++
++      if ( g_pGameDescription->mGameType == "doom3" ) {
++              command_connect_accelerator( "TogglePreview" );
++      }
++
++      command_connect_accelerator( "CameraSpeedInc" );
++      command_connect_accelerator( "CameraSpeedDec" );
++}
++
++
++void GlobalCamera_Benchmark(){
++      CamWnd& camwnd = *g_camwnd;
++      camwnd.BenchMark();
++}
++
++void GlobalCamera_Update(){
++      CamWnd& camwnd = *g_camwnd;
++      CamWnd_Update( camwnd );
++}
++
++camera_draw_mode CamWnd_GetMode(){
++      return camera_t::draw_mode;
++}
++void CamWnd_SetMode( camera_draw_mode mode ){
++      ShaderCache_setBumpEnabled( mode == cd_lighting );
++      camera_t::draw_mode = mode;
++      if ( g_camwnd != 0 ) {
++              CamWnd_Update( *g_camwnd );
++      }
++}
++
++void CamWnd_TogglePreview( void ){
++      // gametype must be doom3 for this function to work
++      // if the gametype is not doom3 something is wrong with the
++      // global command list or somebody else calls this function.
++      ASSERT_MESSAGE( g_pGameDescription->mGameType == "doom3", "CamWnd_TogglePreview called although mGameType is not doom3 compatible" );
++
++      // switch between textured and lighting mode
++      CamWnd_SetMode( ( CamWnd_GetMode() == cd_lighting ) ? cd_texture : cd_lighting );
++}
++
++
++CameraModel* g_camera_model = 0;
++
++void CamWnd_LookThroughCamera( CamWnd& camwnd ){
++      if ( g_camera_model != 0 ) {
++              CamWnd_Add_Handlers_Move( camwnd );
++              g_camera_model->setCameraView( 0, Callback<void()>() );
++              g_camera_model = 0;
++              Camera_updateModelview( camwnd.getCamera() );
++              Camera_updateProjection( camwnd.getCamera() );
++              CamWnd_Update( camwnd );
++      }
++}
++
++inline CameraModel* Instance_getCameraModel( scene::Instance& instance ){
++      return InstanceTypeCast<CameraModel>::cast( instance );
++}
++
++void CamWnd_LookThroughSelected( CamWnd& camwnd ){
++      if ( g_camera_model != 0 ) {
++              CamWnd_LookThroughCamera( camwnd );
++      }
++
++      if ( GlobalSelectionSystem().countSelected() != 0 ) {
++              scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
++              CameraModel* cameraModel = Instance_getCameraModel( instance );
++              if ( cameraModel != 0 ) {
++                      CamWnd_Remove_Handlers_Move( camwnd );
++                      g_camera_model = cameraModel;
++                      g_camera_model->setCameraView( &camwnd.getCameraView(), ReferenceCaller<CamWnd, void(), CamWnd_LookThroughCamera>( camwnd ) );
++              }
++      }
++}
++
++void GlobalCamera_LookThroughSelected(){
++      CamWnd_LookThroughSelected( *g_camwnd );
++}
++
++void GlobalCamera_LookThroughCamera(){
++      CamWnd_LookThroughCamera( *g_camwnd );
++}
++
++struct RenderMode {
++      static void Export(const Callback<void(int)> &returnz) {
++              switch (CamWnd_GetMode()) {
++                      case cd_wire:
++                              returnz(0);
++                              break;
++                      case cd_solid:
++                              returnz(1);
++                              break;
++                      case cd_texture:
++                              returnz(2);
++                              break;
++                      case cd_lighting:
++                              returnz(3);
++                              break;
++              }
++      }
++
++      static void Import(int value) {
++              switch (value) {
++      case 0:
++              CamWnd_SetMode( cd_wire );
++              break;
++      case 1:
++              CamWnd_SetMode( cd_solid );
++              break;
++      case 2:
++              CamWnd_SetMode( cd_texture );
++              break;
++      case 3:
++              CamWnd_SetMode( cd_lighting );
++              break;
++      default:
++              CamWnd_SetMode( cd_texture );
++      }
++}
++};
++
++void Camera_constructPreferences( PreferencesPage& page ){
++      page.appendSlider( "Movement Speed", g_camwindow_globals_private.m_nMoveSpeed, TRUE, 0, 0, 100, MIN_CAM_SPEED, MAX_CAM_SPEED, 1, 10 );
++      page.appendCheckBox( "", "Link strafe speed to movement speed", g_camwindow_globals_private.m_bCamLinkSpeed );
++      page.appendSlider( "Rotation Speed", g_camwindow_globals_private.m_nAngleSpeed, TRUE, 0, 0, 3, 1, 180, 1, 10 );
++      page.appendCheckBox( "", "Invert mouse vertical axis", g_camwindow_globals_private.m_bCamInverseMouse );
++      page.appendCheckBox(
++              "", "Discrete movement",
++              make_property<CamWnd_Move_Discrete>()
++              );
++      page.appendCheckBox(
++              "", "Enable far-clip plane",
++              make_property<Camera_FarClip>()
++              );
++
++      if ( g_pGameDescription->mGameType == "doom3" ) {
++              const char* render_mode[] = { "Wireframe", "Flatshade", "Textured", "Lighting" };
++
++              page.appendCombo(
++                      "Render Mode",
++                      STRING_ARRAY_RANGE( render_mode ),
++                      make_property<RenderMode>()
++                      );
++      }
++      else
++      {
++              const char* render_mode[] = { "Wireframe", "Flatshade", "Textured" };
++
++              page.appendCombo(
++                      "Render Mode",
++                      STRING_ARRAY_RANGE( render_mode ),
++                      make_property<RenderMode>()
++                      );
++      }
++
++      const char* strafe_mode[] = { "Both", "Forward", "Up" };
++
++      page.appendCombo(
++              "Strafe Mode",
++              g_camwindow_globals_private.m_nStrafeMode,
++              STRING_ARRAY_RANGE( strafe_mode )
++              );
++}
++void Camera_constructPage( PreferenceGroup& group ){
++      PreferencesPage page( group.createPage( "Camera", "Camera View Preferences" ) );
++      Camera_constructPreferences( page );
++}
++void Camera_registerPreferencesPage(){
++      PreferencesDialog_addSettingsPage( makeCallbackF(Camera_constructPage) );
++}
++
++#include "preferencesystem.h"
++#include "stringio.h"
++#include "dialog.h"
++
++void CameraSpeed_increase(){
++      if ( g_camwindow_globals_private.m_nMoveSpeed <= ( MAX_CAM_SPEED - CAM_SPEED_STEP - 10 ) ) {
++              g_camwindow_globals_private.m_nMoveSpeed += CAM_SPEED_STEP;
++      }
++      else {
++              g_camwindow_globals_private.m_nMoveSpeed = MAX_CAM_SPEED - 10;
++      }
++}
++
++void CameraSpeed_decrease(){
++      if ( g_camwindow_globals_private.m_nMoveSpeed >= ( MIN_CAM_SPEED + CAM_SPEED_STEP ) ) {
++              g_camwindow_globals_private.m_nMoveSpeed -= CAM_SPEED_STEP;
++      }
++      else {
++              g_camwindow_globals_private.m_nMoveSpeed = MIN_CAM_SPEED;
++      }
++}
++
++/// \brief Initialisation for things that have the same lifespan as this module.
++void CamWnd_Construct(){
++      GlobalCommands_insert( "CenterView", makeCallbackF(GlobalCamera_ResetAngles), Accelerator( GDK_KEY_End ) );
++      GlobalCommands_insert( "CameraFocusOnSelected", makeCallbackF( GlobalCamera_FocusOnSelected ), Accelerator( GDK_Tab ) );
++
++      GlobalToggles_insert( "ToggleCubicClip", makeCallbackF(Camera_ToggleFarClip), ToggleItem::AddCallbackCaller( g_getfarclip_item ), Accelerator( '\\', (GdkModifierType)GDK_CONTROL_MASK ) );
++      GlobalCommands_insert( "CubicClipZoomIn", makeCallbackF(Camera_CubeIn), Accelerator( '[', (GdkModifierType)GDK_CONTROL_MASK ) );
++      GlobalCommands_insert( "CubicClipZoomOut", makeCallbackF(Camera_CubeOut), Accelerator( ']', (GdkModifierType)GDK_CONTROL_MASK ) );
++
++      GlobalCommands_insert( "UpFloor", makeCallbackF(Camera_ChangeFloorUp), Accelerator( GDK_KEY_Prior ) );
++      GlobalCommands_insert( "DownFloor", makeCallbackF(Camera_ChangeFloorDown), Accelerator( GDK_KEY_Next ) );
++
++      GlobalToggles_insert( "ToggleCamera", ToggleShown::ToggleCaller( g_camera_shown ), ToggleItem::AddCallbackCaller( g_camera_shown.m_item ), Accelerator( 'C', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
++//    GlobalCommands_insert( "LookThroughSelected", makeCallbackF(GlobalCamera_LookThroughSelected) );
++//    GlobalCommands_insert( "LookThroughCamera", makeCallbackF(GlobalCamera_LookThroughCamera) );
++
++      if ( g_pGameDescription->mGameType == "doom3" ) {
++              GlobalCommands_insert( "TogglePreview", makeCallbackF(CamWnd_TogglePreview), Accelerator( GDK_KEY_F3 ) );
++      }
++
++      GlobalCommands_insert( "CameraSpeedInc", makeCallbackF(CameraSpeed_increase), Accelerator( GDK_KEY_KP_Add, (GdkModifierType)GDK_SHIFT_MASK ) );
++      GlobalCommands_insert( "CameraSpeedDec", makeCallbackF(CameraSpeed_decrease), Accelerator( GDK_KEY_KP_Subtract, (GdkModifierType)GDK_SHIFT_MASK ) );
++
++      GlobalShortcuts_insert( "CameraForward", Accelerator( GDK_KEY_Up ) );
++      GlobalShortcuts_insert( "CameraBack", Accelerator( GDK_KEY_Down ) );
++      GlobalShortcuts_insert( "CameraLeft", Accelerator( GDK_KEY_Left ) );
++      GlobalShortcuts_insert( "CameraRight", Accelerator( GDK_KEY_Right ) );
++      GlobalShortcuts_insert( "CameraStrafeRight", Accelerator( GDK_KEY_period ) );
++      GlobalShortcuts_insert( "CameraStrafeLeft", Accelerator( GDK_KEY_comma ) );
++
++      GlobalShortcuts_insert( "CameraUp", accelerator_null() );
++      GlobalShortcuts_insert( "CameraDown", accelerator_null() );
++      GlobalShortcuts_insert( "CameraAngleUp", accelerator_null() );
++      GlobalShortcuts_insert( "CameraAngleDown", accelerator_null() );
++
++      GlobalShortcuts_insert( "CameraFreeMoveForward", Accelerator( GDK_Up ) );
++      GlobalShortcuts_insert( "CameraFreeMoveBack", Accelerator( GDK_Down ) );
++      GlobalShortcuts_insert( "CameraFreeMoveLeft", Accelerator( GDK_Left ) );
++      GlobalShortcuts_insert( "CameraFreeMoveRight", Accelerator( GDK_Right ) );
++
++      GlobalShortcuts_insert( "CameraFreeMoveForward2", Accelerator( GDK_Up ) );
++      GlobalShortcuts_insert( "CameraFreeMoveBack2", Accelerator( GDK_Down ) );
++      GlobalShortcuts_insert( "CameraFreeMoveLeft2", Accelerator( GDK_Left ) );
++      GlobalShortcuts_insert( "CameraFreeMoveRight2", Accelerator( GDK_Right ) );
++
++<<<<<<< HEAD
++      GlobalToggles_insert( "ShowStats", makeCallbackF(ShowStatsToggle), ToggleItem::AddCallbackCaller( g_show_stats ) );
++
++      GlobalPreferenceSystem().registerPreference( "ShowStats", make_property_string( g_camwindow_globals_private.m_showStats ) );
++      GlobalPreferenceSystem().registerPreference( "MoveSpeed", make_property_string( g_camwindow_globals_private.m_nMoveSpeed ) );
++      GlobalPreferenceSystem().registerPreference( "CamLinkSpeed", make_property_string( g_camwindow_globals_private.m_bCamLinkSpeed ) );
++      GlobalPreferenceSystem().registerPreference( "AngleSpeed", make_property_string( g_camwindow_globals_private.m_nAngleSpeed ) );
++      GlobalPreferenceSystem().registerPreference( "CamInverseMouse", make_property_string( g_camwindow_globals_private.m_bCamInverseMouse ) );
++      GlobalPreferenceSystem().registerPreference( "CamDiscrete", make_property_string<CamWnd_Move_Discrete>());
++      GlobalPreferenceSystem().registerPreference( "CubicClipping", make_property_string( g_camwindow_globals_private.m_bCubicClipping ) );
++      GlobalPreferenceSystem().registerPreference( "CubicScale", make_property_string( g_camwindow_globals.m_nCubicScale ) );
++      GlobalPreferenceSystem().registerPreference( "SI_Colors4", make_property_string( g_camwindow_globals.color_cameraback ) );
++      GlobalPreferenceSystem().registerPreference( "SI_Colors12", make_property_string( g_camwindow_globals.color_selbrushes3d ) );
++      GlobalPreferenceSystem().registerPreference( "CameraRenderMode", make_property_string<RenderMode>() );
++      GlobalPreferenceSystem().registerPreference( "StrafeMode", make_property_string( g_camwindow_globals_private.m_nStrafeMode ) );
++=======
++      GlobalShortcuts_insert( "CameraFreeMoveUp", accelerator_null() );
++      GlobalShortcuts_insert( "CameraFreeMoveDown", accelerator_null() );
++
++      GlobalToggles_insert( "ShowStats", FreeCaller<ShowStatsToggle>(), ToggleItem::AddCallbackCaller( g_show_stats ) );
++
++      GlobalPreferenceSystem().registerPreference( "ShowStats", BoolImportStringCaller( g_camwindow_globals_private.m_showStats ), BoolExportStringCaller( g_camwindow_globals_private.m_showStats ) );
++      GlobalPreferenceSystem().registerPreference( "MoveSpeed", IntImportStringCaller( g_camwindow_globals_private.m_nMoveSpeed ), IntExportStringCaller( g_camwindow_globals_private.m_nMoveSpeed ) );
++      GlobalPreferenceSystem().registerPreference( "CamLinkSpeed", BoolImportStringCaller( g_camwindow_globals_private.m_bCamLinkSpeed ), BoolExportStringCaller( g_camwindow_globals_private.m_bCamLinkSpeed ) );
++      GlobalPreferenceSystem().registerPreference( "AngleSpeed", IntImportStringCaller( g_camwindow_globals_private.m_nAngleSpeed ), IntExportStringCaller( g_camwindow_globals_private.m_nAngleSpeed ) );
++      GlobalPreferenceSystem().registerPreference( "CamInverseMouse", BoolImportStringCaller( g_camwindow_globals_private.m_bCamInverseMouse ), BoolExportStringCaller( g_camwindow_globals_private.m_bCamInverseMouse ) );
++      GlobalPreferenceSystem().registerPreference( "CamDiscrete", makeBoolStringImportCallback( CamWndMoveDiscreteImportCaller() ), BoolExportStringCaller( g_camwindow_globals_private.m_bCamDiscrete ) );
++      GlobalPreferenceSystem().registerPreference( "CubicClipping", BoolImportStringCaller( g_camwindow_globals_private.m_bCubicClipping ), BoolExportStringCaller( g_camwindow_globals_private.m_bCubicClipping ) );
++      GlobalPreferenceSystem().registerPreference( "CubicScale", IntImportStringCaller( g_camwindow_globals.m_nCubicScale ), IntExportStringCaller( g_camwindow_globals.m_nCubicScale ) );
++      GlobalPreferenceSystem().registerPreference( "SI_Colors4", Vector3ImportStringCaller( g_camwindow_globals.color_cameraback ), Vector3ExportStringCaller( g_camwindow_globals.color_cameraback ) );
++      GlobalPreferenceSystem().registerPreference( "SI_Colors12", Vector3ImportStringCaller( g_camwindow_globals.color_selbrushes3d ), Vector3ExportStringCaller( g_camwindow_globals.color_selbrushes3d ) );
++      GlobalPreferenceSystem().registerPreference( "CameraRenderMode", makeIntStringImportCallback( RenderModeImportCaller() ), makeIntStringExportCallback( RenderModeExportCaller() ) );
++      GlobalPreferenceSystem().registerPreference( "StrafeMode", IntImportStringCaller( g_camwindow_globals_private.m_nStrafeMode ), IntExportStringCaller( g_camwindow_globals_private.m_nStrafeMode ) );
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++
++      CamWnd_constructStatic();
++
++      Camera_registerPreferencesPage();
++}
++void CamWnd_Destroy(){
++      CamWnd_destroyStatic();
++}
index 22d0a963fc21269e6b09e988bfb3ac6f6eac6bb1,240c05f49e8e1ad62aba91da0a804fa4d7645d25..8e749542975c5d62dcab68f6f6784ab4a4dcc0cb
@@@ -1133,61 -1134,73 +1133,61 @@@ static gboolean rotatedlg_delete( ui::W
  
  RotateDialog g_rotate_dialog;
  void DoRotateDlg(){
 -      if ( g_rotate_dialog.window == NULL ) {
 -              g_rotate_dialog.window = create_dialog_window( MainFrame_getWindow(), "Arbitrary rotation", G_CALLBACK( rotatedlg_delete ), &g_rotate_dialog );
 +      if ( !g_rotate_dialog.window ) {
 +              g_rotate_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary rotation", G_CALLBACK(rotatedlg_delete ), &g_rotate_dialog );
  
 -              GtkAccelGroup* accel = gtk_accel_group_new();
 -              gtk_window_add_accel_group( g_rotate_dialog.window, accel );
 +              auto accel = ui::AccelGroup(ui::New);
 +              g_rotate_dialog.window.add_accel_group( accel );
  
                {
 -                      GtkHBox* hbox = create_dialog_hbox( 4, 4 );
 -                      gtk_container_add( GTK_CONTAINER( g_rotate_dialog.window ), GTK_WIDGET( hbox ) );
 +                      auto hbox = create_dialog_hbox( 4, 4 );
 +                      g_rotate_dialog.window.add(hbox);
                        {
 -                              GtkTable* table = create_dialog_table( 3, 2, 4, 4 );
 -                              gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
 +                              auto table = create_dialog_table( 3, 2, 4, 4 );
 +                              hbox.pack_start( table, TRUE, TRUE, 0 );
                                {
 -                                      GtkWidget* label = gtk_label_new( "  X  " );
 -                                      gtk_widget_show( label );
 -                                      gtk_table_attach( table, label, 0, 1, 0, 1,
 -                                                                        (GtkAttachOptions) ( 0 ),
 -                                                                        (GtkAttachOptions) ( 0 ), 0, 0 );
 +                                      ui::Widget label = ui::Label( "  X  " );
 +                                      label.show();
 +                    table.attach(label, {0, 1, 0, 1}, {0, 0});
                                }
                                {
 -                                      GtkWidget* label = gtk_label_new( "  Y  " );
 -                                      gtk_widget_show( label );
 -                                      gtk_table_attach( table, label, 0, 1, 1, 2,
 -                                                                        (GtkAttachOptions) ( 0 ),
 -                                                                        (GtkAttachOptions) ( 0 ), 0, 0 );
 +                                      ui::Widget label = ui::Label( "  Y  " );
 +                                      label.show();
 +                    table.attach(label, {0, 1, 1, 2}, {0, 0});
                                }
                                {
 -                                      GtkWidget* label = gtk_label_new( "  Z  " );
 -                                      gtk_widget_show( label );
 -                                      gtk_table_attach( table, label, 0, 1, 2, 3,
 -                                                                        (GtkAttachOptions) ( 0 ),
 -                                                                        (GtkAttachOptions) ( 0 ), 0, 0 );
 +                                      ui::Widget label = ui::Label( "  Z  " );
 +                                      label.show();
 +                    table.attach(label, {0, 1, 2, 3}, {0, 0});
                                }
                                {
 -                                      GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) );
 -                                      GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 1 ) );
 -                                      gtk_widget_show( GTK_WIDGET( spin ) );
 -                                      gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1,
 -                                                                        (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
 -                                                                        (GtkAttachOptions) ( 0 ), 0, 0 );
 -                                      gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
 +                                      auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
-                                       auto spin = ui::SpinButton( adj, 1, 0 );
++                                      auto spin = ui::SpinButton( adj, 1, 1 );
 +                                      spin.show();
 +                    table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
 +                    spin.dimensions(64, -1);
                                        gtk_spin_button_set_wrap( spin, TRUE );
  
 -                                      gtk_widget_grab_focus( GTK_WIDGET( spin ) );
 +                                      gtk_widget_grab_focus( spin  );
  
                                        g_rotate_dialog.x = spin;
                                }
                                {
 -                                      GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) );
 -                                      GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 1 ) );
 -                                      gtk_widget_show( GTK_WIDGET( spin ) );
 -                                      gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 1, 2,
 -                                                                        (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
 -                                                                        (GtkAttachOptions) ( 0 ), 0, 0 );
 -                                      gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
 +                                      auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
-                                       auto spin = ui::SpinButton( adj, 1, 0 );
++                                      auto spin = ui::SpinButton( adj, 1, 1 );
 +                                      spin.show();
 +                    table.attach(spin, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
 +                    spin.dimensions(64, -1);
                                        gtk_spin_button_set_wrap( spin, TRUE );
  
                                        g_rotate_dialog.y = spin;
                                }
                                {
 -                                      GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) );
 -                                      GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 1 ) );
 -                                      gtk_widget_show( GTK_WIDGET( spin ) );
 -                                      gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 2, 3,
 -                                                                        (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
 -                                                                        (GtkAttachOptions) ( 0 ), 0, 0 );
 -                                      gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
 +                                      auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
-                                       auto spin = ui::SpinButton( adj, 1, 0 );
++                                      auto spin = ui::SpinButton( adj, 1, 1 );
 +                                      spin.show();
 +                    table.attach(spin, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
 +                    spin.dimensions(64, -1);
                                        gtk_spin_button_set_wrap( spin, TRUE );
  
                                        g_rotate_dialog.z = spin;
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a042a21c9564f2d178cd305196c6d082c805c22a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1383 @@@
++/*
++   Copyright (C) 1999-2006 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
++ */
++
++#include "select.h"
++
++#include <gtk/gtk.h>
++
++#include "debugging/debugging.h"
++
++#include "ientity.h"
++#include "iselection.h"
++#include "iundo.h"
++
++#include <vector>
++
++#include "stream/stringstream.h"
++#include "signal/isignal.h"
++#include "shaderlib.h"
++#include "scenelib.h"
++
++#include "gtkutil/idledraw.h"
++#include "gtkutil/dialog.h"
++#include "gtkutil/widget.h"
++#include "brushmanip.h"
++#include "brush.h"
++#include "patchmanip.h"
++#include "patchdialog.h"
++#include "selection.h"
++#include "texwindow.h"
++#include "gtkmisc.h"
++#include "mainframe.h"
++#include "grid.h"
++#include "map.h"
++#include "entityinspector.h"
++
++
++
++select_workzone_t g_select_workzone;
++
++
++/**
++   Loops over all selected brushes and stores their
++   world AABBs in the specified array.
++ */
++class CollectSelectedBrushesBounds : public SelectionSystem::Visitor
++{
++AABB* m_bounds;     // array of AABBs
++Unsigned m_max;     // max AABB-elements in array
++Unsigned& m_count;  // count of valid AABBs stored in array
++
++public:
++CollectSelectedBrushesBounds( AABB* bounds, Unsigned max, Unsigned& count )
++      : m_bounds( bounds ),
++      m_max( max ),
++      m_count( count ){
++      m_count = 0;
++}
++
++void visit( scene::Instance& instance ) const {
++      ASSERT_MESSAGE( m_count <= m_max, "Invalid m_count in CollectSelectedBrushesBounds" );
++
++      // stop if the array is already full
++      if ( m_count == m_max ) {
++              return;
++      }
++
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( ( selectable != 0 )
++               && instance.isSelected() ) {
++              // brushes only
++              if ( Instance_getBrush( instance ) != 0 ) {
++                      m_bounds[m_count] = instance.worldAABB();
++                      ++m_count;
++              }
++      }
++}
++};
++
++/**
++   Selects all objects that intersect one of the bounding AABBs.
++   The exact intersection-method is specified through TSelectionPolicy
++ */
++template<class TSelectionPolicy>
++class SelectByBounds : public scene::Graph::Walker
++{
++AABB* m_aabbs;             // selection aabbs
++Unsigned m_count;          // number of aabbs in m_aabbs
++TSelectionPolicy policy;   // type that contains a custom intersection method aabb<->aabb
++
++public:
++SelectByBounds( AABB* aabbs, Unsigned count )
++      : m_aabbs( aabbs ),
++      m_count( count ){
++}
++
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      if( path.top().get().visible() ){
++              Selectable* selectable = Instance_getSelectable( instance );
++
++              // ignore worldspawn
++              Entity* entity = Node_getEntity( path.top() );
++              if ( entity ) {
++                      if ( string_equal( entity->getKeyValue( "classname" ), "worldspawn" ) ) {
++                              return true;
++                      }
++              }
++
++              if ( ( path.size() > 1 ) &&
++                      ( !path.top().get().isRoot() ) &&
++                      ( selectable != 0 ) &&
++                      ( !node_is_group( path.top() ) )
++                      ) {
++                      for ( Unsigned i = 0; i < m_count; ++i )
++                      {
++                              if ( policy.Evaluate( m_aabbs[i], instance ) ) {
++                                      selectable->setSelected( true );
++                              }
++                      }
++              }
++      }
++      else{
++              return false;
++      }
++
++      return true;
++}
++
++/**
++   Performs selection operation on the global scenegraph.
++   If delete_bounds_src is true, then the objects which were
++   used as source for the selection aabbs will be deleted.
++ */
++static void DoSelection( bool delete_bounds_src = true ){
++      if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
++              // we may not need all AABBs since not all selected objects have to be brushes
++              const Unsigned max = (Unsigned)GlobalSelectionSystem().countSelected();
++              AABB* aabbs = new AABB[max];
++
++              Unsigned count;
++              CollectSelectedBrushesBounds collector( aabbs, max, count );
++              GlobalSelectionSystem().foreachSelected( collector );
++
++              // nothing usable in selection
++              if ( !count ) {
++                      delete[] aabbs;
++                      return;
++              }
++
++              // delete selected objects
++              if ( delete_bounds_src ) { // see deleteSelection
++                      UndoableCommand undo( "deleteSelected" );
++                      Select_Delete();
++              }
++
++              // select objects with bounds
++              GlobalSceneGraph().traverse( SelectByBounds<TSelectionPolicy>( aabbs, count ) );
++
++              SceneChangeNotify();
++              delete[] aabbs;
++      }
++}
++};
++
++/**
++   SelectionPolicy for SelectByBounds
++   Returns true if box and the AABB of instance intersect
++ */
++class SelectionPolicy_Touching
++{
++public:
++bool Evaluate( const AABB& box, scene::Instance& instance ) const {
++      const AABB& other( instance.worldAABB() );
++      for ( Unsigned i = 0; i < 3; ++i )
++      {
++              if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] + other.extents[i] ) ) {
++                      return false;
++              }
++      }
++      return true;
++}
++};
++
++/**
++   SelectionPolicy for SelectByBounds
++   Returns true if the AABB of instance is inside box
++ */
++class SelectionPolicy_Inside
++{
++public:
++bool Evaluate( const AABB& box, scene::Instance& instance ) const {
++      const AABB& other( instance.worldAABB() );
++      for ( Unsigned i = 0; i < 3; ++i )
++      {
++              if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] - other.extents[i] ) ) {
++                      return false;
++              }
++      }
++      return true;
++}
++};
++
++class DeleteSelected : public scene::Graph::Walker
++{
++mutable bool m_remove;
++mutable bool m_removedChild;
++public:
++DeleteSelected()
++      : m_remove( false ), m_removedChild( false ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      m_removedChild = false;
++
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( selectable != 0
++               && selectable->isSelected()
++               && path.size() > 1
++               && !path.top().get().isRoot() ) {
++              m_remove = true;
++
++              return false; // dont traverse into child elements
++      }
++      return true;
++}
++void post( const scene::Path& path, scene::Instance& instance ) const {
++
++      if ( m_removedChild ) {
++              m_removedChild = false;
++
++              // delete empty entities
++              Entity* entity = Node_getEntity( path.top() );
++              if ( entity != 0
++                       && path.top().get_pointer() != Map_FindWorldspawn( g_map )
++                       && Node_getTraversable( path.top() )->empty() ) {
++                      Path_deleteTop( path );
++              }
++      }
++
++      // node should be removed
++      if ( m_remove ) {
++              if ( Node_isEntity( path.parent() ) != 0 ) {
++                      m_removedChild = true;
++              }
++
++              m_remove = false;
++              Path_deleteTop( path );
++      }
++}
++};
++
++void Scene_DeleteSelected( scene::Graph& graph ){
++      graph.traverse( DeleteSelected() );
++      SceneChangeNotify();
++}
++
++void Select_Delete( void ){
++      Scene_DeleteSelected( GlobalSceneGraph() );
++}
++
++class InvertSelectionWalker : public scene::Graph::Walker
++{
++SelectionSystem::EMode m_mode;
++mutable Selectable* m_selectable;
++public:
++InvertSelectionWalker( SelectionSystem::EMode mode )
++      : m_mode( mode ), m_selectable( 0 ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      if( !path.top().get().visible() ){
++              m_selectable = 0;
++              return false;
++      }
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( selectable ) {
++              switch ( m_mode )
++              {
++              case SelectionSystem::eEntity:
++                      if ( Node_isEntity( path.top() ) != 0 ) {
++                              m_selectable = path.top().get().visible() ? selectable : 0;
++                      }
++                      break;
++              case SelectionSystem::ePrimitive:
++                      m_selectable = path.top().get().visible() ? selectable : 0;
++                      break;
++              case SelectionSystem::eComponent:
++                      break;
++              }
++      }
++      return true;
++}
++void post( const scene::Path& path, scene::Instance& instance ) const {
++      if ( m_selectable != 0 ) {
++              m_selectable->setSelected( !m_selectable->isSelected() );
++              m_selectable = 0;
++      }
++}
++};
++
++void Scene_Invert_Selection( scene::Graph& graph ){
++      graph.traverse( InvertSelectionWalker( GlobalSelectionSystem().Mode() ) );
++}
++
++void Select_Invert(){
++      Scene_Invert_Selection( GlobalSceneGraph() );
++}
++
++//interesting printings
++class ExpandSelectionToEntitiesWalker_dbg : public scene::Graph::Walker
++{
++mutable std::size_t m_depth;
++NodeSmartReference worldspawn;
++public:
++ExpandSelectionToEntitiesWalker_dbg() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      ++m_depth;
++      globalOutputStream() << "pre depth_" << m_depth;
++      globalOutputStream() << " path.size()_" << path.size();
++      if ( path.top().get() == worldspawn )
++              globalOutputStream() << " worldspawn";
++      if( path.top().get().isRoot() )
++              globalOutputStream() << " path.top().get().isRoot()";
++      Entity* entity = Node_getEntity( path.top() );
++      if ( entity != 0 ){
++              globalOutputStream() << " entity!=0";
++              if( entity->isContainer() ){
++                      globalOutputStream() << " entity->isContainer()";
++              }
++              globalOutputStream() << " classname_" << entity->getKeyValue( "classname" );
++      }
++      globalOutputStream() << "\n";
++//    globalOutputStream() << "" <<  ;
++//    globalOutputStream() << "" <<  ;
++//    globalOutputStream() << "" <<  ;
++//    globalOutputStream() << "" <<  ;
++      return true;
++}
++void post( const scene::Path& path, scene::Instance& instance ) const {
++      globalOutputStream() << "post depth_" << m_depth;
++      globalOutputStream() << " path.size()_" << path.size();
++      if ( path.top().get() == worldspawn )
++              globalOutputStream() << " worldspawn";
++      if( path.top().get().isRoot() )
++              globalOutputStream() << " path.top().get().isRoot()";
++      Entity* entity = Node_getEntity( path.top() );
++      if ( entity != 0 ){
++              globalOutputStream() << " entity!=0";
++              if( entity->isContainer() ){
++                      globalOutputStream() << " entity->isContainer()";
++              }
++              globalOutputStream() << " classname_" << entity->getKeyValue( "classname" );
++      }
++      globalOutputStream() << "\n";
++      --m_depth;
++}
++};
++
++class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker
++{
++mutable std::size_t m_depth;
++NodeSmartReference worldspawn;
++public:
++ExpandSelectionToEntitiesWalker() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      ++m_depth;
++
++      // ignore worldspawn
++//    NodeSmartReference me( path.top().get() );
++//    if ( me == worldspawn ) {
++//            return false;
++//    }
++
++      if ( m_depth == 2 ) { // entity depth
++              // traverse and select children if any one is selected
++              bool beselected = false;
++              if ( instance.childSelected() ) {
++                      beselected = true;
++                      if( path.top().get() != worldspawn ){
++                              Instance_setSelected( instance, true );
++                      }
++              }
++              return Node_getEntity( path.top() )->isContainer() && beselected;
++      }
++      else if ( m_depth == 3 ) { // primitive depth
++              Instance_setSelected( instance, true );
++              return false;
++      }
++      return true;
++}
++void post( const scene::Path& path, scene::Instance& instance ) const {
++      --m_depth;
++}
++};
++
++void Scene_ExpandSelectionToEntities(){
++      GlobalSceneGraph().traverse( ExpandSelectionToEntitiesWalker() );
++}
++
++
++namespace
++{
++void Selection_UpdateWorkzone(){
++      if ( GlobalSelectionSystem().countSelected() != 0 ) {
++              Select_GetBounds( g_select_workzone.d_work_min, g_select_workzone.d_work_max );
++      }
++}
++typedef FreeCaller<void(), Selection_UpdateWorkzone> SelectionUpdateWorkzoneCaller;
++
++IdleDraw g_idleWorkzone = IdleDraw( SelectionUpdateWorkzoneCaller() );
++}
++
++const select_workzone_t& Select_getWorkZone(){
++      g_idleWorkzone.flush();
++      return g_select_workzone;
++}
++
++void UpdateWorkzone_ForSelection(){
++      g_idleWorkzone.queueDraw();
++}
++
++// update the workzone to the current selection
++void UpdateWorkzone_ForSelectionChanged( const Selectable& selectable ){
++      if ( selectable.isSelected() ) {
++              UpdateWorkzone_ForSelection();
++      }
++}
++
++void Select_SetShader( const char* shader ){
++      if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
++              Scene_BrushSetShader_Selected( GlobalSceneGraph(), shader );
++              Scene_PatchSetShader_Selected( GlobalSceneGraph(), shader );
++      }
++      Scene_BrushSetShader_Component_Selected( GlobalSceneGraph(), shader );
++}
++
++void Select_SetTexdef( const TextureProjection& projection ){
++      if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
++              Scene_BrushSetTexdef_Selected( GlobalSceneGraph(), projection );
++      }
++      Scene_BrushSetTexdef_Component_Selected( GlobalSceneGraph(), projection );
++}
++
++void Select_SetFlags( const ContentsFlagsValue& flags ){
++      if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
++              Scene_BrushSetFlags_Selected( GlobalSceneGraph(), flags );
++      }
++      Scene_BrushSetFlags_Component_Selected( GlobalSceneGraph(), flags );
++}
++
++void Select_GetBounds( Vector3& mins, Vector3& maxs ){
++      AABB bounds;
++      Scene_BoundsSelected( GlobalSceneGraph(), bounds );
++      maxs = vector3_added( bounds.origin, bounds.extents );
++      mins = vector3_subtracted( bounds.origin, bounds.extents );
++}
++
++void Select_GetMid( Vector3& mid ){
++      AABB bounds;
++      Scene_BoundsSelected( GlobalSceneGraph(), bounds );
++      mid = vector3_snapped( bounds.origin );
++}
++
++
++void Select_FlipAxis( int axis ){
++      Vector3 flip( 1, 1, 1 );
++      flip[axis] = -1;
++      GlobalSelectionSystem().scaleSelected( flip );
++}
++
++
++void Select_Scale( float x, float y, float z ){
++      GlobalSelectionSystem().scaleSelected( Vector3( x, y, z ) );
++}
++
++enum axis_t
++{
++      eAxisX = 0,
++      eAxisY = 1,
++      eAxisZ = 2,
++};
++
++enum sign_t
++{
++      eSignPositive = 1,
++      eSignNegative = -1,
++};
++
++inline Matrix4 matrix4_rotation_for_axis90( axis_t axis, sign_t sign ){
++      switch ( axis )
++      {
++      case eAxisX:
++              if ( sign == eSignPositive ) {
++                      return matrix4_rotation_for_sincos_x( 1, 0 );
++              }
++              else
++              {
++                      return matrix4_rotation_for_sincos_x( -1, 0 );
++              }
++      case eAxisY:
++              if ( sign == eSignPositive ) {
++                      return matrix4_rotation_for_sincos_y( 1, 0 );
++              }
++              else
++              {
++                      return matrix4_rotation_for_sincos_y( -1, 0 );
++              }
++      default: //case eAxisZ:
++              if ( sign == eSignPositive ) {
++                      return matrix4_rotation_for_sincos_z( 1, 0 );
++              }
++              else
++              {
++                      return matrix4_rotation_for_sincos_z( -1, 0 );
++              }
++      }
++}
++
++inline void matrix4_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign ){
++      matrix4_multiply_by_matrix4( matrix, matrix4_rotation_for_axis90( axis, sign ) );
++}
++
++inline void matrix4_pivoted_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint ){
++      matrix4_translate_by_vec3( matrix, pivotpoint );
++      matrix4_rotate_by_axis90( matrix, axis, sign );
++      matrix4_translate_by_vec3( matrix, vector3_negated( pivotpoint ) );
++}
++
++inline Quaternion quaternion_for_axis90( axis_t axis, sign_t sign ){
++#if 1
++      switch ( axis )
++      {
++      case eAxisX:
++              if ( sign == eSignPositive ) {
++                      return Quaternion( c_half_sqrt2f, 0, 0, c_half_sqrt2f );
++              }
++              else
++              {
++                      return Quaternion( -c_half_sqrt2f, 0, 0, -c_half_sqrt2f );
++              }
++      case eAxisY:
++              if ( sign == eSignPositive ) {
++                      return Quaternion( 0, c_half_sqrt2f, 0, c_half_sqrt2f );
++              }
++              else
++              {
++                      return Quaternion( 0, -c_half_sqrt2f, 0, -c_half_sqrt2f );
++              }
++      default: //case eAxisZ:
++              if ( sign == eSignPositive ) {
++                      return Quaternion( 0, 0, c_half_sqrt2f, c_half_sqrt2f );
++              }
++              else
++              {
++                      return Quaternion( 0, 0, -c_half_sqrt2f, -c_half_sqrt2f );
++              }
++      }
++#else
++      quaternion_for_matrix4_rotation( matrix4_rotation_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
++#endif
++}
++
++void Select_RotateAxis( int axis, float deg ){
++      if ( fabs( deg ) == 90.f ) {
++              GlobalSelectionSystem().rotateSelected( quaternion_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ), true );
++      }
++      else
++      {
++              switch ( axis )
++              {
++              case 0:
++                      GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_x_degrees( deg ) ), false );
++                      break;
++              case 1:
++                      GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_y_degrees( deg ) ), false );
++                      break;
++              case 2:
++                      GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_z_degrees( deg ) ), false );
++                      break;
++              }
++      }
++}
++
++
++void Select_ShiftTexture( float x, float y ){
++      if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
++              Scene_BrushShiftTexdef_Selected( GlobalSceneGraph(), x, y );
++              Scene_PatchTranslateTexture_Selected( GlobalSceneGraph(), x, y );
++      }
++      //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n';
++      Scene_BrushShiftTexdef_Component_Selected( GlobalSceneGraph(), x, y );
++}
++
++void Select_ScaleTexture( float x, float y ){
++      if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
++              Scene_BrushScaleTexdef_Selected( GlobalSceneGraph(), x, y );
++              Scene_PatchScaleTexture_Selected( GlobalSceneGraph(), x, y );
++      }
++      Scene_BrushScaleTexdef_Component_Selected( GlobalSceneGraph(), x, y );
++}
++
++void Select_RotateTexture( float amt ){
++      if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
++              Scene_BrushRotateTexdef_Selected( GlobalSceneGraph(), amt );
++              Scene_PatchRotateTexture_Selected( GlobalSceneGraph(), amt );
++      }
++      Scene_BrushRotateTexdef_Component_Selected( GlobalSceneGraph(), amt );
++}
++
++// TTimo modified to handle shader architecture:
++// expects shader names at input, comparison relies on shader names .. texture names no longer relevant
++void FindReplaceTextures( const char* pFind, const char* pReplace, bool bSelected ){
++      if ( !texdef_name_valid( pFind ) ) {
++              globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n";
++              return;
++      }
++      if ( !texdef_name_valid( pReplace ) ) {
++              globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n";
++              return;
++      }
++
++      StringOutputStream command;
++      command << "textureFindReplace -find " << pFind << " -replace " << pReplace;
++      UndoableCommand undo( command.c_str() );
++
++      if ( bSelected ) {
++              if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
++                      Scene_BrushFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
++                      Scene_PatchFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
++              }
++              Scene_BrushFindReplaceShader_Component_Selected( GlobalSceneGraph(), pFind, pReplace );
++      }
++      else
++      {
++              Scene_BrushFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
++              Scene_PatchFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
++      }
++}
++
++typedef std::vector<const char*> PropertyValues;
++
++bool propertyvalues_contain( const PropertyValues& propertyvalues, const char *str ){
++      for ( PropertyValues::const_iterator i = propertyvalues.begin(); i != propertyvalues.end(); ++i )
++      {
++              if ( string_equal( str, *i ) ) {
++                      return true;
++              }
++      }
++      return false;
++}
++
++class EntityFindByPropertyValueWalker : public scene::Graph::Walker
++{
++const PropertyValues& m_propertyvalues;
++const char *m_prop;
++const NodeSmartReference worldspawn;
++public:
++EntityFindByPropertyValueWalker( const char *prop, const PropertyValues& propertyvalues )
++      : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      if( !path.top().get().visible() ){
++              return false;
++      }
++      // ignore worldspawn
++      if ( path.top().get() == worldspawn ) {
++              return false;
++      }
++
++      Entity* entity = Node_getEntity( path.top() );
++      if ( entity != 0 ){
++              if( propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
++                      Instance_getSelectable( instance )->setSelected( true );
++                      return true;
++              }
++              return false;
++      }
++      else if( path.size() > 2 && !path.top().get().isRoot() ){
++              Selectable* selectable = Instance_getSelectable( instance );
++              if( selectable != 0 )
++                      selectable->setSelected( true );
++      }
++      return true;
++}
++};
++
++void Scene_EntitySelectByPropertyValues( scene::Graph& graph, const char *prop, const PropertyValues& propertyvalues ){
++      graph.traverse( EntityFindByPropertyValueWalker( prop, propertyvalues ) );
++}
++
++class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
++{
++PropertyValues& m_propertyvalues;
++const char *m_prop;
++const NodeSmartReference worldspawn;
++public:
++EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
++      : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      Entity* entity = Node_getEntity( path.top() );
++      if ( entity != 0 ){
++              if( path.top().get() != worldspawn ){
++                      Selectable* selectable = Instance_getSelectable( instance );
++                      if ( ( selectable != 0 && selectable->isSelected() ) || instance.childSelected() ) {
++                              if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
++                                      m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
++                              }
++                      }
++              }
++              return false;
++      }
++      return true;
++}
++};
++/*
++class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
++{
++PropertyValues& m_propertyvalues;
++const char *m_prop;
++mutable bool m_selected_children;
++const NodeSmartReference worldspawn;
++public:
++EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
++      : m_propertyvalues( propertyvalues ), m_prop( prop ), m_selected_children( false ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( selectable != 0
++               && selectable->isSelected() ) {
++              Entity* entity = Node_getEntity( path.top() );
++              if ( entity != 0 ) {
++                      if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
++                              m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
++                      }
++                      return false;
++              }
++              else{
++                      m_selected_children = true;
++              }
++      }
++      return true;
++}
++void post( const scene::Path& path, scene::Instance& instance ) const {
++      Entity* entity = Node_getEntity( path.top() );
++      if( entity != 0 && m_selected_children ){
++              m_selected_children = false;
++              if( path.top().get() == worldspawn )
++                      return;
++              if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
++                      m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
++              }
++      }
++}
++};
++*/
++void Scene_EntityGetPropertyValues( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
++      graph.traverse( EntityGetSelectedPropertyValuesWalker( prop, propertyvalues ) );
++}
++
++void Select_AllOfType(){
++      if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent ) {
++              if ( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) {
++                      GlobalSelectionSystem().setSelectedAllComponents( false );
++                      Scene_BrushSelectByShader_Component( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
++              }
++      }
++      else
++      {
++              PropertyValues propertyvalues;
++              const char *prop = EntityInspector_getCurrentKey();
++              if ( !prop || !*prop ) {
++                      prop = "classname";
++              }
++              Scene_EntityGetPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
++              GlobalSelectionSystem().setSelectedAll( false );
++              if ( !propertyvalues.empty() ) {
++                      Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
++              }
++              else
++              {
++                      Scene_BrushSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
++                      Scene_PatchSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
++              }
++      }
++}
++
++void Select_Inside( void ){
++      SelectByBounds<SelectionPolicy_Inside>::DoSelection();
++}
++
++void Select_Touching( void ){
++      SelectByBounds<SelectionPolicy_Touching>::DoSelection( false );
++}
++
++void Select_FitTexture( float horizontal, float vertical ){
++      if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
++              Scene_BrushFitTexture_Selected( GlobalSceneGraph(), horizontal, vertical );
++      }
++      Scene_BrushFitTexture_Component_Selected( GlobalSceneGraph(), horizontal, vertical );
++
++      SceneChangeNotify();
++}
++
++
++#include "commands.h"
++#include "dialog.h"
++
++inline void hide_node( scene::Node& node, bool hide ){
++      hide
++      ? node.enable( scene::Node::eHidden )
++      : node.disable( scene::Node::eHidden );
++}
++
++bool g_nodes_be_hidden = false;
++
++ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_hidden_caller( g_nodes_be_hidden );
++
++ToggleItem g_hidden_item( g_hidden_caller );
++
++class HideSelectedWalker : public scene::Graph::Walker
++{
++bool m_hide;
++public:
++HideSelectedWalker( bool hide )
++      : m_hide( hide ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( selectable != 0
++               && selectable->isSelected() ) {
++              g_nodes_be_hidden = m_hide;
++              hide_node( path.top(), m_hide );
++      }
++      return true;
++}
++};
++
++void Scene_Hide_Selected( bool hide ){
++      GlobalSceneGraph().traverse( HideSelectedWalker( hide ) );
++}
++
++void Select_Hide(){
++      Scene_Hide_Selected( true );
++      SceneChangeNotify();
++}
++
++void HideSelected(){
++      Select_Hide();
++      GlobalSelectionSystem().setSelectedAll( false );
++      g_hidden_item.update();
++}
++
++
++class HideAllWalker : public scene::Graph::Walker
++{
++bool m_hide;
++public:
++HideAllWalker( bool hide )
++      : m_hide( hide ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      hide_node( path.top(), m_hide );
++      return true;
++}
++};
++
++void Scene_Hide_All( bool hide ){
++      GlobalSceneGraph().traverse( HideAllWalker( hide ) );
++}
++
++void Select_ShowAllHidden(){
++      Scene_Hide_All( false );
++      SceneChangeNotify();
++      g_nodes_be_hidden = false;
++      g_hidden_item.update();
++}
++
++
++void Selection_Flipx(){
++      UndoableCommand undo( "mirrorSelected -axis x" );
++      Select_FlipAxis( 0 );
++}
++
++void Selection_Flipy(){
++      UndoableCommand undo( "mirrorSelected -axis y" );
++      Select_FlipAxis( 1 );
++}
++
++void Selection_Flipz(){
++      UndoableCommand undo( "mirrorSelected -axis z" );
++      Select_FlipAxis( 2 );
++}
++
++void Selection_Rotatex(){
++      UndoableCommand undo( "rotateSelected -axis x -angle -90" );
++      Select_RotateAxis( 0,-90 );
++}
++
++void Selection_Rotatey(){
++      UndoableCommand undo( "rotateSelected -axis y -angle 90" );
++      Select_RotateAxis( 1, 90 );
++}
++
++void Selection_Rotatez(){
++      UndoableCommand undo( "rotateSelected -axis z -angle -90" );
++      Select_RotateAxis( 2,-90 );
++}
++#include "xywindow.h"
++void Selection_FlipHorizontally(){
++      VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
++      switch ( viewtype )
++      {
++      case XY:
++      case XZ:
++              Selection_Flipx();
++              break;
++      default:
++              Selection_Flipy();
++              break;
++      }
++}
++
++void Selection_FlipVertically(){
++      VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
++      switch ( viewtype )
++      {
++      case XZ:
++      case YZ:
++              Selection_Flipz();
++              break;
++      default:
++              Selection_Flipy();
++              break;
++      }
++}
++
++void Selection_RotateClockwise(){
++      UndoableCommand undo( "rotateSelected Clockwise 90" );
++      VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
++      switch ( viewtype )
++      {
++      case XY:
++              Select_RotateAxis( 2, -90 );
++              break;
++      case XZ:
++              Select_RotateAxis( 1, 90 );
++              break;
++      default:
++              Select_RotateAxis( 0, -90 );
++              break;
++      }
++}
++
++void Selection_RotateAnticlockwise(){
++      UndoableCommand undo( "rotateSelected Anticlockwise 90" );
++      VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
++      switch ( viewtype )
++      {
++      case XY:
++              Select_RotateAxis( 2, 90 );
++              break;
++      case XZ:
++              Select_RotateAxis( 1, -90 );
++              break;
++      default:
++              Select_RotateAxis( 0, 90 );
++              break;
++      }
++
++}
++
++
++
++void Select_registerCommands(){
++      GlobalCommands_insert( "ShowHidden", makeCallbackF( Select_ShowAllHidden ), Accelerator( 'H', (GdkModifierType)GDK_SHIFT_MASK ) );
++      GlobalToggles_insert( "HideSelected", makeCallbackF( HideSelected ), ToggleItem::AddCallbackCaller( g_hidden_item ), Accelerator( 'H' ) );
++
++      GlobalCommands_insert( "MirrorSelectionX", makeCallbackF( Selection_Flipx ) );
++      GlobalCommands_insert( "RotateSelectionX", makeCallbackF( Selection_Rotatex ) );
++      GlobalCommands_insert( "MirrorSelectionY", makeCallbackF( Selection_Flipy ) );
++      GlobalCommands_insert( "RotateSelectionY", makeCallbackF( Selection_Rotatey ) );
++      GlobalCommands_insert( "MirrorSelectionZ", makeCallbackF( Selection_Flipz ) );
++      GlobalCommands_insert( "RotateSelectionZ", makeCallbackF( Selection_Rotatez ) );
++
++      GlobalCommands_insert( "MirrorSelectionHorizontally", makeCallbackF( Selection_FlipHorizontally ) );
++      GlobalCommands_insert( "MirrorSelectionVertically", makeCallbackF( Selection_FlipVertically ) );
++
++      GlobalCommands_insert( "RotateSelectionClockwise", makeCallbackF( Selection_RotateClockwise ) );
++      GlobalCommands_insert( "RotateSelectionAnticlockwise", makeCallbackF( Selection_RotateAnticlockwise ) );
++}
++
++
++void Nudge( int nDim, float fNudge ){
++      Vector3 translate( 0, 0, 0 );
++      translate[nDim] = fNudge;
++
++      GlobalSelectionSystem().translateSelected( translate );
++}
++
++void Selection_NudgeZ( float amount ){
++      StringOutputStream command;
++      command << "nudgeSelected -axis z -amount " << amount;
++      UndoableCommand undo( command.c_str() );
++
++      Nudge( 2, amount );
++}
++
++void Selection_MoveDown(){
++      Selection_NudgeZ( -GetGridSize() );
++}
++
++void Selection_MoveUp(){
++      Selection_NudgeZ( GetGridSize() );
++}
++
++void SceneSelectionChange( const Selectable& selectable ){
++      SceneChangeNotify();
++}
++
++SignalHandlerId Selection_boundsChanged;
++
++void Selection_construct(){
++      typedef FreeCaller<void(const Selectable&), SceneSelectionChange> SceneSelectionChangeCaller;
++      GlobalSelectionSystem().addSelectionChangeCallback( SceneSelectionChangeCaller() );
++      typedef FreeCaller<void(const Selectable&), UpdateWorkzone_ForSelectionChanged> UpdateWorkzoneForSelectionChangedCaller;
++      GlobalSelectionSystem().addSelectionChangeCallback( UpdateWorkzoneForSelectionChangedCaller() );
++      typedef FreeCaller<void(), UpdateWorkzone_ForSelection> UpdateWorkzoneForSelectionCaller;
++      Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( UpdateWorkzoneForSelectionCaller() );
++}
++
++void Selection_destroy(){
++      GlobalSceneGraph().removeBoundsChangedCallback( Selection_boundsChanged );
++}
++
++
++#include "gtkdlgs.h"
++#include <gdk/gdkkeysyms.h>
++
++
++inline Quaternion quaternion_for_euler_xyz_degrees( const Vector3& eulerXYZ ){
++#if 0
++      return quaternion_for_matrix4_rotation( matrix4_rotation_for_euler_xyz_degrees( eulerXYZ ) );
++#elif 0
++      return quaternion_multiplied_by_quaternion(
++                         quaternion_multiplied_by_quaternion(
++                                 quaternion_for_z( degrees_to_radians( eulerXYZ[2] ) ),
++                                 quaternion_for_y( degrees_to_radians( eulerXYZ[1] ) )
++                                 ),
++                         quaternion_for_x( degrees_to_radians( eulerXYZ[0] ) )
++                         );
++#elif 1
++      double cx = cos( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
++      double sx = sin( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
++      double cy = cos( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
++      double sy = sin( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
++      double cz = cos( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
++      double sz = sin( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
++
++      return Quaternion(
++                         cz * cy * sx - sz * sy * cx,
++                         cz * sy * cx + sz * cy * sx,
++                         sz * cy * cx - cz * sy * sx,
++                         cz * cy * cx + sz * sy * sx
++                         );
++#endif
++}
++
++struct RotateDialog
++{
++      ui::SpinButton x{ui::null};
++      ui::SpinButton y{ui::null};
++      ui::SpinButton z{ui::null};
++      ui::Window window{ui::null};
++};
++
++static gboolean rotatedlg_apply( ui::Widget widget, RotateDialog* rotateDialog ){
++      Vector3 eulerXYZ;
++
++      gtk_spin_button_update ( rotateDialog->x );
++      gtk_spin_button_update ( rotateDialog->y );
++      gtk_spin_button_update ( rotateDialog->z );
++      eulerXYZ[0] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->x ) );
++      eulerXYZ[1] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->y ) );
++      eulerXYZ[2] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->z ) );
++
++      StringOutputStream command;
++      command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
++      UndoableCommand undo( command.c_str() );
++
++      GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ), false );
++      return TRUE;
++}
++
++static gboolean rotatedlg_cancel( ui::Widget widget, RotateDialog* rotateDialog ){
++      rotateDialog->window.hide();
++
++      gtk_spin_button_set_value( rotateDialog->x, 0.0f ); // reset to 0 on close
++      gtk_spin_button_set_value( rotateDialog->y, 0.0f );
++      gtk_spin_button_set_value( rotateDialog->z, 0.0f );
++
++      return TRUE;
++}
++
++static gboolean rotatedlg_ok( ui::Widget widget, RotateDialog* rotateDialog ){
++      rotatedlg_apply( widget, rotateDialog );
++//    rotatedlg_cancel( widget, rotateDialog );
++      rotateDialog->window.hide();
++      return TRUE;
++}
++
++static gboolean rotatedlg_delete( ui::Widget widget, GdkEventAny *event, RotateDialog* rotateDialog ){
++      rotatedlg_cancel( widget, rotateDialog );
++      return TRUE;
++}
++
++RotateDialog g_rotate_dialog;
++void DoRotateDlg(){
++      if ( !g_rotate_dialog.window ) {
++              g_rotate_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary rotation", G_CALLBACK(rotatedlg_delete ), &g_rotate_dialog );
++
++              auto accel = ui::AccelGroup(ui::New);
++              g_rotate_dialog.window.add_accel_group( accel );
++
++              {
++                      auto hbox = create_dialog_hbox( 4, 4 );
++                      g_rotate_dialog.window.add(hbox);
++                      {
++                              auto table = create_dialog_table( 3, 2, 4, 4 );
++                              hbox.pack_start( table, TRUE, TRUE, 0 );
++                              {
++                                      ui::Widget label = ui::Label( "  X  " );
++                                      label.show();
++                    table.attach(label, {0, 1, 0, 1}, {0, 0});
++                              }
++                              {
++                                      ui::Widget label = ui::Label( "  Y  " );
++                                      label.show();
++                    table.attach(label, {0, 1, 1, 2}, {0, 0});
++                              }
++                              {
++                                      ui::Widget label = ui::Label( "  Z  " );
++                                      label.show();
++                    table.attach(label, {0, 1, 2, 3}, {0, 0});
++                              }
++                              {
++<<<<<<< HEAD
++                                      auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
++                                      auto spin = ui::SpinButton( adj, 1, 0 );
++                                      spin.show();
++                    table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
++                    spin.dimensions(64, -1);
++=======
++                                      GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) );
++                                      GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 1 ) );
++                                      gtk_widget_show( GTK_WIDGET( spin ) );
++                                      gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1,
++                                                                        (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
++                                                                        (GtkAttachOptions) ( 0 ), 0, 0 );
++                                      gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++                                      gtk_spin_button_set_wrap( spin, TRUE );
++
++                                      gtk_widget_grab_focus( spin  );
++
++                                      g_rotate_dialog.x = spin;
++                              }
++                              {
++<<<<<<< HEAD
++                                      auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
++                                      auto spin = ui::SpinButton( adj, 1, 0 );
++                                      spin.show();
++                    table.attach(spin, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
++                    spin.dimensions(64, -1);
++=======
++                                      GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) );
++                                      GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 1 ) );
++                                      gtk_widget_show( GTK_WIDGET( spin ) );
++                                      gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 1, 2,
++                                                                        (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
++                                                                        (GtkAttachOptions) ( 0 ), 0, 0 );
++                                      gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++                                      gtk_spin_button_set_wrap( spin, TRUE );
++
++                                      g_rotate_dialog.y = spin;
++                              }
++                              {
++<<<<<<< HEAD
++                                      auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
++                                      auto spin = ui::SpinButton( adj, 1, 0 );
++                                      spin.show();
++                    table.attach(spin, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
++                    spin.dimensions(64, -1);
++=======
++                                      GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) );
++                                      GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 1 ) );
++                                      gtk_widget_show( GTK_WIDGET( spin ) );
++                                      gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 2, 3,
++                                                                        (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
++                                                                        (GtkAttachOptions) ( 0 ), 0, 0 );
++                                      gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++                                      gtk_spin_button_set_wrap( spin, TRUE );
++
++                                      g_rotate_dialog.z = spin;
++                              }
++                      }
++                      {
++                              auto vbox = create_dialog_vbox( 4 );
++                              hbox.pack_start( vbox, TRUE, TRUE, 0 );
++                              {
++                                      auto button = create_dialog_button( "OK", G_CALLBACK( rotatedlg_ok ), &g_rotate_dialog );
++                                      vbox.pack_start( button, FALSE, FALSE, 0 );
++                                      widget_make_default( button );
++                                      gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
++                              }
++                              {
++                                      auto button = create_dialog_button( "Cancel", G_CALLBACK( rotatedlg_cancel ), &g_rotate_dialog );
++                                      vbox.pack_start( button, FALSE, FALSE, 0 );
++                                      gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
++                              }
++                              {
++                                      auto button = create_dialog_button( "Apply", G_CALLBACK( rotatedlg_apply ), &g_rotate_dialog );
++                                      vbox.pack_start( button, FALSE, FALSE, 0 );
++                              }
++                      }
++              }
++      }
++
++      g_rotate_dialog.window.show();
++}
++
++
++
++
++
++
++
++
++
++struct ScaleDialog
++{
++      ui::Entry x{ui::null};
++      ui::Entry y{ui::null};
++      ui::Entry z{ui::null};
++      ui::Window window{ui::null};
++};
++
++static gboolean scaledlg_apply( ui::Widget widget, ScaleDialog* scaleDialog ){
++      float sx, sy, sz;
++
++      sx = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->x ) ) ) );
++      sy = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->y ) ) ) );
++      sz = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->z ) ) ) );
++
++      StringOutputStream command;
++      command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz;
++      UndoableCommand undo( command.c_str() );
++
++      Select_Scale( sx, sy, sz );
++
++      return TRUE;
++}
++
++static gboolean scaledlg_cancel( ui::Widget widget, ScaleDialog* scaleDialog ){
++      scaleDialog->window.hide();
++
++      scaleDialog->x.text("1.0");
++      scaleDialog->y.text("1.0");
++      scaleDialog->z.text("1.0");
++
++      return TRUE;
++}
++
++static gboolean scaledlg_ok( ui::Widget widget, ScaleDialog* scaleDialog ){
++      scaledlg_apply( widget, scaleDialog );
++      //scaledlg_cancel( widget, scaleDialog );
++      scaleDialog->window.hide();
++      return TRUE;
++}
++
++static gboolean scaledlg_delete( ui::Widget widget, GdkEventAny *event, ScaleDialog* scaleDialog ){
++      scaledlg_cancel( widget, scaleDialog );
++      return TRUE;
++}
++
++ScaleDialog g_scale_dialog;
++
++void DoScaleDlg(){
++      if ( !g_scale_dialog.window ) {
++              g_scale_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary scale", G_CALLBACK(scaledlg_delete ), &g_scale_dialog );
++
++              auto accel = ui::AccelGroup(ui::New);
++              g_scale_dialog.window.add_accel_group( accel );
++
++              {
++                      auto hbox = create_dialog_hbox( 4, 4 );
++                      g_scale_dialog.window.add(hbox);
++                      {
++                              auto table = create_dialog_table( 3, 2, 4, 4 );
++                              hbox.pack_start( table, TRUE, TRUE, 0 );
++                              {
++                                      ui::Widget label = ui::Label( "  X  " );
++                                      label.show();
++                    table.attach(label, {0, 1, 0, 1}, {0, 0});
++                              }
++                              {
++                                      ui::Widget label = ui::Label( "  Y  " );
++                                      label.show();
++                    table.attach(label, {0, 1, 1, 2}, {0, 0});
++                              }
++                              {
++                                      ui::Widget label = ui::Label( "  Z  " );
++                                      label.show();
++                    table.attach(label, {0, 1, 2, 3}, {0, 0});
++                              }
++                              {
++                                      auto entry = ui::Entry(ui::New);
++                                      entry.text("1.0");
++                                      entry.show();
++                    table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
++
++                                      g_scale_dialog.x = entry;
++                              }
++                              {
++                                      auto entry = ui::Entry(ui::New);
++                                      entry.text("1.0");
++                                      entry.show();
++                    table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
++
++                                      g_scale_dialog.y = entry;
++                              }
++                              {
++                                      auto entry = ui::Entry(ui::New);
++                                      entry.text("1.0");
++                                      entry.show();
++                    table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
++
++                                      g_scale_dialog.z = entry;
++                              }
++                      }
++                      {
++                              auto vbox = create_dialog_vbox( 4 );
++                              hbox.pack_start( vbox, TRUE, TRUE, 0 );
++                              {
++                                      auto button = create_dialog_button( "OK", G_CALLBACK( scaledlg_ok ), &g_scale_dialog );
++                                      vbox.pack_start( button, FALSE, FALSE, 0 );
++                                      widget_make_default( button );
++                                      gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
++                              }
++                              {
++                                      auto button = create_dialog_button( "Cancel", G_CALLBACK( scaledlg_cancel ), &g_scale_dialog );
++                                      vbox.pack_start( button, FALSE, FALSE, 0 );
++                                      gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
++                              }
++                              {
++                                      auto button = create_dialog_button( "Apply", G_CALLBACK( scaledlg_apply ), &g_scale_dialog );
++                                      vbox.pack_start( button, FALSE, FALSE, 0 );
++                              }
++                      }
++              }
++      }
++
++      g_scale_dialog.window.show();
++}
index e8d16c95be6f031f7fb22d5144f4f545b5b21a9e,13255cfc4f7832481caa6673c5f2b283bf3c6815..11e2337e0e63700348c4b790546f00f7d4291949
@@@ -3989,16 -4043,31 +4047,31 @@@ bool mouseDown( DeviceVector position )
  }
  
  void mouseMoved( DeviceVector position ){
-       getSelectionSystem().MoveSelected( *m_view, &position[0] );
+       getSelectionSystem().MoveSelected( *m_view, &position[0], ( m_state & c_modifierShift ) == c_modifierShift );
  }
 -typedef MemberCaller1<Manipulator_, DeviceVector, &Manipulator_::mouseMoved> MouseMovedCaller;
 +typedef MemberCaller<Manipulator_, void(DeviceVector), &Manipulator_::mouseMoved> MouseMovedCaller;
  
  void mouseUp( DeviceVector position ){
        getSelectionSystem().endMove();
        g_mouseMovedCallback.clear();
        g_mouseUpCallback.clear();
  }
 -typedef MemberCaller1<Manipulator_, DeviceVector, &Manipulator_::mouseUp> MouseUpCaller;
 +typedef MemberCaller<Manipulator_, void(DeviceVector), &Manipulator_::mouseUp> MouseUpCaller;
+ void setState( ModifierFlags state ){
+       m_state = state;
+ }
+ ModifierFlags getState() const {
+       return m_state;
+ }
+ void modifierEnable( ModifierFlags type ){
+       setState( bitfield_enable( getState(), type ) );
+ }
+ void modifierDisable( ModifierFlags type ){
+       setState( bitfield_disable( getState(), type ) );
+ }
  };
  
  void Scene_copyClosestTexture( SelectionTest& test );
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..fc01a71e43202e7f90f1523baef780cde232e6bd
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,4231 @@@
++/*
++   Copyright (C) 2001-2006, William Joseph.
++   All Rights Reserved.
++
++   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
++ */
++
++#include "selection.h"
++#include "globaldefs.h"
++
++#include "debugging/debugging.h"
++
++#include <map>
++#include <list>
++#include <set>
++
++#include "windowobserver.h"
++#include "iundo.h"
++#include "ientity.h"
++#include "cullable.h"
++#include "renderable.h"
++#include "selectable.h"
++#include "editable.h"
++
++#include "math/frustum.h"
++#include "signal/signal.h"
++#include "generic/object.h"
++#include "selectionlib.h"
++#include "render.h"
++#include "view.h"
++#include "renderer.h"
++#include "stream/stringstream.h"
++#include "eclasslib.h"
++#include "generic/bitfield.h"
++#include "generic/static.h"
++#include "pivot.h"
++#include "stringio.h"
++#include "container/container.h"
++
++#include "grid.h"
++
++TextOutputStream& ostream_write( TextOutputStream& t, const Vector4& v ){
++      return t << "[ " << v.x() << " " << v.y() << " " << v.z() << " " << v.w() << " ]";
++}
++
++TextOutputStream& ostream_write( TextOutputStream& t, const Matrix4& m ){
++      return t << "[ " << m.x() << " " << m.y() << " " << m.z() << " " << m.t() << " ]";
++}
++
++struct Pivot2World
++{
++      Matrix4 m_worldSpace;
++      Matrix4 m_viewpointSpace;
++      Matrix4 m_viewplaneSpace;
++      Vector3 m_axis_screen;
++
++      void update( const Matrix4& pivot2world, const Matrix4& modelview, const Matrix4& projection, const Matrix4& viewport ){
++              Pivot2World_worldSpace( m_worldSpace, pivot2world, modelview, projection, viewport );
++              Pivot2World_viewpointSpace( m_viewpointSpace, m_axis_screen, pivot2world, modelview, projection, viewport );
++              Pivot2World_viewplaneSpace( m_viewplaneSpace, pivot2world, modelview, projection, viewport );
++      }
++};
++
++
++void point_for_device_point( Vector3& point, const Matrix4& device2object, const float x, const float y, const float z ){
++      // transform from normalised device coords to object coords
++      point = vector4_projected( matrix4_transformed_vector4( device2object, Vector4( x, y, z, 1 ) ) );
++}
++
++void ray_for_device_point( Ray& ray, const Matrix4& device2object, const float x, const float y ){
++      // point at x, y, zNear
++      point_for_device_point( ray.origin, device2object, x, y, -1 );
++
++      // point at x, y, zFar
++      point_for_device_point( ray.direction, device2object, x, y, 1 );
++
++      // construct ray
++      vector3_subtract( ray.direction, ray.origin );
++      vector3_normalise( ray.direction );
++}
++
++bool sphere_intersect_ray( const Vector3& origin, float radius, const Ray& ray, Vector3& intersection ){
++      intersection = vector3_subtracted( origin, ray.origin );
++      const double a = vector3_dot( intersection, ray.direction );
++      const double d = radius * radius - ( vector3_dot( intersection, intersection ) - a * a );
++
++      if ( d > 0 ) {
++              intersection = vector3_added( ray.origin, vector3_scaled( ray.direction, a - sqrt( d ) ) );
++              return true;
++      }
++      else
++      {
++              intersection = vector3_added( ray.origin, vector3_scaled( ray.direction, a ) );
++              return false;
++      }
++}
++
++void ray_intersect_ray( const Ray& ray, const Ray& other, Vector3& intersection ){
++      intersection = vector3_subtracted( ray.origin, other.origin );
++      //float a = 1;//vector3_dot(ray.direction, ray.direction);        // always >= 0
++      double dot = vector3_dot( ray.direction, other.direction );
++      //float c = 1;//vector3_dot(other.direction, other.direction);        // always >= 0
++      double d = vector3_dot( ray.direction, intersection );
++      double e = vector3_dot( other.direction, intersection );
++      double D = 1 - dot * dot; //a*c - dot*dot;       // always >= 0
++
++      if ( D < 0.000001 ) {
++              // the lines are almost parallel
++              intersection = vector3_added( other.origin, vector3_scaled( other.direction, e ) );
++      }
++      else
++      {
++              intersection = vector3_added( other.origin, vector3_scaled( other.direction, ( e - dot * d ) / D ) );
++      }
++}
++
++const Vector3 g_origin( 0, 0, 0 );
++const float g_radius = 64;
++
++void point_on_sphere( Vector3& point, const Matrix4& device2object, const float x, const float y ){
++      Ray ray;
++      ray_for_device_point( ray, device2object, x, y );
++      sphere_intersect_ray( g_origin, g_radius, ray, point );
++}
++
++void point_on_axis( Vector3& point, const Vector3& axis, const Matrix4& device2object, const float x, const float y ){
++      Ray ray;
++      ray_for_device_point( ray, device2object, x, y );
++      ray_intersect_ray( ray, Ray( Vector3( 0, 0, 0 ), axis ), point );
++}
++
++void point_on_plane( Vector3& point, const Matrix4& device2object, const float x, const float y ){
++      Matrix4 object2device( matrix4_full_inverse( device2object ) );
++      point = vector4_projected( matrix4_transformed_vector4( device2object, Vector4( x, y, object2device[14] / object2device[15], 1 ) ) );
++}
++
++//! a and b are unit vectors .. returns angle in radians
++inline float angle_between( const Vector3& a, const Vector3& b ){
++      return static_cast<float>( 2.0 * atan2(
++                                                                 vector3_length( vector3_subtracted( a, b ) ),
++                                                                 vector3_length( vector3_added( a, b ) )
++                                                                 ) );
++}
++
++
++#if GDEF_DEBUG
++class test_quat
++{
++public:
++test_quat( const Vector3& from, const Vector3& to ){
++      Vector4 quaternion( quaternion_for_unit_vectors( from, to ) );
++      Matrix4 matrix( matrix4_rotation_for_quaternion( quaternion_multiplied_by_quaternion( quaternion, c_quaternion_identity ) ) );
++}
++private:
++};
++
++static test_quat bleh( g_vector3_axis_x, g_vector3_axis_y );
++#endif
++
++//! axis is a unit vector
++inline void constrain_to_axis( Vector3& vec, const Vector3& axis ){
++      vec = vector3_normalised( vector3_added( vec, vector3_scaled( axis, -vector3_dot( vec, axis ) ) ) );
++}
++
++//! a and b are unit vectors .. a and b must be orthogonal to axis .. returns angle in radians
++float angle_for_axis( const Vector3& a, const Vector3& b, const Vector3& axis ){
++      if ( vector3_dot( axis, vector3_cross( a, b ) ) > 0.0 ) {
++              return angle_between( a, b );
++      }
++      else{
++              return -angle_between( a, b );
++      }
++}
++
++float distance_for_axis( const Vector3& a, const Vector3& b, const Vector3& axis ){
++      return static_cast<float>( vector3_dot( b, axis ) - vector3_dot( a, axis ) );
++}
++
++class Manipulatable
++{
++public:
++virtual void Construct( const Matrix4& device2manip, const float x, const float y ) = 0;
++virtual void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap ) = 0;
++};
++
++void transform_local2object( Matrix4& object, const Matrix4& local, const Matrix4& local2object ){
++      object = matrix4_multiplied_by_matrix4(
++              matrix4_multiplied_by_matrix4( local2object, local ),
++              matrix4_full_inverse( local2object )
++              );
++}
++
++class Rotatable
++{
++public:
++virtual ~Rotatable() = default;
++virtual void rotate( const Quaternion& rotation ) = 0;
++};
++
++class RotateFree : public Manipulatable
++{
++Vector3 m_start;
++Rotatable& m_rotatable;
++public:
++RotateFree( Rotatable& rotatable )
++      : m_rotatable( rotatable ){
++}
++void Construct( const Matrix4& device2manip, const float x, const float y ){
++      point_on_sphere( m_start, device2manip, x, y );
++      vector3_normalise( m_start );
++}
++void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap ){
++      Vector3 current;
++      point_on_sphere( current, device2manip, x, y );
++
++      if( snap ){
++              Vector3 axis( 0, 0, 0 );
++              for( std::size_t i = 0; i < 3; ++i ){
++                      if( current[i] == 0.0f ){
++                              axis[i] = 1.0f;
++                              break;
++                      }
++              }
++              if( vector3_length_squared( axis ) != 0 ){
++                      constrain_to_axis( current, axis );
++                      m_rotatable.rotate( quaternion_for_axisangle( axis, float_snapped( angle_for_axis( m_start, current, axis ), static_cast<float>( c_pi / 12.0 ) ) ) );
++                      return;
++              }
++      }
++
++      vector3_normalise( current );
++      m_rotatable.rotate( quaternion_for_unit_vectors( m_start, current ) );
++}
++};
++
++class RotateAxis : public Manipulatable
++{
++Vector3 m_axis;
++Vector3 m_start;
++Rotatable& m_rotatable;
++public:
++RotateAxis( Rotatable& rotatable )
++      : m_rotatable( rotatable ){
++}
++void Construct( const Matrix4& device2manip, const float x, const float y ){
++      point_on_sphere( m_start, device2manip, x, y );
++      constrain_to_axis( m_start, m_axis );
++}
++/// \brief Converts current position to a normalised vector orthogonal to axis.
++void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap ){
++      Vector3 current;
++      point_on_sphere( current, device2manip, x, y );
++      constrain_to_axis( current, m_axis );
++
++      if( snap ){
++              m_rotatable.rotate( quaternion_for_axisangle( m_axis, float_snapped( angle_for_axis( m_start, current, m_axis ), static_cast<float>( c_pi / 12.0 ) ) ) );
++      }
++      else{
++              m_rotatable.rotate( quaternion_for_axisangle( m_axis, angle_for_axis( m_start, current, m_axis ) ) );
++      }
++}
++
++void SetAxis( const Vector3& axis ){
++      m_axis = axis;
++}
++};
++
++void translation_local2object( Vector3& object, const Vector3& local, const Matrix4& local2object ){
++      object = matrix4_get_translation_vec3(
++              matrix4_multiplied_by_matrix4(
++                      matrix4_translated_by_vec3( local2object, local ),
++                      matrix4_full_inverse( local2object )
++                      )
++              );
++}
++
++class Translatable
++{
++public:
++virtual ~Translatable() = default;
++virtual void translate( const Vector3& translation ) = 0;
++};
++
++class TranslateAxis : public Manipulatable
++{
++Vector3 m_start;
++Vector3 m_axis;
++Translatable& m_translatable;
++public:
++TranslateAxis( Translatable& translatable )
++      : m_translatable( translatable ){
++}
++void Construct( const Matrix4& device2manip, const float x, const float y ){
++      point_on_axis( m_start, m_axis, device2manip, x, y );
++}
++void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap ){
++      Vector3 current;
++      point_on_axis( current, m_axis, device2manip, x, y );
++      current = vector3_scaled( m_axis, distance_for_axis( m_start, current, m_axis ) );
++
++      translation_local2object( current, current, manip2object );
++      vector3_snap( current, GetSnapGridSize() );
++
++      m_translatable.translate( current );
++}
++
++void SetAxis( const Vector3& axis ){
++      m_axis = axis;
++}
++};
++
++class TranslateFree : public Manipulatable
++{
++private:
++Vector3 m_start;
++Translatable& m_translatable;
++public:
++TranslateFree( Translatable& translatable )
++      : m_translatable( translatable ){
++}
++void Construct( const Matrix4& device2manip, const float x, const float y ){
++      point_on_plane( m_start, device2manip, x, y );
++}
++void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap ){
++      Vector3 current;
++      point_on_plane( current, device2manip, x, y );
++      current = vector3_subtracted( current, m_start );
++
++      if( snap ){
++              for ( std::size_t i = 0; i < 3 ; ++i ){
++                      if( fabs( current[i] ) >= fabs( current[(i + 1) % 3] ) ){
++                              current[(i + 1) % 3] = 0.0f;
++                      }
++                      else{
++                              current[i] = 0.0f;
++                      }
++              }
++      }
++
++      translation_local2object( current, current, manip2object );
++      vector3_snap( current, GetSnapGridSize() );
++
++      m_translatable.translate( current );
++}
++};
++
++void GetSelectionAABB( AABB& bounds );
++const Matrix4& SelectionSystem_GetPivot2World();
++
++class Scalable
++{
++public:
++virtual ~Scalable() = default;
++virtual void scale( const Vector3& scaling ) = 0;
++};
++
++
++class ScaleAxis : public Manipulatable
++{
++private:
++Vector3 m_start;
++Vector3 m_axis;
++Scalable& m_scalable;
++
++Vector3 m_choosen_extent;
++
++public:
++ScaleAxis( Scalable& scalable )
++      : m_scalable( scalable ){
++}
++void Construct( const Matrix4& device2manip, const float x, const float y ){
++      point_on_axis( m_start, m_axis, device2manip, x, y );
++
++      AABB aabb;
++      GetSelectionAABB( aabb );
++      Vector3 transform_origin = vector4_to_vector3( SelectionSystem_GetPivot2World().t() );
++      m_choosen_extent = Vector3(
++                                      std::max( aabb.origin[0] + aabb.extents[0] - transform_origin[0], - aabb.origin[0] + aabb.extents[0] + transform_origin[0] ),
++                                      std::max( aabb.origin[1] + aabb.extents[1] - transform_origin[1], - aabb.origin[1] + aabb.extents[1] + transform_origin[1] ),
++                                      std::max( aabb.origin[2] + aabb.extents[2] - transform_origin[2], - aabb.origin[2] + aabb.extents[2] + transform_origin[2] )
++                                                      );
++
++}
++void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap ){
++      //globalOutputStream() << "manip2object: " << manip2object << "  device2manip: " << device2manip << "  x: " << x << "  y:" << y <<"\n";
++      Vector3 current;
++      point_on_axis( current, m_axis, device2manip, x, y );
++      Vector3 delta = vector3_subtracted( current, m_start );
++
++      translation_local2object( delta, delta, manip2object );
++      vector3_snap( delta, GetSnapGridSize() );
++
++      Vector3 start( vector3_snapped( m_start, GetSnapGridSize() != 0.0f ? GetSnapGridSize() : 0.001f ) );
++      for ( std::size_t i = 0; i < 3 ; ++i ){ //prevent snapping to 0 with big gridsize
++              if( float_snapped( m_start[i], 0.001f ) != 0.0f && start[i] == 0.0f ){
++                      start[i] = GetSnapGridSize();
++              }
++      }
++      //globalOutputStream() << "m_start: " << m_start << "   start: " << start << "   delta: " << delta <<"\n";
++      Vector3 scale(
++              start[0] == 0 ? 1 : 1 + delta[0] / start[0],
++              start[1] == 0 ? 1 : 1 + delta[1] / start[1],
++              start[2] == 0 ? 1 : 1 + delta[2] / start[2]
++              );
++
++      for( std::size_t i = 0; i < 3; i++ ){
++              if( m_choosen_extent[i] > 0.0625f ){ //epsilon to prevent super high scale for set of models, having really small extent, formed by origins
++                      scale[i] = ( m_choosen_extent[i] + delta[i] ) / m_choosen_extent[i];
++              }
++      }
++      if( snap ){
++              for( std::size_t i = 0; i < 3; i++ ){
++                      if( scale[i] == 1.0f ){
++                              scale[i] = vector3_dot( scale, m_axis );
++                      }
++              }
++      }
++      //globalOutputStream() << "scale: " << scale <<"\n";
++      m_scalable.scale( scale );
++}
++
++void SetAxis( const Vector3& axis ){
++      m_axis = axis;
++}
++};
++
++class ScaleFree : public Manipulatable
++{
++private:
++Vector3 m_start;
++Scalable& m_scalable;
++
++Vector3 m_choosen_extent;
++
++public:
++ScaleFree( Scalable& scalable )
++      : m_scalable( scalable ){
++}
++void Construct( const Matrix4& device2manip, const float x, const float y ){
++      point_on_plane( m_start, device2manip, x, y );
++
++      AABB aabb;
++      GetSelectionAABB( aabb );
++      Vector3 transform_origin = vector4_to_vector3( SelectionSystem_GetPivot2World().t() );
++      m_choosen_extent = Vector3(
++                                      std::max( aabb.origin[0] + aabb.extents[0] - transform_origin[0], - aabb.origin[0] + aabb.extents[0] + transform_origin[0] ),
++                                      std::max( aabb.origin[1] + aabb.extents[1] - transform_origin[1], - aabb.origin[1] + aabb.extents[1] + transform_origin[1] ),
++                                      std::max( aabb.origin[2] + aabb.extents[2] - transform_origin[2], - aabb.origin[2] + aabb.extents[2] + transform_origin[2] )
++                                                      );
++}
++void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap ){
++      Vector3 current;
++      point_on_plane( current, device2manip, x, y );
++      Vector3 delta = vector3_subtracted( current, m_start );
++
++      translation_local2object( delta, delta, manip2object );
++      vector3_snap( delta, GetSnapGridSize() );
++
++      Vector3 start( vector3_snapped( m_start, GetSnapGridSize() != 0.0f ? GetSnapGridSize() : 0.001f ) );
++      for ( std::size_t i = 0; i < 3 ; ++i ){ //prevent snapping to 0 with big gridsize
++              if( float_snapped( m_start[i], 0.001f ) != 0.0f && start[i] == 0.0f ){
++                      start[i] = GetSnapGridSize();
++              }
++      }
++      Vector3 scale(
++              start[0] == 0 ? 1 : 1 + delta[0] / start[0],
++              start[1] == 0 ? 1 : 1 + delta[1] / start[1],
++              start[2] == 0 ? 1 : 1 + delta[2] / start[2]
++              );
++
++      //globalOutputStream() << "m_start: " << m_start << "   start: " << start << "   delta: " << delta <<"\n";
++      for( std::size_t i = 0; i < 3; i++ ){
++              if( m_choosen_extent[i] > 0.0625f ){
++                      scale[i] = ( m_choosen_extent[i] + delta[i] ) / m_choosen_extent[i];
++              }
++      }
++      //globalOutputStream() << "pre snap scale: " << scale <<"\n";
++      if( snap ){
++              float bestscale = scale[0];
++              for( std::size_t i = 1; i < 3; i++ ){
++                      //if( fabs( 1.0f - fabs( scale[i] ) ) > fabs( 1.0f - fabs( bestscale ) ) ){
++                      if( fabs( scale[i] ) > fabs( bestscale ) && scale[i] != 1.0f ){ //harder to scale down with this, but glitchier with upper one
++                              bestscale = scale[i];
++                      }
++                      //globalOutputStream() << "bestscale: " << bestscale <<"\n";
++              }
++              for( std::size_t i = 0; i < 3; i++ ){
++                      if( start[i] != 0.0f ){ // !!!!check grid == 0 case
++                              scale[i] = ( scale[i] < 0.0f ) ? -fabs( bestscale ) : fabs( bestscale );
++                      }
++              }
++      }
++      //globalOutputStream() << "scale: " << scale <<"\n";
++      m_scalable.scale( scale );
++}
++};
++
++
++
++
++
++
++
++
++
++
++class RenderableClippedPrimitive : public OpenGLRenderable
++{
++struct primitive_t
++{
++      PointVertex m_points[9];
++      std::size_t m_count;
++};
++Matrix4 m_inverse;
++std::vector<primitive_t> m_primitives;
++public:
++Matrix4 m_world;
++
++void render( RenderStateFlags state ) const {
++      for ( std::size_t i = 0; i < m_primitives.size(); ++i )
++      {
++              glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( PointVertex ), &m_primitives[i].m_points[0].colour );
++              glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &m_primitives[i].m_points[0].vertex );
++              switch ( m_primitives[i].m_count )
++              {
++              case 1: break;
++              case 2: glDrawArrays( GL_LINES, 0, GLsizei( m_primitives[i].m_count ) ); break;
++              default: glDrawArrays( GL_POLYGON, 0, GLsizei( m_primitives[i].m_count ) ); break;
++              }
++      }
++}
++
++void construct( const Matrix4& world2device ){
++      m_inverse = matrix4_full_inverse( world2device );
++      m_world = g_matrix4_identity;
++}
++
++void insert( const Vector4 clipped[9], std::size_t count ){
++      add_one();
++
++      m_primitives.back().m_count = count;
++      for ( std::size_t i = 0; i < count; ++i )
++      {
++              Vector3 world_point( vector4_projected( matrix4_transformed_vector4( m_inverse, clipped[i] ) ) );
++              m_primitives.back().m_points[i].vertex = vertex3f_for_vector3( world_point );
++      }
++}
++
++void destroy(){
++      m_primitives.clear();
++}
++private:
++void add_one(){
++      m_primitives.push_back( primitive_t() );
++
++      const Colour4b colour_clipped( 255, 127, 0, 255 );
++
++      for ( std::size_t i = 0; i < 9; ++i )
++              m_primitives.back().m_points[i].colour = colour_clipped;
++}
++};
++
++#if GDEF_DEBUG
++#define DEBUG_SELECTION
++#endif
++
++#if defined( DEBUG_SELECTION )
++Shader* g_state_clipped;
++RenderableClippedPrimitive g_render_clipped;
++#endif
++
++
++#if 0
++// dist_Point_to_Line(): get the distance of a point to a line.
++//    Input:  a Point P and a Line L (in any dimension)
++//    Return: the shortest distance from P to L
++float
++dist_Point_to_Line( Point P, Line L ){
++      Vector v = L.P1 - L.P0;
++      Vector w = P - L.P0;
++
++      double c1 = dot( w,v );
++      double c2 = dot( v,v );
++      double b = c1 / c2;
++
++      Point Pb = L.P0 + b * v;
++      return d( P, Pb );
++}
++#endif
++
++class Segment3D
++{
++typedef Vector3 point_type;
++public:
++Segment3D( const point_type& _p0, const point_type& _p1 )
++      : p0( _p0 ), p1( _p1 ){
++}
++
++point_type p0, p1;
++};
++
++typedef Vector3 Point3D;
++
++inline double vector3_distance_squared( const Point3D& a, const Point3D& b ){
++      return vector3_length_squared( b - a );
++}
++
++// get the distance of a point to a segment.
++Point3D segment_closest_point_to_point( const Segment3D& segment, const Point3D& point ){
++      Vector3 v = segment.p1 - segment.p0;
++      Vector3 w = point - segment.p0;
++
++      double c1 = vector3_dot( w,v );
++      if ( c1 <= 0 ) {
++              return segment.p0;
++      }
++
++      double c2 = vector3_dot( v,v );
++      if ( c2 <= c1 ) {
++              return segment.p1;
++      }
++
++      return Point3D( segment.p0 + v * ( c1 / c2 ) );
++}
++
++double segment_dist_to_point_3d( const Segment3D& segment, const Point3D& point ){
++      return vector3_distance_squared( point, segment_closest_point_to_point( segment, point ) );
++}
++
++typedef Vector3 point_t;
++typedef const Vector3* point_iterator_t;
++
++// crossing number test for a point in a polygon
++// This code is patterned after [Franklin, 2000]
++bool point_test_polygon_2d( const point_t& P, point_iterator_t start, point_iterator_t finish ){
++      std::size_t crossings = 0;
++
++      // loop through all edges of the polygon
++      for ( point_iterator_t prev = finish - 1, cur = start; cur != finish; prev = cur, ++cur )
++      {  // edge from (*prev) to (*cur)
++              if ( ( ( ( *prev )[1] <= P[1] ) && ( ( *cur )[1] > P[1] ) ) // an upward crossing
++                       || ( ( ( *prev )[1] > P[1] ) && ( ( *cur )[1] <= P[1] ) ) ) { // a downward crossing
++                                                                                    // compute the actual edge-ray intersect x-coordinate
++                      float vt = (float)( P[1] - ( *prev )[1] ) / ( ( *cur )[1] - ( *prev )[1] );
++                      if ( P[0] < ( *prev )[0] + vt * ( ( *cur )[0] - ( *prev )[0] ) ) { // P[0] < intersect
++                              ++crossings; // a valid crossing of y=P[1] right of P[0]
++                      }
++              }
++      }
++      return ( crossings & 0x1 ) != 0; // 0 if even (out), and 1 if odd (in)
++}
++
++inline double triangle_signed_area_XY( const Vector3& p0, const Vector3& p1, const Vector3& p2 ){
++      return ( ( p1[0] - p0[0] ) * ( p2[1] - p0[1] ) ) - ( ( p2[0] - p0[0] ) * ( p1[1] - p0[1] ) );
++}
++
++enum clipcull_t
++{
++      eClipCullNone,
++      eClipCullCW,
++      eClipCullCCW,
++};
++
++
++inline SelectionIntersection select_point_from_clipped( Vector4& clipped ){
++      return SelectionIntersection( clipped[2] / clipped[3], static_cast<float>( vector3_length_squared( Vector3( clipped[0] / clipped[3], clipped[1] / clipped[3], 0 ) ) ) );
++}
++
++void BestPoint( std::size_t count, Vector4 clipped[9], SelectionIntersection& best, clipcull_t cull ){
++      Vector3 normalised[9];
++
++      {
++              for ( std::size_t i = 0; i < count; ++i )
++              {
++                      normalised[i][0] = clipped[i][0] / clipped[i][3];
++                      normalised[i][1] = clipped[i][1] / clipped[i][3];
++                      normalised[i][2] = clipped[i][2] / clipped[i][3];
++              }
++      }
++
++      if ( cull != eClipCullNone && count > 2 ) {
++              double signed_area = triangle_signed_area_XY( normalised[0], normalised[1], normalised[2] );
++
++              if ( ( cull == eClipCullCW && signed_area > 0 )
++                       || ( cull == eClipCullCCW && signed_area < 0 ) ) {
++                      return;
++              }
++      }
++
++      if ( count == 2 ) {
++              Segment3D segment( normalised[0], normalised[1] );
++              Point3D point = segment_closest_point_to_point( segment, Vector3( 0, 0, 0 ) );
++              assign_if_closer( best, SelectionIntersection( point.z(), 0 ) );
++      }
++      else if ( count > 2 && !point_test_polygon_2d( Vector3( 0, 0, 0 ), normalised, normalised + count ) ) {
++              point_iterator_t end = normalised + count;
++              for ( point_iterator_t previous = end - 1, current = normalised; current != end; previous = current, ++current )
++              {
++                      Segment3D segment( *previous, *current );
++                      Point3D point = segment_closest_point_to_point( segment, Vector3( 0, 0, 0 ) );
++                      float depth = point.z();
++                      point.z() = 0;
++                      float distance = static_cast<float>( vector3_length_squared( point ) );
++
++                      assign_if_closer( best, SelectionIntersection( depth, distance ) );
++              }
++      }
++      else if ( count > 2 ) {
++              assign_if_closer(
++                      best,
++                      SelectionIntersection(
++                              static_cast<float>( ray_distance_to_plane(
++                                                                              Ray( Vector3( 0, 0, 0 ), Vector3( 0, 0, 1 ) ),
++                                                                              plane3_for_points( normalised[0], normalised[1], normalised[2] )
++                                                                              ) ),
++                              0
++                              )
++                      );
++      }
++
++#if defined( DEBUG_SELECTION )
++      if ( count >= 2 ) {
++              g_render_clipped.insert( clipped, count );
++      }
++#endif
++}
++
++void LineStrip_BestPoint( const Matrix4& local2view, const PointVertex* vertices, const std::size_t size, SelectionIntersection& best ){
++      Vector4 clipped[2];
++      for ( std::size_t i = 0; ( i + 1 ) < size; ++i )
++      {
++              const std::size_t count = matrix4_clip_line( local2view, vertex3f_to_vector3( vertices[i].vertex ), vertex3f_to_vector3( vertices[i + 1].vertex ), clipped );
++              BestPoint( count, clipped, best, eClipCullNone );
++      }
++}
++
++void LineLoop_BestPoint( const Matrix4& local2view, const PointVertex* vertices, const std::size_t size, SelectionIntersection& best ){
++      Vector4 clipped[2];
++      for ( std::size_t i = 0; i < size; ++i )
++      {
++              const std::size_t count = matrix4_clip_line( local2view, vertex3f_to_vector3( vertices[i].vertex ), vertex3f_to_vector3( vertices[( i + 1 ) % size].vertex ), clipped );
++              BestPoint( count, clipped, best, eClipCullNone );
++      }
++}
++
++void Line_BestPoint( const Matrix4& local2view, const PointVertex vertices[2], SelectionIntersection& best ){
++      Vector4 clipped[2];
++      const std::size_t count = matrix4_clip_line( local2view, vertex3f_to_vector3( vertices[0].vertex ), vertex3f_to_vector3( vertices[1].vertex ), clipped );
++      BestPoint( count, clipped, best, eClipCullNone );
++}
++
++void Circle_BestPoint( const Matrix4& local2view, clipcull_t cull, const PointVertex* vertices, const std::size_t size, SelectionIntersection& best ){
++      Vector4 clipped[9];
++      for ( std::size_t i = 0; i < size; ++i )
++      {
++              const std::size_t count = matrix4_clip_triangle( local2view, g_vector3_identity, vertex3f_to_vector3( vertices[i].vertex ), vertex3f_to_vector3( vertices[( i + 1 ) % size].vertex ), clipped );
++              BestPoint( count, clipped, best, cull );
++      }
++}
++
++void Quad_BestPoint( const Matrix4& local2view, clipcull_t cull, const PointVertex* vertices, SelectionIntersection& best ){
++      Vector4 clipped[9];
++      {
++              const std::size_t count = matrix4_clip_triangle( local2view, vertex3f_to_vector3( vertices[0].vertex ), vertex3f_to_vector3( vertices[1].vertex ), vertex3f_to_vector3( vertices[3].vertex ), clipped );
++              BestPoint( count, clipped, best, cull );
++      }
++      {
++              const std::size_t count = matrix4_clip_triangle( local2view, vertex3f_to_vector3( vertices[1].vertex ), vertex3f_to_vector3( vertices[2].vertex ), vertex3f_to_vector3( vertices[3].vertex ), clipped );
++              BestPoint( count, clipped, best, cull );
++      }
++}
++
++struct FlatShadedVertex
++{
++      Vertex3f vertex;
++      Colour4b colour;
++      Normal3f normal;
++
++      FlatShadedVertex(){
++      }
++};
++
++
++typedef FlatShadedVertex* FlatShadedVertexIterator;
++void Triangles_BestPoint( const Matrix4& local2view, clipcull_t cull, FlatShadedVertexIterator first, FlatShadedVertexIterator last, SelectionIntersection& best ){
++      for ( FlatShadedVertexIterator x( first ), y( first + 1 ), z( first + 2 ); x != last; x += 3, y += 3, z += 3 )
++      {
++              Vector4 clipped[9];
++              BestPoint(
++                      matrix4_clip_triangle(
++                              local2view,
++                              reinterpret_cast<const Vector3&>( ( *x ).vertex ),
++                              reinterpret_cast<const Vector3&>( ( *y ).vertex ),
++                              reinterpret_cast<const Vector3&>( ( *z ).vertex ),
++                              clipped
++                              ),
++                      clipped,
++                      best,
++                      cull
++                      );
++      }
++}
++
++
++typedef std::multimap<SelectionIntersection, Selectable*> SelectableSortedSet;
++
++class SelectionPool : public Selector
++{
++SelectableSortedSet m_pool;
++SelectionIntersection m_intersection;
++Selectable* m_selectable;
++
++public:
++void pushSelectable( Selectable& selectable ){
++      m_intersection = SelectionIntersection();
++      m_selectable = &selectable;
++}
++void popSelectable(){
++      addSelectable( m_intersection, m_selectable );
++      m_intersection = SelectionIntersection();
++}
++void addIntersection( const SelectionIntersection& intersection ){
++      assign_if_closer( m_intersection, intersection );
++}
++void addSelectable( const SelectionIntersection& intersection, Selectable* selectable ){
++      if ( intersection.valid() ) {
++              m_pool.insert( SelectableSortedSet::value_type( intersection, selectable ) );
++      }
++}
++
++typedef SelectableSortedSet::iterator iterator;
++
++iterator begin(){
++      return m_pool.begin();
++}
++iterator end(){
++      return m_pool.end();
++}
++
++bool failed(){
++      return m_pool.empty();
++}
++};
++
++
++const Colour4b g_colour_sphere( 0, 0, 0, 255 );
++const Colour4b g_colour_screen( 0, 255, 255, 255 );
++const Colour4b g_colour_selected( 255, 255, 0, 255 );
++
++inline const Colour4b& colourSelected( const Colour4b& colour, bool selected ){
++      return ( selected ) ? g_colour_selected : colour;
++}
++
++template<typename remap_policy>
++inline void draw_semicircle( const std::size_t segments, const float radius, PointVertex* vertices, remap_policy remap ){
++      const double increment = c_pi / double(segments << 2);
++
++      std::size_t count = 0;
++      float x = radius;
++      float y = 0;
++      remap_policy::set( vertices[segments << 2].vertex, -radius, 0, 0 );
++      while ( count < segments )
++      {
++              PointVertex* i = vertices + count;
++              PointVertex* j = vertices + ( ( segments << 1 ) - ( count + 1 ) );
++
++              PointVertex* k = i + ( segments << 1 );
++              PointVertex* l = j + ( segments << 1 );
++
++#if 0
++              PointVertex* m = i + ( segments << 2 );
++              PointVertex* n = j + ( segments << 2 );
++              PointVertex* o = k + ( segments << 2 );
++              PointVertex* p = l + ( segments << 2 );
++#endif
++
++              remap_policy::set( i->vertex, x,-y, 0 );
++              remap_policy::set( k->vertex,-y,-x, 0 );
++#if 0
++              remap_policy::set( m->vertex,-x, y, 0 );
++              remap_policy::set( o->vertex, y, x, 0 );
++#endif
++
++              ++count;
++
++              {
++                      const double theta = increment * count;
++                      x = static_cast<float>( radius * cos( theta ) );
++                      y = static_cast<float>( radius * sin( theta ) );
++              }
++
++              remap_policy::set( j->vertex, y,-x, 0 );
++              remap_policy::set( l->vertex,-x,-y, 0 );
++#if 0
++              remap_policy::set( n->vertex,-y, x, 0 );
++              remap_policy::set( p->vertex, x, y, 0 );
++#endif
++      }
++}
++
++class Manipulator
++{
++public:
++virtual Manipulatable* GetManipulatable() = 0;
++virtual void testSelect( const View& view, const Matrix4& pivot2world ){
++}
++virtual void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world ){
++}
++virtual void setSelected( bool select ) = 0;
++virtual bool isSelected() const = 0;
++};
++
++
++inline Vector3 normalised_safe( const Vector3& self ){
++      if ( vector3_equal( self, g_vector3_identity ) ) {
++              return g_vector3_identity;
++      }
++      return vector3_normalised( self );
++}
++
++
++class RotateManipulator : public Manipulator
++{
++struct RenderableCircle : public OpenGLRenderable
++{
++      Array<PointVertex> m_vertices;
++
++      RenderableCircle( std::size_t size ) : m_vertices( size ){
++      }
++      void render( RenderStateFlags state ) const {
++              glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( PointVertex ), &m_vertices.data()->colour );
++              glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &m_vertices.data()->vertex );
++              glDrawArrays( GL_LINE_LOOP, 0, GLsizei( m_vertices.size() ) );
++      }
++      void setColour( const Colour4b& colour ){
++              for ( Array<PointVertex>::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i )
++              {
++                      ( *i ).colour = colour;
++              }
++      }
++};
++
++struct RenderableSemiCircle : public OpenGLRenderable
++{
++      Array<PointVertex> m_vertices;
++
++      RenderableSemiCircle( std::size_t size ) : m_vertices( size ){
++      }
++      void render( RenderStateFlags state ) const {
++              glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( PointVertex ), &m_vertices.data()->colour );
++              glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &m_vertices.data()->vertex );
++              glDrawArrays( GL_LINE_STRIP, 0, GLsizei( m_vertices.size() ) );
++      }
++      void setColour( const Colour4b& colour ){
++              for ( Array<PointVertex>::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i )
++              {
++                      ( *i ).colour = colour;
++              }
++      }
++};
++
++RotateFree m_free;
++RotateAxis m_axis;
++Vector3 m_axis_screen;
++RenderableSemiCircle m_circle_x;
++RenderableSemiCircle m_circle_y;
++RenderableSemiCircle m_circle_z;
++RenderableCircle m_circle_screen;
++RenderableCircle m_circle_sphere;
++SelectableBool m_selectable_x;
++SelectableBool m_selectable_y;
++SelectableBool m_selectable_z;
++SelectableBool m_selectable_screen;
++SelectableBool m_selectable_sphere;
++Pivot2World m_pivot;
++Matrix4 m_local2world_x;
++Matrix4 m_local2world_y;
++Matrix4 m_local2world_z;
++bool m_circle_x_visible;
++bool m_circle_y_visible;
++bool m_circle_z_visible;
++public:
++static Shader* m_state_outer;
++
++RotateManipulator( Rotatable& rotatable, std::size_t segments, float radius ) :
++      m_free( rotatable ),
++      m_axis( rotatable ),
++      m_circle_x( ( segments << 2 ) + 1 ),
++      m_circle_y( ( segments << 2 ) + 1 ),
++      m_circle_z( ( segments << 2 ) + 1 ),
++      m_circle_screen( segments << 3 ),
++      m_circle_sphere( segments << 3 ){
++      draw_semicircle( segments, radius, m_circle_x.m_vertices.data(), RemapYZX() );
++      draw_semicircle( segments, radius, m_circle_y.m_vertices.data(), RemapZXY() );
++      draw_semicircle( segments, radius, m_circle_z.m_vertices.data(), RemapXYZ() );
++
++      draw_circle( segments, radius * 1.15f, m_circle_screen.m_vertices.data(), RemapXYZ() );
++      draw_circle( segments, radius, m_circle_sphere.m_vertices.data(), RemapXYZ() );
++
++      m_selectable_sphere.setSelected( true );
++}
++
++
++void UpdateColours(){
++      m_circle_x.setColour( colourSelected( g_colour_x, m_selectable_x.isSelected() ) );
++      m_circle_y.setColour( colourSelected( g_colour_y, m_selectable_y.isSelected() ) );
++      m_circle_z.setColour( colourSelected( g_colour_z, m_selectable_z.isSelected() ) );
++      m_circle_screen.setColour( colourSelected( g_colour_screen, m_selectable_screen.isSelected() ) );
++      m_circle_sphere.setColour( colourSelected( g_colour_sphere, false ) );
++}
++
++void updateCircleTransforms(){
++      Vector3 localViewpoint( matrix4_transformed_direction( matrix4_transposed( m_pivot.m_worldSpace ), vector4_to_vector3( m_pivot.m_viewpointSpace.z() ) ) );
++
++      m_circle_x_visible = !vector3_equal_epsilon( g_vector3_axis_x, localViewpoint, 1e-6f );
++      if ( m_circle_x_visible ) {
++              m_local2world_x = g_matrix4_identity;
++              vector4_to_vector3( m_local2world_x.y() ) = normalised_safe(
++                      vector3_cross( g_vector3_axis_x, localViewpoint )
++                      );
++              vector4_to_vector3( m_local2world_x.z() ) = normalised_safe(
++                      vector3_cross( vector4_to_vector3( m_local2world_x.x() ), vector4_to_vector3( m_local2world_x.y() ) )
++                      );
++              matrix4_premultiply_by_matrix4( m_local2world_x, m_pivot.m_worldSpace );
++      }
++
++      m_circle_y_visible = !vector3_equal_epsilon( g_vector3_axis_y, localViewpoint, 1e-6f );
++      if ( m_circle_y_visible ) {
++              m_local2world_y = g_matrix4_identity;
++              vector4_to_vector3( m_local2world_y.z() ) = normalised_safe(
++                      vector3_cross( g_vector3_axis_y, localViewpoint )
++                      );
++              vector4_to_vector3( m_local2world_y.x() ) = normalised_safe(
++                      vector3_cross( vector4_to_vector3( m_local2world_y.y() ), vector4_to_vector3( m_local2world_y.z() ) )
++                      );
++              matrix4_premultiply_by_matrix4( m_local2world_y, m_pivot.m_worldSpace );
++      }
++
++      m_circle_z_visible = !vector3_equal_epsilon( g_vector3_axis_z, localViewpoint, 1e-6f );
++      if ( m_circle_z_visible ) {
++              m_local2world_z = g_matrix4_identity;
++              vector4_to_vector3( m_local2world_z.x() ) = normalised_safe(
++                      vector3_cross( g_vector3_axis_z, localViewpoint )
++                      );
++              vector4_to_vector3( m_local2world_z.y() ) = normalised_safe(
++                      vector3_cross( vector4_to_vector3( m_local2world_z.z() ), vector4_to_vector3( m_local2world_z.x() ) )
++                      );
++              matrix4_premultiply_by_matrix4( m_local2world_z, m_pivot.m_worldSpace );
++      }
++}
++
++void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world ){
++      m_pivot.update( pivot2world, volume.GetModelview(), volume.GetProjection(), volume.GetViewport() );
++      updateCircleTransforms();
++
++      // temp hack
++      UpdateColours();
++
++      renderer.SetState( m_state_outer, Renderer::eWireframeOnly );
++      renderer.SetState( m_state_outer, Renderer::eFullMaterials );
++
++      renderer.addRenderable( m_circle_screen, m_pivot.m_viewpointSpace );
++      renderer.addRenderable( m_circle_sphere, m_pivot.m_viewpointSpace );
++
++      if ( m_circle_x_visible ) {
++              renderer.addRenderable( m_circle_x, m_local2world_x );
++      }
++      if ( m_circle_y_visible ) {
++              renderer.addRenderable( m_circle_y, m_local2world_y );
++      }
++      if ( m_circle_z_visible ) {
++              renderer.addRenderable( m_circle_z, m_local2world_z );
++      }
++}
++void testSelect( const View& view, const Matrix4& pivot2world ){
++      m_pivot.update( pivot2world, view.GetModelview(), view.GetProjection(), view.GetViewport() );
++      updateCircleTransforms();
++
++      SelectionPool selector;
++
++      {
++              {
++                      Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_local2world_x ) );
++
++#if defined( DEBUG_SELECTION )
++                      g_render_clipped.construct( view.GetViewMatrix() );
++#endif
++
++                      SelectionIntersection best;
++                      LineStrip_BestPoint( local2view, m_circle_x.m_vertices.data(), m_circle_x.m_vertices.size(), best );
++                      selector.addSelectable( best, &m_selectable_x );
++              }
++
++              {
++                      Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_local2world_y ) );
++
++#if defined( DEBUG_SELECTION )
++                      g_render_clipped.construct( view.GetViewMatrix() );
++#endif
++
++                      SelectionIntersection best;
++                      LineStrip_BestPoint( local2view, m_circle_y.m_vertices.data(), m_circle_y.m_vertices.size(), best );
++                      selector.addSelectable( best, &m_selectable_y );
++              }
++
++              {
++                      Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_local2world_z ) );
++
++#if defined( DEBUG_SELECTION )
++                      g_render_clipped.construct( view.GetViewMatrix() );
++#endif
++
++                      SelectionIntersection best;
++                      LineStrip_BestPoint( local2view, m_circle_z.m_vertices.data(), m_circle_z.m_vertices.size(), best );
++                      selector.addSelectable( best, &m_selectable_z );
++              }
++      }
++
++      {
++              Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_pivot.m_viewpointSpace ) );
++
++              {
++                      SelectionIntersection best;
++                      LineLoop_BestPoint( local2view, m_circle_screen.m_vertices.data(), m_circle_screen.m_vertices.size(), best );
++                      selector.addSelectable( best, &m_selectable_screen );
++              }
++
++              {
++                      SelectionIntersection best;
++                      Circle_BestPoint( local2view, eClipCullCW, m_circle_sphere.m_vertices.data(), m_circle_sphere.m_vertices.size(), best );
++                      selector.addSelectable( best, &m_selectable_sphere );
++              }
++      }
++
++      m_axis_screen = m_pivot.m_axis_screen;
++
++      if ( !selector.failed() ) {
++              ( *selector.begin() ).second->setSelected( true );
++      }
++}
++
++Manipulatable* GetManipulatable(){
++      if ( m_selectable_x.isSelected() ) {
++              m_axis.SetAxis( g_vector3_axis_x );
++              return &m_axis;
++      }
++      else if ( m_selectable_y.isSelected() ) {
++              m_axis.SetAxis( g_vector3_axis_y );
++              return &m_axis;
++      }
++      else if ( m_selectable_z.isSelected() ) {
++              m_axis.SetAxis( g_vector3_axis_z );
++              return &m_axis;
++      }
++      else if ( m_selectable_screen.isSelected() ) {
++              m_axis.SetAxis( m_axis_screen );
++              return &m_axis;
++      }
++      else{
++              return &m_free;
++      }
++}
++
++void setSelected( bool select ){
++      m_selectable_x.setSelected( select );
++      m_selectable_y.setSelected( select );
++      m_selectable_z.setSelected( select );
++      m_selectable_screen.setSelected( select );
++}
++bool isSelected() const {
++      return m_selectable_x.isSelected()
++                 | m_selectable_y.isSelected()
++                 | m_selectable_z.isSelected()
++                 | m_selectable_screen.isSelected()
++                 | m_selectable_sphere.isSelected();
++}
++};
++
++Shader* RotateManipulator::m_state_outer;
++
++
++const float arrowhead_length = 16;
++const float arrowhead_radius = 4;
++
++inline void draw_arrowline( const float length, PointVertex* line, const std::size_t axis ){
++      ( *line++ ).vertex = vertex3f_identity;
++      ( *line ).vertex = vertex3f_identity;
++      vertex3f_to_array( ( *line ).vertex )[axis] = length - arrowhead_length;
++}
++
++template<typename VertexRemap, typename NormalRemap>
++inline void draw_arrowhead( const std::size_t segments, const float length, FlatShadedVertex* vertices, VertexRemap, NormalRemap ){
++      std::size_t head_tris = ( segments << 3 );
++      const double head_segment = c_2pi / head_tris;
++      for ( std::size_t i = 0; i < head_tris; ++i )
++      {
++              {
++                      FlatShadedVertex& point = vertices[i * 6 + 0];
++                      VertexRemap::x( point.vertex ) = length - arrowhead_length;
++                      VertexRemap::y( point.vertex ) = arrowhead_radius * static_cast<float>( cos( i * head_segment ) );
++                      VertexRemap::z( point.vertex ) = arrowhead_radius * static_cast<float>( sin( i * head_segment ) );
++                      NormalRemap::x( point.normal ) = arrowhead_radius / arrowhead_length;
++                      NormalRemap::y( point.normal ) = static_cast<float>( cos( i * head_segment ) );
++                      NormalRemap::z( point.normal ) = static_cast<float>( sin( i * head_segment ) );
++              }
++              {
++                      FlatShadedVertex& point = vertices[i * 6 + 1];
++                      VertexRemap::x( point.vertex ) = length;
++                      VertexRemap::y( point.vertex ) = 0;
++                      VertexRemap::z( point.vertex ) = 0;
++                      NormalRemap::x( point.normal ) = arrowhead_radius / arrowhead_length;
++                      NormalRemap::y( point.normal ) = static_cast<float>( cos( ( i + 0.5 ) * head_segment ) );
++                      NormalRemap::z( point.normal ) = static_cast<float>( sin( ( i + 0.5 ) * head_segment ) );
++              }
++              {
++                      FlatShadedVertex& point = vertices[i * 6 + 2];
++                      VertexRemap::x( point.vertex ) = length - arrowhead_length;
++                      VertexRemap::y( point.vertex ) = arrowhead_radius * static_cast<float>( cos( ( i + 1 ) * head_segment ) );
++                      VertexRemap::z( point.vertex ) = arrowhead_radius * static_cast<float>( sin( ( i + 1 ) * head_segment ) );
++                      NormalRemap::x( point.normal ) = arrowhead_radius / arrowhead_length;
++                      NormalRemap::y( point.normal ) = static_cast<float>( cos( ( i + 1 ) * head_segment ) );
++                      NormalRemap::z( point.normal ) = static_cast<float>( sin( ( i + 1 ) * head_segment ) );
++              }
++
++              {
++                      FlatShadedVertex& point = vertices[i * 6 + 3];
++                      VertexRemap::x( point.vertex ) = length - arrowhead_length;
++                      VertexRemap::y( point.vertex ) = 0;
++                      VertexRemap::z( point.vertex ) = 0;
++                      NormalRemap::x( point.normal ) = -1;
++                      NormalRemap::y( point.normal ) = 0;
++                      NormalRemap::z( point.normal ) = 0;
++              }
++              {
++                      FlatShadedVertex& point = vertices[i * 6 + 4];
++                      VertexRemap::x( point.vertex ) = length - arrowhead_length;
++                      VertexRemap::y( point.vertex ) = arrowhead_radius * static_cast<float>( cos( i * head_segment ) );
++                      VertexRemap::z( point.vertex ) = arrowhead_radius * static_cast<float>( sin( i * head_segment ) );
++                      NormalRemap::x( point.normal ) = -1;
++                      NormalRemap::y( point.normal ) = 0;
++                      NormalRemap::z( point.normal ) = 0;
++              }
++              {
++                      FlatShadedVertex& point = vertices[i * 6 + 5];
++                      VertexRemap::x( point.vertex ) = length - arrowhead_length;
++                      VertexRemap::y( point.vertex ) = arrowhead_radius * static_cast<float>( cos( ( i + 1 ) * head_segment ) );
++                      VertexRemap::z( point.vertex ) = arrowhead_radius * static_cast<float>( sin( ( i + 1 ) * head_segment ) );
++                      NormalRemap::x( point.normal ) = -1;
++                      NormalRemap::y( point.normal ) = 0;
++                      NormalRemap::z( point.normal ) = 0;
++              }
++      }
++}
++
++template<typename Triple>
++class TripleRemapXYZ
++{
++public:
++static float& x( Triple& triple ){
++      return triple.x();
++}
++static float& y( Triple& triple ){
++      return triple.y();
++}
++static float& z( Triple& triple ){
++      return triple.z();
++}
++};
++
++template<typename Triple>
++class TripleRemapYZX
++{
++public:
++static float& x( Triple& triple ){
++      return triple.y();
++}
++static float& y( Triple& triple ){
++      return triple.z();
++}
++static float& z( Triple& triple ){
++      return triple.x();
++}
++};
++
++template<typename Triple>
++class TripleRemapZXY
++{
++public:
++static float& x( Triple& triple ){
++      return triple.z();
++}
++static float& y( Triple& triple ){
++      return triple.x();
++}
++static float& z( Triple& triple ){
++      return triple.y();
++}
++};
++
++void vector3_print( const Vector3& v ){
++      globalOutputStream() << "( " << v.x() << " " << v.y() << " " << v.z() << " )";
++}
++
++class TranslateManipulator : public Manipulator
++{
++struct RenderableArrowLine : public OpenGLRenderable
++{
++      PointVertex m_line[2];
++
++      RenderableArrowLine(){
++      }
++      void render( RenderStateFlags state ) const {
++              glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( PointVertex ), &m_line[0].colour );
++              glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &m_line[0].vertex );
++              glDrawArrays( GL_LINES, 0, 2 );
++      }
++      void setColour( const Colour4b& colour ){
++              m_line[0].colour = colour;
++              m_line[1].colour = colour;
++      }
++};
++struct RenderableArrowHead : public OpenGLRenderable
++{
++      Array<FlatShadedVertex> m_vertices;
++
++      RenderableArrowHead( std::size_t size )
++              : m_vertices( size ){
++      }
++      void render( RenderStateFlags state ) const {
++              glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( FlatShadedVertex ), &m_vertices.data()->colour );
++              glVertexPointer( 3, GL_FLOAT, sizeof( FlatShadedVertex ), &m_vertices.data()->vertex );
++              glNormalPointer( GL_FLOAT, sizeof( FlatShadedVertex ), &m_vertices.data()->normal );
++              glDrawArrays( GL_TRIANGLES, 0, GLsizei( m_vertices.size() ) );
++      }
++      void setColour( const Colour4b& colour ){
++              for ( Array<FlatShadedVertex>::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i )
++              {
++                      ( *i ).colour = colour;
++              }
++      }
++};
++struct RenderableQuad : public OpenGLRenderable
++{
++      PointVertex m_quad[4];
++      void render( RenderStateFlags state ) const {
++              glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( PointVertex ), &m_quad[0].colour );
++              glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &m_quad[0].vertex );
++              glDrawArrays( GL_LINE_LOOP, 0, 4 );
++      }
++      void setColour( const Colour4b& colour ){
++              m_quad[0].colour = colour;
++              m_quad[1].colour = colour;
++              m_quad[2].colour = colour;
++              m_quad[3].colour = colour;
++      }
++};
++
++TranslateFree m_free;
++TranslateAxis m_axis;
++RenderableArrowLine m_arrow_x;
++RenderableArrowLine m_arrow_y;
++RenderableArrowLine m_arrow_z;
++RenderableArrowHead m_arrow_head_x;
++RenderableArrowHead m_arrow_head_y;
++RenderableArrowHead m_arrow_head_z;
++RenderableQuad m_quad_screen;
++SelectableBool m_selectable_x;
++SelectableBool m_selectable_y;
++SelectableBool m_selectable_z;
++SelectableBool m_selectable_screen;
++Pivot2World m_pivot;
++public:
++static Shader* m_state_wire;
++static Shader* m_state_fill;
++
++TranslateManipulator( Translatable& translatable, std::size_t segments, float length ) :
++      m_free( translatable ),
++      m_axis( translatable ),
++      m_arrow_head_x( 3 * 2 * ( segments << 3 ) ),
++      m_arrow_head_y( 3 * 2 * ( segments << 3 ) ),
++      m_arrow_head_z( 3 * 2 * ( segments << 3 ) ){
++      draw_arrowline( length, m_arrow_x.m_line, 0 );
++      draw_arrowhead( segments, length, m_arrow_head_x.m_vertices.data(), TripleRemapXYZ<Vertex3f>(), TripleRemapXYZ<Normal3f>() );
++      draw_arrowline( length, m_arrow_y.m_line, 1 );
++      draw_arrowhead( segments, length, m_arrow_head_y.m_vertices.data(), TripleRemapYZX<Vertex3f>(), TripleRemapYZX<Normal3f>() );
++      draw_arrowline( length, m_arrow_z.m_line, 2 );
++      draw_arrowhead( segments, length, m_arrow_head_z.m_vertices.data(), TripleRemapZXY<Vertex3f>(), TripleRemapZXY<Normal3f>() );
++
++      draw_quad( 16, m_quad_screen.m_quad );
++}
++
++void UpdateColours(){
++      m_arrow_x.setColour( colourSelected( g_colour_x, m_selectable_x.isSelected() ) );
++      m_arrow_head_x.setColour( colourSelected( g_colour_x, m_selectable_x.isSelected() ) );
++      m_arrow_y.setColour( colourSelected( g_colour_y, m_selectable_y.isSelected() ) );
++      m_arrow_head_y.setColour( colourSelected( g_colour_y, m_selectable_y.isSelected() ) );
++      m_arrow_z.setColour( colourSelected( g_colour_z, m_selectable_z.isSelected() ) );
++      m_arrow_head_z.setColour( colourSelected( g_colour_z, m_selectable_z.isSelected() ) );
++      m_quad_screen.setColour( colourSelected( g_colour_screen, m_selectable_screen.isSelected() ) );
++}
++
++bool manipulator_show_axis( const Pivot2World& pivot, const Vector3& axis ){
++      return fabs( vector3_dot( pivot.m_axis_screen, axis ) ) < 0.95;
++}
++
++void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world ){
++      m_pivot.update( pivot2world, volume.GetModelview(), volume.GetProjection(), volume.GetViewport() );
++
++      // temp hack
++      UpdateColours();
++
++      Vector3 x = vector3_normalised( vector4_to_vector3( m_pivot.m_worldSpace.x() ) );
++      bool show_x = manipulator_show_axis( m_pivot, x );
++
++      Vector3 y = vector3_normalised( vector4_to_vector3( m_pivot.m_worldSpace.y() ) );
++      bool show_y = manipulator_show_axis( m_pivot, y );
++
++      Vector3 z = vector3_normalised( vector4_to_vector3( m_pivot.m_worldSpace.z() ) );
++      bool show_z = manipulator_show_axis( m_pivot, z );
++
++      renderer.SetState( m_state_wire, Renderer::eWireframeOnly );
++      renderer.SetState( m_state_wire, Renderer::eFullMaterials );
++
++      if ( show_x ) {
++              renderer.addRenderable( m_arrow_x, m_pivot.m_worldSpace );
++      }
++      if ( show_y ) {
++              renderer.addRenderable( m_arrow_y, m_pivot.m_worldSpace );
++      }
++      if ( show_z ) {
++              renderer.addRenderable( m_arrow_z, m_pivot.m_worldSpace );
++      }
++
++      renderer.addRenderable( m_quad_screen, m_pivot.m_viewplaneSpace );
++
++      renderer.SetState( m_state_fill, Renderer::eWireframeOnly );
++      renderer.SetState( m_state_fill, Renderer::eFullMaterials );
++
++      if ( show_x ) {
++              renderer.addRenderable( m_arrow_head_x, m_pivot.m_worldSpace );
++      }
++      if ( show_y ) {
++              renderer.addRenderable( m_arrow_head_y, m_pivot.m_worldSpace );
++      }
++      if ( show_z ) {
++              renderer.addRenderable( m_arrow_head_z, m_pivot.m_worldSpace );
++      }
++}
++void testSelect( const View& view, const Matrix4& pivot2world ){
++      m_pivot.update( pivot2world, view.GetModelview(), view.GetProjection(), view.GetViewport() );
++
++      SelectionPool selector;
++
++      Vector3 x = vector3_normalised( vector4_to_vector3( m_pivot.m_worldSpace.x() ) );
++      bool show_x = manipulator_show_axis( m_pivot, x );
++
++      Vector3 y = vector3_normalised( vector4_to_vector3( m_pivot.m_worldSpace.y() ) );
++      bool show_y = manipulator_show_axis( m_pivot, y );
++
++      Vector3 z = vector3_normalised( vector4_to_vector3( m_pivot.m_worldSpace.z() ) );
++      bool show_z = manipulator_show_axis( m_pivot, z );
++
++      {
++              Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_pivot.m_viewpointSpace ) );
++
++              {
++                      SelectionIntersection best;
++                      Quad_BestPoint( local2view, eClipCullCW, m_quad_screen.m_quad, best );
++                      if ( best.valid() ) {
++                              best = SelectionIntersection( 0, 0 );
++                              selector.addSelectable( best, &m_selectable_screen );
++                      }
++              }
++      }
++
++      {
++              Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_pivot.m_worldSpace ) );
++
++#if defined( DEBUG_SELECTION )
++              g_render_clipped.construct( view.GetViewMatrix() );
++#endif
++
++              if ( show_x ) {
++                      SelectionIntersection best;
++                      Line_BestPoint( local2view, m_arrow_x.m_line, best );
++                      Triangles_BestPoint( local2view, eClipCullCW, m_arrow_head_x.m_vertices.begin(), m_arrow_head_x.m_vertices.end(), best );
++                      selector.addSelectable( best, &m_selectable_x );
++              }
++
++              if ( show_y ) {
++                      SelectionIntersection best;
++                      Line_BestPoint( local2view, m_arrow_y.m_line, best );
++                      Triangles_BestPoint( local2view, eClipCullCW, m_arrow_head_y.m_vertices.begin(), m_arrow_head_y.m_vertices.end(), best );
++                      selector.addSelectable( best, &m_selectable_y );
++              }
++
++              if ( show_z ) {
++                      SelectionIntersection best;
++                      Line_BestPoint( local2view, m_arrow_z.m_line, best );
++                      Triangles_BestPoint( local2view, eClipCullCW, m_arrow_head_z.m_vertices.begin(), m_arrow_head_z.m_vertices.end(), best );
++                      selector.addSelectable( best, &m_selectable_z );
++              }
++      }
++
++      if ( !selector.failed() ) {
++              ( *selector.begin() ).second->setSelected( true );
++      }
++}
++
++Manipulatable* GetManipulatable(){
++      if ( m_selectable_x.isSelected() ) {
++              m_axis.SetAxis( g_vector3_axis_x );
++              return &m_axis;
++      }
++      else if ( m_selectable_y.isSelected() ) {
++              m_axis.SetAxis( g_vector3_axis_y );
++              return &m_axis;
++      }
++      else if ( m_selectable_z.isSelected() ) {
++              m_axis.SetAxis( g_vector3_axis_z );
++              return &m_axis;
++      }
++      else
++      {
++              return &m_free;
++      }
++}
++
++void setSelected( bool select ){
++      m_selectable_x.setSelected( select );
++      m_selectable_y.setSelected( select );
++      m_selectable_z.setSelected( select );
++      m_selectable_screen.setSelected( select );
++}
++bool isSelected() const {
++      return m_selectable_x.isSelected()
++                 | m_selectable_y.isSelected()
++                 | m_selectable_z.isSelected()
++                 | m_selectable_screen.isSelected();
++}
++};
++
++Shader* TranslateManipulator::m_state_wire;
++Shader* TranslateManipulator::m_state_fill;
++
++class ScaleManipulator : public Manipulator
++{
++struct RenderableArrow : public OpenGLRenderable
++{
++      PointVertex m_line[2];
++
++      void render( RenderStateFlags state ) const {
++              glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( PointVertex ), &m_line[0].colour );
++              glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &m_line[0].vertex );
++              glDrawArrays( GL_LINES, 0, 2 );
++      }
++      void setColour( const Colour4b& colour ){
++              m_line[0].colour = colour;
++              m_line[1].colour = colour;
++      }
++};
++struct RenderableQuad : public OpenGLRenderable
++{
++      PointVertex m_quad[4];
++      void render( RenderStateFlags state ) const {
++              glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( PointVertex ), &m_quad[0].colour );
++              glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &m_quad[0].vertex );
++              glDrawArrays( GL_QUADS, 0, 4 );
++      }
++      void setColour( const Colour4b& colour ){
++              m_quad[0].colour = colour;
++              m_quad[1].colour = colour;
++              m_quad[2].colour = colour;
++              m_quad[3].colour = colour;
++      }
++};
++
++ScaleFree m_free;
++ScaleAxis m_axis;
++RenderableArrow m_arrow_x;
++RenderableArrow m_arrow_y;
++RenderableArrow m_arrow_z;
++RenderableQuad m_quad_screen;
++SelectableBool m_selectable_x;
++SelectableBool m_selectable_y;
++SelectableBool m_selectable_z;
++SelectableBool m_selectable_screen;
++Pivot2World m_pivot;
++public:
++ScaleManipulator( Scalable& scalable, std::size_t segments, float length ) :
++      m_free( scalable ),
++      m_axis( scalable ){
++      draw_arrowline( length, m_arrow_x.m_line, 0 );
++      draw_arrowline( length, m_arrow_y.m_line, 1 );
++      draw_arrowline( length, m_arrow_z.m_line, 2 );
++
++      draw_quad( 16, m_quad_screen.m_quad );
++}
++
++Pivot2World& getPivot(){
++      return m_pivot;
++}
++
++void UpdateColours(){
++      m_arrow_x.setColour( colourSelected( g_colour_x, m_selectable_x.isSelected() ) );
++      m_arrow_y.setColour( colourSelected( g_colour_y, m_selectable_y.isSelected() ) );
++      m_arrow_z.setColour( colourSelected( g_colour_z, m_selectable_z.isSelected() ) );
++      m_quad_screen.setColour( colourSelected( g_colour_screen, m_selectable_screen.isSelected() ) );
++}
++
++void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world ){
++      m_pivot.update( pivot2world, volume.GetModelview(), volume.GetProjection(), volume.GetViewport() );
++
++      // temp hack
++      UpdateColours();
++
++      renderer.addRenderable( m_arrow_x, m_pivot.m_worldSpace );
++      renderer.addRenderable( m_arrow_y, m_pivot.m_worldSpace );
++      renderer.addRenderable( m_arrow_z, m_pivot.m_worldSpace );
++
++      renderer.addRenderable( m_quad_screen, m_pivot.m_viewpointSpace );
++}
++void testSelect( const View& view, const Matrix4& pivot2world ){
++      m_pivot.update( pivot2world, view.GetModelview(), view.GetProjection(), view.GetViewport() );
++
++      SelectionPool selector;
++
++      {
++              Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_pivot.m_worldSpace ) );
++
++#if defined( DEBUG_SELECTION )
++              g_render_clipped.construct( view.GetViewMatrix() );
++#endif
++
++              {
++                      SelectionIntersection best;
++                      Line_BestPoint( local2view, m_arrow_x.m_line, best );
++                      selector.addSelectable( best, &m_selectable_x );
++              }
++
++              {
++                      SelectionIntersection best;
++                      Line_BestPoint( local2view, m_arrow_y.m_line, best );
++                      selector.addSelectable( best, &m_selectable_y );
++              }
++
++              {
++                      SelectionIntersection best;
++                      Line_BestPoint( local2view, m_arrow_z.m_line, best );
++                      selector.addSelectable( best, &m_selectable_z );
++              }
++      }
++
++      {
++              Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_pivot.m_viewpointSpace ) );
++
++              {
++                      SelectionIntersection best;
++                      Quad_BestPoint( local2view, eClipCullCW, m_quad_screen.m_quad, best );
++                      selector.addSelectable( best, &m_selectable_screen );
++              }
++      }
++
++      if ( !selector.failed() ) {
++              ( *selector.begin() ).second->setSelected( true );
++      }
++}
++
++Manipulatable* GetManipulatable(){
++      if ( m_selectable_x.isSelected() ) {
++              m_axis.SetAxis( g_vector3_axis_x );
++              return &m_axis;
++      }
++      else if ( m_selectable_y.isSelected() ) {
++              m_axis.SetAxis( g_vector3_axis_y );
++              return &m_axis;
++      }
++      else if ( m_selectable_z.isSelected() ) {
++              m_axis.SetAxis( g_vector3_axis_z );
++              return &m_axis;
++      }
++      else{
++              return &m_free;
++      }
++}
++
++void setSelected( bool select ){
++      m_selectable_x.setSelected( select );
++      m_selectable_y.setSelected( select );
++      m_selectable_z.setSelected( select );
++      m_selectable_screen.setSelected( select );
++}
++bool isSelected() const {
++      return m_selectable_x.isSelected()
++                 | m_selectable_y.isSelected()
++                 | m_selectable_z.isSelected()
++                 | m_selectable_screen.isSelected();
++}
++};
++
++
++inline PlaneSelectable* Instance_getPlaneSelectable( scene::Instance& instance ){
++      return InstanceTypeCast<PlaneSelectable>::cast( instance );
++}
++
++class PlaneSelectableSelectPlanes : public scene::Graph::Walker
++{
++Selector& m_selector;
++SelectionTest& m_test;
++PlaneCallback m_selectedPlaneCallback;
++public:
++PlaneSelectableSelectPlanes( Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback )
++      : m_selector( selector ), m_test( test ), m_selectedPlaneCallback( selectedPlaneCallback ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      if ( path.top().get().visible() ) {
++              Selectable* selectable = Instance_getSelectable( instance );
++              if ( selectable != 0 && selectable->isSelected() ) {
++                      PlaneSelectable* planeSelectable = Instance_getPlaneSelectable( instance );
++                      if ( planeSelectable != 0 ) {
++                              planeSelectable->selectPlanes( m_selector, m_test, m_selectedPlaneCallback );
++                      }
++              }
++      }
++      return true;
++}
++};
++
++class PlaneSelectableSelectReversedPlanes : public scene::Graph::Walker
++{
++Selector& m_selector;
++const SelectedPlanes& m_selectedPlanes;
++public:
++PlaneSelectableSelectReversedPlanes( Selector& selector, const SelectedPlanes& selectedPlanes )
++      : m_selector( selector ), m_selectedPlanes( selectedPlanes ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      if ( path.top().get().visible() ) {
++              Selectable* selectable = Instance_getSelectable( instance );
++              if ( selectable != 0 && selectable->isSelected() ) {
++                      PlaneSelectable* planeSelectable = Instance_getPlaneSelectable( instance );
++                      if ( planeSelectable != 0 ) {
++                              planeSelectable->selectReversedPlanes( m_selector, m_selectedPlanes );
++                      }
++              }
++      }
++      return true;
++}
++};
++
++void Scene_forEachPlaneSelectable_selectPlanes( scene::Graph& graph, Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback ){
++      graph.traverse( PlaneSelectableSelectPlanes( selector, test, selectedPlaneCallback ) );
++}
++
++void Scene_forEachPlaneSelectable_selectReversedPlanes( scene::Graph& graph, Selector& selector, const SelectedPlanes& selectedPlanes ){
++      graph.traverse( PlaneSelectableSelectReversedPlanes( selector, selectedPlanes ) );
++}
++
++
++class PlaneLess
++{
++public:
++bool operator()( const Plane3& plane, const Plane3& other ) const {
++      if ( plane.a < other.a ) {
++              return true;
++      }
++      if ( other.a < plane.a ) {
++              return false;
++      }
++
++      if ( plane.b < other.b ) {
++              return true;
++      }
++      if ( other.b < plane.b ) {
++              return false;
++      }
++
++      if ( plane.c < other.c ) {
++              return true;
++      }
++      if ( other.c < plane.c ) {
++              return false;
++      }
++
++      if ( plane.d < other.d ) {
++              return true;
++      }
++      if ( other.d < plane.d ) {
++              return false;
++      }
++
++      return false;
++}
++};
++
++typedef std::set<Plane3, PlaneLess> PlaneSet;
++
++inline void PlaneSet_insert( PlaneSet& self, const Plane3& plane ){
++      self.insert( plane );
++}
++
++inline bool PlaneSet_contains( const PlaneSet& self, const Plane3& plane ){
++      return self.find( plane ) != self.end();
++}
++
++
++class SelectedPlaneSet : public SelectedPlanes
++{
++PlaneSet m_selectedPlanes;
++public:
++bool empty() const {
++      return m_selectedPlanes.empty();
++}
++
++void insert( const Plane3& plane ){
++      PlaneSet_insert( m_selectedPlanes, plane );
++}
++bool contains( const Plane3& plane ) const {
++      return PlaneSet_contains( m_selectedPlanes, plane );
++}
++typedef MemberCaller<SelectedPlaneSet, void(const Plane3&), &SelectedPlaneSet::insert> InsertCaller;
++};
++
++
++bool Scene_forEachPlaneSelectable_selectPlanes( scene::Graph& graph, Selector& selector, SelectionTest& test ){
++      SelectedPlaneSet selectedPlanes;
++
++      Scene_forEachPlaneSelectable_selectPlanes( graph, selector, test, SelectedPlaneSet::InsertCaller( selectedPlanes ) );
++      Scene_forEachPlaneSelectable_selectReversedPlanes( graph, selector, selectedPlanes );
++
++      return !selectedPlanes.empty();
++}
++
++void Scene_Translate_Component_Selected( scene::Graph& graph, const Vector3& translation );
++void Scene_Translate_Selected( scene::Graph& graph, const Vector3& translation );
++void Scene_TestSelect_Primitive( Selector& selector, SelectionTest& test, const VolumeTest& volume );
++void Scene_TestSelect_Component( Selector& selector, SelectionTest& test, const VolumeTest& volume, SelectionSystem::EComponentMode componentMode );
++void Scene_TestSelect_Component_Selected( Selector& selector, SelectionTest& test, const VolumeTest& volume, SelectionSystem::EComponentMode componentMode );
++void Scene_SelectAll_Component( bool select, SelectionSystem::EComponentMode componentMode );
++
++class ResizeTranslatable : public Translatable
++{
++void translate( const Vector3& translation ){
++      Scene_Translate_Component_Selected( GlobalSceneGraph(), translation );
++}
++};
++
++class DragTranslatable : public Translatable
++{
++void translate( const Vector3& translation ){
++      if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent ) {
++              Scene_Translate_Component_Selected( GlobalSceneGraph(), translation );
++      }
++      else
++      {
++              Scene_Translate_Selected( GlobalSceneGraph(), translation );
++      }
++}
++};
++
++class SelectionVolume : public SelectionTest
++{
++Matrix4 m_local2view;
++const View& m_view;
++clipcull_t m_cull;
++Vector3 m_near;
++Vector3 m_far;
++public:
++SelectionVolume( const View& view )
++      : m_view( view ){
++}
++
++const VolumeTest& getVolume() const {
++      return m_view;
++}
++
++const Vector3& getNear() const {
++      return m_near;
++}
++const Vector3& getFar() const {
++      return m_far;
++}
++
++void BeginMesh( const Matrix4& localToWorld, bool twoSided ){
++      m_local2view = matrix4_multiplied_by_matrix4( m_view.GetViewMatrix(), localToWorld );
++
++      // Cull back-facing polygons based on winding being clockwise or counter-clockwise.
++      // Don't cull if the view is wireframe and the polygons are two-sided.
++      m_cull = twoSided && !m_view.fill() ? eClipCullNone : ( matrix4_handedness( localToWorld ) == MATRIX4_RIGHTHANDED ) ? eClipCullCW : eClipCullCCW;
++
++      {
++              Matrix4 screen2world( matrix4_full_inverse( m_local2view ) );
++
++              m_near = vector4_projected(
++                      matrix4_transformed_vector4(
++                              screen2world,
++                              Vector4( 0, 0, -1, 1 )
++                              )
++                      );
++
++              m_far = vector4_projected(
++                      matrix4_transformed_vector4(
++                              screen2world,
++                              Vector4( 0, 0, 1, 1 )
++                              )
++                      );
++      }
++
++#if defined( DEBUG_SELECTION )
++      g_render_clipped.construct( m_view.GetViewMatrix() );
++#endif
++}
++void TestPoint( const Vector3& point, SelectionIntersection& best ){
++      Vector4 clipped;
++      if ( matrix4_clip_point( m_local2view, point, clipped ) == c_CLIP_PASS ) {
++              best = select_point_from_clipped( clipped );
++      }
++}
++void TestPolygon( const VertexPointer& vertices, std::size_t count, SelectionIntersection& best ){
++      Vector4 clipped[9];
++      for ( std::size_t i = 0; i + 2 < count; ++i )
++      {
++              BestPoint(
++                      matrix4_clip_triangle(
++                              m_local2view,
++                              reinterpret_cast<const Vector3&>( vertices[0] ),
++                              reinterpret_cast<const Vector3&>( vertices[i + 1] ),
++                              reinterpret_cast<const Vector3&>( vertices[i + 2] ),
++                              clipped
++                              ),
++                      clipped,
++                      best,
++                      m_cull
++                      );
++      }
++}
++void TestLineLoop( const VertexPointer& vertices, std::size_t count, SelectionIntersection& best ){
++      if ( count == 0 ) {
++              return;
++      }
++      Vector4 clipped[9];
++      for ( VertexPointer::iterator i = vertices.begin(), end = i + count, prev = i + ( count - 1 ); i != end; prev = i, ++i )
++      {
++              BestPoint(
++                      matrix4_clip_line(
++                              m_local2view,
++                              reinterpret_cast<const Vector3&>( ( *prev ) ),
++                              reinterpret_cast<const Vector3&>( ( *i ) ),
++                              clipped
++                              ),
++                      clipped,
++                      best,
++                      m_cull
++                      );
++      }
++}
++void TestLineStrip( const VertexPointer& vertices, std::size_t count, SelectionIntersection& best ){
++      if ( count == 0 ) {
++              return;
++      }
++      Vector4 clipped[9];
++      for ( VertexPointer::iterator i = vertices.begin(), end = i + count, next = i + 1; next != end; i = next, ++next )
++      {
++              BestPoint(
++                      matrix4_clip_line(
++                              m_local2view,
++                              reinterpret_cast<const Vector3&>( ( *i ) ),
++                              reinterpret_cast<const Vector3&>( ( *next ) ),
++                              clipped
++                              ),
++                      clipped,
++                      best,
++                      m_cull
++                      );
++      }
++}
++void TestLines( const VertexPointer& vertices, std::size_t count, SelectionIntersection& best ){
++      if ( count == 0 ) {
++              return;
++      }
++      Vector4 clipped[9];
++      for ( VertexPointer::iterator i = vertices.begin(), end = i + count; i != end; i += 2 )
++      {
++              BestPoint(
++                      matrix4_clip_line(
++                              m_local2view,
++                              reinterpret_cast<const Vector3&>( ( *i ) ),
++                              reinterpret_cast<const Vector3&>( ( *( i + 1 ) ) ),
++                              clipped
++                              ),
++                      clipped,
++                      best,
++                      m_cull
++                      );
++      }
++}
++void TestTriangles( const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best ){
++      Vector4 clipped[9];
++      for ( IndexPointer::iterator i( indices.begin() ); i != indices.end(); i += 3 )
++      {
++              BestPoint(
++                      matrix4_clip_triangle(
++                              m_local2view,
++                              reinterpret_cast<const Vector3&>( vertices[*i] ),
++                              reinterpret_cast<const Vector3&>( vertices[*( i + 1 )] ),
++                              reinterpret_cast<const Vector3&>( vertices[*( i + 2 )] ),
++                              clipped
++                              ),
++                      clipped,
++                      best,
++                      m_cull
++                      );
++      }
++}
++void TestQuads( const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best ){
++      Vector4 clipped[9];
++      for ( IndexPointer::iterator i( indices.begin() ); i != indices.end(); i += 4 )
++      {
++              BestPoint(
++                      matrix4_clip_triangle(
++                              m_local2view,
++                              reinterpret_cast<const Vector3&>( vertices[*i] ),
++                              reinterpret_cast<const Vector3&>( vertices[*( i + 1 )] ),
++                              reinterpret_cast<const Vector3&>( vertices[*( i + 3 )] ),
++                              clipped
++                              ),
++                      clipped,
++                      best,
++                      m_cull
++                      );
++              BestPoint(
++                      matrix4_clip_triangle(
++                              m_local2view,
++                              reinterpret_cast<const Vector3&>( vertices[*( i + 1 )] ),
++                              reinterpret_cast<const Vector3&>( vertices[*( i + 2 )] ),
++                              reinterpret_cast<const Vector3&>( vertices[*( i + 3 )] ),
++                              clipped
++                              ),
++                      clipped,
++                      best,
++                      m_cull
++                      );
++      }
++}
++void TestQuadStrip( const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best ){
++      Vector4 clipped[9];
++      for ( IndexPointer::iterator i( indices.begin() ); i + 2 != indices.end(); i += 2 )
++      {
++              BestPoint(
++                      matrix4_clip_triangle(
++                              m_local2view,
++                              reinterpret_cast<const Vector3&>( vertices[*i] ),
++                              reinterpret_cast<const Vector3&>( vertices[*( i + 1 )] ),
++                              reinterpret_cast<const Vector3&>( vertices[*( i + 2 )] ),
++                              clipped
++                              ),
++                      clipped,
++                      best,
++                      m_cull
++                      );
++              BestPoint(
++                      matrix4_clip_triangle(
++                              m_local2view,
++                              reinterpret_cast<const Vector3&>( vertices[*( i + 2 )] ),
++                              reinterpret_cast<const Vector3&>( vertices[*( i + 1 )] ),
++                              reinterpret_cast<const Vector3&>( vertices[*( i + 3 )] ),
++                              clipped
++                              ),
++                      clipped,
++                      best,
++                      m_cull
++                      );
++      }
++}
++};
++
++class SelectionCounter
++{
++public:
++using func = void(const Selectable &);
++
++SelectionCounter( const SelectionChangeCallback& onchanged )
++      : m_count( 0 ), m_onchanged( onchanged ){
++}
++void operator()( const Selectable& selectable ){
++      if ( selectable.isSelected() ) {
++              ++m_count;
++      }
++      else
++      {
++              ASSERT_MESSAGE( m_count != 0, "selection counter underflow" );
++              --m_count;
++      }
++
++      m_onchanged( selectable );
++}
++bool empty() const {
++      return m_count == 0;
++}
++std::size_t size() const {
++      return m_count;
++}
++private:
++std::size_t m_count;
++SelectionChangeCallback m_onchanged;
++};
++
++inline void ConstructSelectionTest( View& view, const rect_t selection_box ){
++      view.EnableScissor( selection_box.min[0], selection_box.max[0], selection_box.min[1], selection_box.max[1] );
++}
++
++inline const rect_t SelectionBoxForPoint( const float device_point[2], const float device_epsilon[2] ){
++      rect_t selection_box;
++      selection_box.min[0] = device_point[0] - device_epsilon[0];
++      selection_box.min[1] = device_point[1] - device_epsilon[1];
++      selection_box.max[0] = device_point[0] + device_epsilon[0];
++      selection_box.max[1] = device_point[1] + device_epsilon[1];
++      return selection_box;
++}
++
++inline const rect_t SelectionBoxForArea( const float device_point[2], const float device_delta[2] ){
++      rect_t selection_box;
++      selection_box.min[0] = ( device_delta[0] < 0 ) ? ( device_point[0] + device_delta[0] ) : ( device_point[0] );
++      selection_box.min[1] = ( device_delta[1] < 0 ) ? ( device_point[1] + device_delta[1] ) : ( device_point[1] );
++      selection_box.max[0] = ( device_delta[0] > 0 ) ? ( device_point[0] + device_delta[0] ) : ( device_point[0] );
++      selection_box.max[1] = ( device_delta[1] > 0 ) ? ( device_point[1] + device_delta[1] ) : ( device_point[1] );
++      return selection_box;
++}
++
++Quaternion construct_local_rotation( const Quaternion& world, const Quaternion& localToWorld ){
++      return quaternion_normalised( quaternion_multiplied_by_quaternion(
++                                                                        quaternion_normalised( quaternion_multiplied_by_quaternion(
++                                                                                                                               quaternion_inverse( localToWorld ),
++                                                                                                                               world
++                                                                                                                               ) ),
++                                                                        localToWorld
++                                                                        ) );
++}
++
++inline void matrix4_assign_rotation( Matrix4& matrix, const Matrix4& other ){
++      matrix[0] = other[0];
++      matrix[1] = other[1];
++      matrix[2] = other[2];
++      matrix[4] = other[4];
++      matrix[5] = other[5];
++      matrix[6] = other[6];
++      matrix[8] = other[8];
++      matrix[9] = other[9];
++      matrix[10] = other[10];
++}
++
++void matrix4_assign_rotation_for_pivot( Matrix4& matrix, scene::Instance& instance ){
++      Editable* editable = Node_getEditable( instance.path().top() );
++      if ( editable != 0 ) {
++              matrix4_assign_rotation( matrix, matrix4_multiplied_by_matrix4( instance.localToWorld(), editable->getLocalPivot() ) );
++      }
++      else
++      {
++              matrix4_assign_rotation( matrix, instance.localToWorld() );
++      }
++}
++
++inline bool Instance_isSelectedComponents( scene::Instance& instance ){
++      ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable( instance );
++      return componentSelectionTestable != 0
++                 && componentSelectionTestable->isSelectedComponents();
++}
++
++class TranslateSelected : public SelectionSystem::Visitor
++{
++const Vector3& m_translate;
++public:
++TranslateSelected( const Vector3& translate )
++      : m_translate( translate ){
++}
++void visit( scene::Instance& instance ) const {
++      Transformable* transform = Instance_getTransformable( instance );
++      if ( transform != 0 ) {
++              transform->setType( TRANSFORM_PRIMITIVE );
++              transform->setTranslation( m_translate );
++      }
++}
++};
++
++void Scene_Translate_Selected( scene::Graph& graph, const Vector3& translation ){
++      if ( GlobalSelectionSystem().countSelected() != 0 ) {
++              GlobalSelectionSystem().foreachSelected( TranslateSelected( translation ) );
++      }
++}
++
++Vector3 get_local_pivot( const Vector3& world_pivot, const Matrix4& localToWorld ){
++      return Vector3(
++                         matrix4_transformed_point(
++                                 matrix4_full_inverse( localToWorld ),
++                                 world_pivot
++                                 )
++                         );
++}
++
++void translation_for_pivoted_matrix_transform( Vector3& parent_translation, const Matrix4& local_transform, const Vector3& world_pivot, const Matrix4& localToWorld, const Matrix4& localToParent ){
++      // we need a translation inside the parent system to move the origin of this object to the right place
++
++      // mathematically, it must fulfill:
++      //
++      //   local_translation local_transform local_pivot = local_pivot
++      //   local_translation = local_pivot - local_transform local_pivot
++      //
++      //   or maybe?
++      //   local_transform local_translation local_pivot = local_pivot
++      //                   local_translation local_pivot = local_transform^-1 local_pivot
++      //                 local_translation + local_pivot = local_transform^-1 local_pivot
++      //                   local_translation             = local_transform^-1 local_pivot - local_pivot
++
++      Vector3 local_pivot( get_local_pivot( world_pivot, localToWorld ) );
++
++      Vector3 local_translation(
++              vector3_subtracted(
++                      local_pivot,
++                      matrix4_transformed_point(
++                              local_transform,
++                              local_pivot
++                              )
++              /*
++                  matrix4_transformed_point(
++                      matrix4_full_inverse(local_transform),
++                      local_pivot
++                  ),
++                  local_pivot
++               */
++                      )
++              );
++
++      translation_local2object( parent_translation, local_translation, localToParent );
++
++      /*
++         // verify it!
++         globalOutputStream() << "World pivot is at " << world_pivot << "\n";
++         globalOutputStream() << "Local pivot is at " << local_pivot << "\n";
++         globalOutputStream() << "Transformation " << local_transform << " moves it to: " << matrix4_transformed_point(local_transform, local_pivot) << "\n";
++         globalOutputStream() << "Must move by " << local_translation << " in the local system" << "\n";
++         globalOutputStream() << "Must move by " << parent_translation << " in the parent system" << "\n";
++       */
++}
++
++void translation_for_pivoted_rotation( Vector3& parent_translation, const Quaternion& local_rotation, const Vector3& world_pivot, const Matrix4& localToWorld, const Matrix4& localToParent ){
++      translation_for_pivoted_matrix_transform( parent_translation, matrix4_rotation_for_quaternion_quantised( local_rotation ), world_pivot, localToWorld, localToParent );
++}
++
++void translation_for_pivoted_scale( Vector3& parent_translation, const Vector3& world_scale, const Vector3& world_pivot, const Matrix4& localToWorld, const Matrix4& localToParent ){
++      Matrix4 local_transform(
++              matrix4_multiplied_by_matrix4(
++                      matrix4_full_inverse( localToWorld ),
++                      matrix4_multiplied_by_matrix4(
++                              matrix4_scale_for_vec3( world_scale ),
++                              localToWorld
++                              )
++                      )
++              );
++      local_transform.tx() = local_transform.ty() = local_transform.tz() = 0; // cancel translation parts
++      translation_for_pivoted_matrix_transform( parent_translation, local_transform, world_pivot, localToWorld, localToParent );
++}
++
++class rotate_selected : public SelectionSystem::Visitor
++{
++const Quaternion& m_rotate;
++const Vector3& m_world_pivot;
++public:
++rotate_selected( const Quaternion& rotation, const Vector3& world_pivot )
++      : m_rotate( rotation ), m_world_pivot( world_pivot ){
++}
++void visit( scene::Instance& instance ) const {
++      TransformNode* transformNode = Node_getTransformNode( instance.path().top() );
++      if ( transformNode != 0 ) {
++              Transformable* transform = Instance_getTransformable( instance );
++              if ( transform != 0 ) {
++                      transform->setType( TRANSFORM_PRIMITIVE );
++                      transform->setScale( c_scale_identity );
++                      transform->setTranslation( c_translation_identity );
++
++                      transform->setType( TRANSFORM_PRIMITIVE );
++                      transform->setRotation( m_rotate );
++
++                      {
++                              Editable* editable = Node_getEditable( instance.path().top() );
++                              const Matrix4& localPivot = editable != 0 ? editable->getLocalPivot() : g_matrix4_identity;
++
++                              Vector3 parent_translation;
++                              translation_for_pivoted_rotation(
++                                      parent_translation,
++                                      m_rotate,
++                                      m_world_pivot,
++                                      matrix4_multiplied_by_matrix4( instance.localToWorld(), localPivot ),
++                                      matrix4_multiplied_by_matrix4( transformNode->localToParent(), localPivot )
++                                      );
++
++                              transform->setTranslation( parent_translation );
++                      }
++              }
++      }
++}
++};
++
++void Scene_Rotate_Selected( scene::Graph& graph, const Quaternion& rotation, const Vector3& world_pivot ){
++      if ( GlobalSelectionSystem().countSelected() != 0 ) {
++              GlobalSelectionSystem().foreachSelected( rotate_selected( rotation, world_pivot ) );
++      }
++}
++
++class scale_selected : public SelectionSystem::Visitor
++{
++const Vector3& m_scale;
++const Vector3& m_world_pivot;
++public:
++scale_selected( const Vector3& scaling, const Vector3& world_pivot )
++      : m_scale( scaling ), m_world_pivot( world_pivot ){
++}
++void visit( scene::Instance& instance ) const {
++      TransformNode* transformNode = Node_getTransformNode( instance.path().top() );
++      if ( transformNode != 0 ) {
++              Transformable* transform = Instance_getTransformable( instance );
++              if ( transform != 0 ) {
++                      transform->setType( TRANSFORM_PRIMITIVE );
++                      transform->setScale( c_scale_identity );
++                      transform->setTranslation( c_translation_identity );
++
++                      transform->setType( TRANSFORM_PRIMITIVE );
++                      transform->setScale( m_scale );
++                      {
++                              Editable* editable = Node_getEditable( instance.path().top() );
++                              const Matrix4& localPivot = editable != 0 ? editable->getLocalPivot() : g_matrix4_identity;
++
++                              Vector3 parent_translation;
++                              translation_for_pivoted_scale(
++                                      parent_translation,
++                                      m_scale,
++                                      m_world_pivot,
++                                      matrix4_multiplied_by_matrix4( instance.localToWorld(), localPivot ),
++                                      matrix4_multiplied_by_matrix4( transformNode->localToParent(), localPivot )
++                                      );
++
++                              transform->setTranslation( parent_translation );
++                      }
++              }
++      }
++}
++};
++
++void Scene_Scale_Selected( scene::Graph& graph, const Vector3& scaling, const Vector3& world_pivot ){
++      if ( GlobalSelectionSystem().countSelected() != 0 ) {
++              GlobalSelectionSystem().foreachSelected( scale_selected( scaling, world_pivot ) );
++      }
++}
++
++
++class translate_component_selected : public SelectionSystem::Visitor
++{
++const Vector3& m_translate;
++public:
++translate_component_selected( const Vector3& translate )
++      : m_translate( translate ){
++}
++void visit( scene::Instance& instance ) const {
++      Transformable* transform = Instance_getTransformable( instance );
++      if ( transform != 0 ) {
++              transform->setType( TRANSFORM_COMPONENT );
++              transform->setTranslation( m_translate );
++      }
++}
++};
++
++void Scene_Translate_Component_Selected( scene::Graph& graph, const Vector3& translation ){
++      if ( GlobalSelectionSystem().countSelected() != 0 ) {
++              GlobalSelectionSystem().foreachSelectedComponent( translate_component_selected( translation ) );
++      }
++}
++
++class rotate_component_selected : public SelectionSystem::Visitor
++{
++const Quaternion& m_rotate;
++const Vector3& m_world_pivot;
++public:
++rotate_component_selected( const Quaternion& rotation, const Vector3& world_pivot )
++      : m_rotate( rotation ), m_world_pivot( world_pivot ){
++}
++void visit( scene::Instance& instance ) const {
++      Transformable* transform = Instance_getTransformable( instance );
++      if ( transform != 0 ) {
++              Vector3 parent_translation;
++              translation_for_pivoted_rotation( parent_translation, m_rotate, m_world_pivot, instance.localToWorld(), Node_getTransformNode( instance.path().top() )->localToParent() );
++
++              transform->setType( TRANSFORM_COMPONENT );
++              transform->setRotation( m_rotate );
++              transform->setTranslation( parent_translation );
++      }
++}
++};
++
++void Scene_Rotate_Component_Selected( scene::Graph& graph, const Quaternion& rotation, const Vector3& world_pivot ){
++      if ( GlobalSelectionSystem().countSelectedComponents() != 0 ) {
++              GlobalSelectionSystem().foreachSelectedComponent( rotate_component_selected( rotation, world_pivot ) );
++      }
++}
++
++class scale_component_selected : public SelectionSystem::Visitor
++{
++const Vector3& m_scale;
++const Vector3& m_world_pivot;
++public:
++scale_component_selected( const Vector3& scaling, const Vector3& world_pivot )
++      : m_scale( scaling ), m_world_pivot( world_pivot ){
++}
++void visit( scene::Instance& instance ) const {
++      Transformable* transform = Instance_getTransformable( instance );
++      if ( transform != 0 ) {
++              Vector3 parent_translation;
++              translation_for_pivoted_scale( parent_translation, m_scale, m_world_pivot, instance.localToWorld(), Node_getTransformNode( instance.path().top() )->localToParent() );
++
++              transform->setType( TRANSFORM_COMPONENT );
++              transform->setScale( m_scale );
++              transform->setTranslation( parent_translation );
++      }
++}
++};
++
++void Scene_Scale_Component_Selected( scene::Graph& graph, const Vector3& scaling, const Vector3& world_pivot ){
++      if ( GlobalSelectionSystem().countSelectedComponents() != 0 ) {
++              GlobalSelectionSystem().foreachSelectedComponent( scale_component_selected( scaling, world_pivot ) );
++      }
++}
++
++
++class BooleanSelector : public Selector
++{
++bool m_selected;
++SelectionIntersection m_intersection;
++Selectable* m_selectable;
++public:
++BooleanSelector() : m_selected( false ){
++}
++
++void pushSelectable( Selectable& selectable ){
++      m_intersection = SelectionIntersection();
++      m_selectable = &selectable;
++}
++void popSelectable(){
++      if ( m_intersection.valid() ) {
++              m_selected = true;
++      }
++      m_intersection = SelectionIntersection();
++}
++void addIntersection( const SelectionIntersection& intersection ){
++      if ( m_selectable->isSelected() ) {
++              assign_if_closer( m_intersection, intersection );
++      }
++}
++
++bool isSelected(){
++      return m_selected;
++}
++};
++
++class BestSelector : public Selector
++{
++SelectionIntersection m_intersection;
++Selectable* m_selectable;
++SelectionIntersection m_bestIntersection;
++std::list<Selectable*> m_bestSelectable;
++public:
++BestSelector() : m_bestIntersection( SelectionIntersection() ), m_bestSelectable( 0 ){
++}
++
++void pushSelectable( Selectable& selectable ){
++      m_intersection = SelectionIntersection();
++      m_selectable = &selectable;
++}
++void popSelectable(){
++      if ( m_intersection.equalEpsilon( m_bestIntersection, 0.25f, 0.001f ) ) {
++              m_bestSelectable.push_back( m_selectable );
++              m_bestIntersection = m_intersection;
++      }
++      else if ( m_intersection < m_bestIntersection ) {
++              m_bestSelectable.clear();
++              m_bestSelectable.push_back( m_selectable );
++              m_bestIntersection = m_intersection;
++      }
++      m_intersection = SelectionIntersection();
++}
++void addIntersection( const SelectionIntersection& intersection ){
++      assign_if_closer( m_intersection, intersection );
++}
++
++std::list<Selectable*>& best(){
++      return m_bestSelectable;
++}
++};
++
++class DragManipulator : public Manipulator
++{
++TranslateFree m_freeResize;
++TranslateFree m_freeDrag;
++ResizeTranslatable m_resize;
++DragTranslatable m_drag;
++SelectableBool m_dragSelectable;
++public:
++
++bool m_selected;
++
++DragManipulator() : m_freeResize( m_resize ), m_freeDrag( m_drag ), m_selected( false ){
++}
++
++Manipulatable* GetManipulatable(){
++      return m_dragSelectable.isSelected() ? &m_freeDrag : &m_freeResize;
++}
++
++void testSelect( const View& view, const Matrix4& pivot2world ){
++      SelectionPool selector;
++
++      SelectionVolume test( view );
++
++      if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
++              BooleanSelector booleanSelector;
++
++              Scene_TestSelect_Primitive( booleanSelector, test, view );
++
++              if ( booleanSelector.isSelected() ) {
++                      selector.addSelectable( SelectionIntersection( 0, 0 ), &m_dragSelectable );
++                      m_selected = false;
++              }
++              else
++              {
++                      m_selected = Scene_forEachPlaneSelectable_selectPlanes( GlobalSceneGraph(), selector, test );
++              }
++      }
++      else
++      {
++              BestSelector bestSelector;
++              Scene_TestSelect_Component_Selected( bestSelector, test, view, GlobalSelectionSystem().ComponentMode() );
++              for ( std::list<Selectable*>::iterator i = bestSelector.best().begin(); i != bestSelector.best().end(); ++i )
++              {
++                      if ( !( *i )->isSelected() ) {
++                              GlobalSelectionSystem().setSelectedAllComponents( false );
++                      }
++                      m_selected = false;
++                      selector.addSelectable( SelectionIntersection( 0, 0 ), ( *i ) );
++                      m_dragSelectable.setSelected( true );
++              }
++      }
++
++      for ( SelectionPool::iterator i = selector.begin(); i != selector.end(); ++i )
++      {
++              ( *i ).second->setSelected( true );
++      }
++}
++
++void setSelected( bool select ){
++      m_selected = select;
++      m_dragSelectable.setSelected( select );
++}
++bool isSelected() const {
++      return m_selected || m_dragSelectable.isSelected();
++}
++};
++
++class ClipManipulator : public Manipulator
++{
++public:
++
++Manipulatable* GetManipulatable(){
++      ERROR_MESSAGE( "clipper is not manipulatable" );
++      return 0;
++}
++
++void setSelected( bool select ){
++}
++bool isSelected() const {
++      return false;
++}
++};
++
++class select_all : public scene::Graph::Walker
++{
++bool m_select;
++public:
++select_all( bool select )
++      : m_select( select ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( selectable != 0 ) {
++              selectable->setSelected( m_select );
++      }
++      return true;
++}
++};
++
++class select_all_component : public scene::Graph::Walker
++{
++bool m_select;
++SelectionSystem::EComponentMode m_mode;
++public:
++select_all_component( bool select, SelectionSystem::EComponentMode mode )
++      : m_select( select ), m_mode( mode ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable( instance );
++      if ( componentSelectionTestable ) {
++              componentSelectionTestable->setSelectedComponents( m_select, m_mode );
++      }
++      return true;
++}
++};
++
++void Scene_SelectAll_Component( bool select, SelectionSystem::EComponentMode componentMode ){
++      GlobalSceneGraph().traverse( select_all_component( select, componentMode ) );
++}
++
++
++// RadiantSelectionSystem
++class RadiantSelectionSystem :
++      public SelectionSystem,
++      public Translatable,
++      public Rotatable,
++      public Scalable,
++      public Renderable
++{
++mutable Matrix4 m_pivot2world;
++Matrix4 m_pivot2world_start;
++Matrix4 m_manip2pivot_start;
++Translation m_translation;
++Rotation m_rotation;
++Scale m_scale;
++public:
++static Shader* m_state;
++bool m_bPreferPointEntsIn2D;
++private:
++EManipulatorMode m_manipulator_mode;
++Manipulator* m_manipulator;
++
++// state
++bool m_undo_begun;
++EMode m_mode;
++EComponentMode m_componentmode;
++
++SelectionCounter m_count_primitive;
++SelectionCounter m_count_component;
++
++TranslateManipulator m_translate_manipulator;
++RotateManipulator m_rotate_manipulator;
++ScaleManipulator m_scale_manipulator;
++DragManipulator m_drag_manipulator;
++ClipManipulator m_clip_manipulator;
++
++typedef SelectionList<scene::Instance> selection_t;
++selection_t m_selection;
++selection_t m_component_selection;
++
++Signal1<const Selectable&> m_selectionChanged_callbacks;
++
++void ConstructPivot() const;
++void setCustomPivotOrigin( Vector3& point ) const;
++public:
++void getSelectionAABB( AABB& bounds ) const;
++private:
++mutable bool m_pivotChanged;
++bool m_pivot_moving;
++mutable bool m_pivotIsCustom;
++
++void Scene_TestSelect( Selector& selector, SelectionTest& test, const View& view, SelectionSystem::EMode mode, SelectionSystem::EComponentMode componentMode );
++
++bool nothingSelected() const {
++      return ( Mode() == eComponent && m_count_component.empty() )
++                 || ( Mode() == ePrimitive && m_count_primitive.empty() );
++}
++
++
++public:
++enum EModifier
++{
++      eManipulator,
++      eToggle,
++      eReplace,
++      eCycle,
++      eSelect,
++      eDeselect,
++};
++
++RadiantSelectionSystem() :
++      m_bPreferPointEntsIn2D( true ),
++      m_undo_begun( false ),
++      m_mode( ePrimitive ),
++      m_componentmode( eDefault ),
++      m_count_primitive( SelectionChangedCaller( *this ) ),
++      m_count_component( SelectionChangedCaller( *this ) ),
++      m_translate_manipulator( *this, 2, 64 ),
++      m_rotate_manipulator( *this, 8, 64 ),
++      m_scale_manipulator( *this, 0, 64 ),
++      m_pivotChanged( false ),
++      m_pivot_moving( false ),
++      m_pivotIsCustom( false ){
++      SetManipulatorMode( eTranslate );
++      pivotChanged();
++      addSelectionChangeCallback( PivotChangedSelectionCaller( *this ) );
++      AddGridChangeCallback( PivotChangedCaller( *this ) );
++}
++void pivotChanged() const {
++      m_pivotChanged = true;
++      SceneChangeNotify();
++}
++typedef ConstMemberCaller<RadiantSelectionSystem, void(), &RadiantSelectionSystem::pivotChanged> PivotChangedCaller;
++void pivotChangedSelection( const Selectable& selectable ){
++      pivotChanged();
++}
++typedef MemberCaller<RadiantSelectionSystem, void(const Selectable&), &RadiantSelectionSystem::pivotChangedSelection> PivotChangedSelectionCaller;
++
++void SetMode( EMode mode ){
++      if ( m_mode != mode ) {
++              m_mode = mode;
++              pivotChanged();
++      }
++}
++EMode Mode() const {
++      return m_mode;
++}
++void SetComponentMode( EComponentMode mode ){
++      m_componentmode = mode;
++}
++EComponentMode ComponentMode() const {
++      return m_componentmode;
++}
++void SetManipulatorMode( EManipulatorMode mode ){
++      m_pivotIsCustom = false;
++      m_manipulator_mode = mode;
++      switch ( m_manipulator_mode )
++      {
++      case eTranslate: m_manipulator = &m_translate_manipulator; break;
++      case eRotate: m_manipulator = &m_rotate_manipulator; break;
++      case eScale: m_manipulator = &m_scale_manipulator; break;
++      case eDrag: m_manipulator = &m_drag_manipulator; break;
++      case eClip: m_manipulator = &m_clip_manipulator; break;
++      }
++      pivotChanged();
++}
++EManipulatorMode ManipulatorMode() const {
++      return m_manipulator_mode;
++}
++
++SelectionChangeCallback getObserver( EMode mode ){
++      if ( mode == ePrimitive ) {
++              return makeCallback( m_count_primitive );
++      }
++      else
++      {
++              return makeCallback( m_count_component );
++      }
++}
++std::size_t countSelected() const {
++      return m_count_primitive.size();
++}
++std::size_t countSelectedComponents() const {
++      return m_count_component.size();
++}
++void onSelectedChanged( scene::Instance& instance, const Selectable& selectable ){
++      if ( selectable.isSelected() ) {
++              m_selection.append( instance );
++      }
++      else
++      {
++              m_selection.erase( instance );
++      }
++
++      ASSERT_MESSAGE( m_selection.size() == m_count_primitive.size(), "selection-tracking error" );
++}
++void onComponentSelection( scene::Instance& instance, const Selectable& selectable ){
++      if ( selectable.isSelected() ) {
++              m_component_selection.append( instance );
++      }
++      else
++      {
++              m_component_selection.erase( instance );
++      }
++
++      ASSERT_MESSAGE( m_component_selection.size() == m_count_component.size(), "selection-tracking error" );
++}
++scene::Instance& ultimateSelected() const {
++      ASSERT_MESSAGE( m_selection.size() > 0, "no instance selected" );
++      return m_selection.back();
++}
++scene::Instance& penultimateSelected() const {
++      ASSERT_MESSAGE( m_selection.size() > 1, "only one instance selected" );
++      return *( *( --( --m_selection.end() ) ) );
++}
++void setSelectedAll( bool selected ){
++      GlobalSceneGraph().traverse( select_all( selected ) );
++
++      m_manipulator->setSelected( selected );
++}
++void setSelectedAllComponents( bool selected ){
++      Scene_SelectAll_Component( selected, SelectionSystem::eVertex );
++      Scene_SelectAll_Component( selected, SelectionSystem::eEdge );
++      Scene_SelectAll_Component( selected, SelectionSystem::eFace );
++
++      m_manipulator->setSelected( selected );
++}
++
++void foreachSelected( const Visitor& visitor ) const {
++      selection_t::const_iterator i = m_selection.begin();
++      while ( i != m_selection.end() )
++      {
++              visitor.visit( *( *( i++ ) ) );
++      }
++}
++void foreachSelectedComponent( const Visitor& visitor ) const {
++      selection_t::const_iterator i = m_component_selection.begin();
++      while ( i != m_component_selection.end() )
++      {
++              visitor.visit( *( *( i++ ) ) );
++      }
++}
++
++void addSelectionChangeCallback( const SelectionChangeHandler& handler ){
++      m_selectionChanged_callbacks.connectLast( handler );
++}
++void selectionChanged( const Selectable& selectable ){
++      m_selectionChanged_callbacks( selectable );
++}
++typedef MemberCaller<RadiantSelectionSystem, void(const Selectable&), &RadiantSelectionSystem::selectionChanged> SelectionChangedCaller;
++
++
++void startMove(){
++      m_pivot2world_start = GetPivot2World();
++}
++
++bool SelectManipulator( const View& view, const float device_point[2], const float device_epsilon[2] ){
++      if ( !nothingSelected() || ( ManipulatorMode() == eDrag && Mode() == eComponent ) ) {
++#if defined ( DEBUG_SELECTION )
++              g_render_clipped.destroy();
++#endif
++
++              m_manipulator->setSelected( false );
++
++              if ( !nothingSelected() || ( ManipulatorMode() == eDrag && Mode() == eComponent ) ) {
++                      View scissored( view );
++                      ConstructSelectionTest( scissored, SelectionBoxForPoint( device_point, device_epsilon ) );
++                      m_manipulator->testSelect( scissored, GetPivot2World() );
++              }
++
++              startMove();
++
++              m_pivot_moving = m_manipulator->isSelected();
++
++              if ( m_pivot_moving ) {
++                      Pivot2World pivot;
++                      pivot.update( GetPivot2World(), view.GetModelview(), view.GetProjection(), view.GetViewport() );
++
++                      m_manip2pivot_start = matrix4_multiplied_by_matrix4( matrix4_full_inverse( m_pivot2world_start ), pivot.m_worldSpace );
++
++                      Matrix4 device2manip;
++                      ConstructDevice2Manip( device2manip, m_pivot2world_start, view.GetModelview(), view.GetProjection(), view.GetViewport() );
++                      m_manipulator->GetManipulatable()->Construct( device2manip, device_point[0], device_point[1] );
++
++                      m_undo_begun = false;
++              }
++
++              SceneChangeNotify();
++      }
++
++      return m_pivot_moving;
++}
++
++void deselectAll(){
++      if ( Mode() == eComponent ) {
++              setSelectedAllComponents( false );
++      }
++      else
++      {
++              setSelectedAll( false );
++      }
++}
++
++void deselectComponentsOrAll( bool components ){
++      if ( components ) {
++              setSelectedAllComponents( false );
++      }
++      else
++      {
++              deselectAll();
++      }
++}
++
++void SelectPoint( const View& view, const float device_point[2], const float device_epsilon[2], RadiantSelectionSystem::EModifier modifier, bool face ){
++      //globalOutputStream() << device_point[0] << "   " << device_point[1] << "\n";
++      ASSERT_MESSAGE( fabs( device_point[0] ) <= 1.0f && fabs( device_point[1] ) <= 1.0f, "point-selection error" );
++
++      if ( modifier == eReplace ) {
++              deselectComponentsOrAll( face );
++      }
++/*
++//nothingSelected() doesn't consider faces, selected in non-component mode, m
++      if ( modifier == eCycle && nothingSelected() ){
++              modifier = eReplace;
++      }
++*/
++  #if defined ( DEBUG_SELECTION )
++      g_render_clipped.destroy();
++  #endif
++
++      {
++              View scissored( view );
++              ConstructSelectionTest( scissored, SelectionBoxForPoint( device_point, device_epsilon ) );
++
++              SelectionVolume volume( scissored );
++              SelectionPool selector;
++              SelectionPool selector_point_ents;
++              const bool prefer_point_ents = m_bPreferPointEntsIn2D && Mode() == ePrimitive && !view.fill() && !face
++                      && ( modifier == RadiantSelectionSystem::eReplace || modifier == RadiantSelectionSystem::eSelect || modifier == RadiantSelectionSystem::eDeselect );
++
++              if( prefer_point_ents ){
++                      Scene_TestSelect( selector_point_ents, volume, scissored, eEntity, ComponentMode() );
++              }
++              if( prefer_point_ents && !selector_point_ents.failed() ){
++                      switch ( modifier )
++                      {
++                      // if cycle mode not enabled, enable it
++                      case RadiantSelectionSystem::eReplace:
++                      {
++                              // select closest
++                              ( *selector_point_ents.begin() ).second->setSelected( true );
++                      }
++                      break;
++                      case RadiantSelectionSystem::eSelect:
++                      {
++                              SelectionPool::iterator best = selector_point_ents.begin();
++                              if( !( *best ).second->isSelected() ){
++                                      ( *best ).second->setSelected( true );
++                              }
++                              SelectionPool::iterator i = best;
++                              ++i;
++                              while ( i != selector_point_ents.end() )
++                              {
++                                      if( ( *i ).first.equalEpsilon( ( *best ).first, 0.25f, 0.000001f ) ){
++                                              if( !( *i ).second->isSelected() ){
++                                                      ( *i ).second->setSelected( true );
++                                              }
++                                      }
++                                      else{
++                                              break;
++                                      }
++                                      ++i;
++                              }
++                      }
++                      break;
++                      case RadiantSelectionSystem::eDeselect:
++                      {
++                              SelectionPool::iterator best = selector_point_ents.begin();
++                              if( ( *best ).second->isSelected() ){
++                                      ( *best ).second->setSelected( false );
++                              }
++                              SelectionPool::iterator i = best;
++                              ++i;
++                              while ( i != selector_point_ents.end() )
++                              {
++                                      if( ( *i ).first.equalEpsilon( ( *best ).first, 0.25f, 0.000001f ) ){
++                                              if( ( *i ).second->isSelected() ){
++                                                      ( *i ).second->setSelected( false );
++                                              }
++                                      }
++                                      else{
++                                              break;
++                                      }
++                                      ++i;
++                              }
++                      }
++                      break;
++                      default:
++                              break;
++                      }
++              }
++              else{
++                      if ( face ){
++                              Scene_TestSelect_Component( selector, volume, scissored, eFace );
++                      }
++                      else{
++                              Scene_TestSelect( selector, volume, scissored, Mode(), ComponentMode() );
++                      }
++
++                      if ( !selector.failed() ) {
++                              switch ( modifier )
++                              {
++                              case RadiantSelectionSystem::eToggle:
++                              {
++                                      SelectableSortedSet::iterator best = selector.begin();
++                                      // toggle selection of the object with least depth
++                                      if ( ( *best ).second->isSelected() ) {
++                                              ( *best ).second->setSelected( false );
++                                      }
++                                      else{
++                                              ( *best ).second->setSelected( true );
++                                      }
++                              }
++                              break;
++                              // if cycle mode not enabled, enable it
++                              case RadiantSelectionSystem::eReplace:
++                              {
++                                      // select closest
++                                      ( *selector.begin() ).second->setSelected( true );
++                              }
++                              break;
++                              // select the next object in the list from the one already selected
++                              case RadiantSelectionSystem::eCycle:
++                              {
++                                      bool CycleSelectionOccured = false;
++                                      SelectionPool::iterator i = selector.begin();
++                                      while ( i != selector.end() )
++                                      {
++                                              if ( ( *i ).second->isSelected() ) {
++                                                      deselectComponentsOrAll( face );
++                                                      ++i;
++                                                      if ( i != selector.end() ) {
++                                                              i->second->setSelected( true );
++                                                      }
++                                                      else
++                                                      {
++                                                              selector.begin()->second->setSelected( true );
++                                                      }
++                                                      CycleSelectionOccured = true;
++                                                      break;
++                                              }
++                                              ++i;
++                                      }
++                                      if( !CycleSelectionOccured ){
++                                              deselectComponentsOrAll( face );
++                                              ( *selector.begin() ).second->setSelected( true );
++                                      }
++                              }
++                              break;
++                              case RadiantSelectionSystem::eSelect:
++                              {
++                                      SelectionPool::iterator best = selector.begin();
++                                      if( !( *best ).second->isSelected() ){
++                                              ( *best ).second->setSelected( true );
++                                      }
++                                      SelectionPool::iterator i = best;
++                                      ++i;
++                                      while ( i != selector.end() )
++                                      {
++                                              if( ( *i ).first.equalEpsilon( ( *best ).first, 0.25f, 0.000001f ) ){
++                                                      if( !( *i ).second->isSelected() ){
++                                                              ( *i ).second->setSelected( true );
++                                                      }
++                                              }
++                                              else{
++                                                      break;
++                                              }
++                                              ++i;
++                                      }
++                              }
++                              break;
++                              case RadiantSelectionSystem::eDeselect:
++                              {
++                                      SelectionPool::iterator best = selector.begin();
++                                      if( ( *best ).second->isSelected() ){
++                                              ( *best ).second->setSelected( false );
++                                      }
++                                      SelectionPool::iterator i = best;
++                                      ++i;
++                                      while ( i != selector.end() )
++                                      {
++                                              if( ( *i ).first.equalEpsilon( ( *best ).first, 0.25f, 0.000001f ) ){
++                                                      if( ( *i ).second->isSelected() ){
++                                                              ( *i ).second->setSelected( false );
++                                                      }
++                                              }
++                                              else{
++                                                      break;
++                                              }
++                                              ++i;
++                                      }
++                              }
++                              break;
++                              default:
++                                      break;
++                              }
++                      }
++                      else if( modifier == eCycle ){
++                              deselectComponentsOrAll( face );
++                      }
++              }
++      }
++}
++
++bool SelectPoint_InitPaint( const View& view, const float device_point[2], const float device_epsilon[2], bool face ){
++      ASSERT_MESSAGE( fabs( device_point[0] ) <= 1.0f && fabs( device_point[1] ) <= 1.0f, "point-selection error" );
++  #if defined ( DEBUG_SELECTION )
++      g_render_clipped.destroy();
++  #endif
++
++      {
++              View scissored( view );
++              ConstructSelectionTest( scissored, SelectionBoxForPoint( device_point, device_epsilon ) );
++
++              SelectionVolume volume( scissored );
++              SelectionPool selector;
++              SelectionPool selector_point_ents;
++              const bool prefer_point_ents = m_bPreferPointEntsIn2D && Mode() == ePrimitive && !view.fill() && !face;
++
++              if( prefer_point_ents ){
++                      Scene_TestSelect( selector_point_ents, volume, scissored, eEntity, ComponentMode() );
++              }
++              if( prefer_point_ents && !selector_point_ents.failed() ){
++                      SelectableSortedSet::iterator best = selector_point_ents.begin();
++                      const bool wasSelected = ( *best ).second->isSelected();
++                      ( *best ).second->setSelected( !wasSelected );
++                      SelectableSortedSet::iterator i = best;
++                      ++i;
++                      while ( i != selector_point_ents.end() )
++                      {
++                              if( ( *i ).first.equalEpsilon( ( *best ).first, 0.25f, 0.000001f ) ){
++                                      ( *i ).second->setSelected( !wasSelected );
++                              }
++                              else{
++                                      break;
++                              }
++                              ++i;
++                      }
++                      return !wasSelected;
++              }
++              else{//do primitives, if ents failed
++                      if ( face ){
++                              Scene_TestSelect_Component( selector, volume, scissored, eFace );
++                      }
++                      else{
++                              Scene_TestSelect( selector, volume, scissored, Mode(), ComponentMode() );
++                      }
++                      if ( !selector.failed() ){
++                              SelectableSortedSet::iterator best = selector.begin();
++                              const bool wasSelected = ( *best ).second->isSelected();
++                              ( *best ).second->setSelected( !wasSelected );
++                              SelectableSortedSet::iterator i = best;
++                              ++i;
++                              while ( i != selector.end() )
++                              {
++                                      if( ( *i ).first.equalEpsilon( ( *best ).first, 0.25f, 0.000001f ) ){
++                                              ( *i ).second->setSelected( !wasSelected );
++                                      }
++                                      else{
++                                              break;
++                                      }
++                                      ++i;
++                              }
++                              return !wasSelected;
++                      }
++                      else{
++                              return true;
++                      }
++              }
++      }
++}
++
++void SelectArea( const View& view, const float device_point[2], const float device_delta[2], RadiantSelectionSystem::EModifier modifier, bool face ){
++      if ( modifier == eReplace ) {
++              deselectComponentsOrAll( face );
++      }
++
++  #if defined ( DEBUG_SELECTION )
++      g_render_clipped.destroy();
++  #endif
++
++      {
++              View scissored( view );
++              ConstructSelectionTest( scissored, SelectionBoxForArea( device_point, device_delta ) );
++
++              SelectionVolume volume( scissored );
++              SelectionPool pool;
++              if ( face ) {
++                      Scene_TestSelect_Component( pool, volume, scissored, eFace );
++              }
++              else
++              {
++                      Scene_TestSelect( pool, volume, scissored, Mode(), ComponentMode() );
++              }
++
++              for ( SelectionPool::iterator i = pool.begin(); i != pool.end(); ++i )
++              {
++                      ( *i ).second->setSelected( !( modifier == RadiantSelectionSystem::eToggle && ( *i ).second->isSelected() ) );
++              }
++      }
++}
++
++
++void translate( const Vector3& translation ){
++      if ( !nothingSelected() ) {
++              //ASSERT_MESSAGE(!m_pivotChanged, "pivot is invalid");
++
++              m_translation = translation;
++
++              m_pivot2world = m_pivot2world_start;
++              matrix4_translate_by_vec3( m_pivot2world, translation );
++
++              if ( Mode() == eComponent ) {
++                      Scene_Translate_Component_Selected( GlobalSceneGraph(), m_translation );
++              }
++              else
++              {
++                      Scene_Translate_Selected( GlobalSceneGraph(), m_translation );
++              }
++
++              SceneChangeNotify();
++      }
++}
++void outputTranslation( TextOutputStream& ostream ){
++      ostream << " -xyz " << m_translation.x() << " " << m_translation.y() << " " << m_translation.z();
++}
++void rotate( const Quaternion& rotation ){
++      if ( !nothingSelected() ) {
++              //ASSERT_MESSAGE(!m_pivotChanged, "pivot is invalid");
++
++              m_rotation = rotation;
++
++              if ( Mode() == eComponent ) {
++                      Scene_Rotate_Component_Selected( GlobalSceneGraph(), m_rotation, vector4_to_vector3( m_pivot2world.t() ) );
++
++                      matrix4_assign_rotation_for_pivot( m_pivot2world, m_component_selection.back() );
++              }
++              else
++              {
++                      Scene_Rotate_Selected( GlobalSceneGraph(), m_rotation, vector4_to_vector3( m_pivot2world.t() ) );
++
++                      matrix4_assign_rotation_for_pivot( m_pivot2world, m_selection.back() );
++              }
++
++              SceneChangeNotify();
++      }
++}
++void outputRotation( TextOutputStream& ostream ){
++      ostream << " -eulerXYZ " << m_rotation.x() << " " << m_rotation.y() << " " << m_rotation.z();
++}
++void scale( const Vector3& scaling ){
++      if ( !nothingSelected() ) {
++              m_scale = scaling;
++
++              if ( Mode() == eComponent ) {
++                      Scene_Scale_Component_Selected( GlobalSceneGraph(), m_scale, vector4_to_vector3( m_pivot2world.t() ) );
++              }
++              else
++              {
++                      Scene_Scale_Selected( GlobalSceneGraph(), m_scale, vector4_to_vector3( m_pivot2world.t() ) );
++              }
++
++              SceneChangeNotify();
++      }
++}
++void outputScale( TextOutputStream& ostream ){
++      ostream << " -scale " << m_scale.x() << " " << m_scale.y() << " " << m_scale.z();
++}
++
++void rotateSelected( const Quaternion& rotation, bool snapOrigin ){
++      if( snapOrigin && !m_pivotIsCustom ){
++              m_pivot2world.tx() = float_snapped( m_pivot2world.tx(), GetSnapGridSize() );
++              m_pivot2world.ty() = float_snapped( m_pivot2world.ty(), GetSnapGridSize() );
++              m_pivot2world.tz() = float_snapped( m_pivot2world.tz(), GetSnapGridSize() );
++      }
++      startMove();
++      rotate( rotation );
++      freezeTransforms();
++}
++void translateSelected( const Vector3& translation ){
++      startMove();
++      translate( translation );
++      freezeTransforms();
++}
++void scaleSelected( const Vector3& scaling ){
++      startMove();
++      scale( scaling );
++      freezeTransforms();
++}
++
++void MoveSelected( const View& view, const float device_point[2], bool snap ){
++      if ( m_manipulator->isSelected() ) {
++              if ( !m_undo_begun ) {
++                      m_undo_begun = true;
++                      GlobalUndoSystem().start();
++              }
++
++              Matrix4 device2manip;
++              ConstructDevice2Manip( device2manip, m_pivot2world_start, view.GetModelview(), view.GetProjection(), view.GetViewport() );
++              m_manipulator->GetManipulatable()->Transform( m_manip2pivot_start, device2manip, device_point[0], device_point[1], snap );
++      }
++}
++
++/// \todo Support view-dependent nudge.
++void NudgeManipulator( const Vector3& nudge, const Vector3& view ){
++      if ( ManipulatorMode() == eTranslate || ManipulatorMode() == eDrag ) {
++              translateSelected( nudge );
++      }
++}
++
++void endMove();
++void freezeTransforms();
++
++void renderSolid( Renderer& renderer, const VolumeTest& volume ) const;
++void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const {
++      renderSolid( renderer, volume );
++}
++
++const Matrix4& GetPivot2World() const {
++      ConstructPivot();
++      return m_pivot2world;
++}
++
++static void constructStatic(){
++      m_state = GlobalShaderCache().capture( "$POINT" );
++  #if defined( DEBUG_SELECTION )
++      g_state_clipped = GlobalShaderCache().capture( "$DEBUG_CLIPPED" );
++  #endif
++      TranslateManipulator::m_state_wire = GlobalShaderCache().capture( "$WIRE_OVERLAY" );
++      TranslateManipulator::m_state_fill = GlobalShaderCache().capture( "$FLATSHADE_OVERLAY" );
++      RotateManipulator::m_state_outer = GlobalShaderCache().capture( "$WIRE_OVERLAY" );
++}
++
++static void destroyStatic(){
++  #if defined( DEBUG_SELECTION )
++      GlobalShaderCache().release( "$DEBUG_CLIPPED" );
++  #endif
++      GlobalShaderCache().release( "$WIRE_OVERLAY" );
++      GlobalShaderCache().release( "$FLATSHADE_OVERLAY" );
++      GlobalShaderCache().release( "$WIRE_OVERLAY" );
++      GlobalShaderCache().release( "$POINT" );
++}
++};
++
++Shader* RadiantSelectionSystem::m_state = 0;
++
++
++namespace
++{
++RadiantSelectionSystem* g_RadiantSelectionSystem;
++
++inline RadiantSelectionSystem& getSelectionSystem(){
++      return *g_RadiantSelectionSystem;
++}
++}
++
++#include "map.h"
++
++class testselect_entity_visible : public scene::Graph::Walker
++{
++Selector& m_selector;
++SelectionTest& m_test;
++public:
++testselect_entity_visible( Selector& selector, SelectionTest& test )
++      : m_selector( selector ), m_test( test ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      if( path.top().get_pointer() == Map_GetWorldspawn( g_map ) ||
++              node_is_group( path.top().get() ) ){
++              return false;
++      }
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( selectable != 0
++               && Node_isEntity( path.top() ) ) {
++              m_selector.pushSelectable( *selectable );
++      }
++
++      SelectionTestable* selectionTestable = Instance_getSelectionTestable( instance );
++      if ( selectionTestable ) {
++              selectionTestable->testSelect( m_selector, m_test );
++      }
++
++      return true;
++}
++void post( const scene::Path& path, scene::Instance& instance ) const {
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( selectable != 0
++               && Node_isEntity( path.top() ) ) {
++              m_selector.popSelectable();
++      }
++}
++};
++
++class testselect_primitive_visible : public scene::Graph::Walker
++{
++Selector& m_selector;
++SelectionTest& m_test;
++public:
++testselect_primitive_visible( Selector& selector, SelectionTest& test )
++      : m_selector( selector ), m_test( test ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( selectable != 0 ) {
++              m_selector.pushSelectable( *selectable );
++      }
++
++      SelectionTestable* selectionTestable = Instance_getSelectionTestable( instance );
++      if ( selectionTestable ) {
++              selectionTestable->testSelect( m_selector, m_test );
++      }
++
++      return true;
++}
++void post( const scene::Path& path, scene::Instance& instance ) const {
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( selectable != 0 ) {
++              m_selector.popSelectable();
++      }
++}
++};
++
++class testselect_component_visible : public scene::Graph::Walker
++{
++Selector& m_selector;
++SelectionTest& m_test;
++SelectionSystem::EComponentMode m_mode;
++public:
++testselect_component_visible( Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode )
++      : m_selector( selector ), m_test( test ), m_mode( mode ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable( instance );
++      if ( componentSelectionTestable ) {
++              componentSelectionTestable->testSelectComponents( m_selector, m_test, m_mode );
++      }
++
++      return true;
++}
++};
++
++
++class testselect_component_visible_selected : public scene::Graph::Walker
++{
++Selector& m_selector;
++SelectionTest& m_test;
++SelectionSystem::EComponentMode m_mode;
++public:
++testselect_component_visible_selected( Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode )
++      : m_selector( selector ), m_test( test ), m_mode( mode ){
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( selectable != 0 && selectable->isSelected() ) {
++              ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable( instance );
++              if ( componentSelectionTestable ) {
++                      componentSelectionTestable->testSelectComponents( m_selector, m_test, m_mode );
++              }
++      }
++
++      return true;
++}
++};
++
++void Scene_TestSelect_Primitive( Selector& selector, SelectionTest& test, const VolumeTest& volume ){
++      Scene_forEachVisible( GlobalSceneGraph(), volume, testselect_primitive_visible( selector, test ) );
++}
++
++void Scene_TestSelect_Component_Selected( Selector& selector, SelectionTest& test, const VolumeTest& volume, SelectionSystem::EComponentMode componentMode ){
++      Scene_forEachVisible( GlobalSceneGraph(), volume, testselect_component_visible_selected( selector, test, componentMode ) );
++}
++
++void Scene_TestSelect_Component( Selector& selector, SelectionTest& test, const VolumeTest& volume, SelectionSystem::EComponentMode componentMode ){
++      Scene_forEachVisible( GlobalSceneGraph(), volume, testselect_component_visible( selector, test, componentMode ) );
++}
++
++void RadiantSelectionSystem::Scene_TestSelect( Selector& selector, SelectionTest& test, const View& view, SelectionSystem::EMode mode, SelectionSystem::EComponentMode componentMode ){
++      switch ( mode )
++      {
++      case eEntity:
++      {
++              Scene_forEachVisible( GlobalSceneGraph(), view, testselect_entity_visible( selector, test ) );
++      }
++      break;
++      case ePrimitive:
++              Scene_TestSelect_Primitive( selector, test, view );
++              break;
++      case eComponent:
++              Scene_TestSelect_Component_Selected( selector, test, view, componentMode );
++              break;
++      }
++}
++
++class FreezeTransforms : public scene::Graph::Walker
++{
++public:
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      TransformNode* transformNode = Node_getTransformNode( path.top() );
++      if ( transformNode != 0 ) {
++              Transformable* transform = Instance_getTransformable( instance );
++              if ( transform != 0 ) {
++                      transform->freezeTransform();
++              }
++      }
++      return true;
++}
++};
++
++void RadiantSelectionSystem::freezeTransforms(){
++      GlobalSceneGraph().traverse( FreezeTransforms() );
++}
++
++
++void RadiantSelectionSystem::endMove(){
++      freezeTransforms();
++
++      if ( Mode() == ePrimitive ) {
++              if ( ManipulatorMode() == eDrag ) {
++                      Scene_SelectAll_Component( false, SelectionSystem::eFace );
++              }
++      }
++
++      m_pivot_moving = false;
++      pivotChanged();
++
++      SceneChangeNotify();
++
++      if ( m_undo_begun ) {
++              StringOutputStream command;
++
++              if ( ManipulatorMode() == eTranslate ) {
++                      command << "translateTool";
++                      outputTranslation( command );
++              }
++              else if ( ManipulatorMode() == eRotate ) {
++                      command << "rotateTool";
++                      outputRotation( command );
++              }
++              else if ( ManipulatorMode() == eScale ) {
++                      command << "scaleTool";
++                      outputScale( command );
++              }
++              else if ( ManipulatorMode() == eDrag ) {
++                      command << "dragTool";
++              }
++
++              GlobalUndoSystem().finish( command.c_str() );
++      }
++
++}
++
++inline AABB Instance_getPivotBounds( scene::Instance& instance ){
++      Entity* entity = Node_getEntity( instance.path().top() );
++      if ( entity != 0
++               && ( entity->getEntityClass().fixedsize
++                        || !node_is_group( instance.path().top() ) ) ) {
++              Editable* editable = Node_getEditable( instance.path().top() );
++              if ( editable != 0 ) {
++                      return AABB( vector4_to_vector3( matrix4_multiplied_by_matrix4( instance.localToWorld(), editable->getLocalPivot() ).t() ), Vector3( 0, 0, 0 ) );
++              }
++              else
++              {
++                      return AABB( vector4_to_vector3( instance.localToWorld().t() ), Vector3( 0, 0, 0 ) );
++              }
++      }
++
++      return instance.worldAABB();
++}
++
++class bounds_selected : public scene::Graph::Walker
++{
++AABB& m_bounds;
++public:
++bounds_selected( AABB& bounds )
++      : m_bounds( bounds ){
++      m_bounds = AABB();
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( selectable != 0
++               && selectable->isSelected() ) {
++              aabb_extend_by_aabb_safe( m_bounds, Instance_getPivotBounds( instance ) );
++      }
++      return true;
++}
++};
++
++class bounds_selected_component : public scene::Graph::Walker
++{
++AABB& m_bounds;
++public:
++bounds_selected_component( AABB& bounds )
++      : m_bounds( bounds ){
++      m_bounds = AABB();
++}
++bool pre( const scene::Path& path, scene::Instance& instance ) const {
++      Selectable* selectable = Instance_getSelectable( instance );
++      if ( selectable != 0
++               && selectable->isSelected() ) {
++              ComponentEditable* componentEditable = Instance_getComponentEditable( instance );
++              if ( componentEditable ) {
++                      aabb_extend_by_aabb_safe( m_bounds, aabb_for_oriented_aabb_safe( componentEditable->getSelectedComponentsBounds(), instance.localToWorld() ) );
++              }
++      }
++      return true;
++}
++};
++
++void Scene_BoundsSelected( scene::Graph& graph, AABB& bounds ){
++      graph.traverse( bounds_selected( bounds ) );
++}
++
++void Scene_BoundsSelectedComponent( scene::Graph& graph, AABB& bounds ){
++      graph.traverse( bounds_selected_component( bounds ) );
++}
++
++#if 0
++inline void pivot_for_node( Matrix4& pivot, scene::Node& node, scene::Instance& instance ){
++      ComponentEditable* componentEditable = Instance_getComponentEditable( instance );
++      if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent
++               && componentEditable != 0 ) {
++              pivot = matrix4_translation_for_vec3( componentEditable->getSelectedComponentsBounds().origin );
++      }
++      else
++      {
++              Bounded* bounded = Instance_getBounded( instance );
++              if ( bounded != 0 ) {
++                      pivot = matrix4_translation_for_vec3( bounded->localAABB().origin );
++              }
++              else
++              {
++                      pivot = g_matrix4_identity;
++              }
++      }
++}
++#endif
++
++void RadiantSelectionSystem::ConstructPivot() const {
++      if ( !m_pivotChanged || m_pivot_moving || m_pivotIsCustom ) {
++              return;
++      }
++      m_pivotChanged = false;
++
++      Vector3 m_object_pivot;
++
++      if ( !nothingSelected() ) {
++              {
++                      AABB bounds;
++                      if ( Mode() == eComponent ) {
++                              Scene_BoundsSelectedComponent( GlobalSceneGraph(), bounds );
++                      }
++                      else
++                      {
++                              Scene_BoundsSelected( GlobalSceneGraph(), bounds );
++                      }
++                      m_object_pivot = bounds.origin;
++              }
++
++              //vector3_snap( m_object_pivot, GetSnapGridSize() );
++              //globalOutputStream() << m_object_pivot << "\n";
++              m_pivot2world = matrix4_translation_for_vec3( m_object_pivot );
++
++              switch ( m_manipulator_mode )
++              {
++              case eTranslate:
++                      break;
++              case eRotate:
++                      if ( Mode() == eComponent ) {
++                              matrix4_assign_rotation_for_pivot( m_pivot2world, m_component_selection.back() );
++                      }
++                      else
++                      {
++                              matrix4_assign_rotation_for_pivot( m_pivot2world, m_selection.back() );
++                      }
++                      break;
++              case eScale:
++                      if ( Mode() == eComponent ) {
++                              matrix4_assign_rotation_for_pivot( m_pivot2world, m_component_selection.back() );
++                      }
++                      else
++                      {
++                              matrix4_assign_rotation_for_pivot( m_pivot2world, m_selection.back() );
++                      }
++                      break;
++              default:
++                      break;
++              }
++      }
++}
++
++void RadiantSelectionSystem::setCustomPivotOrigin( Vector3& point ) const {
++      if ( !nothingSelected() && ( m_manipulator_mode == eTranslate || m_manipulator_mode == eRotate || m_manipulator_mode == eScale ) ) {
++              AABB bounds;
++              if ( Mode() == eComponent ) {
++                      Scene_BoundsSelectedComponent( GlobalSceneGraph(), bounds );
++              }
++              else
++              {
++                      Scene_BoundsSelected( GlobalSceneGraph(), bounds );
++              }
++              //globalOutputStream() << point << "\n";
++              for( std::size_t i = 0; i < 3; i++ ){
++                      if( point[i] < 900000.0f ){
++                              float bestsnapDist = fabs( bounds.origin[i] - point[i] );
++                              float bestsnapTo = bounds.origin[i];
++                              float othersnapDist = fabs( bounds.origin[i] + bounds.extents[i] - point[i] );
++                              if( othersnapDist < bestsnapDist ){
++                                      bestsnapDist = othersnapDist;
++                                      bestsnapTo = bounds.origin[i] + bounds.extents[i];
++                              }
++                              othersnapDist = fabs( bounds.origin[i] - bounds.extents[i] - point[i] );
++                              if( othersnapDist < bestsnapDist ){
++                                      bestsnapDist = othersnapDist;
++                                      bestsnapTo = bounds.origin[i] - bounds.extents[i];
++                              }
++                              othersnapDist = fabs( float_snapped( point[i], GetSnapGridSize() ) - point[i] );
++                              if( othersnapDist < bestsnapDist ){
++                                      bestsnapDist = othersnapDist;
++                                      bestsnapTo = float_snapped( point[i], GetSnapGridSize() );
++                              }
++                              point[i] = bestsnapTo;
++
++                              m_pivot2world[i + 12] = point[i]; //m_pivot2world.tx() .ty() .tz()
++                      }
++              }
++
++              switch ( m_manipulator_mode )
++              {
++              case eTranslate:
++                      break;
++              case eRotate:
++                      if ( Mode() == eComponent ) {
++                              matrix4_assign_rotation_for_pivot( m_pivot2world, m_component_selection.back() );
++                      }
++                      else
++                      {
++                              matrix4_assign_rotation_for_pivot( m_pivot2world, m_selection.back() );
++                      }
++                      break;
++              case eScale:
++                      if ( Mode() == eComponent ) {
++                              matrix4_assign_rotation_for_pivot( m_pivot2world, m_component_selection.back() );
++                      }
++                      else
++                      {
++                              matrix4_assign_rotation_for_pivot( m_pivot2world, m_selection.back() );
++                      }
++                      break;
++              default:
++                      break;
++              }
++
++              m_pivotIsCustom = true;
++      }
++}
++
++void RadiantSelectionSystem::getSelectionAABB( AABB& bounds ) const {
++      if ( !nothingSelected() ) {
++              if ( Mode() == eComponent ) {
++                      Scene_BoundsSelectedComponent( GlobalSceneGraph(), bounds );
++              }
++              else
++              {
++                      Scene_BoundsSelected( GlobalSceneGraph(), bounds );
++              }
++      }
++}
++
++void GetSelectionAABB( AABB& bounds ){
++      getSelectionSystem().getSelectionAABB( bounds );
++}
++
++const Matrix4& SelectionSystem_GetPivot2World(){
++      return getSelectionSystem().GetPivot2World();
++}
++
++void RadiantSelectionSystem::renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
++      //if(view->TestPoint(m_object_pivot))
++      if ( !nothingSelected() ) {
++              renderer.Highlight( Renderer::ePrimitive, false );
++              renderer.Highlight( Renderer::eFace, false );
++
++              renderer.SetState( m_state, Renderer::eWireframeOnly );
++              renderer.SetState( m_state, Renderer::eFullMaterials );
++
++              m_manipulator->render( renderer, volume, GetPivot2World() );
++      }
++
++#if defined( DEBUG_SELECTION )
++      renderer.SetState( g_state_clipped, Renderer::eWireframeOnly );
++      renderer.SetState( g_state_clipped, Renderer::eFullMaterials );
++      renderer.addRenderable( g_render_clipped, g_render_clipped.m_world );
++#endif
++}
++
++#include "preferencesystem.h"
++#include "preferences.h"
++
++void SelectionSystem_constructPreferences( PreferencesPage& page ){
++      page.appendCheckBox( "", "Prefer point entities in 2D", getSelectionSystem().m_bPreferPointEntsIn2D );
++}
++void SelectionSystem_constructPage( PreferenceGroup& group ){
++      PreferencesPage page( group.createPage( "Selection", "Selection System Settings" ) );
++      SelectionSystem_constructPreferences( page );
++}
++void SelectionSystem_registerPreferencesPage(){
++      PreferencesDialog_addSettingsPage( FreeCaller<void(PreferenceGroup&), SelectionSystem_constructPage>() );
++}
++
++
++
++void SelectionSystem_OnBoundsChanged(){
++      getSelectionSystem().pivotChanged();
++}
++
++SignalHandlerId SelectionSystem_boundsChanged;
++
++void SelectionSystem_Construct(){
++      RadiantSelectionSystem::constructStatic();
++
++      g_RadiantSelectionSystem = new RadiantSelectionSystem;
++
++      SelectionSystem_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( FreeCaller<void(), SelectionSystem_OnBoundsChanged>() );
++
++      GlobalShaderCache().attachRenderable( getSelectionSystem() );
++
++      GlobalPreferenceSystem().registerPreference( "PreferPointEntsIn2D", make_property_string( getSelectionSystem().m_bPreferPointEntsIn2D ) );
++      SelectionSystem_registerPreferencesPage();
++}
++
++void SelectionSystem_Destroy(){
++      GlobalShaderCache().detachRenderable( getSelectionSystem() );
++
++      GlobalSceneGraph().removeBoundsChangedCallback( SelectionSystem_boundsChanged );
++
++      delete g_RadiantSelectionSystem;
++
++      RadiantSelectionSystem::destroyStatic();
++}
++
++
++
++
++inline float screen_normalised( float pos, std::size_t size ){
++      return ( ( 2.0f * pos ) / size ) - 1.0f;
++}
++
++typedef Vector2 DeviceVector;
++
++inline DeviceVector window_to_normalised_device( WindowVector window, std::size_t width, std::size_t height ){
++      return DeviceVector( screen_normalised( window.x(), width ), screen_normalised( height - 1 - window.y(), height ) );
++}
++
++inline float device_constrained( float pos ){
++      return std::min( 1.0f, std::max( -1.0f, pos ) );
++}
++
++inline DeviceVector device_constrained( DeviceVector device ){
++      return DeviceVector( device_constrained( device.x() ), device_constrained( device.y() ) );
++}
++
++inline float window_constrained( float pos, std::size_t origin, std::size_t size ){
++      return std::min( static_cast<float>( origin + size ), std::max( static_cast<float>( origin ), pos ) );
++}
++
++inline WindowVector window_constrained( WindowVector window, std::size_t x, std::size_t y, std::size_t width, std::size_t height ){
++      return WindowVector( window_constrained( window.x(), x, width ), window_constrained( window.y(), y, height ) );
++}
++
++typedef Callback<void(DeviceVector)> MouseEventCallback;
++
++Single<MouseEventCallback> g_mouseMovedCallback;
++Single<MouseEventCallback> g_mouseUpCallback;
++
++#if 1
++const ButtonIdentifier c_button_select = c_buttonLeft;
++const ButtonIdentifier c_button_select2 = c_buttonRight;
++const ModifierFlags c_modifier_manipulator = c_modifierNone;
++const ModifierFlags c_modifier_toggle = c_modifierShift;
++const ModifierFlags c_modifier_replace = c_modifierShift | c_modifierAlt;
++const ModifierFlags c_modifier_face = c_modifierControl;
++#else
++const ButtonIdentifier c_button_select = c_buttonLeft;
++const ModifierFlags c_modifier_manipulator = c_modifierNone;
++const ModifierFlags c_modifier_toggle = c_modifierControl;
++const ModifierFlags c_modifier_replace = c_modifierNone;
++const ModifierFlags c_modifier_face = c_modifierShift;
++#endif
++const ModifierFlags c_modifier_toggle_face = c_modifier_toggle | c_modifier_face;
++const ModifierFlags c_modifier_replace_face = c_modifier_replace | c_modifier_face;
++
++const ButtonIdentifier c_button_texture = c_buttonMiddle;
++const ModifierFlags c_modifier_apply_texture1 = c_modifierControl | c_modifierShift;
++const ModifierFlags c_modifier_apply_texture2 = c_modifierControl;
++const ModifierFlags c_modifier_apply_texture3 =                     c_modifierShift;
++const ModifierFlags c_modifier_copy_texture = c_modifierNone;
++
++class Selector_
++{
++RadiantSelectionSystem::EModifier modifier_for_state( ModifierFlags state ){
++      if ( ( state == c_modifier_toggle || state == c_modifier_toggle_face || state == c_modifier_face ) ) {
++              if( m_mouse2 ){
++                      return RadiantSelectionSystem::eReplace;
++              }
++              else{
++                      return RadiantSelectionSystem::eToggle;
++              }
++      }
++      return RadiantSelectionSystem::eManipulator;
++}
++
++rect_t getDeviceArea() const {
++      DeviceVector delta( m_current - m_start );
++      if ( selecting() && fabs( delta.x() ) > m_epsilon.x() && fabs( delta.y() ) > m_epsilon.y() ) {
++              return SelectionBoxForArea( &m_start[0], &delta[0] );
++      }
++      else
++      {
++              rect_t default_area = { { 0, 0, }, { 0, 0, }, };
++              return default_area;
++      }
++}
++
++public:
++DeviceVector m_start;
++DeviceVector m_current;
++DeviceVector m_epsilon;
++ModifierFlags m_state;
++bool m_mouse2;
++bool m_mouseMoved;
++bool m_mouseMovedWhilePressed;
++bool m_paintSelect;
++const View* m_view;
++RectangleCallback m_window_update;
++
++Selector_() : m_start( 0.0f, 0.0f ), m_current( 0.0f, 0.0f ), m_state( c_modifierNone ), m_mouse2( false ), m_mouseMoved( false ), m_mouseMovedWhilePressed( false ){
++}
++
++void draw_area(){
++      m_window_update( getDeviceArea() );
++}
++
++void testSelect( DeviceVector position ){
++      RadiantSelectionSystem::EModifier modifier = modifier_for_state( m_state );
++      if ( modifier != RadiantSelectionSystem::eManipulator ) {
++              DeviceVector delta( position - m_start );
++              if ( fabs( delta.x() ) > m_epsilon.x() && fabs( delta.y() ) > m_epsilon.y() ) {
++                      DeviceVector delta( position - m_start );
++                      //getSelectionSystem().SelectArea( *m_view, &m_start[0], &delta[0], modifier, ( m_state & c_modifier_face ) != c_modifierNone );
++                      getSelectionSystem().SelectArea( *m_view, &m_start[0], &delta[0], RadiantSelectionSystem::eToggle, ( m_state & c_modifier_face ) != c_modifierNone );
++              }
++              else if( !m_mouseMovedWhilePressed ){
++                      if ( modifier == RadiantSelectionSystem::eReplace && !m_mouseMoved ) {
++                              modifier = RadiantSelectionSystem::eCycle;
++                      }
++                      getSelectionSystem().SelectPoint( *m_view, &position[0], &m_epsilon[0], modifier, ( m_state & c_modifier_face ) != c_modifierNone );
++              }
++      }
++
++      m_start = m_current = DeviceVector( 0.0f, 0.0f );
++      draw_area();
++}
++
++void testSelect_simpleM1( DeviceVector position ){
++      /*RadiantSelectionSystem::EModifier modifier = RadiantSelectionSystem::eReplace;
++      DeviceVector delta( position - m_start );
++      if ( fabs( delta.x() ) < m_epsilon.x() && fabs( delta.y() ) < m_epsilon.y() ) {
++              modifier = RadiantSelectionSystem::eCycle;
++      }
++      getSelectionSystem().SelectPoint( *m_view, &position[0], &m_epsilon[0], modifier, false );*/
++      getSelectionSystem().SelectPoint( *m_view, &position[0], &m_epsilon[0], m_mouseMoved ? RadiantSelectionSystem::eReplace : RadiantSelectionSystem::eCycle, false );
++      m_start = m_current = device_constrained( position );
++}
++
++
++bool selecting() const {
++      return m_state != c_modifier_manipulator && m_mouse2;
++}
++
++void setState( ModifierFlags state ){
++      bool was_selecting = selecting();
++      m_state = state;
++      if ( was_selecting ^ selecting() ) {
++              draw_area();
++      }
++}
++
++ModifierFlags getState() const {
++      return m_state;
++}
++
++void modifierEnable( ModifierFlags type ){
++      setState( bitfield_enable( getState(), type ) );
++}
++void modifierDisable( ModifierFlags type ){
++      setState( bitfield_disable( getState(), type ) );
++}
++
++void mouseDown( DeviceVector position ){
++      m_start = m_current = device_constrained( position );
++      if( !m_mouse2 && m_state != c_modifierNone ){
++              m_paintSelect = getSelectionSystem().SelectPoint_InitPaint( *m_view, &position[0], &m_epsilon[0], ( m_state & c_modifier_face ) != c_modifierNone );
++      }
++}
++
++void mouseMoved( DeviceVector position ){
++      m_current = device_constrained( position );
++      m_mouseMovedWhilePressed = true;
++      if( m_mouse2 ){
++              draw_area();
++      }
++      else if( m_state != c_modifier_manipulator ){
++              getSelectionSystem().SelectPoint( *m_view, &m_current[0], &m_epsilon[0],
++                                                                              m_paintSelect ? RadiantSelectionSystem::eSelect : RadiantSelectionSystem::eDeselect,
++                                                                              ( m_state & c_modifier_face ) != c_modifierNone );
++      }
++}
++typedef MemberCaller<Selector_, void(DeviceVector), &Selector_::mouseMoved> MouseMovedCaller;
++
++void mouseUp( DeviceVector position ){
++      if( m_mouse2 ){
++              testSelect( device_constrained( position ) );
++      }
++      else{
++              m_start = m_current = DeviceVector( 0.0f, 0.0f );
++      }
++
++      g_mouseMovedCallback.clear();
++      g_mouseUpCallback.clear();
++}
++typedef MemberCaller<Selector_, void(DeviceVector), &Selector_::mouseUp> MouseUpCaller;
++};
++
++
++class Manipulator_
++{
++public:
++DeviceVector m_epsilon;
++const View* m_view;
++ModifierFlags m_state;
++
++Manipulator_() : m_state( c_modifierNone ){
++}
++
++bool mouseDown( DeviceVector position ){
++      return getSelectionSystem().SelectManipulator( *m_view, &position[0], &m_epsilon[0] );
++}
++
++void mouseMoved( DeviceVector position ){
++      getSelectionSystem().MoveSelected( *m_view, &position[0], ( m_state & c_modifierShift ) == c_modifierShift );
++}
++typedef MemberCaller<Manipulator_, void(DeviceVector), &Manipulator_::mouseMoved> MouseMovedCaller;
++
++void mouseUp( DeviceVector position ){
++      getSelectionSystem().endMove();
++      g_mouseMovedCallback.clear();
++      g_mouseUpCallback.clear();
++}
++<<<<<<< HEAD
++typedef MemberCaller<Manipulator_, void(DeviceVector), &Manipulator_::mouseUp> MouseUpCaller;
++=======
++typedef MemberCaller1<Manipulator_, DeviceVector, &Manipulator_::mouseUp> MouseUpCaller;
++
++void setState( ModifierFlags state ){
++      m_state = state;
++}
++
++ModifierFlags getState() const {
++      return m_state;
++}
++
++void modifierEnable( ModifierFlags type ){
++      setState( bitfield_enable( getState(), type ) );
++}
++void modifierDisable( ModifierFlags type ){
++      setState( bitfield_disable( getState(), type ) );
++}
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++};
++
++void Scene_copyClosestTexture( SelectionTest& test );
++void Scene_applyClosestTexture( SelectionTest& test );
++
++class RadiantWindowObserver : public SelectionSystemWindowObserver
++{
++enum
++{
++      SELECT_EPSILON = 8,
++};
++
++int m_width;
++int m_height;
++
++bool m_mouse_down;
++
++public:
++Selector_ m_selector;
++Manipulator_ m_manipulator;
++
++RadiantWindowObserver() : m_mouse_down( false ){
++}
++void release(){
++      delete this;
++}
++void setView( const View& view ){
++      m_selector.m_view = &view;
++      m_manipulator.m_view = &view;
++}
++void setRectangleDrawCallback( const RectangleCallback& callback ){
++      m_selector.m_window_update = callback;
++}
++void onSizeChanged( int width, int height ){
++      m_width = width;
++      m_height = height;
++      DeviceVector epsilon( SELECT_EPSILON / static_cast<float>( m_width ), SELECT_EPSILON / static_cast<float>( m_height ) );
++      m_selector.m_epsilon = m_manipulator.m_epsilon = epsilon;
++}
++void onMouseDown( const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers ){
++      if ( button == c_button_select || ( button == c_button_select2 && modifiers != c_modifierNone ) ) {
++              m_mouse_down = true;
++              //m_selector.m_mouseMoved = false;
++
++              DeviceVector devicePosition( window_to_normalised_device( position, m_width, m_height ) );
++              if ( modifiers == c_modifier_manipulator && m_manipulator.mouseDown( devicePosition ) ) {
++                      g_mouseMovedCallback.insert( MouseEventCallback( Manipulator_::MouseMovedCaller( m_manipulator ) ) );
++                      g_mouseUpCallback.insert( MouseEventCallback( Manipulator_::MouseUpCaller( m_manipulator ) ) );
++              }
++              else
++              {
++                      if ( button == c_button_select ) {
++                              m_selector.m_mouse2 = false;
++                      }
++                      else{
++                              m_selector.m_mouse2 = true;
++                      }
++                      m_selector.mouseDown( devicePosition );
++                      g_mouseMovedCallback.insert( MouseEventCallback( Selector_::MouseMovedCaller( m_selector ) ) );
++                      g_mouseUpCallback.insert( MouseEventCallback( Selector_::MouseUpCaller( m_selector ) ) );
++              }
++      }
++      else if ( button == c_button_texture ) {
++              DeviceVector devicePosition( device_constrained( window_to_normalised_device( position, m_width, m_height ) ) );
++
++              View scissored( *m_selector.m_view );
++              ConstructSelectionTest( scissored, SelectionBoxForPoint( &devicePosition[0], &m_selector.m_epsilon[0] ) );
++              SelectionVolume volume( scissored );
++
++              if ( modifiers == c_modifier_apply_texture1 || modifiers == c_modifier_apply_texture2 || modifiers == c_modifier_apply_texture3 ) {
++                      Scene_applyClosestTexture( volume );
++              }
++              else if ( modifiers == c_modifier_copy_texture ) {
++                      Scene_copyClosestTexture( volume );
++              }
++      }
++}
++void onMouseMotion( const WindowVector& position, ModifierFlags modifiers ){
++      m_selector.m_mouseMoved = true;
++      if ( m_mouse_down && !g_mouseMovedCallback.empty() ) {
++              m_selector.m_mouseMovedWhilePressed = true;
++              g_mouseMovedCallback.get() ( window_to_normalised_device( position, m_width, m_height ) );
++      }
++}
++void onMouseUp( const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers ){
++      if ( ( button == c_button_select || button == c_button_select2 ) && !g_mouseUpCallback.empty() ) {
++              m_mouse_down = false;
++
++              g_mouseUpCallback.get() ( window_to_normalised_device( position, m_width, m_height ) );
++      }
++      //L button w/o scene changed = tunnel selection
++      if( // !getSelectionSystem().m_undo_begun &&
++              modifiers == c_modifierNone && button == c_button_select &&
++              //( !m_selector.m_mouseMoved || !m_mouse_down ) &&
++              !m_selector.m_mouseMovedWhilePressed &&
++              ( getSelectionSystem().Mode() != SelectionSystem::eComponent || getSelectionSystem().ManipulatorMode() != SelectionSystem::eDrag ) ){
++              m_selector.testSelect_simpleM1( device_constrained( window_to_normalised_device( position, m_width, m_height ) ) );
++      }
++      //getSelectionSystem().m_undo_begun = false;
++      m_selector.m_mouseMoved = false;
++      m_selector.m_mouseMovedWhilePressed = false;
++}
++void onModifierDown( ModifierFlags type ){
++      m_selector.modifierEnable( type );
++      m_manipulator.modifierEnable( type );
++}
++void onModifierUp( ModifierFlags type ){
++      m_selector.modifierDisable( type );
++      m_manipulator.modifierDisable( type );
++}
++};
++
++
++
++SelectionSystemWindowObserver* NewWindowObserver(){
++      return new RadiantWindowObserver;
++}
++
++
++
++#include "modulesystem/singletonmodule.h"
++#include "modulesystem/moduleregistry.h"
++
++class SelectionDependencies :
++      public GlobalSceneGraphModuleRef,
++      public GlobalShaderCacheModuleRef,
++      public GlobalOpenGLModuleRef
++{
++};
++
++class SelectionAPI : public TypeSystemRef
++{
++SelectionSystem* m_selection;
++public:
++typedef SelectionSystem Type;
++STRING_CONSTANT( Name, "*" );
++
++SelectionAPI(){
++      SelectionSystem_Construct();
++
++      m_selection = &getSelectionSystem();
++}
++~SelectionAPI(){
++      SelectionSystem_Destroy();
++}
++SelectionSystem* getTable(){
++      return m_selection;
++}
++};
++
++typedef SingletonModule<SelectionAPI, SelectionDependencies> SelectionModule;
++typedef Static<SelectionModule> StaticSelectionModule;
++StaticRegisterModule staticRegisterSelection( StaticSelectionModule::instance() );
index 240901552b29e914328346df32ef78c75c98486a,819d4bc3803b2d8a2e9f8bba54f6c56e5ee612d9..23174f1c544c8c5a39cc78444b133d9a144ece41
@@@ -531,23 -475,14 +533,23 @@@ bool TextureSearch_IsShown( const char
        }
  }
  
 -CopiedString g_notex;
 -CopiedString g_shadernotex;
 -
  // if texture_showinuse jump over non in-use textures
- bool Texture_IsShown( IShader* shader, bool show_shaders, bool show_textures, bool hideUnused ){
+ bool Texture_IsShown( IShader* shader, bool show_shaders, bool show_textures, bool hideUnused, bool hideNonShadersInCommon ){
 -      // filter notex / shadernotex images
 -      if ( g_TextureBrowser_filterNotex && ( string_equal( g_notex.c_str(), shader->getTexture()->name ) || string_equal( g_shadernotex.c_str(), shader->getTexture()->name ) ) ) {
 -              return false;
 +      // filter missing shaders
 +      // ugly: filter on built-in fallback name after substitution
 +      if ( g_TextureBrowser_filterMissing ) {
 +              if ( isMissing( shader->getTexture()->name ) ) {
 +                      return false;
 +              }
 +      }
 +      // filter the fallback (notex/shadernotex) for missing shaders or editor image
 +      if ( g_TextureBrowser_filterFallback ) {
 +              if ( isNotex( shader->getName() ) ) {
 +                      return false;
 +              }
 +              if ( isNotex( shader->getTexture()->name ) ) {
 +                      return false;
 +              }
        }
  
        if ( g_TextureBrowser_currentDirectory == "Untagged" ) {
@@@ -918,62 -836,43 +925,56 @@@ void TextureBrowser_ShowTagSearchResult
  
  bool TextureBrowser_hideUnused();
  
 -void TextureBrowser_hideUnusedExport( const BoolImportCallback& importer ){
 +void TextureBrowser_hideUnusedExport( const Callback<void(bool)> & importer ){
        importer( TextureBrowser_hideUnused() );
  }
 -typedef FreeCaller1<const BoolImportCallback&, TextureBrowser_hideUnusedExport> TextureBrowserHideUnusedExport;
  
 -void TextureBrowser_showShadersExport( const BoolImportCallback& importer ){
 +typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_hideUnusedExport> TextureBrowserHideUnusedExport;
 +
 +void TextureBrowser_showShadersExport( const Callback<void(bool)> & importer ){
        importer( GlobalTextureBrowser().m_showShaders );
  }
 -typedef FreeCaller1<const BoolImportCallback&, TextureBrowser_showShadersExport> TextureBrowserShowShadersExport;
  
 -void TextureBrowser_showTexturesExport( const BoolImportCallback& importer ){
 +typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShadersExport> TextureBrowserShowShadersExport;
 +
 +void TextureBrowser_showTexturesExport( const Callback<void(bool)> & importer ){
        importer( GlobalTextureBrowser().m_showTextures );
  }
 -typedef FreeCaller1<const BoolImportCallback&, TextureBrowser_showTexturesExport> TextureBrowserShowTexturesExport;
  
 -void TextureBrowser_showShaderlistOnly( const BoolImportCallback& importer ){
 +typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showTexturesExport> TextureBrowserShowTexturesExport;
 +
 +void TextureBrowser_showShaderlistOnly( const Callback<void(bool)> & importer ){
        importer( g_TextureBrowser_shaderlistOnly );
  }
 -typedef FreeCaller1<const BoolImportCallback&, TextureBrowser_showShaderlistOnly> TextureBrowserShowShaderlistOnlyExport;
  
 -void TextureBrowser_fixedSize( const BoolImportCallback& importer ){
 +typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShaderlistOnly> TextureBrowserShowShaderlistOnlyExport;
 +
 +void TextureBrowser_fixedSize( const Callback<void(bool)> & importer ){
        importer( g_TextureBrowser_fixedSize );
  }
 -typedef FreeCaller1<const BoolImportCallback&, TextureBrowser_fixedSize> TextureBrowser_FixedSizeExport;
  
 -void TextureBrowser_filterNotex( const BoolImportCallback& importer ){
 -      importer( g_TextureBrowser_filterNotex );
 +typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_fixedSize> TextureBrowser_FixedSizeExport;
 +
 +void TextureBrowser_filterMissing( const Callback<void(bool)> & importer ){
 +      importer( g_TextureBrowser_filterMissing );
  }
 -typedef FreeCaller1<const BoolImportCallback&, TextureBrowser_filterNotex> TextureBrowser_filterNotexExport;
  
 -void TextureBrowser_enableAlpha( const BoolImportCallback& importer ){
 +typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_filterMissing> TextureBrowser_filterMissingExport;
 +
 +void TextureBrowser_filterFallback( const Callback<void(bool)> & importer ){
 +      importer( g_TextureBrowser_filterFallback );
 +}
 +
 +typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_filterFallback> TextureBrowser_filterFallbackExport;
 +
 +void TextureBrowser_enableAlpha( const Callback<void(bool)> & importer ){
        importer( g_TextureBrowser_enableAlpha );
  }
 -typedef FreeCaller1<const BoolImportCallback&, TextureBrowser_enableAlpha> TextureBrowser_enableAlphaExport;
 +
 +typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_enableAlpha> TextureBrowser_enableAlphaExport;
  
  void TextureBrowser_SetHideUnused( TextureBrowser& textureBrowser, bool hideUnused ){
-       if ( hideUnused ) {
-               textureBrowser.m_hideUnused = true;
-       }
-       else
-       {
-               textureBrowser.m_hideUnused = false;
-       }
+       textureBrowser.m_hideUnused = hideUnused;
  
        textureBrowser.m_hideunused_item.update();
  
@@@ -2815,8 -2719,15 +2816,16 @@@ void TextureBrowser_constructPreference
                const char* startup_shaders[] = { "None", TextureBrowser_getComonShadersName() };
                page.appendCombo( "Load Shaders at Startup", reinterpret_cast<int&>( GlobalTextureBrowser().m_startupShaders ), STRING_ARRAY_RANGE( startup_shaders ) );
        }
+       {
+               StringOutputStream sstream( 256 );
+               sstream << "Hide nonShaders in " << TextureBrowser_getComonShadersDir() << " folder";
+               page.appendCheckBox(
+                       "", sstream.c_str(),
+                       GlobalTextureBrowser().m_hideNonShadersInCommon
+                       );
+       }
  }
 +
  void TextureBrowser_constructPage( PreferenceGroup& group ){
        PreferencesPage page( group.createPage( "Texture Browser", "Texture Browser Preferences" ) );
        TextureBrowser_constructPreferences( page );
@@@ -2834,39 -2747,48 +2843,40 @@@ void TextureBrowser_registerPreferences
  void TextureClipboard_textureSelected( const char* shader );
  
  void TextureBrowser_Construct(){
 -      GlobalCommands_insert( "ShaderInfo", FreeCaller<TextureBrowser_shaderInfo>() );
 -      GlobalCommands_insert( "ShowUntagged", FreeCaller<TextureBrowser_showUntagged>() );
 -      GlobalCommands_insert( "AddTag", FreeCaller<TextureBrowser_addTag>() );
 -      GlobalCommands_insert( "RenameTag", FreeCaller<TextureBrowser_renameTag>() );
 -      GlobalCommands_insert( "DeleteTag", FreeCaller<TextureBrowser_deleteTag>() );
 -      GlobalCommands_insert( "CopyTag", FreeCaller<TextureBrowser_copyTag>() );
 -      GlobalCommands_insert( "PasteTag", FreeCaller<TextureBrowser_pasteTag>() );
 -      GlobalCommands_insert( "RefreshShaders", FreeCaller<RefreshShaders>() );
 -      GlobalToggles_insert( "ShowInUse", FreeCaller<TextureBrowser_ToggleHideUnused>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_hideunused_item ), Accelerator( 'U' ) );
 -      GlobalCommands_insert( "ShowAllTextures", FreeCaller<TextureBrowser_showAll>(), Accelerator( 'A', (GdkModifierType)GDK_CONTROL_MASK ) );
 -      GlobalCommands_insert( "ToggleTextures", FreeCaller<TextureBrowser_toggleShow>(), Accelerator( 'T' ) );
 -      GlobalToggles_insert( "ToggleShowShaders", FreeCaller<TextureBrowser_ToggleShowShaders>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showshaders_item ) );
 -      GlobalToggles_insert( "ToggleShowTextures", FreeCaller<TextureBrowser_ToggleShowTextures>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showtextures_item ) );
 -      GlobalToggles_insert( "ToggleShowShaderlistOnly", FreeCaller<TextureBrowser_ToggleShowShaderListOnly>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showshaderlistonly_item ) );
 -      GlobalToggles_insert( "FixedSize", FreeCaller<TextureBrowser_FixedSize>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_fixedsize_item ) );
 -      GlobalToggles_insert( "FilterNotex", FreeCaller<TextureBrowser_FilterNotex>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_filternotex_item ) );
 -      GlobalToggles_insert( "EnableAlpha", FreeCaller<TextureBrowser_EnableAlpha>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_enablealpha_item ) );
 -
 -      GlobalPreferenceSystem().registerPreference( "TextureScale",
 -                                                                                               makeSizeStringImportCallback( TextureBrowserSetScaleCaller( g_TextureBrowser ) ),
 -                                                                                               SizeExportStringCaller( g_TextureBrowser.m_textureScale )
 -                                                                                               );
 -      GlobalPreferenceSystem().registerPreference( "UniformTextureSize",
 -                                                                                              makeIntStringImportCallback(UniformTextureSizeImportCaller(g_TextureBrowser)),
 -                                                                                              IntExportStringCaller(g_TextureBrowser.m_uniformTextureSize) );
 -      GlobalPreferenceSystem().registerPreference( "UniformTextureMinSize",
 -                                                                                              makeIntStringImportCallback(UniformTextureMinSizeImportCaller(g_TextureBrowser)),
 -                                                                                              IntExportStringCaller(g_TextureBrowser.m_uniformTextureMinSize) );
 -      GlobalPreferenceSystem().registerPreference( "TextureScrollbar",
 -                                                                                               makeBoolStringImportCallback( TextureBrowserImportShowScrollbarCaller( g_TextureBrowser ) ),
 -                                                                                               BoolExportStringCaller( GlobalTextureBrowser().m_showTextureScrollbar )
 -                                                                                               );
 -      GlobalPreferenceSystem().registerPreference( "ShowShaders", BoolImportStringCaller( GlobalTextureBrowser().m_showShaders ), BoolExportStringCaller( GlobalTextureBrowser().m_showShaders ) );
 -      GlobalPreferenceSystem().registerPreference( "ShowTextures", BoolImportStringCaller( GlobalTextureBrowser().m_showTextures ), BoolExportStringCaller( GlobalTextureBrowser().m_showTextures ) );
 -      GlobalPreferenceSystem().registerPreference( "ShowShaderlistOnly", BoolImportStringCaller( g_TextureBrowser_shaderlistOnly ), BoolExportStringCaller( g_TextureBrowser_shaderlistOnly ) );
 -      GlobalPreferenceSystem().registerPreference( "FixedSize", BoolImportStringCaller( g_TextureBrowser_fixedSize ), BoolExportStringCaller( g_TextureBrowser_fixedSize ) );
 -      GlobalPreferenceSystem().registerPreference( "FilterNotex", BoolImportStringCaller( g_TextureBrowser_filterNotex ), BoolExportStringCaller( g_TextureBrowser_filterNotex ) );
 -      GlobalPreferenceSystem().registerPreference( "EnableAlpha", BoolImportStringCaller( g_TextureBrowser_enableAlpha ), BoolExportStringCaller( g_TextureBrowser_enableAlpha ) );
 -      GlobalPreferenceSystem().registerPreference( "LoadShaders", IntImportStringCaller( reinterpret_cast<int&>( GlobalTextureBrowser().m_startupShaders ) ), IntExportStringCaller( reinterpret_cast<int&>( GlobalTextureBrowser().m_startupShaders ) ) );
 -      GlobalPreferenceSystem().registerPreference( "WheelMouseInc", SizeImportStringCaller( GlobalTextureBrowser().m_mouseWheelScrollIncrement ), SizeExportStringCaller( GlobalTextureBrowser().m_mouseWheelScrollIncrement ) );
 -      GlobalPreferenceSystem().registerPreference( "SI_Colors0", Vector3ImportStringCaller( GlobalTextureBrowser().color_textureback ), Vector3ExportStringCaller( GlobalTextureBrowser().color_textureback ) );
 -      GlobalPreferenceSystem().registerPreference( "HideNonShadersInCommon", BoolImportStringCaller( GlobalTextureBrowser().m_hideNonShadersInCommon ), BoolExportStringCaller( GlobalTextureBrowser().m_hideNonShadersInCommon ) );
 +      GlobalCommands_insert( "ShaderInfo", makeCallbackF(TextureBrowser_shaderInfo) );
 +      GlobalCommands_insert( "ShowUntagged", makeCallbackF(TextureBrowser_showUntagged) );
 +      GlobalCommands_insert( "AddTag", makeCallbackF(TextureBrowser_addTag) );
 +      GlobalCommands_insert( "RenameTag", makeCallbackF(TextureBrowser_renameTag) );
 +      GlobalCommands_insert( "DeleteTag", makeCallbackF(TextureBrowser_deleteTag) );
 +      GlobalCommands_insert( "CopyTag", makeCallbackF(TextureBrowser_copyTag) );
 +      GlobalCommands_insert( "PasteTag", makeCallbackF(TextureBrowser_pasteTag) );
 +      GlobalCommands_insert( "RefreshShaders", makeCallbackF(VFS_Refresh) );
 +      GlobalToggles_insert( "ShowInUse", makeCallbackF(TextureBrowser_ToggleHideUnused), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_hideunused_item ), Accelerator( 'U' ) );
 +      GlobalCommands_insert( "ShowAllTextures", makeCallbackF(TextureBrowser_showAll), Accelerator( 'A', (GdkModifierType)GDK_CONTROL_MASK ) );
 +      GlobalCommands_insert( "ToggleTextures", makeCallbackF(TextureBrowser_toggleShow), Accelerator( 'T' ) );
 +      GlobalToggles_insert( "ToggleShowShaders", makeCallbackF(TextureBrowser_ToggleShowShaders), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showshaders_item ) );
 +      GlobalToggles_insert( "ToggleShowTextures", makeCallbackF(TextureBrowser_ToggleShowTextures), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showtextures_item ) );
 +      GlobalToggles_insert( "ToggleShowShaderlistOnly", makeCallbackF(TextureBrowser_ToggleShowShaderListOnly),
 + ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showshaderlistonly_item ) );
 +      GlobalToggles_insert( "FixedSize", makeCallbackF(TextureBrowser_FixedSize), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_fixedsize_item ) );
 +      GlobalToggles_insert( "FilterMissing", makeCallbackF(TextureBrowser_FilterMissing), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_filternotex_item ) );
 +      GlobalToggles_insert( "FilterFallback", makeCallbackF(TextureBrowser_FilterFallback), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_hidenotex_item ) );
 +      GlobalToggles_insert( "EnableAlpha", makeCallbackF(TextureBrowser_EnableAlpha), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_enablealpha_item ) );
 +
 +      GlobalPreferenceSystem().registerPreference( "TextureScale", make_property_string<TextureScale>(g_TextureBrowser) );
 +      GlobalPreferenceSystem().registerPreference( "UniformTextureSize", make_property_string<UniformTextureSize>(g_TextureBrowser) );
 +      GlobalPreferenceSystem().registerPreference( "UniformTextureMinSize", make_property_string<UniformTextureMinSize>(g_TextureBrowser) );
 +      GlobalPreferenceSystem().registerPreference( "TextureScrollbar", make_property_string<TextureBrowser_ShowScrollbar>(GlobalTextureBrowser()));
 +      GlobalPreferenceSystem().registerPreference( "ShowShaders", make_property_string( GlobalTextureBrowser().m_showShaders ) );
 +      GlobalPreferenceSystem().registerPreference( "ShowTextures", make_property_string( GlobalTextureBrowser().m_showTextures ) );
 +      GlobalPreferenceSystem().registerPreference( "ShowShaderlistOnly", make_property_string( g_TextureBrowser_shaderlistOnly ) );
 +      GlobalPreferenceSystem().registerPreference( "FixedSize", make_property_string( g_TextureBrowser_fixedSize ) );
 +      GlobalPreferenceSystem().registerPreference( "FilterMissing", make_property_string( g_TextureBrowser_filterMissing ) );
 +      GlobalPreferenceSystem().registerPreference( "EnableAlpha", make_property_string( g_TextureBrowser_enableAlpha ) );
 +      GlobalPreferenceSystem().registerPreference( "LoadShaders", make_property_string( reinterpret_cast<int&>( GlobalTextureBrowser().m_startupShaders ) ) );
 +      GlobalPreferenceSystem().registerPreference( "WheelMouseInc", make_property_string( GlobalTextureBrowser().m_mouseWheelScrollIncrement ) );
 +      GlobalPreferenceSystem().registerPreference( "SI_Colors0", make_property_string( GlobalTextureBrowser().color_textureback ) );
++      GlobalPreferenceSystem().registerPreference( "HideNonShadersInCommon", make_property_string( GlobalTextureBrowser().m_hideNonShadersInCommon ) );
  
        g_TextureBrowser.shader = texdef_name_default();
  
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a24c46dd19a614d9718a54de64cd92016043cfa9
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2954 @@@
++/*
++   Copyright (C) 1999-2006 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)
++//
++
++#include "texwindow.h"
++
++#include <gtk/gtk.h>
++
++#include "debugging/debugging.h"
++#include "warnings.h"
++
++#include "defaults.h"
++#include "ifilesystem.h"
++#include "iundo.h"
++#include "igl.h"
++#include "iarchive.h"
++#include "moduleobserver.h"
++
++#include <set>
++#include <string>
++#include <vector>
++
++#include <uilib/uilib.h>
++
++#include "signal/signal.h"
++#include "math/vector.h"
++#include "texturelib.h"
++#include "string/string.h"
++#include "shaderlib.h"
++#include "os/file.h"
++#include "os/path.h"
++#include "stream/memstream.h"
++#include "stream/textfilestream.h"
++#include "stream/stringstream.h"
++#include "cmdlib.h"
++#include "texmanip.h"
++#include "textures.h"
++#include "convert.h"
++
++#include "gtkutil/menu.h"
++#include "gtkutil/nonmodal.h"
++#include "gtkutil/cursor.h"
++#include "gtkutil/widget.h"
++#include "gtkutil/glwidget.h"
++#include "gtkutil/messagebox.h"
++
++#include "error.h"
++#include "map.h"
++#include "qgl.h"
++#include "select.h"
++#include "brush_primit.h"
++#include "brushmanip.h"
++#include "patchmanip.h"
++#include "plugin.h"
++#include "qe3.h"
++#include "gtkdlgs.h"
++#include "gtkmisc.h"
++#include "mainframe.h"
++#include "findtexturedialog.h"
++#include "surfacedialog.h"
++#include "patchdialog.h"
++#include "groupdialog.h"
++#include "preferences.h"
++#include "shaders.h"
++#include "commands.h"
++
++bool TextureBrowser_showWads(){
++      return !string_empty( g_pGameDescription->getKeyValue( "show_wads" ) );
++}
++
++void TextureBrowser_queueDraw( TextureBrowser& textureBrowser );
++
++bool string_equal_start( const char* string, StringRange start ){
++      return string_equal_n( string, start.first, start.last - start.first );
++}
++
++typedef std::set<CopiedString> TextureGroups;
++
++void TextureGroups_addWad( TextureGroups& groups, const char* archive ){
++      if ( extension_equal( path_get_extension( archive ), "wad" ) ) {
++#if 1
++              groups.insert( archive );
++#else
++              CopiedString archiveBaseName( path_get_filename_start( archive ), path_get_filename_base_end( archive ) );
++              groups.insert( archiveBaseName );
++#endif
++      }
++}
++
++typedef ReferenceCaller<TextureGroups, void(const char*), TextureGroups_addWad> TextureGroupsAddWadCaller;
++
++namespace
++{
++bool g_TextureBrowser_shaderlistOnly = false;
++bool g_TextureBrowser_fixedSize = true;
++bool g_TextureBrowser_filterMissing = false;
++bool g_TextureBrowser_filterFallback = true;
++bool g_TextureBrowser_enableAlpha = false;
++}
++
++CopiedString g_notex;
++CopiedString g_shadernotex;
++
++bool isMissing(const char* name);
++
++bool isNotex(const char* name);
++
++bool isMissing(const char* name){
++      if ( string_equal( g_notex.c_str(), name ) ) {
++              return true;
++      }
++      if ( string_equal( g_shadernotex.c_str(), name ) ) {
++              return true;
++      }
++      return false;
++}
++
++bool isNotex(const char* name){
++      if ( string_equal_suffix( name, "/" DEFAULT_NOTEX_BASENAME ) ) {
++              return true;
++      }
++      if ( string_equal_suffix( name, "/" DEFAULT_SHADERNOTEX_BASENAME ) ) {
++              return true;
++      }
++      return false;
++}
++
++void TextureGroups_addShader( TextureGroups& groups, const char* shaderName ){
++      const char* texture = path_make_relative( shaderName, "textures/" );
++
++      // hide notex / shadernotex images
++      if ( g_TextureBrowser_filterFallback ) {
++              if ( isNotex( shaderName ) ) {
++                      return;
++              }
++              if ( isNotex( texture ) ) {
++                      return;
++              }
++      }
++
++      if ( texture != shaderName ) {
++              const char* last = path_remove_directory( texture );
++              if ( !string_empty( last ) ) {
++                      groups.insert( CopiedString( StringRange( texture, --last ) ) );
++              }
++      }
++}
++
++typedef ReferenceCaller<TextureGroups, void(const char*), TextureGroups_addShader> TextureGroupsAddShaderCaller;
++
++void TextureGroups_addDirectory( TextureGroups& groups, const char* directory ){
++      groups.insert( directory );
++}
++
++typedef ReferenceCaller<TextureGroups, void(const char*), TextureGroups_addDirectory> TextureGroupsAddDirectoryCaller;
++
++class DeferredAdjustment
++{
++gdouble m_value;
++guint m_handler;
++
++typedef void ( *ValueChangedFunction )( void* data, gdouble value );
++
++ValueChangedFunction m_function;
++void* m_data;
++
++static gboolean deferred_value_changed( gpointer data ){
++      reinterpret_cast<DeferredAdjustment*>( data )->m_function(
++              reinterpret_cast<DeferredAdjustment*>( data )->m_data,
++              reinterpret_cast<DeferredAdjustment*>( data )->m_value
++              );
++      reinterpret_cast<DeferredAdjustment*>( data )->m_handler = 0;
++      reinterpret_cast<DeferredAdjustment*>( data )->m_value = 0;
++      return FALSE;
++}
++
++public:
++DeferredAdjustment( ValueChangedFunction function, void* data ) : m_value( 0 ), m_handler( 0 ), m_function( function ), m_data( data ){
++}
++
++void flush(){
++      if ( m_handler != 0 ) {
++              g_source_remove( m_handler );
++              deferred_value_changed( this );
++      }
++}
++
++void value_changed( gdouble value ){
++      m_value = value;
++      if ( m_handler == 0 ) {
++              m_handler = g_idle_add( deferred_value_changed, this );
++      }
++}
++
++static void adjustment_value_changed(ui::Adjustment adjustment, DeferredAdjustment* self ){
++      self->value_changed( gtk_adjustment_get_value(adjustment) );
++}
++};
++
++
++class TextureBrowser;
++
++typedef ReferenceCaller<TextureBrowser, void(), TextureBrowser_queueDraw> TextureBrowserQueueDrawCaller;
++
++void TextureBrowser_scrollChanged( void* data, gdouble value );
++
++
++enum StartupShaders
++{
++      STARTUPSHADERS_NONE = 0,
++      STARTUPSHADERS_COMMON,
++};
++
++void TextureBrowser_hideUnusedExport( const Callback<void(bool)> & importer );
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_hideUnusedExport> TextureBrowserHideUnusedExport;
++
++void TextureBrowser_showShadersExport( const Callback<void(bool)> & importer );
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShadersExport> TextureBrowserShowShadersExport;
++
++void TextureBrowser_showTexturesExport( const Callback<void(bool)> & importer );
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showTexturesExport> TextureBrowserShowTexturesExport;
++
++void TextureBrowser_showShaderlistOnly( const Callback<void(bool)> & importer );
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShaderlistOnly> TextureBrowserShowShaderlistOnlyExport;
++
++void TextureBrowser_fixedSize( const Callback<void(bool)> & importer );
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_fixedSize> TextureBrowserFixedSizeExport;
++
++void TextureBrowser_filterMissing( const Callback<void(bool)> & importer );
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_filterMissing> TextureBrowserFilterMissingExport;
++
++void TextureBrowser_filterFallback( const Callback<void(bool)> & importer );
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_filterFallback> TextureBrowserFilterFallbackExport;
++
++void TextureBrowser_enableAlpha( const Callback<void(bool)> & importer );
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_enableAlpha> TextureBrowserEnableAlphaExport;
++
++class TextureBrowser
++{
++public:
++int width, height;
++int originy;
++int m_nTotalHeight;
++
++CopiedString shader;
++
++ui::Window m_parent{ui::null};
++ui::GLArea m_gl_widget{ui::null};
++ui::Widget m_texture_scroll{ui::null};
++ui::TreeView m_treeViewTree{ui::New};
++ui::TreeView m_treeViewTags{ui::null};
++ui::Frame m_tag_frame{ui::null};
++ui::ListStore m_assigned_store{ui::null};
++ui::ListStore m_available_store{ui::null};
++ui::TreeView m_assigned_tree{ui::null};
++ui::TreeView m_available_tree{ui::null};
++ui::Widget m_scr_win_tree{ui::null};
++ui::Widget m_scr_win_tags{ui::null};
++ui::Widget m_tag_notebook{ui::null};
++ui::Button m_search_button{ui::null};
++ui::Widget m_shader_info_item{ui::null};
++
++std::set<CopiedString> m_all_tags;
++ui::ListStore m_all_tags_list{ui::null};
++std::vector<CopiedString> m_copied_tags;
++std::set<CopiedString> m_found_shaders;
++
++ToggleItem m_hideunused_item;
++ToggleItem m_hidenotex_item;
++ToggleItem m_showshaders_item;
++ToggleItem m_showtextures_item;
++ToggleItem m_showshaderlistonly_item;
++ToggleItem m_fixedsize_item;
++ToggleItem m_filternotex_item;
++ToggleItem m_enablealpha_item;
++
++guint m_sizeHandler;
++guint m_exposeHandler;
++
++bool m_heightChanged;
++bool m_originInvalid;
++
++DeferredAdjustment m_scrollAdjustment;
++FreezePointer m_freezePointer;
++
++Vector3 color_textureback;
++// the increment step we use against the wheel mouse
++std::size_t m_mouseWheelScrollIncrement;
++std::size_t m_textureScale;
++// make the texture increments match the grid changes
++bool m_showShaders;
++bool m_showTextures;
++bool m_showTextureScrollbar;
++StartupShaders m_startupShaders;
++// if true, the texture window will only display in-use shaders
++// if false, all the shaders in memory are displayed
++bool m_hideUnused;
++bool m_rmbSelected;
++bool m_searchedTags;
++bool m_tags;
++bool m_move_started;
++// The uniform size (in pixels) that textures are resized to when m_resizeTextures is true.
++int m_uniformTextureSize;
++int m_uniformTextureMinSize;
++
++<<<<<<< HEAD
++=======
++bool m_hideNonShadersInCommon;
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++// Return the display width of a texture in the texture browser
++void getTextureWH( qtexture_t* tex, int &W, int &H ){
++              // Don't use uniform size
++              W = (int)( tex->width * ( (float)m_textureScale / 100 ) );
++              H = (int)( tex->height * ( (float)m_textureScale / 100 ) );
++              if ( W < 1 ) W = 1;
++              if ( H < 1 ) H = 1;
++
++      if ( g_TextureBrowser_fixedSize ){
++              if      ( W >= H ) {
++                      // Texture is square, or wider than it is tall
++                      if ( W >= m_uniformTextureSize ){
++                              H = m_uniformTextureSize * H / W;
++                              W = m_uniformTextureSize;
++                      }
++                      else if ( W <= m_uniformTextureMinSize ){
++                              H = m_uniformTextureMinSize * H / W;
++                              W = m_uniformTextureMinSize;
++                      }
++              }
++              else {
++                      // Texture taller than it is wide
++                      if ( H >= m_uniformTextureSize ){
++                              W = m_uniformTextureSize * W / H;
++                              H = m_uniformTextureSize;
++                      }
++                      else if ( H <= m_uniformTextureMinSize ){
++                              W = m_uniformTextureMinSize * W / H;
++                              H = m_uniformTextureMinSize;
++                      }
++              }
++      }
++}
++
++TextureBrowser() :
++      m_texture_scroll( ui::null ),
++      m_hideunused_item( TextureBrowserHideUnusedExport() ),
++      m_hidenotex_item( TextureBrowserFilterFallbackExport() ),
++      m_showshaders_item( TextureBrowserShowShadersExport() ),
++      m_showtextures_item( TextureBrowserShowTexturesExport() ),
++      m_showshaderlistonly_item( TextureBrowserShowShaderlistOnlyExport() ),
++      m_fixedsize_item( TextureBrowserFixedSizeExport() ),
++      m_filternotex_item( TextureBrowserFilterMissingExport() ),
++      m_enablealpha_item( TextureBrowserEnableAlphaExport() ),
++      m_heightChanged( true ),
++      m_originInvalid( true ),
++      m_scrollAdjustment( TextureBrowser_scrollChanged, this ),
++      color_textureback( 0.25f, 0.25f, 0.25f ),
++      m_mouseWheelScrollIncrement( 64 ),
++      m_textureScale( 50 ),
++      m_showShaders( true ),
++      m_showTextures( true ),
++      m_showTextureScrollbar( true ),
++      m_startupShaders( STARTUPSHADERS_NONE ),
++      m_hideUnused( false ),
++      m_rmbSelected( false ),
++      m_searchedTags( false ),
++      m_tags( false ),
++      m_uniformTextureSize( 160 ),
++      m_uniformTextureMinSize( 48 ),
++      m_hideNonShadersInCommon( true ),
++      m_move_started( false ){
++}
++};
++
++void ( *TextureBrowser_textureSelected )( const char* shader );
++
++
++void TextureBrowser_updateScroll( TextureBrowser& textureBrowser );
++
++
++const char* TextureBrowser_getComonShadersName(){
++      const char* value = g_pGameDescription->getKeyValue( "common_shaders_name" );
++      if ( !string_empty( value ) ) {
++              return value;
++      }
++      return "Common";
++}
++
++const char* TextureBrowser_getComonShadersDir(){
++      const char* value = g_pGameDescription->getKeyValue( "common_shaders_dir" );
++      if ( !string_empty( value ) ) {
++              return value;
++      }
++      return "common/";
++}
++
++inline int TextureBrowser_fontHeight( TextureBrowser& textureBrowser ){
++      return GlobalOpenGL().m_font->getPixelHeight();
++}
++
++const char* TextureBrowser_GetSelectedShader( TextureBrowser& textureBrowser ){
++      return textureBrowser.shader.c_str();
++}
++
++void TextureBrowser_SetStatus( TextureBrowser& textureBrowser, const char* name ){
++      IShader* shader = QERApp_Shader_ForName( name );
++      qtexture_t* q = shader->getTexture();
++      StringOutputStream strTex( 256 );
++      strTex << name << " W: " << Unsigned( q->width ) << " H: " << Unsigned( q->height );
++      shader->DecRef();
++      g_pParentWnd->SetStatusText( g_pParentWnd->m_texture_status, strTex.c_str() );
++}
++
++void TextureBrowser_Focus( TextureBrowser& textureBrowser, const char* name );
++
++void TextureBrowser_SetSelectedShader( TextureBrowser& textureBrowser, const char* shader ){
++      textureBrowser.shader = shader;
++      TextureBrowser_SetStatus( textureBrowser, shader );
++      TextureBrowser_Focus( textureBrowser, shader );
++
++      if ( FindTextureDialog_isOpen() ) {
++              FindTextureDialog_selectTexture( shader );
++      }
++
++      // disable the menu item "shader info" if no shader was selected
++      IShader* ishader = QERApp_Shader_ForName( shader );
++      CopiedString filename = ishader->getShaderFileName();
++
++      if ( filename.empty() ) {
++              if ( textureBrowser.m_shader_info_item != NULL ) {
++                      gtk_widget_set_sensitive( textureBrowser.m_shader_info_item, FALSE );
++              }
++      }
++      else {
++              gtk_widget_set_sensitive( textureBrowser.m_shader_info_item, TRUE );
++      }
++
++      ishader->DecRef();
++}
++
++
++CopiedString g_TextureBrowser_currentDirectory;
++
++/*
++   ============================================================================
++
++   TEXTURE LAYOUT
++
++   TTimo: now based on a rundown through all the shaders
++   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
++   ============================================================================
++ */
++
++class TextureLayout
++{
++public:
++// texture layout functions
++// TTimo: now based on shaders
++int current_x, current_y, current_row;
++};
++
++void Texture_StartPos( TextureLayout& layout ){
++      layout.current_x = 8;
++      layout.current_y = -8;
++      layout.current_row = 0;
++}
++
++void Texture_NextPos( TextureBrowser& textureBrowser, TextureLayout& layout, qtexture_t* current_texture, int *x, int *y ){
++      qtexture_t* q = current_texture;
++
++      int nWidth, nHeight;
++      textureBrowser.getTextureWH( q, nWidth, nHeight );
++      if ( layout.current_x + nWidth > textureBrowser.width - 8 && layout.current_row ) { // go to the next row unless the texture is the first on the row
++              layout.current_x = 8;
++              layout.current_y -= layout.current_row + TextureBrowser_fontHeight( textureBrowser ) + 4;//+4
++              layout.current_row = 0;
++      }
++
++      *x = layout.current_x;
++      *y = layout.current_y;
++
++      // Is our texture larger than the row? If so, grow the
++      // row height to match it
++
++      if ( layout.current_row < nHeight ) {
++              layout.current_row = nHeight;
++      }
++
++      // never go less than 96, or the names get all crunched up
++      layout.current_x += nWidth < 96 ? 96 : nWidth;
++      layout.current_x += 8;
++}
++
++bool TextureSearch_IsShown( const char* name ){
++      std::set<CopiedString>::iterator iter;
++
++      iter = GlobalTextureBrowser().m_found_shaders.find( name );
++
++      if ( iter == GlobalTextureBrowser().m_found_shaders.end() ) {
++              return false;
++      }
++      else {
++              return true;
++      }
++}
++
++// if texture_showinuse jump over non in-use textures
++<<<<<<< HEAD
++bool Texture_IsShown( IShader* shader, bool show_shaders, bool show_textures, bool hideUnused ){
++      // filter missing shaders
++      // ugly: filter on built-in fallback name after substitution
++      if ( g_TextureBrowser_filterMissing ) {
++              if ( isMissing( shader->getTexture()->name ) ) {
++                      return false;
++              }
++      }
++      // filter the fallback (notex/shadernotex) for missing shaders or editor image
++      if ( g_TextureBrowser_filterFallback ) {
++              if ( isNotex( shader->getName() ) ) {
++                      return false;
++              }
++              if ( isNotex( shader->getTexture()->name ) ) {
++                      return false;
++              }
++=======
++bool Texture_IsShown( IShader* shader, bool show_shaders, bool show_textures, bool hideUnused, bool hideNonShadersInCommon ){
++      // filter notex / shadernotex images
++      if ( g_TextureBrowser_filterNotex && ( string_equal( g_notex.c_str(), shader->getTexture()->name ) || string_equal( g_shadernotex.c_str(), shader->getTexture()->name ) ) ) {
++              return false;
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++      }
++
++      if ( g_TextureBrowser_currentDirectory == "Untagged" ) {
++              std::set<CopiedString>::iterator iter;
++
++              iter = GlobalTextureBrowser().m_found_shaders.find( shader->getName() );
++
++              if ( iter == GlobalTextureBrowser().m_found_shaders.end() ) {
++                      return false;
++              }
++              else {
++                      return true;
++              }
++      }
++
++      if ( !shader_equal_prefix( shader->getName(), "textures/" ) ) {
++              return false;
++      }
++
++      if ( !show_shaders && !shader->IsDefault() ) {
++              return false;
++      }
++
++      if ( !show_textures && shader->IsDefault() ) {
++              return false;
++      }
++
++      if ( hideUnused && !shader->IsInUse() ) {
++              return false;
++      }
++
++      if( hideNonShadersInCommon && shader->IsDefault() && !shader->IsInUse() //&& g_TextureBrowser_currentDirectory != ""
++              && shader_equal_prefix( shader_get_textureName( shader->getName() ), TextureBrowser_getComonShadersDir() ) ){
++              return false;
++      }
++
++      if ( GlobalTextureBrowser().m_searchedTags ) {
++              if ( !TextureSearch_IsShown( shader->getName() ) ) {
++                      return false;
++              }
++              else {
++                      return true;
++              }
++      }
++      else {
++              if ( !shader_equal_prefix( shader_get_textureName( shader->getName() ), g_TextureBrowser_currentDirectory.c_str() ) ) {
++                      return false;
++              }
++      }
++
++      return true;
++}
++
++void TextureBrowser_heightChanged( TextureBrowser& textureBrowser ){
++      textureBrowser.m_heightChanged = true;
++
++      TextureBrowser_updateScroll( textureBrowser );
++      TextureBrowser_queueDraw( textureBrowser );
++}
++
++void TextureBrowser_evaluateHeight( TextureBrowser& textureBrowser ){
++      if ( textureBrowser.m_heightChanged ) {
++              textureBrowser.m_heightChanged = false;
++
++              textureBrowser.m_nTotalHeight = 0;
++
++              TextureLayout layout;
++              Texture_StartPos( layout );
++              for ( QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement() )
++              {
++                      IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
++
++                      if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_showTextures, textureBrowser.m_hideUnused, textureBrowser.m_hideNonShadersInCommon ) ) {
++                              continue;
++                      }
++
++                      int x, y;
++                      Texture_NextPos( textureBrowser, layout, shader->getTexture(), &x, &y );
++                      int nWidth, nHeight;
++                      textureBrowser.getTextureWH( shader->getTexture(), nWidth, nHeight );
++                      textureBrowser.m_nTotalHeight = std::max( textureBrowser.m_nTotalHeight, abs( layout.current_y ) + TextureBrowser_fontHeight( textureBrowser ) + nHeight + 4 );
++              }
++      }
++}
++
++int TextureBrowser_TotalHeight( TextureBrowser& textureBrowser ){
++      TextureBrowser_evaluateHeight( textureBrowser );
++      return textureBrowser.m_nTotalHeight;
++}
++
++inline const int& min_int( const int& left, const int& right ){
++      return std::min( left, right );
++}
++
++void TextureBrowser_clampOriginY( TextureBrowser& textureBrowser ){
++      if ( textureBrowser.originy > 0 ) {
++              textureBrowser.originy = 0;
++      }
++      int lower = min_int( textureBrowser.height - TextureBrowser_TotalHeight( textureBrowser ), 0 );
++      if ( textureBrowser.originy < lower ) {
++              textureBrowser.originy = lower;
++      }
++}
++
++int TextureBrowser_getOriginY( TextureBrowser& textureBrowser ){
++      if ( textureBrowser.m_originInvalid ) {
++              textureBrowser.m_originInvalid = false;
++              TextureBrowser_clampOriginY( textureBrowser );
++              TextureBrowser_updateScroll( textureBrowser );
++      }
++      return textureBrowser.originy;
++}
++
++void TextureBrowser_setOriginY( TextureBrowser& textureBrowser, int originy ){
++      textureBrowser.originy = originy;
++      TextureBrowser_clampOriginY( textureBrowser );
++      TextureBrowser_updateScroll( textureBrowser );
++      TextureBrowser_queueDraw( textureBrowser );
++}
++
++
++Signal0 g_activeShadersChangedCallbacks;
++
++void TextureBrowser_addActiveShadersChangedCallback( const SignalHandler& handler ){
++      g_activeShadersChangedCallbacks.connectLast( handler );
++}
++
++void TextureBrowser_constructTreeStore();
++
++class ShadersObserver : public ModuleObserver
++{
++Signal0 m_realiseCallbacks;
++public:
++void realise(){
++      m_realiseCallbacks();
++      /* texturebrowser tree update on vfs restart */
++//    TextureBrowser_constructTreeStore();
++}
++
++void unrealise(){
++}
++
++void insert( const SignalHandler& handler ){
++      m_realiseCallbacks.connectLast( handler );
++}
++};
++
++namespace
++{
++ShadersObserver g_ShadersObserver;
++}
++
++void TextureBrowser_addShadersRealiseCallback( const SignalHandler& handler ){
++      g_ShadersObserver.insert( handler );
++}
++
++void TextureBrowser_activeShadersChanged( TextureBrowser& textureBrowser ){
++      TextureBrowser_heightChanged( textureBrowser );
++      textureBrowser.m_originInvalid = true;
++
++      g_activeShadersChangedCallbacks();
++}
++
++struct TextureBrowser_ShowScrollbar {
++      static void Export(const TextureBrowser &self, const Callback<void(bool)> &returnz) {
++              returnz(self.m_showTextureScrollbar);
++      }
++
++      static void Import(TextureBrowser &self, bool value) {
++              self.m_showTextureScrollbar = value;
++              if (self.m_texture_scroll) {
++                      self.m_texture_scroll.visible(self.m_showTextureScrollbar);
++                      TextureBrowser_updateScroll(self);
++              }
++      }
++};
++
++
++/*
++   ==============
++   TextureBrowser_ShowDirectory
++   relies on texture_directory global for the directory to use
++   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)
++   ==============
++ */
++
++bool endswith( const char *haystack, const char *needle ){
++      size_t lh = strlen( haystack );
++      size_t ln = strlen( needle );
++      if ( lh < ln ) {
++              return false;
++      }
++      return !memcmp( haystack + ( lh - ln ), needle, ln );
++}
++
++bool texture_name_ignore( const char* name ){
++      StringOutputStream strTemp( string_length( name ) );
++      strTemp << LowerCase( name );
++
++      return
++              endswith( strTemp.c_str(), ".specular" ) ||
++              endswith( strTemp.c_str(), ".glow" ) ||
++              endswith( strTemp.c_str(), ".bump" ) ||
++              endswith( strTemp.c_str(), ".diffuse" ) ||
++              endswith( strTemp.c_str(), ".blend" ) ||
++              endswith( strTemp.c_str(), ".alpha" ) ||
++              endswith( strTemp.c_str(), "_alpha" ) ||
++              /* Quetoo */
++              endswith( strTemp.c_str(), "_h" ) ||
++              endswith( strTemp.c_str(), "_local" ) ||
++              endswith( strTemp.c_str(), "_nm" ) ||
++              endswith( strTemp.c_str(), "_s" ) ||
++              /* DarkPlaces */
++              endswith( strTemp.c_str(), "_bump" ) ||
++              endswith( strTemp.c_str(), "_glow" ) ||
++              endswith( strTemp.c_str(), "_gloss" ) ||
++              endswith( strTemp.c_str(), "_luma" ) ||
++              endswith( strTemp.c_str(), "_norm" ) ||
++              endswith( strTemp.c_str(), "_pants" ) ||
++              endswith( strTemp.c_str(), "_shirt" ) ||
++              endswith( strTemp.c_str(), "_reflect" ) ||
++              /* Unvanquished */
++              endswith( strTemp.c_str(), "_d" ) ||
++              endswith( strTemp.c_str(), "_n" ) ||
++              endswith( strTemp.c_str(), "_p" ) ||
++              endswith( strTemp.c_str(), "_g" ) ||
++              endswith( strTemp.c_str(), "_a" ) ||
++              0;
++}
++
++class LoadShaderVisitor : public Archive::Visitor
++{
++public:
++void visit( const char* name ){
++      IShader* shader = QERApp_Shader_ForName( CopiedString( StringRange( name, path_get_filename_base_end( name ) ) ).c_str() );
++      shader->DecRef();
++}
++};
++
++void TextureBrowser_SetHideUnused( TextureBrowser& textureBrowser, bool hideUnused );
++
++ui::Widget g_page_textures{ui::null};
++
++void TextureBrowser_toggleShow(){
++      GroupDialog_showPage( g_page_textures );
++}
++
++
++void TextureBrowser_updateTitle(){
++      GroupDialog_updatePageTitle( g_page_textures );
++}
++
++
++class TextureCategoryLoadShader
++{
++const char* m_directory;
++std::size_t& m_count;
++public:
++using func = void(const char *);
++
++TextureCategoryLoadShader( const char* directory, std::size_t& count )
++      : m_directory( directory ), m_count( count ){
++      m_count = 0;
++}
++
++void operator()( const char* name ) const {
++      if ( shader_equal_prefix( name, "textures/" )
++               && shader_equal_prefix( name + string_length( "textures/" ), m_directory ) ) {
++              ++m_count;
++              // request the shader, this will load the texture if needed
++              // this Shader_ForName call is a kind of hack
++              IShader *pFoo = QERApp_Shader_ForName( name );
++              pFoo->DecRef();
++      }
++}
++};
++
++void TextureDirectory_loadTexture( const char* directory, const char* texture ){
++      StringOutputStream name( 256 );
++      name << directory << StringRange( texture, path_get_filename_base_end( texture ) );
++
++      if ( texture_name_ignore( name.c_str() ) ) {
++              return;
++      }
++
++      if ( !shader_valid( name.c_str() ) ) {
++              globalOutputStream() << "Skipping invalid texture name: [" << name.c_str() << "]\n";
++              return;
++      }
++
++      // if a texture is already in use to represent a shader, ignore it
++      IShader* shader = QERApp_Shader_ForName( name.c_str() );
++      shader->DecRef();
++}
++
++typedef ConstPointerCaller<char, void(const char*), TextureDirectory_loadTexture> TextureDirectoryLoadTextureCaller;
++
++class LoadTexturesByTypeVisitor : public ImageModules::Visitor
++{
++const char* m_dirstring;
++public:
++LoadTexturesByTypeVisitor( const char* dirstring )
++      : m_dirstring( dirstring ){
++}
++
++void visit( const char* minor, const _QERPlugImageTable& table ) const {
++      GlobalFileSystem().forEachFile( m_dirstring, minor, TextureDirectoryLoadTextureCaller( m_dirstring ) );
++}
++};
++
++void TextureBrowser_ShowDirectory( TextureBrowser& textureBrowser, const char* directory ){
++      if ( TextureBrowser_showWads() ) {
++              Archive* archive = GlobalFileSystem().getArchive( directory );
++              ASSERT_NOTNULL( archive );
++              LoadShaderVisitor visitor;
++              archive->forEachFile( Archive::VisitorFunc( visitor, Archive::eFiles, 0 ), "textures/" );
++      }
++      else
++      {
++              g_TextureBrowser_currentDirectory = directory;
++              TextureBrowser_heightChanged( textureBrowser );
++
++              std::size_t shaders_count;
++              GlobalShaderSystem().foreachShaderName(makeCallback( TextureCategoryLoadShader( directory, shaders_count ) ) );
++              globalOutputStream() << "Showing " << Unsigned( shaders_count ) << " shaders.\n";
++
++              if ( g_pGameDescription->mGameType != "doom3" ) {
++                      // load remaining texture files
++
++                      StringOutputStream dirstring( 64 );
++                      dirstring << "textures/" << directory;
++
++                      Radiant_getImageModules().foreachModule( LoadTexturesByTypeVisitor( dirstring.c_str() ) );
++              }
++      }
++
++      // we'll display the newly loaded textures + all the ones already in use
++      TextureBrowser_SetHideUnused( textureBrowser, false );
++
++      TextureBrowser_updateTitle();
++}
++
++void TextureBrowser_ShowTagSearchResult( TextureBrowser& textureBrowser, const char* directory ){
++      g_TextureBrowser_currentDirectory = directory;
++      TextureBrowser_heightChanged( textureBrowser );
++
++      std::size_t shaders_count;
++      GlobalShaderSystem().foreachShaderName(makeCallback( TextureCategoryLoadShader( directory, shaders_count ) ) );
++      globalOutputStream() << "Showing " << Unsigned( shaders_count ) << " shaders.\n";
++
++      if ( g_pGameDescription->mGameType != "doom3" ) {
++              // load remaining texture files
++              StringOutputStream dirstring( 64 );
++              dirstring << "textures/" << directory;
++
++              {
++                      LoadTexturesByTypeVisitor visitor( dirstring.c_str() );
++                      Radiant_getImageModules().foreachModule( visitor );
++              }
++      }
++
++      // we'll display the newly loaded textures + all the ones already in use
++      TextureBrowser_SetHideUnused( textureBrowser, false );
++}
++
++
++bool TextureBrowser_hideUnused();
++
++void TextureBrowser_hideUnusedExport( const Callback<void(bool)> & importer ){
++      importer( TextureBrowser_hideUnused() );
++}
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_hideUnusedExport> TextureBrowserHideUnusedExport;
++
++void TextureBrowser_showShadersExport( const Callback<void(bool)> & importer ){
++      importer( GlobalTextureBrowser().m_showShaders );
++}
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShadersExport> TextureBrowserShowShadersExport;
++
++void TextureBrowser_showTexturesExport( const Callback<void(bool)> & importer ){
++      importer( GlobalTextureBrowser().m_showTextures );
++}
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showTexturesExport> TextureBrowserShowTexturesExport;
++
++void TextureBrowser_showShaderlistOnly( const Callback<void(bool)> & importer ){
++      importer( g_TextureBrowser_shaderlistOnly );
++}
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShaderlistOnly> TextureBrowserShowShaderlistOnlyExport;
++
++void TextureBrowser_fixedSize( const Callback<void(bool)> & importer ){
++      importer( g_TextureBrowser_fixedSize );
++}
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_fixedSize> TextureBrowser_FixedSizeExport;
++
++void TextureBrowser_filterMissing( const Callback<void(bool)> & importer ){
++      importer( g_TextureBrowser_filterMissing );
++}
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_filterMissing> TextureBrowser_filterMissingExport;
++
++void TextureBrowser_filterFallback( const Callback<void(bool)> & importer ){
++      importer( g_TextureBrowser_filterFallback );
++}
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_filterFallback> TextureBrowser_filterFallbackExport;
++
++void TextureBrowser_enableAlpha( const Callback<void(bool)> & importer ){
++      importer( g_TextureBrowser_enableAlpha );
++}
++
++typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_enableAlpha> TextureBrowser_enableAlphaExport;
++
++void TextureBrowser_SetHideUnused( TextureBrowser& textureBrowser, bool hideUnused ){
++      textureBrowser.m_hideUnused = hideUnused;
++
++      textureBrowser.m_hideunused_item.update();
++
++      TextureBrowser_heightChanged( textureBrowser );
++      textureBrowser.m_originInvalid = true;
++}
++
++void TextureBrowser_ShowStartupShaders( TextureBrowser& textureBrowser ){
++      if ( textureBrowser.m_startupShaders == STARTUPSHADERS_COMMON ) {
++              TextureBrowser_ShowDirectory( textureBrowser, TextureBrowser_getComonShadersDir() );
++      }
++}
++
++
++//++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
++// scroll origin so the specified texture is completely on screen
++// if current texture is not displayed, nothing is changed
++void TextureBrowser_Focus( TextureBrowser& textureBrowser, const char* name ){
++      TextureLayout layout;
++      // scroll origin so the texture is completely on screen
++      Texture_StartPos( layout );
++
++      for ( QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement() )
++      {
++              IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
++
++              if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_showTextures, textureBrowser.m_hideUnused, textureBrowser.m_hideNonShadersInCommon ) ) {
++                      continue;
++              }
++
++              int x, y;
++              Texture_NextPos( textureBrowser, layout, shader->getTexture(), &x, &y );
++              qtexture_t* q = shader->getTexture();
++              if ( !q ) {
++                      break;
++              }
++
++              // we have found when texdef->name and the shader name match
++              // NOTE: as everywhere else for our comparisons, we are not case sensitive
++              if ( shader_equal( name, shader->getName() ) ) {
++                      //int textureHeight = (int)( q->height * ( (float)textureBrowser.m_textureScale / 100 ) ) + 2 * TextureBrowser_fontHeight( textureBrowser );
++                      int textureWidth, textureHeight;
++                      textureBrowser.getTextureWH( q, textureWidth, textureHeight );
++                      textureHeight += 2 * TextureBrowser_fontHeight( textureBrowser );
++
++
++                      int originy = TextureBrowser_getOriginY( textureBrowser );
++                      if ( y > originy ) {
++                              originy = y + 4;
++                      }
++
++                      if ( y - textureHeight < originy - textureBrowser.height ) {
++                              originy = ( y - textureHeight ) + textureBrowser.height;
++                      }
++
++                      TextureBrowser_setOriginY( textureBrowser, originy );
++                      return;
++              }
++      }
++}
++
++IShader* Texture_At( TextureBrowser& textureBrowser, int mx, int my ){
++      my += TextureBrowser_getOriginY( textureBrowser ) - textureBrowser.height;
++
++      TextureLayout layout;
++      Texture_StartPos( layout );
++      for ( QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement() )
++      {
++              IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
++
++              if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_showTextures, textureBrowser.m_hideUnused, textureBrowser.m_hideNonShadersInCommon ) ) {
++                      continue;
++              }
++
++              int x, y;
++              Texture_NextPos( textureBrowser, layout, shader->getTexture(), &x, &y );
++              qtexture_t  *q = shader->getTexture();
++              if ( !q ) {
++                      break;
++              }
++
++              int nWidth, nHeight;
++              textureBrowser.getTextureWH( q, nWidth, nHeight );
++              if ( mx > x && mx - x < nWidth
++                       && my < y && y - my < nHeight + TextureBrowser_fontHeight( textureBrowser ) ) {
++                      return shader;
++              }
++      }
++
++      return 0;
++}
++
++/*
++   ==============
++   SelectTexture
++
++   By mouse click
++   ==============
++ */
++void SelectTexture( TextureBrowser& textureBrowser, int mx, int my, bool bShift ){
++      IShader* shader = Texture_At( textureBrowser, mx, my );
++      if ( shader != 0 ) {
++              if ( bShift ) {
++                      if ( shader->IsDefault() ) {
++                              globalOutputStream() << "ERROR: " << shader->getName() << " is not a shader, it's a texture.\n";
++                      }
++                      else{
++                              ViewShader( shader->getShaderFileName(), shader->getName() );
++                      }
++              }
++              else
++              {
++                      TextureBrowser_SetSelectedShader( textureBrowser, shader->getName() );
++                      TextureBrowser_textureSelected( shader->getName() );
++
++                      if ( !FindTextureDialog_isOpen() && !textureBrowser.m_rmbSelected ) {
++                              UndoableCommand undo( "textureNameSetSelected" );
++                              Select_SetShader( shader->getName() );
++                      }
++              }
++      }
++}
++
++/*
++   ============================================================================
++
++   MOUSE ACTIONS
++
++   ============================================================================
++ */
++
++void TextureBrowser_trackingDelta( int x, int y, unsigned int state, void* data ){
++      TextureBrowser& textureBrowser = *reinterpret_cast<TextureBrowser*>( data );
++      if ( y != 0 ) {
++              int scale = 1;
++
++              if ( state & GDK_SHIFT_MASK ) {
++                      scale = 4;
++              }
++
++              int originy = TextureBrowser_getOriginY( textureBrowser );
++              originy += y * scale;
++              TextureBrowser_setOriginY( textureBrowser, originy );
++      }
++}
++
++void TextureBrowser_Tracking_MouseUp( TextureBrowser& textureBrowser ){
++      textureBrowser.m_move_started = false;
++      textureBrowser.m_freezePointer.unfreeze_pointer( textureBrowser.m_parent, false );
++}
++
++void TextureBrowser_Tracking_MouseDown( TextureBrowser& textureBrowser ){
++      if( textureBrowser.m_move_started ){
++              TextureBrowser_Tracking_MouseUp( textureBrowser );
++      }
++      textureBrowser.m_move_started = true;
++      textureBrowser.m_freezePointer.freeze_pointer( textureBrowser.m_parent, textureBrowser.m_gl_widget, TextureBrowser_trackingDelta, &textureBrowser );
++}
++
++void TextureBrowser_Selection_MouseDown( TextureBrowser& textureBrowser, guint32 flags, int pointx, int pointy ){
++      SelectTexture( textureBrowser, pointx, textureBrowser.height - 1 - pointy, ( flags & GDK_SHIFT_MASK ) != 0 );
++}
++
++/*
++   ============================================================================
++
++   DRAWING
++
++   ============================================================================
++ */
++
++/*
++   ============
++   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( TextureBrowser& textureBrowser ){
++      int originy = TextureBrowser_getOriginY( textureBrowser );
++
++      glClearColor( textureBrowser.color_textureback[0],
++                                textureBrowser.color_textureback[1],
++                                textureBrowser.color_textureback[2],
++                                0 );
++      glViewport( 0, 0, textureBrowser.width, textureBrowser.height );
++      glMatrixMode( GL_PROJECTION );
++      glLoadIdentity();
++
++      glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
++      glDisable( GL_DEPTH_TEST );
++
++      //glDisable( GL_BLEND );
++      if ( g_TextureBrowser_enableAlpha ) {
++              glEnable( GL_BLEND );
++              glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
++      }
++      else {
++              glDisable( GL_BLEND );
++      }
++
++      glOrtho( 0, textureBrowser.width, originy - textureBrowser.height, originy, -100, 100 );
++      glEnable( GL_TEXTURE_2D );
++
++      glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
++
++      int last_y = 0, last_height = 0;
++
++      TextureLayout layout;
++      Texture_StartPos( layout );
++      for ( QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement() )
++      {
++              IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
++
++              if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_showTextures, textureBrowser.m_hideUnused, textureBrowser.m_hideNonShadersInCommon ) ) {
++                      continue;
++              }
++
++              int x, y;
++              Texture_NextPos( textureBrowser, layout, shader->getTexture(), &x, &y );
++              qtexture_t *q = shader->getTexture();
++              if ( !q ) {
++                      break;
++              }
++
++              int nWidth, nHeight;
++              textureBrowser.getTextureWH( q, nWidth, nHeight );
++
++              if ( y != last_y ) {
++                      last_y = y;
++                      last_height = 0;
++              }
++              last_height = std::max( nHeight, last_height );
++
++              // Is this texture visible?
++              if ( ( y - nHeight - TextureBrowser_fontHeight( textureBrowser ) < originy )
++                       && ( y > originy - textureBrowser.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
++                      glLineWidth( 1 );
++                      const float xf = (float)x;
++                      const float yf = (float)( y - TextureBrowser_fontHeight( textureBrowser ) );
++                      float xfMax = xf + 1.5 + nWidth;
++                      float xfMin = xf - 1.5;
++                      float yfMax = yf + 1.5;
++                      float yfMin = yf - nHeight - 1.5;
++
++                      //selected texture
++                      if ( shader_equal( TextureBrowser_GetSelectedShader( textureBrowser ), shader->getName() ) ) {
++                              glLineWidth( 2 );
++                              if ( textureBrowser.m_rmbSelected ) {
++                                      glColor3f( 0,0,1 );
++                              }
++                              else {
++                                      glColor3f( 1,0,0 );
++                              }
++                              xfMax += .5;
++                              xfMin -= .5;
++                              yfMax += .5;
++                              yfMin -= .5;
++                              glDisable( GL_TEXTURE_2D );
++                              glBegin( GL_LINE_LOOP );
++                              glVertex2f( xfMin ,yfMax );
++                              glVertex2f( xfMin ,yfMin );
++                              glVertex2f( xfMax ,yfMin );
++                              glVertex2f( xfMax ,yfMax );
++                              glEnd();
++                              glEnable( GL_TEXTURE_2D );
++                      }
++                      // highlight in-use textures
++                      else if ( !textureBrowser.m_hideUnused && shader->IsInUse() ) {
++                              glColor3f( 0.5,1,0.5 );
++                              glDisable( GL_TEXTURE_2D );
++                              glBegin( GL_LINE_LOOP );
++                              glVertex2f( xfMin ,yfMax );
++                              glVertex2f( xfMin ,yfMin );
++                              glVertex2f( xfMax ,yfMin );
++                              glVertex2f( xfMax ,yfMax );
++                              glEnd();
++                              glEnable( GL_TEXTURE_2D );
++                      }
++                      // shader white border:
++                      else if ( !shader->IsDefault() ) {
++                              glColor3f( 1, 1, 1 );
++                              glDisable( GL_TEXTURE_2D );
++                              glBegin( GL_LINE_LOOP );
++                              glVertex2f( xfMin ,yfMax );
++                              glVertex2f( xfMin ,yfMin );
++                              glVertex2f( xfMax ,yfMin );
++                              glVertex2f( xfMax ,yfMax );
++                              glEnd();
++                              glEnable( GL_TEXTURE_2D );
++                      }
++
++                      // shader stipple:
++                      if ( !shader->IsDefault() ) {
++                              glEnable( GL_LINE_STIPPLE );
++                              glLineStipple( 1, 0xF000 );
++                              glDisable( GL_TEXTURE_2D );
++                              glBegin( GL_LINE_LOOP );
++                              glColor3f( 0, 0, 0 );
++                              glVertex2f( xfMin ,yfMax );
++                              glVertex2f( xfMin ,yfMin );
++                              glVertex2f( xfMax ,yfMin );
++                              glVertex2f( xfMax ,yfMax );
++                              glEnd();
++                              glDisable( GL_LINE_STIPPLE );
++                              glEnable( GL_TEXTURE_2D );
++                      }
++
++                      // draw checkerboard for transparent textures
++                      if ( g_TextureBrowser_enableAlpha )
++                      {
++                              glDisable( GL_TEXTURE_2D );
++                              glBegin( GL_QUADS );
++                              int font_height = TextureBrowser_fontHeight( textureBrowser );
++                              for ( int i = 0; i < nHeight; i += 8 )
++                                      for ( int j = 0; j < nWidth; j += 8 )
++                                      {
++                                              unsigned char color = (i + j) / 8 % 2 ? 0x66 : 0x99;
++                                              glColor3ub( color, color, color );
++                                              int left = j;
++                                              int right = std::min(j+8, nWidth);
++                                              int top = i;
++                                              int bottom = std::min(i+8, nHeight);
++                                              glVertex2i(x + right, y - nHeight - font_height + top);
++                                              glVertex2i(x + left,  y - nHeight - font_height + top);
++                                              glVertex2i(x + left,  y - nHeight - font_height + bottom);
++                                              glVertex2i(x + right, y - nHeight - font_height + bottom);
++                                      }
++                              glEnd();
++                              glEnable( GL_TEXTURE_2D );
++                      }
++
++                      // Draw the texture
++                      glBindTexture( GL_TEXTURE_2D, q->texture_number );
++                      GlobalOpenGL_debugAssertNoErrors();
++                      glColor3f( 1,1,1 );
++                      glBegin( GL_QUADS );
++                      glTexCoord2i( 0,0 );
++                      glVertex2i( x,y - TextureBrowser_fontHeight( textureBrowser ) );
++                      glTexCoord2i( 1,0 );
++                      glVertex2i( x + nWidth,y - TextureBrowser_fontHeight( textureBrowser ) );
++                      glTexCoord2i( 1,1 );
++                      glVertex2i( x + nWidth,y - TextureBrowser_fontHeight( textureBrowser ) - nHeight );
++                      glTexCoord2i( 0,1 );
++                      glVertex2i( x,y - TextureBrowser_fontHeight( textureBrowser ) - nHeight );
++                      glEnd();
++
++                      // draw the texture name
++                      glDisable( GL_TEXTURE_2D );
++                      glColor3f( 1,1,1 );
++
++                      glRasterPos2i( x, y - TextureBrowser_fontHeight( textureBrowser ) + 2 );//+5
++
++                      // don't draw the directory name
++                      const char* name = shader->getName();
++                      name += strlen( name );
++                      while ( name != shader->getName() && *( name - 1 ) != '/' && *( name - 1 ) != '\\' )
++                              name--;
++
++                      GlobalOpenGL().drawString( name );
++                      glEnable( GL_TEXTURE_2D );
++              }
++
++              //int totalHeight = abs(y) + last_height + TextureBrowser_fontHeight(textureBrowser) + 4;
++      }
++
++
++      // reset the current texture
++      glBindTexture( GL_TEXTURE_2D, 0 );
++      //qglFinish();
++}
++
++void TextureBrowser_queueDraw( TextureBrowser& textureBrowser ){
++      if ( textureBrowser.m_gl_widget ) {
++              gtk_widget_queue_draw( textureBrowser.m_gl_widget );
++      }
++}
++
++
++void TextureBrowser_setScale( TextureBrowser& textureBrowser, std::size_t scale ){
++      textureBrowser.m_textureScale = scale;
++
++      textureBrowser.m_heightChanged = true;
++      textureBrowser.m_originInvalid = true;
++      g_activeShadersChangedCallbacks();
++
++      TextureBrowser_queueDraw( textureBrowser );
++}
++
++void TextureBrowser_setUniformSize( TextureBrowser& textureBrowser, std::size_t scale ){
++      textureBrowser.m_uniformTextureSize = scale;
++
++      textureBrowser.m_heightChanged = true;
++      textureBrowser.m_originInvalid = true;
++      g_activeShadersChangedCallbacks();
++
++      TextureBrowser_queueDraw( textureBrowser );
++}
++
++void TextureBrowser_setUniformMinSize( TextureBrowser& textureBrowser, std::size_t scale ){
++      textureBrowser.m_uniformTextureMinSize = scale;
++
++      textureBrowser.m_heightChanged = true;
++      textureBrowser.m_originInvalid = true;
++      g_activeShadersChangedCallbacks();
++
++      TextureBrowser_queueDraw( textureBrowser );
++}
++
++void TextureBrowser_MouseWheel( TextureBrowser& textureBrowser, bool bUp ){
++      int originy = TextureBrowser_getOriginY( textureBrowser );
++
++      if ( bUp ) {
++              originy += int(textureBrowser.m_mouseWheelScrollIncrement);
++      }
++      else
++      {
++              originy -= int(textureBrowser.m_mouseWheelScrollIncrement);
++      }
++
++      TextureBrowser_setOriginY( textureBrowser, originy );
++}
++
++XmlTagBuilder TagBuilder;
++
++enum
++{
++      TAG_COLUMN,
++      N_COLUMNS
++};
++
++void BuildStoreAssignedTags( ui::ListStore store, const char* shader, TextureBrowser* textureBrowser ){
++      GtkTreeIter iter;
++
++      store.clear();
++
++      std::vector<CopiedString> assigned_tags;
++      TagBuilder.GetShaderTags( shader, assigned_tags );
++
++      for ( size_t i = 0; i < assigned_tags.size(); i++ )
++      {
++              store.append(TAG_COLUMN, assigned_tags[i].c_str());
++      }
++}
++
++void BuildStoreAvailableTags(   ui::ListStore storeAvailable,
++                                                              ui::ListStore storeAssigned,
++                                                              const std::set<CopiedString>& allTags,
++                                                              TextureBrowser* textureBrowser ){
++      GtkTreeIter iterAssigned;
++      GtkTreeIter iterAvailable;
++      std::set<CopiedString>::const_iterator iterAll;
++      gchar* tag_assigned;
++
++      storeAvailable.clear();
++
++      bool row = gtk_tree_model_get_iter_first(storeAssigned, &iterAssigned ) != 0;
++
++      if ( !row ) { // does the shader have tags assigned?
++              for ( iterAll = allTags.begin(); iterAll != allTags.end(); ++iterAll )
++              {
++                      storeAvailable.append(TAG_COLUMN, (*iterAll).c_str());
++              }
++      }
++      else
++      {
++              while ( row ) // available tags = all tags - assigned tags
++              {
++                      gtk_tree_model_get(storeAssigned, &iterAssigned, TAG_COLUMN, &tag_assigned, -1 );
++
++                      for ( iterAll = allTags.begin(); iterAll != allTags.end(); ++iterAll )
++                      {
++                              if ( strcmp( (char*)tag_assigned, ( *iterAll ).c_str() ) != 0 ) {
++                                      storeAvailable.append(TAG_COLUMN, (*iterAll).c_str());
++                              }
++                              else
++                              {
++                                      row = gtk_tree_model_iter_next(storeAssigned, &iterAssigned ) != 0;
++
++                                      if ( row ) {
++                                              gtk_tree_model_get(storeAssigned, &iterAssigned, TAG_COLUMN, &tag_assigned, -1 );
++                                      }
++                              }
++                      }
++              }
++      }
++}
++
++gboolean TextureBrowser_button_press( ui::Widget widget, GdkEventButton* event, TextureBrowser* textureBrowser ){
++      if ( event->type == GDK_BUTTON_PRESS ) {
++              if ( event->button == 3 ) {
++                      if ( GlobalTextureBrowser().m_tags ) {
++                              textureBrowser->m_rmbSelected = true;
++                              TextureBrowser_Selection_MouseDown( *textureBrowser, event->state, static_cast<int>( event->x ), static_cast<int>( event->y ) );
++
++                              BuildStoreAssignedTags( textureBrowser->m_assigned_store, textureBrowser->shader.c_str(), textureBrowser );
++                              BuildStoreAvailableTags( textureBrowser->m_available_store, textureBrowser->m_assigned_store, textureBrowser->m_all_tags, textureBrowser );
++                              textureBrowser->m_heightChanged = true;
++                              textureBrowser->m_tag_frame.show();
++
++                ui::process();
++
++                              TextureBrowser_Focus( *textureBrowser, textureBrowser->shader.c_str() );
++                      }
++                      else
++                      {
++                              TextureBrowser_Tracking_MouseDown( *textureBrowser );
++                      }
++              }
++              else if ( event->button == 1 ) {
++                      TextureBrowser_Selection_MouseDown( *textureBrowser, event->state, static_cast<int>( event->x ), static_cast<int>( event->y ) );
++
++                      if ( GlobalTextureBrowser().m_tags ) {
++                              textureBrowser->m_rmbSelected = false;
++                              textureBrowser->m_tag_frame.hide();
++                      }
++              }
++      }
++      else if ( event->type == GDK_2BUTTON_PRESS && event->button == 1 ) {
++              #define GARUX_DISABLE_2BUTTON
++              #ifndef GARUX_DISABLE_2BUTTON
++              CopiedString texName = textureBrowser->shader;
++              //const char* sh = texName.c_str();
++              char* sh = const_cast<char*>( texName.c_str() );
++              char* dir = strrchr( sh, '/' );
++              if( dir != NULL ){
++                      *(dir + 1) = '\0';
++                      dir = strchr( sh, '/' );
++                      if( dir != NULL ){
++                              dir++;
++                              if( *dir != '\0'){
++                                      ScopeDisableScreenUpdates disableScreenUpdates( dir, "Loading Textures" );
++                                      TextureBrowser_ShowDirectory( *textureBrowser, dir );
++                                      TextureBrowser_Focus( *textureBrowser, textureBrowser->shader.c_str() );
++                                      TextureBrowser_queueDraw( *textureBrowser );
++                              }
++                      }
++              }
++              #endif
++      }
++      else if ( event->type == GDK_2BUTTON_PRESS && event->button == 3 ) {
++              ScopeDisableScreenUpdates disableScreenUpdates( TextureBrowser_getComonShadersDir(), "Loading Textures" );
++              TextureBrowser_ShowDirectory( *textureBrowser, TextureBrowser_getComonShadersDir() );
++              TextureBrowser_queueDraw( *textureBrowser );
++      }
++      return FALSE;
++}
++
++gboolean TextureBrowser_button_release( ui::Widget widget, GdkEventButton* event, TextureBrowser* textureBrowser ){
++      if ( event->type == GDK_BUTTON_RELEASE ) {
++              if ( event->button == 3 ) {
++                      if ( !GlobalTextureBrowser().m_tags ) {
++                              TextureBrowser_Tracking_MouseUp( *textureBrowser );
++                      }
++              }
++      }
++      return FALSE;
++}
++
++gboolean TextureBrowser_motion( ui::Widget widget, GdkEventMotion *event, TextureBrowser* textureBrowser ){
++      return FALSE;
++}
++
++gboolean TextureBrowser_scroll( ui::Widget widget, GdkEventScroll* event, TextureBrowser* textureBrowser ){
++      if ( event->direction == GDK_SCROLL_UP ) {
++              TextureBrowser_MouseWheel( *textureBrowser, true );
++      }
++      else if ( event->direction == GDK_SCROLL_DOWN ) {
++              TextureBrowser_MouseWheel( *textureBrowser, false );
++      }
++      return FALSE;
++}
++
++void TextureBrowser_scrollChanged( void* data, gdouble value ){
++      //globalOutputStream() << "vertical scroll\n";
++      TextureBrowser_setOriginY( *reinterpret_cast<TextureBrowser*>( data ), -(int)value );
++}
++
++static void TextureBrowser_verticalScroll(ui::Adjustment adjustment, TextureBrowser* textureBrowser ){
++      textureBrowser->m_scrollAdjustment.value_changed( gtk_adjustment_get_value(adjustment) );
++}
++
++void TextureBrowser_updateScroll( TextureBrowser& textureBrowser ){
++      if ( textureBrowser.m_showTextureScrollbar ) {
++              int totalHeight = TextureBrowser_TotalHeight( textureBrowser );
++
++              totalHeight = std::max( totalHeight, textureBrowser.height );
++
++        auto vadjustment = gtk_range_get_adjustment( GTK_RANGE( textureBrowser.m_texture_scroll ) );
++
++              gtk_adjustment_set_value(vadjustment, -TextureBrowser_getOriginY( textureBrowser ));
++              gtk_adjustment_set_page_size(vadjustment, textureBrowser.height);
++              gtk_adjustment_set_page_increment(vadjustment, textureBrowser.height / 2);
++              gtk_adjustment_set_step_increment(vadjustment, 20);
++              gtk_adjustment_set_lower(vadjustment, 0);
++              gtk_adjustment_set_upper(vadjustment, totalHeight);
++
++              g_signal_emit_by_name( G_OBJECT( vadjustment ), "changed" );
++      }
++}
++
++gboolean TextureBrowser_size_allocate( ui::Widget widget, GtkAllocation* allocation, TextureBrowser* textureBrowser ){
++      textureBrowser->width = allocation->width;
++      textureBrowser->height = allocation->height;
++      TextureBrowser_heightChanged( *textureBrowser );
++      textureBrowser->m_originInvalid = true;
++      TextureBrowser_queueDraw( *textureBrowser );
++      return FALSE;
++}
++
++gboolean TextureBrowser_expose( ui::Widget widget, GdkEventExpose* event, TextureBrowser* textureBrowser ){
++      if ( glwidget_make_current( textureBrowser->m_gl_widget ) != FALSE ) {
++              GlobalOpenGL_debugAssertNoErrors();
++              TextureBrowser_evaluateHeight( *textureBrowser );
++              Texture_Draw( *textureBrowser );
++              GlobalOpenGL_debugAssertNoErrors();
++              glwidget_swap_buffers( textureBrowser->m_gl_widget );
++      }
++      return FALSE;
++}
++
++
++TextureBrowser g_TextureBrowser;
++
++TextureBrowser& GlobalTextureBrowser(){
++      return g_TextureBrowser;
++}
++
++bool TextureBrowser_hideUnused(){
++      return g_TextureBrowser.m_hideUnused;
++}
++
++void TextureBrowser_ToggleHideUnused(){
++      if ( g_TextureBrowser.m_hideUnused ) {
++              TextureBrowser_SetHideUnused( g_TextureBrowser, false );
++      }
++      else
++      {
++              TextureBrowser_SetHideUnused( g_TextureBrowser, true );
++      }
++}
++
++void TextureGroups_constructTreeModel( TextureGroups groups, ui::TreeStore store ){
++      // put the information from the old textures menu into a treeview
++      GtkTreeIter iter, child;
++
++      TextureGroups::const_iterator i = groups.begin();
++      while ( i != groups.end() )
++      {
++              const char* dirName = ( *i ).c_str();
++              const char* firstUnderscore = strchr( dirName, '_' );
++              StringRange dirRoot( dirName, ( firstUnderscore == 0 ) ? dirName : firstUnderscore + 1 );
++
++              TextureGroups::const_iterator next = i;
++              ++next;
++              if ( firstUnderscore != 0
++                       && next != groups.end()
++                       && string_equal_start( ( *next ).c_str(), dirRoot ) ) {
++                      gtk_tree_store_append( store, &iter, NULL );
++                      gtk_tree_store_set( store, &iter, 0, CopiedString( StringRange( dirName, firstUnderscore ) ).c_str(), -1 );
++
++                      // keep going...
++                      while ( i != groups.end() && string_equal_start( ( *i ).c_str(), dirRoot ) )
++                      {
++                              gtk_tree_store_append( store, &child, &iter );
++                              gtk_tree_store_set( store, &child, 0, ( *i ).c_str(), -1 );
++                              ++i;
++                      }
++              }
++              else
++              {
++                      gtk_tree_store_append( store, &iter, NULL );
++                      gtk_tree_store_set( store, &iter, 0, dirName, -1 );
++                      ++i;
++              }
++      }
++}
++
++TextureGroups TextureGroups_constructTreeView(){
++      TextureGroups groups;
++
++      if ( TextureBrowser_showWads() ) {
++              GlobalFileSystem().forEachArchive( TextureGroupsAddWadCaller( groups ) );
++      }
++      else
++      {
++              // scan texture dirs and pak files only if not restricting to shaderlist
++              if ( g_pGameDescription->mGameType != "doom3" && !g_TextureBrowser_shaderlistOnly ) {
++                      GlobalFileSystem().forEachDirectory( "textures/", TextureGroupsAddDirectoryCaller( groups ) );
++              }
++
++              GlobalShaderSystem().foreachShaderName( TextureGroupsAddShaderCaller( groups ) );
++      }
++
++      return groups;
++}
++
++void TextureBrowser_constructTreeStore(){
++      TextureGroups groups = TextureGroups_constructTreeView();
++      auto store = ui::TreeStore::from(gtk_tree_store_new( 1, G_TYPE_STRING ));
++      TextureGroups_constructTreeModel( groups, store );
++      std::set<CopiedString>::iterator iter;
++
++      gtk_tree_view_set_model(g_TextureBrowser.m_treeViewTree, store);
++
++      g_object_unref( G_OBJECT( store ) );
++}
++
++void TextureBrowser_constructTreeStoreTags(){
++      //TextureGroups groups;
++      auto store = ui::TreeStore::from(gtk_tree_store_new( 1, G_TYPE_STRING ));
++    auto model = g_TextureBrowser.m_all_tags_list;
++
++      gtk_tree_view_set_model(g_TextureBrowser.m_treeViewTags, model );
++
++      g_object_unref( G_OBJECT( store ) );
++}
++
++void TreeView_onRowActivated( ui::TreeView treeview, ui::TreePath path, ui::TreeViewColumn col, gpointer userdata ){
++      GtkTreeIter iter;
++
++    auto model = gtk_tree_view_get_model(treeview );
++
++      if ( gtk_tree_model_get_iter( model, &iter, path ) ) {
++              gchar dirName[1024];
++
++              gchar* buffer;
++              gtk_tree_model_get( model, &iter, 0, &buffer, -1 );
++              strcpy( dirName, buffer );
++              g_free( buffer );
++
++              g_TextureBrowser.m_searchedTags = false;
++
++              if ( !TextureBrowser_showWads() ) {
++                      strcat( dirName, "/" );
++              }
++
++              ScopeDisableScreenUpdates disableScreenUpdates( dirName, "Loading Textures" );
++              TextureBrowser_ShowDirectory( GlobalTextureBrowser(), dirName );
++              TextureBrowser_queueDraw( GlobalTextureBrowser() );
++              //deactivate, so SPACE and RETURN wont be broken for 2d
++              gtk_window_set_focus( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( treeview ) ) ), NULL );
++      }
++}
++
++void TextureBrowser_createTreeViewTree(){
++      gtk_tree_view_set_enable_search(g_TextureBrowser.m_treeViewTree, FALSE );
++
++      gtk_tree_view_set_headers_visible(g_TextureBrowser.m_treeViewTree, FALSE );
++      g_TextureBrowser.m_treeViewTree.connect( "row-activated", (GCallback) TreeView_onRowActivated, NULL );
++
++      auto renderer = ui::CellRendererText(ui::New);
++      gtk_tree_view_insert_column_with_attributes(g_TextureBrowser.m_treeViewTree, -1, "", renderer, "text", 0, NULL );
++
++      TextureBrowser_constructTreeStore();
++}
++
++void TextureBrowser_addTag();
++
++void TextureBrowser_renameTag();
++
++void TextureBrowser_deleteTag();
++
++void TextureBrowser_createContextMenu( ui::Widget treeview, GdkEventButton *event ){
++      ui::Widget menu = ui::Menu(ui::New);
++
++      ui::Widget menuitem = ui::MenuItem( "Add tag" );
++      menuitem.connect( "activate", (GCallback)TextureBrowser_addTag, treeview );
++      gtk_menu_shell_append( GTK_MENU_SHELL( menu ), menuitem );
++
++      menuitem = ui::MenuItem( "Rename tag" );
++      menuitem.connect( "activate", (GCallback)TextureBrowser_renameTag, treeview );
++      gtk_menu_shell_append( GTK_MENU_SHELL( menu ), menuitem );
++
++      menuitem = ui::MenuItem( "Delete tag" );
++      menuitem.connect( "activate", (GCallback)TextureBrowser_deleteTag, treeview );
++      gtk_menu_shell_append( GTK_MENU_SHELL( menu ), menuitem );
++
++      gtk_widget_show_all( menu );
++
++      gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL,
++                                      ( event != NULL ) ? event->button : 0,
++                                      gdk_event_get_time( (GdkEvent*)event ) );
++}
++
++gboolean TreeViewTags_onButtonPressed( ui::TreeView treeview, GdkEventButton *event ){
++      if ( event->type == GDK_BUTTON_PRESS && event->button == 3 ) {
++              GtkTreePath *path;
++        auto selection = gtk_tree_view_get_selection(treeview );
++
++              if ( gtk_tree_view_get_path_at_pos(treeview, event->x, event->y, &path, NULL, NULL, NULL ) ) {
++                      gtk_tree_selection_unselect_all( selection );
++                      gtk_tree_selection_select_path( selection, path );
++                      gtk_tree_path_free( path );
++              }
++
++              TextureBrowser_createContextMenu( treeview, event );
++              return TRUE;
++      }
++      return FALSE;
++}
++
++void TextureBrowser_createTreeViewTags(){
++      g_TextureBrowser.m_treeViewTags = ui::TreeView(ui::New);
++      gtk_tree_view_set_enable_search(g_TextureBrowser.m_treeViewTags, FALSE );
++
++      g_TextureBrowser.m_treeViewTags.connect( "button-press-event", (GCallback)TreeViewTags_onButtonPressed, NULL );
++
++      gtk_tree_view_set_headers_visible(g_TextureBrowser.m_treeViewTags, FALSE );
++
++      auto renderer = ui::CellRendererText(ui::New);
++      gtk_tree_view_insert_column_with_attributes(g_TextureBrowser.m_treeViewTags, -1, "", renderer, "text", 0, NULL );
++
++      TextureBrowser_constructTreeStoreTags();
++}
++
++ui::MenuItem TextureBrowser_constructViewMenu( ui::Menu menu ){
++      ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic( "_View" ));
++
++      if ( g_Layout_enableDetachableMenus.m_value ) {
++              menu_tearoff( menu );
++      }
++
++      create_check_menu_item_with_mnemonic( menu, "Hide _Unused", "ShowInUse" );
++      if ( string_empty( g_pGameDescription->getKeyValue( "show_wads" ) ) ) {
++              create_check_menu_item_with_mnemonic( menu, "Hide Image Missing", "FilterMissing" );
++      }
++
++      // hide notex and shadernotex on texture browser: no one wants to apply them
++      create_check_menu_item_with_mnemonic( menu, "Hide Fallback", "FilterFallback" );
++
++      menu_separator( menu );
++
++
++      // we always want to show shaders but don't want a "Show Shaders" menu for doom3 and .wad file games
++      if ( g_pGameDescription->mGameType == "doom3" || !string_empty( g_pGameDescription->getKeyValue( "show_wads" ) ) ) {
++              g_TextureBrowser.m_showShaders = true;
++      }
++      else
++      {
++              create_check_menu_item_with_mnemonic( menu, "Show shaders", "ToggleShowShaders" );
++              create_check_menu_item_with_mnemonic( menu, "Show textures", "ToggleShowTextures" );
++              menu_separator( menu );
++      }
++
++      if ( g_TextureBrowser.m_tags ) {
++              create_menu_item_with_mnemonic( menu, "Show Untagged", "ShowUntagged" );
++      }
++      if ( g_pGameDescription->mGameType != "doom3" && string_empty( g_pGameDescription->getKeyValue( "show_wads" ) ) ) {
++              create_check_menu_item_with_mnemonic( menu, "ShaderList Only", "ToggleShowShaderlistOnly" );
++      }
++
++      menu_separator( menu );
++      create_check_menu_item_with_mnemonic( menu, "Fixed Size", "FixedSize" );
++      create_check_menu_item_with_mnemonic( menu, "Transparency", "EnableAlpha" );
++
++      if ( string_empty( g_pGameDescription->getKeyValue( "show_wads" ) ) ) {
++              menu_separator( menu );
++              g_TextureBrowser.m_shader_info_item = ui::Widget(create_menu_item_with_mnemonic( menu, "Shader Info", "ShaderInfo"  ));
++              gtk_widget_set_sensitive( g_TextureBrowser.m_shader_info_item, FALSE );
++      }
++
++
++      return textures_menu_item;
++}
++
++void Popup_View_Menu( GtkWidget *widget, GtkMenu *menu ){
++      gtk_menu_popup( menu, NULL, NULL, NULL, NULL, 1, gtk_get_current_event_time() );
++}
++
++ui::MenuItem TextureBrowser_constructToolsMenu( ui::Menu menu ){
++      ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic( "_Tools" ));
++
++      if ( g_Layout_enableDetachableMenus.m_value ) {
++              menu_tearoff( menu );
++      }
++
++      create_menu_item_with_mnemonic( menu, "Flush & Reload Shaders", "RefreshShaders" );
++      create_menu_item_with_mnemonic( menu, "Find / Replace...", "FindReplaceTextures" );
++
++      return textures_menu_item;
++}
++
++ui::MenuItem TextureBrowser_constructTagsMenu( ui::Menu menu ){
++      ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic( "T_ags" ));
++
++      if ( g_Layout_enableDetachableMenus.m_value ) {
++              menu_tearoff( menu );
++      }
++
++      create_menu_item_with_mnemonic( menu, "Add tag", "AddTag" );
++      create_menu_item_with_mnemonic( menu, "Rename tag", "RenameTag" );
++      create_menu_item_with_mnemonic( menu, "Delete tag", "DeleteTag" );
++      menu_separator( menu );
++      create_menu_item_with_mnemonic( menu, "Copy tags from selected", "CopyTag" );
++      create_menu_item_with_mnemonic( menu, "Paste tags to selected", "PasteTag" );
++
++      return textures_menu_item;
++}
++
++gboolean TextureBrowser_tagMoveHelper( ui::TreeModel model, ui::TreePath path, GtkTreeIter iter, GSList** selected ){
++      g_assert( selected != NULL );
++
++    auto rowref = gtk_tree_row_reference_new( model, path );
++      *selected = g_slist_append( *selected, rowref );
++
++      return FALSE;
++}
++
++void TextureBrowser_assignTags(){
++      GSList* selected = NULL;
++      GSList* node;
++      gchar* tag_assigned;
++
++    auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_available_tree );
++
++      gtk_tree_selection_selected_foreach( selection, (GtkTreeSelectionForeachFunc)TextureBrowser_tagMoveHelper, &selected );
++
++      if ( selected != NULL ) {
++              for ( node = selected; node != NULL; node = node->next )
++              {
++            auto path = gtk_tree_row_reference_get_path( (GtkTreeRowReference*)node->data );
++
++                      if ( path ) {
++                              GtkTreeIter iter;
++
++                              if ( gtk_tree_model_get_iter(g_TextureBrowser.m_available_store, &iter, path ) ) {
++                                      gtk_tree_model_get(g_TextureBrowser.m_available_store, &iter, TAG_COLUMN, &tag_assigned, -1 );
++                                      if ( !TagBuilder.CheckShaderTag( g_TextureBrowser.shader.c_str() ) ) {
++                                              // create a custom shader/texture entry
++                                              IShader* ishader = QERApp_Shader_ForName( g_TextureBrowser.shader.c_str() );
++                                              CopiedString filename = ishader->getShaderFileName();
++
++                                              if ( filename.empty() ) {
++                                                      // it's a texture
++                                                      TagBuilder.AddShaderNode( g_TextureBrowser.shader.c_str(), CUSTOM, TEXTURE );
++                                              }
++                                              else {
++                                                      // it's a shader
++                                                      TagBuilder.AddShaderNode( g_TextureBrowser.shader.c_str(), CUSTOM, SHADER );
++                                              }
++                                              ishader->DecRef();
++                                      }
++                                      TagBuilder.AddShaderTag( g_TextureBrowser.shader.c_str(), (char*)tag_assigned, TAG );
++
++                                      gtk_list_store_remove( g_TextureBrowser.m_available_store, &iter );
++                                      g_TextureBrowser.m_assigned_store.append(TAG_COLUMN, tag_assigned);
++                              }
++                      }
++              }
++
++              g_slist_foreach( selected, (GFunc)gtk_tree_row_reference_free, NULL );
++
++              // Save changes
++              TagBuilder.SaveXmlDoc();
++      }
++      g_slist_free( selected );
++}
++
++void TextureBrowser_removeTags(){
++      GSList* selected = NULL;
++      GSList* node;
++      gchar* tag;
++
++    auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_assigned_tree );
++
++      gtk_tree_selection_selected_foreach( selection, (GtkTreeSelectionForeachFunc)TextureBrowser_tagMoveHelper, &selected );
++
++      if ( selected != NULL ) {
++              for ( node = selected; node != NULL; node = node->next )
++              {
++            auto path = gtk_tree_row_reference_get_path( (GtkTreeRowReference*)node->data );
++
++                      if ( path ) {
++                              GtkTreeIter iter;
++
++                              if ( gtk_tree_model_get_iter(g_TextureBrowser.m_assigned_store, &iter, path ) ) {
++                                      gtk_tree_model_get(g_TextureBrowser.m_assigned_store, &iter, TAG_COLUMN, &tag, -1 );
++                                      TagBuilder.DeleteShaderTag( g_TextureBrowser.shader.c_str(), tag );
++                                      gtk_list_store_remove( g_TextureBrowser.m_assigned_store, &iter );
++                              }
++                      }
++              }
++
++              g_slist_foreach( selected, (GFunc)gtk_tree_row_reference_free, NULL );
++
++              // Update the "available tags list"
++              BuildStoreAvailableTags( g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store, g_TextureBrowser.m_all_tags, &g_TextureBrowser );
++
++              // Save changes
++              TagBuilder.SaveXmlDoc();
++      }
++      g_slist_free( selected );
++}
++
++void TextureBrowser_buildTagList(){
++      g_TextureBrowser.m_all_tags_list.clear();
++
++      std::set<CopiedString>::iterator iter;
++
++      for ( iter = g_TextureBrowser.m_all_tags.begin(); iter != g_TextureBrowser.m_all_tags.end(); ++iter )
++      {
++              g_TextureBrowser.m_all_tags_list.append(TAG_COLUMN, (*iter).c_str());
++      }
++}
++
++void TextureBrowser_searchTags(){
++      GSList* selected = NULL;
++      GSList* node;
++      gchar* tag;
++      char buffer[256];
++      char tags_searched[256];
++
++    auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_treeViewTags );
++
++      gtk_tree_selection_selected_foreach( selection, (GtkTreeSelectionForeachFunc)TextureBrowser_tagMoveHelper, &selected );
++
++      if ( selected != NULL ) {
++              strcpy( buffer, "/root/*/*[tag='" );
++              strcpy( tags_searched, "[TAGS] " );
++
++              for ( node = selected; node != NULL; node = node->next )
++              {
++            auto path = gtk_tree_row_reference_get_path( (GtkTreeRowReference*)node->data );
++
++                      if ( path ) {
++                              GtkTreeIter iter;
++
++                              if ( gtk_tree_model_get_iter(g_TextureBrowser.m_all_tags_list, &iter, path ) ) {
++                                      gtk_tree_model_get(g_TextureBrowser.m_all_tags_list, &iter, TAG_COLUMN, &tag, -1 );
++
++                                      strcat( buffer, tag );
++                                      strcat( tags_searched, tag );
++                                      if ( node != g_slist_last( node ) ) {
++                                              strcat( buffer, "' and tag='" );
++                                              strcat( tags_searched, ", " );
++                                      }
++                              }
++                      }
++              }
++
++              strcat( buffer, "']" );
++
++              g_slist_foreach( selected, (GFunc)gtk_tree_row_reference_free, NULL );
++
++              g_TextureBrowser.m_found_shaders.clear(); // delete old list
++              TagBuilder.TagSearch( buffer, g_TextureBrowser.m_found_shaders );
++
++              if ( !g_TextureBrowser.m_found_shaders.empty() ) { // found something
++                      size_t shaders_found = g_TextureBrowser.m_found_shaders.size();
++
++                      globalOutputStream() << "Found " << (unsigned int)shaders_found << " textures and shaders with " << tags_searched << "\n";
++                      ScopeDisableScreenUpdates disableScreenUpdates( "Searching...", "Loading Textures" );
++
++                      std::set<CopiedString>::iterator iter;
++
++                      for ( iter = g_TextureBrowser.m_found_shaders.begin(); iter != g_TextureBrowser.m_found_shaders.end(); iter++ )
++                      {
++                              std::string path = ( *iter ).c_str();
++                              size_t pos = path.find_last_of( "/", path.size() );
++                              std::string name = path.substr( pos + 1, path.size() );
++                              path = path.substr( 0, pos + 1 );
++                              TextureDirectory_loadTexture( path.c_str(), name.c_str() );
++                      }
++              }
++              g_TextureBrowser.m_searchedTags = true;
++              g_TextureBrowser_currentDirectory = tags_searched;
++
++              g_TextureBrowser.m_nTotalHeight = 0;
++              TextureBrowser_setOriginY( g_TextureBrowser, 0 );
++              TextureBrowser_heightChanged( g_TextureBrowser );
++              TextureBrowser_updateTitle();
++      }
++      g_slist_free( selected );
++}
++
++void TextureBrowser_toggleSearchButton(){
++      gint page = gtk_notebook_get_current_page( GTK_NOTEBOOK( g_TextureBrowser.m_tag_notebook ) );
++
++      if ( page == 0 ) { // tag page
++              gtk_widget_show_all( g_TextureBrowser.m_search_button );
++      }
++      else {
++              g_TextureBrowser.m_search_button.hide();
++      }
++}
++
++void TextureBrowser_constructTagNotebook(){
++      g_TextureBrowser.m_tag_notebook = ui::Widget::from(gtk_notebook_new());
++      ui::Widget labelTags = ui::Label( "Tags" );
++      ui::Widget labelTextures = ui::Label( "Textures" );
++
++      gtk_notebook_append_page( GTK_NOTEBOOK( g_TextureBrowser.m_tag_notebook ), g_TextureBrowser.m_scr_win_tree, labelTextures );
++      gtk_notebook_append_page( GTK_NOTEBOOK( g_TextureBrowser.m_tag_notebook ), g_TextureBrowser.m_scr_win_tags, labelTags );
++
++      g_TextureBrowser.m_tag_notebook.connect( "switch-page", G_CALLBACK( TextureBrowser_toggleSearchButton ), NULL );
++
++      gtk_widget_show_all( g_TextureBrowser.m_tag_notebook );
++}
++
++void TextureBrowser_constructSearchButton(){
++      auto image = ui::Widget::from(gtk_image_new_from_stock( GTK_STOCK_FIND, GTK_ICON_SIZE_SMALL_TOOLBAR ));
++      g_TextureBrowser.m_search_button = ui::Button(ui::New);
++      g_TextureBrowser.m_search_button.connect( "clicked", G_CALLBACK( TextureBrowser_searchTags ), NULL );
++      gtk_widget_set_tooltip_text(g_TextureBrowser.m_search_button, "Search with selected tags");
++      g_TextureBrowser.m_search_button.add(image);
++}
++
++void TextureBrowser_checkTagFile(){
++      const char SHADERTAG_FILE[] = "shadertags.xml";
++      CopiedString default_filename, rc_filename;
++      StringOutputStream stream( 256 );
++
++      stream << LocalRcPath_get();
++      stream << SHADERTAG_FILE;
++      rc_filename = stream.c_str();
++
++      if ( file_exists( rc_filename.c_str() ) ) {
++              g_TextureBrowser.m_tags = TagBuilder.OpenXmlDoc( rc_filename.c_str() );
++
++              if ( g_TextureBrowser.m_tags ) {
++                      globalOutputStream() << "Loading tag file " << rc_filename.c_str() << ".\n";
++              }
++      }
++      else
++      {
++              // load default tagfile
++              stream.clear();
++              stream << g_pGameDescription->mGameToolsPath.c_str();
++              stream << SHADERTAG_FILE;
++              default_filename = stream.c_str();
++
++              if ( file_exists( default_filename.c_str() ) ) {
++                      g_TextureBrowser.m_tags = TagBuilder.OpenXmlDoc( default_filename.c_str(), rc_filename.c_str() );
++
++                      if ( g_TextureBrowser.m_tags ) {
++                              globalOutputStream() << "Loading default tag file " << default_filename.c_str() << ".\n";
++                      }
++              }
++              else
++              {
++                      globalOutputStream() << "Unable to find default tag file " << default_filename.c_str() << ". No tag support. Plugins -> ShaderPlug -> Create tag file: to start using tags\n";
++              }
++      }
++}
++
++void TextureBrowser_SetNotex(){
++      IShader* notex = QERApp_Shader_ForName( DEFAULT_NOTEX_NAME );
++      IShader* shadernotex = QERApp_Shader_ForName( DEFAULT_SHADERNOTEX_NAME );
++
++      g_notex = notex->getTexture()->name;
++      g_shadernotex = shadernotex->getTexture()->name;
++
++      notex->DecRef();
++      shadernotex->DecRef();
++}
++
++ui::Widget TextureBrowser_constructWindow( ui::Window toplevel ){
++      // The gl_widget and the tag assignment frame should be packed into a GtkVPaned with the slider
++      // position stored in local.pref. gtk_paned_get_position() and gtk_paned_set_position() don't
++      // seem to work in gtk 2.4 and the arrow buttons don't handle GTK_FILL, so here's another thing
++      // for the "once-the-gtk-libs-are-updated-TODO-list" :x
++
++      TextureBrowser_checkTagFile();
++      TextureBrowser_SetNotex();
++
++      GlobalShaderSystem().setActiveShadersChangedNotify( ReferenceCaller<TextureBrowser, void(), TextureBrowser_activeShadersChanged>( g_TextureBrowser ) );
++
++      g_TextureBrowser.m_parent = toplevel;
++
++      auto table = ui::Table(3, 3, FALSE);
++      auto vbox = ui::VBox(FALSE, 0);
++      table.attach(vbox, {0, 1, 1, 3}, {GTK_FILL, GTK_FILL});
++      vbox.show();
++
++      // ui::Widget menu_bar{ui::null};
++      auto toolbar = ui::Toolbar::from( gtk_toolbar_new() );
++
++      { // menu bar
++              // menu_bar = ui::Widget::from(gtk_menu_bar_new());
++              auto menu_view = ui::Menu(ui::New);
++              // auto view_item = TextureBrowser_constructViewMenu( menu_view );
++              TextureBrowser_constructViewMenu( menu_view );
++              gtk_menu_set_title( menu_view, "View" );
++              // gtk_menu_item_set_submenu( GTK_MENU_ITEM( view_item ), menu_view );
++              // gtk_menu_shell_append( GTK_MENU_SHELL( menu_bar ), view_item );
++
++              //gtk_table_attach( GTK_TABLE( table ), GTK_WIDGET( toolbar ), 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0 );
++              gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( toolbar ), FALSE, FALSE, 0 );
++
++              //view menu button
++              {
++                      auto button = toolbar_append_button( toolbar, "View", "texbro_view.png" );
++                      button.dimensions( 22, 22 );
++                      button.connect( "clicked", G_CALLBACK( Popup_View_Menu ), menu_view );
++
++                      //to show detached menu over floating tex bro
++                      gtk_menu_attach_to_widget( GTK_MENU( menu_view ), GTK_WIDGET( button ), NULL );
++              }
++              {
++                      auto button = toolbar_append_button( toolbar, "Find / Replace...", "texbro_gtk-find-and-replace.png", "FindReplaceTextures" );
++                      button.dimensions( 22, 22 );
++              }
++              {
++                      auto button = toolbar_append_button( toolbar, "Flush & Reload Shaders", "texbro_refresh.png", "RefreshShaders" );
++                      button.dimensions( 22, 22 );
++              }
++              toolbar.show();
++
++/*
++              auto menu_tools = ui::Menu(ui::New);
++              auto tools_item = TextureBrowser_constructToolsMenu( menu_tools );
++              gtk_menu_item_set_submenu( GTK_MENU_ITEM( tools_item ), menu_tools );
++              gtk_menu_shell_append( GTK_MENU_SHELL( menu_bar ), tools_item );
++*/
++              // table.attach(menu_bar, {0, 3, 0, 1}, {GTK_FILL, GTK_SHRINK});
++              // menu_bar.show();
++      }
++      { // Texture TreeView
++              g_TextureBrowser.m_scr_win_tree = ui::ScrolledWindow(ui::New);
++              gtk_container_set_border_width( GTK_CONTAINER( g_TextureBrowser.m_scr_win_tree ), 0 );
++
++              // vertical only scrolling for treeview
++              gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( g_TextureBrowser.m_scr_win_tree ), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS );
++
++              g_TextureBrowser.m_scr_win_tree.show();
++
++              TextureBrowser_createTreeViewTree();
++
++              gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( g_TextureBrowser.m_scr_win_tree ), g_TextureBrowser.m_treeViewTree  );
++              g_TextureBrowser.m_treeViewTree.show();
++      }
++      { // gl_widget scrollbar
++              auto w = ui::Widget::from(gtk_vscrollbar_new( ui::Adjustment( 0,0,0,1,1,0 ) ));
++              table.attach(w, {2, 3, 1, 2}, {GTK_SHRINK, GTK_FILL});
++              w.show();
++              g_TextureBrowser.m_texture_scroll = w;
++
++              auto vadjustment = ui::Adjustment::from(gtk_range_get_adjustment( GTK_RANGE( g_TextureBrowser.m_texture_scroll ) ));
++              vadjustment.connect( "value_changed", G_CALLBACK( TextureBrowser_verticalScroll ), &g_TextureBrowser );
++
++              g_TextureBrowser.m_texture_scroll.visible(g_TextureBrowser.m_showTextureScrollbar);
++      }
++      { // gl_widget
++              g_TextureBrowser.m_gl_widget = glwidget_new( FALSE );
++              g_object_ref( g_TextureBrowser.m_gl_widget._handle );
++
++              gtk_widget_set_events( g_TextureBrowser.m_gl_widget, GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK );
++              gtk_widget_set_can_focus( g_TextureBrowser.m_gl_widget, true );
++
++              table.attach(g_TextureBrowser.m_gl_widget, {1, 2, 1, 2});
++              g_TextureBrowser.m_gl_widget.show();
++
++              g_TextureBrowser.m_sizeHandler = g_TextureBrowser.m_gl_widget.connect( "size_allocate", G_CALLBACK( TextureBrowser_size_allocate ), &g_TextureBrowser );
++              g_TextureBrowser.m_exposeHandler = g_TextureBrowser.m_gl_widget.on_render( G_CALLBACK( TextureBrowser_expose ), &g_TextureBrowser );
++
++              g_TextureBrowser.m_gl_widget.connect( "button_press_event", G_CALLBACK( TextureBrowser_button_press ), &g_TextureBrowser );
++              g_TextureBrowser.m_gl_widget.connect( "button_release_event", G_CALLBACK( TextureBrowser_button_release ), &g_TextureBrowser );
++              g_TextureBrowser.m_gl_widget.connect( "motion_notify_event", G_CALLBACK( TextureBrowser_motion ), &g_TextureBrowser );
++              g_TextureBrowser.m_gl_widget.connect( "scroll_event", G_CALLBACK( TextureBrowser_scroll ), &g_TextureBrowser );
++      }
++
++      // tag stuff
++      if ( g_TextureBrowser.m_tags ) {
++              { // fill tag GtkListStore
++                      g_TextureBrowser.m_all_tags_list = ui::ListStore::from(gtk_list_store_new( N_COLUMNS, G_TYPE_STRING ));
++            auto sortable = GTK_TREE_SORTABLE( g_TextureBrowser.m_all_tags_list );
++                      gtk_tree_sortable_set_sort_column_id( sortable, TAG_COLUMN, GTK_SORT_ASCENDING );
++
++                      TagBuilder.GetAllTags( g_TextureBrowser.m_all_tags );
++                      TextureBrowser_buildTagList();
++              }
++              { // tag menu bar
++                      auto menu_tags = ui::Menu(ui::New);
++                      // auto tags_item = TextureBrowser_constructTagsMenu( menu_tags );
++                      TextureBrowser_constructTagsMenu( menu_tags );
++                      // gtk_menu_item_set_submenu( GTK_MENU_ITEM( tags_item ), menu_tags );
++                      // gtk_menu_shell_append( GTK_MENU_SHELL( menu_bar ), tags_item );
++
++                      auto button = toolbar_append_button( toolbar, "Tags", "texbro_tags.png" );
++                      button.dimensions( 22, 22 );
++                      button.connect( "clicked", G_CALLBACK( Popup_View_Menu ), menu_tags );
++              }
++              { // Tag TreeView
++                      g_TextureBrowser.m_scr_win_tags = ui::ScrolledWindow(ui::New);
++                      gtk_container_set_border_width( GTK_CONTAINER( g_TextureBrowser.m_scr_win_tags ), 0 );
++
++                      // vertical only scrolling for treeview
++                      gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( g_TextureBrowser.m_scr_win_tags ), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS );
++
++                      TextureBrowser_createTreeViewTags();
++
++            auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_treeViewTags );
++                      gtk_tree_selection_set_mode( selection, GTK_SELECTION_MULTIPLE );
++
++                      gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( g_TextureBrowser.m_scr_win_tags ), g_TextureBrowser.m_treeViewTags  );
++                      g_TextureBrowser.m_treeViewTags.show();
++              }
++              { // Texture/Tag notebook
++                      TextureBrowser_constructTagNotebook();
++                      vbox.pack_start( g_TextureBrowser.m_tag_notebook, TRUE, TRUE, 0 );
++              }
++              { // Tag search button
++                      TextureBrowser_constructSearchButton();
++                      vbox.pack_end(g_TextureBrowser.m_search_button, FALSE, FALSE, 0);
++              }
++              auto frame_table = ui::Table(3, 3, FALSE);
++              { // Tag frame
++
++                      g_TextureBrowser.m_tag_frame = ui::Frame( "Tag assignment" );
++                      gtk_frame_set_label_align( GTK_FRAME( g_TextureBrowser.m_tag_frame ), 0.5, 0.5 );
++                      gtk_frame_set_shadow_type( GTK_FRAME( g_TextureBrowser.m_tag_frame ), GTK_SHADOW_NONE );
++
++                      table.attach(g_TextureBrowser.m_tag_frame, {1, 3, 2, 3}, {GTK_FILL, GTK_SHRINK});
++
++                      frame_table.show();
++
++                      g_TextureBrowser.m_tag_frame.add(frame_table);
++              }
++              { // assigned tag list
++                      ui::Widget scrolled_win = ui::ScrolledWindow(ui::New);
++                      gtk_container_set_border_width( GTK_CONTAINER( scrolled_win ), 0 );
++                      gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolled_win ), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS );
++
++                      g_TextureBrowser.m_assigned_store = ui::ListStore::from(gtk_list_store_new( N_COLUMNS, G_TYPE_STRING ));
++
++            auto sortable = GTK_TREE_SORTABLE( g_TextureBrowser.m_assigned_store );
++                      gtk_tree_sortable_set_sort_column_id( sortable, TAG_COLUMN, GTK_SORT_ASCENDING );
++
++                      auto renderer = ui::CellRendererText(ui::New);
++
++                      g_TextureBrowser.m_assigned_tree = ui::TreeView(ui::TreeModel::from(g_TextureBrowser.m_assigned_store._handle));
++                      g_TextureBrowser.m_assigned_store.unref();
++                      g_TextureBrowser.m_assigned_tree.connect( "row-activated", (GCallback) TextureBrowser_removeTags, NULL );
++                      gtk_tree_view_set_headers_visible(g_TextureBrowser.m_assigned_tree, FALSE );
++
++            auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_assigned_tree );
++                      gtk_tree_selection_set_mode( selection, GTK_SELECTION_MULTIPLE );
++
++            auto column = ui::TreeViewColumn( "", renderer, {{"text", TAG_COLUMN}} );
++                      gtk_tree_view_append_column(g_TextureBrowser.m_assigned_tree, column );
++                      g_TextureBrowser.m_assigned_tree.show();
++
++                      scrolled_win.show();
++                      gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( scrolled_win ), g_TextureBrowser.m_assigned_tree  );
++
++                      frame_table.attach(scrolled_win, {0, 1, 1, 3}, {GTK_FILL, GTK_FILL});
++              }
++              { // available tag list
++                      ui::Widget scrolled_win = ui::ScrolledWindow(ui::New);
++                      gtk_container_set_border_width( GTK_CONTAINER( scrolled_win ), 0 );
++                      gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolled_win ), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS );
++
++                      g_TextureBrowser.m_available_store = ui::ListStore::from(gtk_list_store_new( N_COLUMNS, G_TYPE_STRING ));
++            auto sortable = GTK_TREE_SORTABLE( g_TextureBrowser.m_available_store );
++                      gtk_tree_sortable_set_sort_column_id( sortable, TAG_COLUMN, GTK_SORT_ASCENDING );
++
++                      auto renderer = ui::CellRendererText(ui::New);
++
++                      g_TextureBrowser.m_available_tree = ui::TreeView(ui::TreeModel::from(g_TextureBrowser.m_available_store._handle));
++                      g_TextureBrowser.m_available_store.unref();
++                      g_TextureBrowser.m_available_tree.connect( "row-activated", (GCallback) TextureBrowser_assignTags, NULL );
++                      gtk_tree_view_set_headers_visible(g_TextureBrowser.m_available_tree, FALSE );
++
++            auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_available_tree );
++                      gtk_tree_selection_set_mode( selection, GTK_SELECTION_MULTIPLE );
++
++            auto column = ui::TreeViewColumn( "", renderer, {{"text", TAG_COLUMN}} );
++                      gtk_tree_view_append_column(g_TextureBrowser.m_available_tree, column );
++                      g_TextureBrowser.m_available_tree.show();
++
++                      scrolled_win.show();
++                      gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( scrolled_win ), g_TextureBrowser.m_available_tree  );
++
++                      frame_table.attach(scrolled_win, {2, 3, 1, 3}, {GTK_FILL, GTK_FILL});
++              }
++              { // tag arrow buttons
++                      auto m_btn_left = ui::Button(ui::New);
++                      auto m_btn_right = ui::Button(ui::New);
++                      auto m_arrow_left = ui::Widget::from(gtk_arrow_new( GTK_ARROW_LEFT, GTK_SHADOW_OUT ));
++                      auto m_arrow_right = ui::Widget::from(gtk_arrow_new( GTK_ARROW_RIGHT, GTK_SHADOW_OUT ));
++                      m_btn_left.add(m_arrow_left);
++                      m_btn_right.add(m_arrow_right);
++
++                      // workaround. the size of the tag frame depends of the requested size of the arrow buttons.
++                      m_arrow_left.dimensions(-1, 68);
++                      m_arrow_right.dimensions(-1, 68);
++
++                      frame_table.attach(m_btn_left, {1, 2, 1, 2}, {GTK_SHRINK, GTK_EXPAND});
++                      frame_table.attach(m_btn_right, {1, 2, 2, 3}, {GTK_SHRINK, GTK_EXPAND});
++
++                      m_btn_left.connect( "clicked", G_CALLBACK( TextureBrowser_assignTags ), NULL );
++                      m_btn_right.connect( "clicked", G_CALLBACK( TextureBrowser_removeTags ), NULL );
++
++                      m_btn_left.show();
++                      m_btn_right.show();
++                      m_arrow_left.show();
++                      m_arrow_right.show();
++              }
++              { // tag fram labels
++                      ui::Widget m_lbl_assigned = ui::Label( "Assigned" );
++                      ui::Widget m_lbl_unassigned = ui::Label( "Available" );
++
++                      frame_table.attach(m_lbl_assigned, {0, 1, 0, 1}, {GTK_EXPAND, GTK_SHRINK});
++                      frame_table.attach(m_lbl_unassigned, {2, 3, 0, 1}, {GTK_EXPAND, GTK_SHRINK});
++
++                      m_lbl_assigned.show();
++                      m_lbl_unassigned.show();
++              }
++      }
++      else { // no tag support, show the texture tree only
++              vbox.pack_start( g_TextureBrowser.m_scr_win_tree, TRUE, TRUE, 0 );
++      }
++
++      // TODO do we need this?
++      //gtk_container_set_focus_chain(GTK_CONTAINER(hbox_table), NULL);
++
++      return table;
++}
++
++void TextureBrowser_destroyWindow(){
++      GlobalShaderSystem().setActiveShadersChangedNotify( Callback<void()>() );
++
++      g_signal_handler_disconnect( G_OBJECT( g_TextureBrowser.m_gl_widget ), g_TextureBrowser.m_sizeHandler );
++      g_signal_handler_disconnect( G_OBJECT( g_TextureBrowser.m_gl_widget ), g_TextureBrowser.m_exposeHandler );
++
++      g_TextureBrowser.m_gl_widget.unref();
++}
++
++const Vector3& TextureBrowser_getBackgroundColour( TextureBrowser& textureBrowser ){
++      return textureBrowser.color_textureback;
++}
++
++void TextureBrowser_setBackgroundColour( TextureBrowser& textureBrowser, const Vector3& colour ){
++      textureBrowser.color_textureback = colour;
++      TextureBrowser_queueDraw( textureBrowser );
++}
++
++void TextureBrowser_selectionHelper( ui::TreeModel model, ui::TreePath path, GtkTreeIter* iter, GSList** selected ){
++      g_assert( selected != NULL );
++
++      gchar* name;
++      gtk_tree_model_get( model, iter, TAG_COLUMN, &name, -1 );
++      *selected = g_slist_append( *selected, name );
++}
++
++void TextureBrowser_shaderInfo(){
++      const char* name = TextureBrowser_GetSelectedShader( g_TextureBrowser );
++      IShader* shader = QERApp_Shader_ForName( name );
++
++      DoShaderInfoDlg( name, shader->getShaderFileName(), "Shader Info" );
++
++      shader->DecRef();
++}
++
++void TextureBrowser_addTag(){
++      CopiedString tag;
++
++      EMessageBoxReturn result = DoShaderTagDlg( &tag, "Add shader tag" );
++
++      if ( result == eIDOK && !tag.empty() ) {
++              GtkTreeIter iter;
++              g_TextureBrowser.m_all_tags.insert( tag.c_str() );
++              gtk_list_store_append( g_TextureBrowser.m_available_store, &iter );
++              gtk_list_store_set( g_TextureBrowser.m_available_store, &iter, TAG_COLUMN, tag.c_str(), -1 );
++
++              // Select the currently added tag in the available list
++        auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_available_tree );
++              gtk_tree_selection_select_iter( selection, &iter );
++
++              g_TextureBrowser.m_all_tags_list.append(TAG_COLUMN, tag.c_str());
++      }
++}
++
++void TextureBrowser_renameTag(){
++      /* WORKAROUND: The tag treeview is set to GTK_SELECTION_MULTIPLE. Because
++         gtk_tree_selection_get_selected() doesn't work with GTK_SELECTION_MULTIPLE,
++         we need to count the number of selected rows first and use
++         gtk_tree_selection_selected_foreach() then to go through the list of selected
++         rows (which always containins a single row).
++       */
++
++      GSList* selected = NULL;
++
++    auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_treeViewTags );
++      gtk_tree_selection_selected_foreach( selection, GtkTreeSelectionForeachFunc( TextureBrowser_selectionHelper ), &selected );
++
++      if ( g_slist_length( selected ) == 1 ) { // we only rename a single tag
++              CopiedString newTag;
++              EMessageBoxReturn result = DoShaderTagDlg( &newTag, "Rename shader tag" );
++
++              if ( result == eIDOK && !newTag.empty() ) {
++                      GtkTreeIter iterList;
++                      gchar* rowTag;
++                      gchar* oldTag = (char*)selected->data;
++
++                      bool row = gtk_tree_model_get_iter_first(g_TextureBrowser.m_all_tags_list, &iterList ) != 0;
++
++                      while ( row )
++                      {
++                              gtk_tree_model_get(g_TextureBrowser.m_all_tags_list, &iterList, TAG_COLUMN, &rowTag, -1 );
++
++                              if ( strcmp( rowTag, oldTag ) == 0 ) {
++                                      gtk_list_store_set( g_TextureBrowser.m_all_tags_list, &iterList, TAG_COLUMN, newTag.c_str(), -1 );
++                              }
++                              row = gtk_tree_model_iter_next(g_TextureBrowser.m_all_tags_list, &iterList ) != 0;
++                      }
++
++                      TagBuilder.RenameShaderTag( oldTag, newTag.c_str() );
++
++                      g_TextureBrowser.m_all_tags.erase( (CopiedString)oldTag );
++                      g_TextureBrowser.m_all_tags.insert( newTag );
++
++                      BuildStoreAssignedTags( g_TextureBrowser.m_assigned_store, g_TextureBrowser.shader.c_str(), &g_TextureBrowser );
++                      BuildStoreAvailableTags( g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store, g_TextureBrowser.m_all_tags, &g_TextureBrowser );
++              }
++      }
++      else
++      {
++              ui::alert( g_TextureBrowser.m_parent, "Select a single tag for renaming." );
++      }
++}
++
++void TextureBrowser_deleteTag(){
++      GSList* selected = NULL;
++
++    auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_treeViewTags );
++      gtk_tree_selection_selected_foreach( selection, GtkTreeSelectionForeachFunc( TextureBrowser_selectionHelper ), &selected );
++
++      if ( g_slist_length( selected ) == 1 ) { // we only delete a single tag
++              auto result = ui::alert( g_TextureBrowser.m_parent, "Are you sure you want to delete the selected tag?", "Delete Tag", ui::alert_type::YESNO, ui::alert_icon::Question );
++
++              if ( result == ui::alert_response::YES ) {
++                      GtkTreeIter iterSelected;
++                      gchar *rowTag;
++
++                      gchar* tagSelected = (char*)selected->data;
++
++                      bool row = gtk_tree_model_get_iter_first(g_TextureBrowser.m_all_tags_list, &iterSelected ) != 0;
++
++                      while ( row )
++                      {
++                              gtk_tree_model_get(g_TextureBrowser.m_all_tags_list, &iterSelected, TAG_COLUMN, &rowTag, -1 );
++
++                              if ( strcmp( rowTag, tagSelected ) == 0 ) {
++                                      gtk_list_store_remove( g_TextureBrowser.m_all_tags_list, &iterSelected );
++                                      break;
++                              }
++                              row = gtk_tree_model_iter_next(g_TextureBrowser.m_all_tags_list, &iterSelected ) != 0;
++                      }
++
++                      TagBuilder.DeleteTag( tagSelected );
++                      g_TextureBrowser.m_all_tags.erase( (CopiedString)tagSelected );
++
++                      BuildStoreAssignedTags( g_TextureBrowser.m_assigned_store, g_TextureBrowser.shader.c_str(), &g_TextureBrowser );
++                      BuildStoreAvailableTags( g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store, g_TextureBrowser.m_all_tags, &g_TextureBrowser );
++              }
++      }
++      else {
++              ui::alert( g_TextureBrowser.m_parent, "Select a single tag for deletion." );
++      }
++}
++
++void TextureBrowser_copyTag(){
++      g_TextureBrowser.m_copied_tags.clear();
++      TagBuilder.GetShaderTags( g_TextureBrowser.shader.c_str(), g_TextureBrowser.m_copied_tags );
++}
++
++void TextureBrowser_pasteTag(){
++      IShader* ishader = QERApp_Shader_ForName( g_TextureBrowser.shader.c_str() );
++      CopiedString shader = g_TextureBrowser.shader.c_str();
++
++      if ( !TagBuilder.CheckShaderTag( shader.c_str() ) ) {
++              CopiedString shaderFile = ishader->getShaderFileName();
++              if ( shaderFile.empty() ) {
++                      // it's a texture
++                      TagBuilder.AddShaderNode( shader.c_str(), CUSTOM, TEXTURE );
++              }
++              else
++              {
++                      // it's a shader
++                      TagBuilder.AddShaderNode( shader.c_str(), CUSTOM, SHADER );
++              }
++
++              for ( size_t i = 0; i < g_TextureBrowser.m_copied_tags.size(); ++i )
++              {
++                      TagBuilder.AddShaderTag( shader.c_str(), g_TextureBrowser.m_copied_tags[i].c_str(), TAG );
++              }
++      }
++      else
++      {
++              for ( size_t i = 0; i < g_TextureBrowser.m_copied_tags.size(); ++i )
++              {
++                      if ( !TagBuilder.CheckShaderTag( shader.c_str(), g_TextureBrowser.m_copied_tags[i].c_str() ) ) {
++                              // the tag doesn't exist - let's add it
++                              TagBuilder.AddShaderTag( shader.c_str(), g_TextureBrowser.m_copied_tags[i].c_str(), TAG );
++                      }
++              }
++      }
++
++      ishader->DecRef();
++
++      TagBuilder.SaveXmlDoc();
++      BuildStoreAssignedTags( g_TextureBrowser.m_assigned_store, shader.c_str(), &g_TextureBrowser );
++      BuildStoreAvailableTags( g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store, g_TextureBrowser.m_all_tags, &g_TextureBrowser );
++}
++
++void TextureBrowser_RefreshShaders(){
++
++      /* When shaders are refreshed, forces reloading the textures as well.
++      Previously it would at best only display shaders, at worst mess up some textured objects. */
++
++    auto selection = gtk_tree_view_get_selection(GlobalTextureBrowser().m_treeViewTree);
++      GtkTreeModel* model = NULL;
++      GtkTreeIter iter;
++      if ( gtk_tree_selection_get_selected (selection, &model, &iter) )
++      {
++              gchar dirName[1024];
++
++              gchar* buffer;
++              gtk_tree_model_get( model, &iter, 0, &buffer, -1 );
++              strcpy( dirName, buffer );
++              g_free( buffer );
++              if ( !TextureBrowser_showWads() ) {
++                      strcat( dirName, "/" );
++              }
++
++              ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Shaders" );
++              GlobalShaderSystem().refresh();
++              /* texturebrowser tree update on vfs restart */
++              TextureBrowser_constructTreeStore();
++              UpdateAllWindows();
++
++              TextureBrowser_ShowDirectory( GlobalTextureBrowser(), dirName );
++              TextureBrowser_queueDraw( GlobalTextureBrowser() );
++      }
++
++      else{
++              ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Shaders" );
++              GlobalShaderSystem().refresh();
++              /* texturebrowser tree update on vfs restart */
++              TextureBrowser_constructTreeStore();
++              UpdateAllWindows();
++      }
++}
++
++void TextureBrowser_ToggleShowShaders(){
++      g_TextureBrowser.m_showShaders ^= 1;
++      g_TextureBrowser.m_showshaders_item.update();
++
++      g_TextureBrowser.m_heightChanged = true;
++      g_TextureBrowser.m_originInvalid = true;
++      g_activeShadersChangedCallbacks();
++
++      TextureBrowser_queueDraw( g_TextureBrowser );
++}
++
++void TextureBrowser_ToggleShowTextures(){
++      g_TextureBrowser.m_showTextures ^= 1;
++      g_TextureBrowser.m_showtextures_item.update();
++
++      g_TextureBrowser.m_heightChanged = true;
++      g_TextureBrowser.m_originInvalid = true;
++      g_activeShadersChangedCallbacks();
++
++      TextureBrowser_queueDraw( g_TextureBrowser );
++}
++
++void TextureBrowser_ToggleShowShaderListOnly(){
++      g_TextureBrowser_shaderlistOnly ^= 1;
++      g_TextureBrowser.m_showshaderlistonly_item.update();
++
++      TextureBrowser_constructTreeStore();
++}
++
++void TextureBrowser_showAll(){
++      g_TextureBrowser_currentDirectory = "";
++      g_TextureBrowser.m_searchedTags = false;
++//    TextureBrowser_SetHideUnused( g_TextureBrowser, false );
++      TextureBrowser_ToggleHideUnused();
++      //TextureBrowser_heightChanged( g_TextureBrowser );
++      TextureBrowser_updateTitle();
++}
++
++void TextureBrowser_showUntagged(){
++      auto result = ui::alert( g_TextureBrowser.m_parent, "WARNING! This function might need a lot of memory and time. Are you sure you want to use it?", "Show Untagged", ui::alert_type::YESNO, ui::alert_icon::Warning );
++
++      if ( result == ui::alert_response::YES ) {
++              g_TextureBrowser.m_found_shaders.clear();
++              TagBuilder.GetUntagged( g_TextureBrowser.m_found_shaders );
++              std::set<CopiedString>::iterator iter;
++
++              ScopeDisableScreenUpdates disableScreenUpdates( "Searching untagged textures...", "Loading Textures" );
++
++              for ( iter = g_TextureBrowser.m_found_shaders.begin(); iter != g_TextureBrowser.m_found_shaders.end(); iter++ )
++              {
++                      std::string path = ( *iter ).c_str();
++                      size_t pos = path.find_last_of( "/", path.size() );
++                      std::string name = path.substr( pos + 1, path.size() );
++                      path = path.substr( 0, pos + 1 );
++                      TextureDirectory_loadTexture( path.c_str(), name.c_str() );
++                      globalErrorStream() << path.c_str() << name.c_str() << "\n";
++              }
++
++              g_TextureBrowser_currentDirectory = "Untagged";
++              TextureBrowser_queueDraw( GlobalTextureBrowser() );
++              TextureBrowser_heightChanged( g_TextureBrowser );
++              TextureBrowser_updateTitle();
++      }
++}
++
++void TextureBrowser_FixedSize(){
++      g_TextureBrowser_fixedSize ^= 1;
++      GlobalTextureBrowser().m_fixedsize_item.update();
++      TextureBrowser_activeShadersChanged( GlobalTextureBrowser() );
++}
++
++void TextureBrowser_FilterMissing(){
++      g_TextureBrowser_filterMissing ^= 1;
++      GlobalTextureBrowser().m_filternotex_item.update();
++      TextureBrowser_activeShadersChanged( GlobalTextureBrowser() );
++      TextureBrowser_RefreshShaders();
++}
++
++void TextureBrowser_FilterFallback(){
++      g_TextureBrowser_filterFallback ^= 1;
++      GlobalTextureBrowser().m_hidenotex_item.update();
++      TextureBrowser_activeShadersChanged( GlobalTextureBrowser() );
++      TextureBrowser_RefreshShaders();
++}
++
++void TextureBrowser_EnableAlpha(){
++      g_TextureBrowser_enableAlpha ^= 1;
++      GlobalTextureBrowser().m_enablealpha_item.update();
++      TextureBrowser_activeShadersChanged( GlobalTextureBrowser() );
++}
++
++void TextureBrowser_exportTitle( const Callback<void(const char *)> & importer ){
++      StringOutputStream buffer( 64 );
++      buffer << "Textures: ";
++      if ( !string_empty( g_TextureBrowser_currentDirectory.c_str() ) ) {
++              buffer << g_TextureBrowser_currentDirectory.c_str();
++      }
++      else
++      {
++              buffer << "all";
++      }
++      importer( buffer.c_str() );
++}
++
++struct TextureScale {
++      static void Export(const TextureBrowser &self, const Callback<void(int)> &returnz) {
++              switch (self.m_textureScale) {
++                      case 10:
++                              returnz(0);
++                              break;
++                      case 25:
++                              returnz(1);
++                              break;
++                      case 50:
++                              returnz(2);
++                              break;
++                      case 100:
++                              returnz(3);
++                              break;
++                      case 200:
++                              returnz(4);
++                              break;
++              }
++      }
++
++      static void Import(TextureBrowser &self, int value) {
++              switch (value) {
++                      case 0:
++                              TextureBrowser_setScale(self, 10);
++                              break;
++                      case 1:
++                              TextureBrowser_setScale(self, 25);
++                              break;
++                      case 2:
++                              TextureBrowser_setScale(self, 50);
++                              break;
++                      case 3:
++                              TextureBrowser_setScale(self, 100);
++                              break;
++                      case 4:
++                              TextureBrowser_setScale(self, 200);
++                              break;
++              }
++      }
++};
++
++struct UniformTextureSize {
++      static void Export(const TextureBrowser &self, const Callback<void(int)> &returnz) {
++              returnz(g_TextureBrowser.m_uniformTextureSize);
++      }
++
++      static void Import(TextureBrowser &self, int value) {
++              if (value > 16)
++                      TextureBrowser_setUniformSize(self, value);
++      }
++};
++
++struct UniformTextureMinSize {
++      static void Export(const TextureBrowser &self, const Callback<void(int)> &returnz) {
++              returnz(g_TextureBrowser.m_uniformTextureMinSize);
++      }
++
++      static void Import(TextureBrowser &self, int value) {
++              if (value > 16)
++                      TextureBrowser_setUniformSize(self, value);
++      }
++};
++
++void TextureBrowser_constructPreferences( PreferencesPage& page ){
++      page.appendCheckBox(
++              "", "Texture scrollbar",
++              make_property<TextureBrowser_ShowScrollbar>(GlobalTextureBrowser())
++              );
++      {
++              const char* texture_scale[] = { "10%", "25%", "50%", "100%", "200%" };
++              page.appendCombo(
++                      "Texture Thumbnail Scale",
++                      STRING_ARRAY_RANGE( texture_scale ),
++                      make_property<TextureScale>(GlobalTextureBrowser())
++                      );
++      }
++      page.appendSpinner( "Thumbnails Max Size", GlobalTextureBrowser().m_uniformTextureSize, GlobalTextureBrowser().m_uniformTextureSize, 16, 8192 );
++      page.appendSpinner( "Thumbnails Min Size", GlobalTextureBrowser().m_uniformTextureMinSize, GlobalTextureBrowser().m_uniformTextureMinSize, 16, 8192 );
++      page.appendEntry( "Mousewheel Increment", GlobalTextureBrowser().m_mouseWheelScrollIncrement );
++      {
++              const char* startup_shaders[] = { "None", TextureBrowser_getComonShadersName() };
++              page.appendCombo( "Load Shaders at Startup", reinterpret_cast<int&>( GlobalTextureBrowser().m_startupShaders ), STRING_ARRAY_RANGE( startup_shaders ) );
++      }
++      {
++              StringOutputStream sstream( 256 );
++              sstream << "Hide nonShaders in " << TextureBrowser_getComonShadersDir() << " folder";
++              page.appendCheckBox(
++                      "", sstream.c_str(),
++                      GlobalTextureBrowser().m_hideNonShadersInCommon
++                      );
++      }
++}
++
++void TextureBrowser_constructPage( PreferenceGroup& group ){
++      PreferencesPage page( group.createPage( "Texture Browser", "Texture Browser Preferences" ) );
++      TextureBrowser_constructPreferences( page );
++}
++
++void TextureBrowser_registerPreferencesPage(){
++      PreferencesDialog_addSettingsPage( makeCallbackF(TextureBrowser_constructPage) );
++}
++
++
++#include "preferencesystem.h"
++#include "stringio.h"
++
++
++void TextureClipboard_textureSelected( const char* shader );
++
++void TextureBrowser_Construct(){
++<<<<<<< HEAD
++      GlobalCommands_insert( "ShaderInfo", makeCallbackF(TextureBrowser_shaderInfo) );
++      GlobalCommands_insert( "ShowUntagged", makeCallbackF(TextureBrowser_showUntagged) );
++      GlobalCommands_insert( "AddTag", makeCallbackF(TextureBrowser_addTag) );
++      GlobalCommands_insert( "RenameTag", makeCallbackF(TextureBrowser_renameTag) );
++      GlobalCommands_insert( "DeleteTag", makeCallbackF(TextureBrowser_deleteTag) );
++      GlobalCommands_insert( "CopyTag", makeCallbackF(TextureBrowser_copyTag) );
++      GlobalCommands_insert( "PasteTag", makeCallbackF(TextureBrowser_pasteTag) );
++      GlobalCommands_insert( "RefreshShaders", makeCallbackF(VFS_Refresh) );
++      GlobalToggles_insert( "ShowInUse", makeCallbackF(TextureBrowser_ToggleHideUnused), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_hideunused_item ), Accelerator( 'U' ) );
++      GlobalCommands_insert( "ShowAllTextures", makeCallbackF(TextureBrowser_showAll), Accelerator( 'A', (GdkModifierType)GDK_CONTROL_MASK ) );
++      GlobalCommands_insert( "ToggleTextures", makeCallbackF(TextureBrowser_toggleShow), Accelerator( 'T' ) );
++      GlobalToggles_insert( "ToggleShowShaders", makeCallbackF(TextureBrowser_ToggleShowShaders), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showshaders_item ) );
++      GlobalToggles_insert( "ToggleShowTextures", makeCallbackF(TextureBrowser_ToggleShowTextures), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showtextures_item ) );
++      GlobalToggles_insert( "ToggleShowShaderlistOnly", makeCallbackF(TextureBrowser_ToggleShowShaderListOnly),
++ ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showshaderlistonly_item ) );
++      GlobalToggles_insert( "FixedSize", makeCallbackF(TextureBrowser_FixedSize), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_fixedsize_item ) );
++      GlobalToggles_insert( "FilterMissing", makeCallbackF(TextureBrowser_FilterMissing), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_filternotex_item ) );
++      GlobalToggles_insert( "FilterFallback", makeCallbackF(TextureBrowser_FilterFallback), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_hidenotex_item ) );
++      GlobalToggles_insert( "EnableAlpha", makeCallbackF(TextureBrowser_EnableAlpha), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_enablealpha_item ) );
++
++      GlobalPreferenceSystem().registerPreference( "TextureScale", make_property_string<TextureScale>(g_TextureBrowser) );
++      GlobalPreferenceSystem().registerPreference( "UniformTextureSize", make_property_string<UniformTextureSize>(g_TextureBrowser) );
++      GlobalPreferenceSystem().registerPreference( "UniformTextureMinSize", make_property_string<UniformTextureMinSize>(g_TextureBrowser) );
++      GlobalPreferenceSystem().registerPreference( "TextureScrollbar", make_property_string<TextureBrowser_ShowScrollbar>(GlobalTextureBrowser()));
++      GlobalPreferenceSystem().registerPreference( "ShowShaders", make_property_string( GlobalTextureBrowser().m_showShaders ) );
++      GlobalPreferenceSystem().registerPreference( "ShowTextures", make_property_string( GlobalTextureBrowser().m_showTextures ) );
++      GlobalPreferenceSystem().registerPreference( "ShowShaderlistOnly", make_property_string( g_TextureBrowser_shaderlistOnly ) );
++      GlobalPreferenceSystem().registerPreference( "FixedSize", make_property_string( g_TextureBrowser_fixedSize ) );
++      GlobalPreferenceSystem().registerPreference( "FilterMissing", make_property_string( g_TextureBrowser_filterMissing ) );
++      GlobalPreferenceSystem().registerPreference( "EnableAlpha", make_property_string( g_TextureBrowser_enableAlpha ) );
++      GlobalPreferenceSystem().registerPreference( "LoadShaders", make_property_string( reinterpret_cast<int&>( GlobalTextureBrowser().m_startupShaders ) ) );
++      GlobalPreferenceSystem().registerPreference( "WheelMouseInc", make_property_string( GlobalTextureBrowser().m_mouseWheelScrollIncrement ) );
++      GlobalPreferenceSystem().registerPreference( "SI_Colors0", make_property_string( GlobalTextureBrowser().color_textureback ) );
++=======
++      GlobalCommands_insert( "ShaderInfo", FreeCaller<TextureBrowser_shaderInfo>() );
++      GlobalCommands_insert( "ShowUntagged", FreeCaller<TextureBrowser_showUntagged>() );
++      GlobalCommands_insert( "AddTag", FreeCaller<TextureBrowser_addTag>() );
++      GlobalCommands_insert( "RenameTag", FreeCaller<TextureBrowser_renameTag>() );
++      GlobalCommands_insert( "DeleteTag", FreeCaller<TextureBrowser_deleteTag>() );
++      GlobalCommands_insert( "CopyTag", FreeCaller<TextureBrowser_copyTag>() );
++      GlobalCommands_insert( "PasteTag", FreeCaller<TextureBrowser_pasteTag>() );
++      GlobalCommands_insert( "RefreshShaders", FreeCaller<RefreshShaders>() );
++      GlobalToggles_insert( "ShowInUse", FreeCaller<TextureBrowser_ToggleHideUnused>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_hideunused_item ), Accelerator( 'U' ) );
++      GlobalCommands_insert( "ShowAllTextures", FreeCaller<TextureBrowser_showAll>(), Accelerator( 'A', (GdkModifierType)GDK_CONTROL_MASK ) );
++      GlobalCommands_insert( "ToggleTextures", FreeCaller<TextureBrowser_toggleShow>(), Accelerator( 'T' ) );
++      GlobalToggles_insert( "ToggleShowShaders", FreeCaller<TextureBrowser_ToggleShowShaders>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showshaders_item ) );
++      GlobalToggles_insert( "ToggleShowTextures", FreeCaller<TextureBrowser_ToggleShowTextures>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showtextures_item ) );
++      GlobalToggles_insert( "ToggleShowShaderlistOnly", FreeCaller<TextureBrowser_ToggleShowShaderListOnly>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showshaderlistonly_item ) );
++      GlobalToggles_insert( "FixedSize", FreeCaller<TextureBrowser_FixedSize>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_fixedsize_item ) );
++      GlobalToggles_insert( "FilterNotex", FreeCaller<TextureBrowser_FilterNotex>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_filternotex_item ) );
++      GlobalToggles_insert( "EnableAlpha", FreeCaller<TextureBrowser_EnableAlpha>(), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_enablealpha_item ) );
++
++      GlobalPreferenceSystem().registerPreference( "TextureScale",
++                                                                                               makeSizeStringImportCallback( TextureBrowserSetScaleCaller( g_TextureBrowser ) ),
++                                                                                               SizeExportStringCaller( g_TextureBrowser.m_textureScale )
++                                                                                               );
++      GlobalPreferenceSystem().registerPreference( "UniformTextureSize",
++                                                                                              makeIntStringImportCallback(UniformTextureSizeImportCaller(g_TextureBrowser)),
++                                                                                              IntExportStringCaller(g_TextureBrowser.m_uniformTextureSize) );
++      GlobalPreferenceSystem().registerPreference( "UniformTextureMinSize",
++                                                                                              makeIntStringImportCallback(UniformTextureMinSizeImportCaller(g_TextureBrowser)),
++                                                                                              IntExportStringCaller(g_TextureBrowser.m_uniformTextureMinSize) );
++      GlobalPreferenceSystem().registerPreference( "TextureScrollbar",
++                                                                                               makeBoolStringImportCallback( TextureBrowserImportShowScrollbarCaller( g_TextureBrowser ) ),
++                                                                                               BoolExportStringCaller( GlobalTextureBrowser().m_showTextureScrollbar )
++                                                                                               );
++      GlobalPreferenceSystem().registerPreference( "ShowShaders", BoolImportStringCaller( GlobalTextureBrowser().m_showShaders ), BoolExportStringCaller( GlobalTextureBrowser().m_showShaders ) );
++      GlobalPreferenceSystem().registerPreference( "ShowTextures", BoolImportStringCaller( GlobalTextureBrowser().m_showTextures ), BoolExportStringCaller( GlobalTextureBrowser().m_showTextures ) );
++      GlobalPreferenceSystem().registerPreference( "ShowShaderlistOnly", BoolImportStringCaller( g_TextureBrowser_shaderlistOnly ), BoolExportStringCaller( g_TextureBrowser_shaderlistOnly ) );
++      GlobalPreferenceSystem().registerPreference( "FixedSize", BoolImportStringCaller( g_TextureBrowser_fixedSize ), BoolExportStringCaller( g_TextureBrowser_fixedSize ) );
++      GlobalPreferenceSystem().registerPreference( "FilterNotex", BoolImportStringCaller( g_TextureBrowser_filterNotex ), BoolExportStringCaller( g_TextureBrowser_filterNotex ) );
++      GlobalPreferenceSystem().registerPreference( "EnableAlpha", BoolImportStringCaller( g_TextureBrowser_enableAlpha ), BoolExportStringCaller( g_TextureBrowser_enableAlpha ) );
++      GlobalPreferenceSystem().registerPreference( "LoadShaders", IntImportStringCaller( reinterpret_cast<int&>( GlobalTextureBrowser().m_startupShaders ) ), IntExportStringCaller( reinterpret_cast<int&>( GlobalTextureBrowser().m_startupShaders ) ) );
++      GlobalPreferenceSystem().registerPreference( "WheelMouseInc", SizeImportStringCaller( GlobalTextureBrowser().m_mouseWheelScrollIncrement ), SizeExportStringCaller( GlobalTextureBrowser().m_mouseWheelScrollIncrement ) );
++      GlobalPreferenceSystem().registerPreference( "SI_Colors0", Vector3ImportStringCaller( GlobalTextureBrowser().color_textureback ), Vector3ExportStringCaller( GlobalTextureBrowser().color_textureback ) );
++      GlobalPreferenceSystem().registerPreference( "HideNonShadersInCommon", BoolImportStringCaller( GlobalTextureBrowser().m_hideNonShadersInCommon ), BoolExportStringCaller( GlobalTextureBrowser().m_hideNonShadersInCommon ) );
++>>>>>>> 3a78d902017a780e65f21f12c709aa746dfcab84
++
++      g_TextureBrowser.shader = texdef_name_default();
++
++      Textures_setModeChangedNotify( ReferenceCaller<TextureBrowser, void(), TextureBrowser_queueDraw>( g_TextureBrowser ) );
++
++      TextureBrowser_registerPreferencesPage();
++
++      GlobalShaderSystem().attach( g_ShadersObserver );
++
++      TextureBrowser_textureSelected = TextureClipboard_textureSelected;
++}
++
++void TextureBrowser_Destroy(){
++      GlobalShaderSystem().detach( g_ShadersObserver );
++
++      Textures_setModeChangedNotify( Callback<void()>() );
++}
++
++ui::Widget TextureBrowser_getGLWidget(){
++      return GlobalTextureBrowser().m_gl_widget;
++}
Simple merge