1a251b05173f15a77225dfda9025de501c118053
[xonotic/netradiant.git] / plugins / textool / TexTool.cpp
1 /*
2    Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3    For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 //-----------------------------------------------------------------------------
23 //
24 // DESCRIPTION:
25 // main plugin implementation
26 // texturing tools for Radiant
27 //
28
29 #include "StdAfx.h"
30
31 static void dialog_button_callback( GtkWidget *widget, gpointer data ){
32         GtkWidget *parent;
33         int *loop, *ret;
34
35         parent = gtk_widget_get_toplevel( widget );
36         loop = (int*)g_object_get_data( G_OBJECT( parent ), "loop" );
37         ret = (int*)g_object_get_data( G_OBJECT( parent ), "ret" );
38
39         *loop = 0;
40         *ret = gpointer_to_int( data );
41 }
42
43 static gint dialog_delete_callback( GtkWidget *widget, GdkEvent* event, gpointer data ){
44         int *loop;
45
46         gtk_widget_hide( widget );
47         loop = (int*)g_object_get_data( G_OBJECT( widget ), "loop" );
48         *loop = 0;
49
50         return TRUE;
51 }
52
53 int DoMessageBox( const char* lpText, const char* lpCaption, guint32 uType ){
54         GtkWidget *w, *vbox, *hbox;
55         int mode = ( uType & MB_TYPEMASK ), ret, loop = 1;
56
57         auto window = ui::Window( ui::window_type::TOP );
58         window.connect( "delete_event",
59                                                 G_CALLBACK( dialog_delete_callback ), NULL );
60         window.connect( "destroy",
61                                                 G_CALLBACK( gtk_widget_destroy ), NULL );
62         gtk_window_set_title( GTK_WINDOW( window ), lpCaption );
63         gtk_container_set_border_width( GTK_CONTAINER( window ), 10 );
64         g_object_set_data( G_OBJECT( window ), "loop", &loop );
65         g_object_set_data( G_OBJECT( window ), "ret", &ret );
66         gtk_widget_realize( window );
67
68         vbox = ui::VBox( FALSE, 10 );
69         window.add(vbox);
70         gtk_widget_show( vbox );
71
72         w = ui::Label( lpText );
73         gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 2 );
74         gtk_label_set_justify( GTK_LABEL( w ), GTK_JUSTIFY_LEFT );
75         gtk_widget_show( w );
76
77         w = gtk_hseparator_new();
78         gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 2 );
79         gtk_widget_show( w );
80
81         hbox = ui::HBox( FALSE, 10 );
82         gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 2 );
83         gtk_widget_show( hbox );
84
85         if ( mode == MB_OK ) {
86                 w = ui::Button( "Ok" );
87                 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
88                 w.connect( "clicked",
89                                                         G_CALLBACK( dialog_button_callback ), GINT_TO_POINTER( IDOK ) );
90                 gtk_widget_set_can_default( w, true );
91                 gtk_widget_grab_default( w );
92                 gtk_widget_show( w );
93                 ret = IDOK;
94         }
95         else if ( mode ==  MB_OKCANCEL ) {
96                 w = ui::Button( "Ok" );
97                 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
98                 w.connect( "clicked",
99                                                         G_CALLBACK( dialog_button_callback ), GINT_TO_POINTER( IDOK ) );
100                 gtk_widget_set_can_default( w, true );
101                 gtk_widget_grab_default( w );
102                 gtk_widget_show( w );
103
104                 w = ui::Button( "Cancel" );
105                 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
106                 w.connect( "clicked",
107                                                         G_CALLBACK( dialog_button_callback ), GINT_TO_POINTER( IDCANCEL ) );
108                 gtk_widget_show( w );
109                 ret = IDCANCEL;
110         }
111         else if ( mode == MB_YESNOCANCEL ) {
112                 w = ui::Button( "Yes" );
113                 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
114                 w.connect( "clicked",
115                                                         G_CALLBACK( dialog_button_callback ), GINT_TO_POINTER( IDYES ) );
116                 gtk_widget_set_can_default( w, true );
117                 gtk_widget_grab_default( w );
118                 gtk_widget_show( w );
119
120                 w = ui::Button( "No" );
121                 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
122                 w.connect( "clicked",
123                                                         G_CALLBACK( dialog_button_callback ), GINT_TO_POINTER( IDNO ) );
124                 gtk_widget_show( w );
125
126                 w = ui::Button( "Cancel" );
127                 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
128                 w.connect( "clicked",
129                                                         G_CALLBACK( dialog_button_callback ), GINT_TO_POINTER( IDCANCEL ) );
130                 gtk_widget_show( w );
131                 ret = IDCANCEL;
132         }
133         else /* if (mode == MB_YESNO) */
134         {
135                 w = ui::Button( "Yes" );
136                 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
137                 w.connect( "clicked",
138                                                         G_CALLBACK( dialog_button_callback ), GINT_TO_POINTER( IDYES ) );
139                 gtk_widget_set_can_default( w, true );
140                 gtk_widget_grab_default( w );
141                 gtk_widget_show( w );
142
143                 w = ui::Button( "No" );
144                 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
145                 w.connect( "clicked",
146                                                         G_CALLBACK( dialog_button_callback ), GINT_TO_POINTER( IDNO ) );
147                 gtk_widget_show( w );
148                 ret = IDNO;
149         }
150
151         gtk_widget_show( window );
152         gtk_grab_add( window );
153
154         while ( loop )
155                 gtk_main_iteration();
156
157         gtk_grab_remove( window );
158         gtk_widget_destroy( window );
159
160         return ret;
161 }
162
163 // Radiant function table
164 _QERFuncTable_1 g_FuncTable;
165
166 // plugin name
167 const char *PLUGIN_NAME = "Q3 Texture Tools";
168
169 // commands in the menu
170 const char *PLUGIN_COMMANDS = "About...;Go...";
171
172 // cast to GtkWidget*
173 void *g_pMainWnd;
174 IWindow *g_pToolWnd = NULL; // handle to the window
175 CWindowListener g_Listen;
176
177 // plugin interfaces ---------------------------
178 bool g_bQglInitDone = false;
179 OpenGLBinding g_QglTable;
180 bool g_bSelectedFaceInitDone = false;
181 _QERSelectedFaceTable g_SelectedFaceTable;
182 bool g_bUITable = false;
183 _QERUITable g_UITable;
184
185 // selected face -------------------------------
186 // we use this one to commit / read with Radiant
187 _QERFaceData g_SelectedFaceData;
188 // g_pSelectedFaceWindings gets allocated with MAX_POINTS_ON_WINDING at plugin startup ( QERPlug_Init )
189 winding_t               *g_pSelectedFaceWinding = NULL;
190 const float g_ViewportRatio = 1.2f;
191 // usefull class to manage the 2D view
192 C2DView g_2DView;
193 // control points to move the polygon
194 CControlPointsManagerBFace g_ControlPointsBFace;
195 // tells if a face is selected and we have something to render in the TexWindow
196 bool g_bTexViewReady = false;
197 // data for texture work
198 int g_NumPoints;
199 CtrlPts_t g_WorkWinding;
200 // reference _QERFaceData we use on Cancel, and for Commit
201 _QERFaceData g_CancelFaceData;
202
203 // patches -------------------------------------
204 bool g_bPatch = false;
205 //++timo we use this one to grab selected patchMesh_t
206 // FIXME: update when there's a real interface to read/write patches
207 bool g_bSurfaceTableInitDone = false;
208 _QERAppSurfaceTable g_SurfaceTable;
209 CControlPointsManagerPatch g_ControlPointsPatch;
210 // data for texture work
211 patchMesh_t*            g_pPatch;
212 // we only use ctrl[][].st in this one
213 patchMesh_t g_WorkPatch;
214 // copy of initial g_pPatch for Cancel situation
215 patchMesh_t g_CancelPatch;
216
217 // ---------------------------------------------
218 // holds the manager we are currently using
219 CControlPointsManager   *g_pManager = NULL;
220
221 // ---------------------------------------------
222 // globals flags for user preferences
223 //++timo TODO: this should be retrieved from the Editor's .INI prefs in a dedicated interface
224 // update camera view during manipulation ?
225 bool g_bPrefsUpdateCameraView = true;
226
227 // misc ----------------------------------------
228 bool g_bHelp = false;
229 //++timo FIXME: used to close the plugin window if InitTexView fails
230 // it's dirty, only use is to prevent infinite loop in DialogProc
231 bool g_bClosing = false;
232
233 const char *PLUGIN_ABOUT = "Texture Tools for Radiant\n\n"
234                                                    "Gtk port by Leonardo Zide (leo@lokigames.com)\n"
235                                                    "Original version by Timothee \"TTimo\" Besset (timo@qeradiant.com)";
236
237 extern "C" void* WINAPI QERPlug_GetFuncTable(){
238         return &g_FuncTable;
239 }
240
241 const char* QERPlug_Init( void* hApp, void *pWidget ){
242         int size;
243         GtkWidget* pMainWidget = static_cast<GtkWidget*>( pWidget );
244
245         g_pMainWnd = pMainWidget;
246         memset( &g_FuncTable, 0, sizeof( _QERFuncTable_1 ) );
247         g_FuncTable.m_nSize = sizeof( _QERFuncTable_1 );
248         size = (int)( (winding_t *)0 )->points[MAX_POINTS_ON_WINDING];
249         g_pSelectedFaceWinding = (winding_t *)malloc( size );
250         memset( g_pSelectedFaceWinding, 0, size );
251         return "Texture tools for Radiant";
252 }
253
254 const char* QERPlug_GetName(){
255         return (char*)PLUGIN_NAME;
256 }
257
258 const char* QERPlug_GetCommandList(){
259         return PLUGIN_COMMANDS;
260 }
261
262 char *TranslateString( char *buf ){
263         static char buf2[32768];
264         int i, l;
265         char  *out;
266
267         l = strlen( buf );
268         out = buf2;
269         for ( i = 0 ; i < l ; i++ )
270         {
271                 if ( buf[i] == '\n' ) {
272                         *out++ = '\r';
273                         *out++ = '\n';
274                 }
275                 else{
276                         *out++ = buf[i];
277                 }
278         }
279         *out++ = 0;
280
281         return buf2;
282 }
283
284 // called by InitTexView to fit the view against the bounding box of control points
285 void FitView( IWindow* hwndDlg, int TexSize[2] ){
286         // apply a ratio to get the area we'll draw
287         g_2DView.m_Center[0] = 0.5f * ( g_2DView.m_Mins[0] + g_2DView.m_Maxs[0] );
288         g_2DView.m_Center[1] = 0.5f * ( g_2DView.m_Mins[1] + g_2DView.m_Maxs[1] );
289         g_2DView.m_Mins[0] = g_2DView.m_Center[0] + g_ViewportRatio * ( g_2DView.m_Mins[0] - g_2DView.m_Center[0] );
290         g_2DView.m_Mins[1] = g_2DView.m_Center[1] + g_ViewportRatio * ( g_2DView.m_Mins[1] - g_2DView.m_Center[1] );
291         g_2DView.m_Maxs[0] = g_2DView.m_Center[0] + g_ViewportRatio * ( g_2DView.m_Maxs[0] - g_2DView.m_Center[0] );
292         g_2DView.m_Maxs[1] = g_2DView.m_Center[1] + g_ViewportRatio * ( g_2DView.m_Maxs[1] - g_2DView.m_Center[1] );
293
294         g_2DView.m_rect.left = 0;
295         g_2DView.m_rect.top = 0;
296         g_2DView.m_rect.bottom = hwndDlg->getHeight();
297         g_2DView.m_rect.right = hwndDlg->getWidth();
298
299         // we need to draw this area, now compute a bigger area so the texture scale is the same along X and Y
300         // compute box shape in XY space, let's say X <-> S we'll get a ratio for Y:
301         if ( !g_bPatch ) {
302                 g_SelectedFaceTable.m_pfnGetTextureSize( 0, TexSize );
303         }
304         else
305         {
306                 TexSize[0] = g_pPatch->d_texture->width;
307                 TexSize[1] = g_pPatch->d_texture->height;
308         }
309         // we want a texture with the same X / Y ratio
310         // compute XY space / window size ratio
311         float SSize = (float)fabs( g_2DView.m_Maxs[0] - g_2DView.m_Mins[0] );
312         float TSize = (float)fabs( g_2DView.m_Maxs[1] - g_2DView.m_Mins[1] );
313         float XSize = TexSize[0] * SSize;
314         float YSize = TexSize[1] * TSize;
315         float RatioX = XSize / (float)abs( g_2DView.m_rect.left - g_2DView.m_rect.right );
316         float RatioY = YSize / (float)abs( g_2DView.m_rect.top - g_2DView.m_rect.bottom );
317         if ( RatioX > RatioY ) {
318                 YSize = (float)abs( g_2DView.m_rect.top - g_2DView.m_rect.bottom ) * RatioX;
319                 TSize = YSize / (float)TexSize[1];
320         }
321         else
322         {
323                 XSize = (float)abs( g_2DView.m_rect.left - g_2DView.m_rect.right ) * RatioY;
324                 SSize = XSize / (float)TexSize[0];
325         }
326         g_2DView.m_Mins[0] = g_2DView.m_Center[0] - 0.5f * SSize;
327         g_2DView.m_Maxs[0] = g_2DView.m_Center[0] + 0.5f * SSize;
328         g_2DView.m_Mins[1] = g_2DView.m_Center[1] - 0.5f * TSize;
329         g_2DView.m_Maxs[1] = g_2DView.m_Center[1] + 0.5f * TSize;
330 }
331
332 // call this one each time we need to re-init
333 //++timo TODO: re-init objects state, g_2DView and g_ControlPointsManager
334 void InitTexView( IWindow* hwndDlg ){
335         // size of the texture we are working on
336         int TexSize[2];
337         g_bTexViewReady = false;
338         if ( g_SelectedFaceTable.m_pfnGetSelectedFaceCount() != 0 ) {
339                 g_SelectedFaceTable.m_pfnGetFaceInfo( 0, &g_SelectedFaceData, g_pSelectedFaceWinding );
340                 g_bPatch = false;
341                 int i;
342                 // we have something selected
343                 // setup: compute BBox for the winding ( in ST space )
344                 //++timo FIXME: move this in a C2DView member ? used as well for patches
345                 g_2DView.m_Mins[0] = +9999.0f; g_2DView.m_Mins[1] = +9999.0f;
346                 g_2DView.m_Maxs[0] = -9999.0f; g_2DView.m_Maxs[1] = -9999.0f;
347                 for ( i = 0; i < g_pSelectedFaceWinding->numpoints; i++ )
348                 {
349                         if ( g_pSelectedFaceWinding->points[i][3] < g_2DView.m_Mins[0] ) {
350                                 g_2DView.m_Mins[0] = g_pSelectedFaceWinding->points[i][3];
351                         }
352                         if ( g_pSelectedFaceWinding->points[i][3] > g_2DView.m_Maxs[0] ) {
353                                 g_2DView.m_Maxs[0] = g_pSelectedFaceWinding->points[i][3];
354                         }
355                         if ( g_pSelectedFaceWinding->points[i][4] < g_2DView.m_Mins[1] ) {
356                                 g_2DView.m_Mins[1] = g_pSelectedFaceWinding->points[i][4];
357                         }
358                         if ( g_pSelectedFaceWinding->points[i][4] > g_2DView.m_Maxs[1] ) {
359                                 g_2DView.m_Maxs[1] = g_pSelectedFaceWinding->points[i][4];
360                         }
361                 }
362                 // NOTE: FitView will read and init TexSize
363                 FitView( hwndDlg, TexSize );
364                 // now init the work tables
365                 g_NumPoints = g_pSelectedFaceWinding->numpoints;
366                 for ( i = 0; i < g_NumPoints; i++ )
367                 {
368                         g_WorkWinding.data[i][0] = g_pSelectedFaceWinding->points[i][3];
369                         g_WorkWinding.data[i][1] = g_pSelectedFaceWinding->points[i][4];
370                 }
371                 g_ControlPointsBFace.Init( g_NumPoints, &g_WorkWinding, &g_2DView, TexSize, &g_SelectedFaceData, &g_QglTable );
372                 // init snap-to-grid
373                 float fTexStep[2];
374                 fTexStep[0] = 1.0f / float(TexSize[0]);
375                 fTexStep[1] = 1.0f / float(TexSize[1]);
376                 g_2DView.SetGrid( fTexStep[0], fTexStep[1] );
377                 g_pManager = &g_ControlPointsBFace;
378                 // prepare the "Cancel" data
379                 memcpy( &g_CancelFaceData, &g_SelectedFaceData, sizeof( _QERFaceData ) );
380                 // we are done
381                 g_bTexViewReady = true;
382         }
383         else if ( g_SurfaceTable.m_pfnAnyPatchesSelected() ) {
384                 g_pPatch = g_SurfaceTable.m_pfnGetSelectedPatch();
385                 g_bPatch = true;
386                 int i,j;
387                 // compute BBox for all patch points
388                 g_2DView.m_Mins[0] = +9999.0f; g_2DView.m_Mins[1] = +9999.0f;
389                 g_2DView.m_Maxs[0] = -9999.0f; g_2DView.m_Maxs[1] = -9999.0f;
390                 for ( i = 0; i < g_pPatch->width; i++ )
391                 {
392                         for ( j = 0; j < g_pPatch->height; j++ )
393                         {
394                                 if ( g_pPatch->ctrl[i][j].st[0] < g_2DView.m_Mins[0] ) {
395                                         g_2DView.m_Mins[0] = g_pPatch->ctrl[i][j].st[0];
396                                 }
397                                 if ( g_pPatch->ctrl[i][j].st[0] > g_2DView.m_Maxs[0] ) {
398                                         g_2DView.m_Maxs[0] = g_pPatch->ctrl[i][j].st[0];
399                                 }
400                                 if ( g_pPatch->ctrl[i][j].st[1] < g_2DView.m_Mins[1] ) {
401                                         g_2DView.m_Mins[1] = g_pPatch->ctrl[i][j].st[1];
402                                 }
403                                 if ( g_pPatch->ctrl[i][j].st[1] > g_2DView.m_Maxs[1] ) {
404                                         g_2DView.m_Maxs[1] = g_pPatch->ctrl[i][j].st[1];
405                                 }
406                         }
407                 }
408                 FitView( hwndDlg, TexSize );
409                 // init the work tables
410                 g_WorkPatch = *g_pPatch;
411                 g_ControlPointsPatch.Init( &g_WorkPatch, &g_2DView, &g_QglTable, g_pPatch );
412                 // init snap-to-grid
413                 float fTexStep[2];
414                 fTexStep[0] = 1.0f / float(TexSize[0]);
415                 fTexStep[1] = 1.0f / float(TexSize[1]);
416                 g_2DView.SetGrid( fTexStep[0], fTexStep[1] );
417                 g_pManager = &g_ControlPointsPatch;
418                 // prepare the "cancel" data
419                 g_CancelPatch = *g_pPatch;
420                 // we are done
421                 g_bTexViewReady = true;
422         }
423 }
424
425 void Textool_Validate(){
426         // validate current situation into the main view
427         g_pManager->Commit();
428         // for a brush face we have an aditionnal step
429         if ( !g_bPatch ) {
430                 // tell Radiant to update (will also send update windows messages )
431                 g_SelectedFaceTable.m_pfnSetFaceInfo( 0, &g_SelectedFaceData );
432         }
433         else
434         {
435                 // ask to rebuild the patch display data
436                 g_pPatch->bDirty = true;
437                 // send a repaint to the camera window as well
438                 g_FuncTable.m_pfnSysUpdateWindows( W_CAMERA );
439         }
440         // we'll need to update after that as well:
441         g_bTexViewReady = false;
442         // send a repaint message
443         g_pToolWnd->Redraw();
444 }
445
446 void Textool_Cancel(){
447         if ( !g_bPatch ) {
448                 // tell Radiant to update (will also send update windows messages )
449                 g_SelectedFaceTable.m_pfnSetFaceInfo( 0, &g_CancelFaceData );
450         }
451         else
452         {
453                 *g_pPatch = g_CancelPatch;
454                 g_pPatch->bDirty = true;
455                 g_FuncTable.m_pfnSysUpdateWindows( W_CAMERA );
456         }
457         // do not call destroy, decref it
458         g_pToolWnd->DecRef();
459         g_pToolWnd = NULL;
460 }
461
462 static void DoExpose(){
463         int i,j;
464
465         g_2DView.PreparePaint();
466         g_QglTable.m_pfn_qglColor3f( 1, 1, 1 );
467         // draw the texture background
468         g_QglTable.m_pfn_qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
469         if ( !g_bPatch ) {
470                 g_QglTable.m_pfn_qglBindTexture( GL_TEXTURE_2D, g_SelectedFaceTable.m_pfnGetTextureNumber( 0 ) );
471         }
472         else
473         {
474                 g_QglTable.m_pfn_qglBindTexture( GL_TEXTURE_2D, g_pPatch->d_texture->texture_number );
475         }
476
477         g_QglTable.m_pfn_qglEnable( GL_TEXTURE_2D );
478         g_QglTable.m_pfn_qglBegin( GL_QUADS );
479         g_QglTable.m_pfn_qglTexCoord2f( g_2DView.m_Mins[0], g_2DView.m_Mins[1] );
480         g_QglTable.m_pfn_qglVertex2f( g_2DView.m_Mins[0], g_2DView.m_Mins[1] );
481         g_QglTable.m_pfn_qglTexCoord2f( g_2DView.m_Maxs[0], g_2DView.m_Mins[1] );
482         g_QglTable.m_pfn_qglVertex2f( g_2DView.m_Maxs[0], g_2DView.m_Mins[1] );
483         g_QglTable.m_pfn_qglTexCoord2f( g_2DView.m_Maxs[0], g_2DView.m_Maxs[1] );
484         g_QglTable.m_pfn_qglVertex2f( g_2DView.m_Maxs[0], g_2DView.m_Maxs[1] );
485         g_QglTable.m_pfn_qglTexCoord2f( g_2DView.m_Mins[0], g_2DView.m_Maxs[1] );
486         g_QglTable.m_pfn_qglVertex2f( g_2DView.m_Mins[0], g_2DView.m_Maxs[1] );
487         g_QglTable.m_pfn_qglEnd();
488         g_QglTable.m_pfn_qglDisable( GL_TEXTURE_2D );
489
490         if ( !g_bPatch ) {
491                 g_QglTable.m_pfn_qglBegin( GL_LINE_LOOP );
492                 for ( i = 0; i < g_NumPoints; i++ )
493                 {
494                         g_QglTable.m_pfn_qglVertex2f( g_WorkWinding.data[i][0], g_WorkWinding.data[i][1] );
495                 }
496                 g_QglTable.m_pfn_qglEnd();
497         }
498         else
499         {
500                 g_QglTable.m_pfn_qglBegin( GL_LINES );
501                 for ( i = 0; i < g_pPatch->width; i++ )
502                         for ( j = 0; j < g_pPatch->height; j++ )
503                         {
504                                 if ( i < g_pPatch->width - 1 ) {
505                                         g_QglTable.m_pfn_qglVertex2f( g_WorkPatch.ctrl[i][j].st[0], g_WorkPatch.ctrl[i][j].st[1] );
506                                         g_QglTable.m_pfn_qglVertex2f( g_WorkPatch.ctrl[i + 1][j].st[0], g_WorkPatch.ctrl[i + 1][j].st[1] );
507                                 }
508
509                                 if ( j < g_pPatch->height - 1 ) {
510                                         g_QglTable.m_pfn_qglVertex2f( g_WorkPatch.ctrl[i][j].st[0], g_WorkPatch.ctrl[i][j].st[1] );
511                                         g_QglTable.m_pfn_qglVertex2f( g_WorkPatch.ctrl[i][j + 1].st[0], g_WorkPatch.ctrl[i][j + 1].st[1] );
512                                 }
513                         }
514                 g_QglTable.m_pfn_qglEnd();
515         }
516
517         // let the control points manager render
518         g_pManager->render();
519 }
520
521 static bool CanProcess(){
522         if ( !g_bTexViewReady && !g_bClosing ) {
523                 InitTexView( g_pToolWnd );
524
525                 if ( !g_bTexViewReady ) {
526                         g_bClosing = true;
527                         DoMessageBox( "You must have brush primitives activated in your project settings and\n"
528                                                   "have a patch or a single face selected to use the TexTool plugin.\n"
529                                                   "See plugins/TexToolHelp for documentation.", "TexTool plugin", MB_ICONERROR | MB_OK );
530                         // decref, this will destroy
531                         g_pToolWnd->DecRef();
532                         g_pToolWnd = NULL;
533                         return 0;
534                 }
535                 else{
536                         g_bClosing = false;
537                 }
538         }
539         else if ( !g_bTexViewReady && g_bClosing ) {
540                 return 0;
541         }
542
543         return 1;
544 }
545
546 #if 0
547 static void button_press( GtkWidget *widget, GdkEventButton *event, gpointer data ){
548         if ( CanProcess() ) {
549                 switch ( event->button )
550                 {
551                 case 1:
552                         g_pManager->OnLButtonDown( event->x, event->y ); break;
553                 case 3:
554                         g_2DView.OnRButtonDown( event->x, event->y ); break;
555                 }
556         }
557 }
558
559 static void button_release( GtkWidget *widget, GdkEventButton *event, gpointer data ){
560         if ( CanProcess() ) {
561                 switch ( event->button )
562                 {
563                 case 1:
564                         g_pManager->OnLButtonUp( event->x, event->y ); break;
565                 case 3:
566                         g_2DView.OnRButtonUp( event->x, event->y ); break;
567                 }
568         }
569 }
570
571 static void motion( GtkWidget *widget, GdkEventMotion *event, gpointer data ){
572         if ( CanProcess() ) {
573                 if ( g_2DView.OnMouseMove( event->x, event->y ) ) {
574                         return;
575                 }
576
577                 if ( g_pManager->OnMouseMove( event->x, event->y ) ) {
578                         return;
579                 }
580         }
581 }
582
583 static gint expose( GtkWidget *widget, GdkEventExpose *event, gpointer data ){
584         if ( event->count > 0 ) {
585                 return TRUE;
586         }
587
588         if ( !CanProcess() ) {
589                 return TRUE;
590         }
591
592         if ( g_bTexViewReady ) {
593                 g_2DView.m_rect.bottom = widget->allocation.height;
594                 g_2DView.m_rect.right = widget->allocation.width;
595
596                 if ( !g_QglTable.m_pfn_glwidget_make_current( g_pToolWidget ) ) {
597                         Sys_Printf( "TexTool: glMakeCurrent failed\n" );
598                         return TRUE;
599                 }
600
601                 DoExpose();
602
603                 g_QglTable.m_pfn_glwidget_swap_buffers( g_pToolWidget );
604         }
605
606         return TRUE;
607 }
608
609 static gint keypress( GtkWidget* widget, GdkEventKey* event, gpointer data ){
610         unsigned int code = gdk_keyval_to_upper( event->keyval );
611
612         if ( code == GDK_Escape ) {
613                 gtk_widget_destroy( g_pToolWnd );
614                 g_pToolWnd = NULL;
615                 return TRUE;
616         }
617
618         if ( CanProcess() ) {
619                 if ( g_2DView.OnKeyDown( code ) ) {
620                         return FALSE;
621                 }
622
623                 if ( code == GDK_Return ) {
624                         Textool_Validate();
625                         return FALSE;
626                 }
627         }
628
629         return TRUE;
630 }
631
632 static gint close( GtkWidget *widget, GdkEvent* event, gpointer data ){
633         gtk_widget_destroy( widget );
634         g_pToolWnd = NULL;
635
636         return TRUE;
637 }
638
639 static GtkWidget* CreateOpenGLWidget(){
640         g_pToolWidget = g_QglTable.m_pfn_glwidget_new( FALSE, g_QglTable.m_pfn_GetQeglobalsGLWidget() );
641
642         gtk_widget_set_events( g_pToolWidget, GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK |
643                                                    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK );
644
645         // Connect signal handlers
646         g_pToolWidget.connect( "expose_event", G_CALLBACK( expose ), NULL );
647         g_pToolWidget.connect( "motion_notify_event",
648                                                 G_CALLBACK( motion ), NULL );
649         g_pToolWidget.connect( "button_press_event",
650                                                 G_CALLBACK( button_press ), NULL );
651         g_pToolWidget.connect( "button_release_event",
652                                                 G_CALLBACK( button_release ), NULL );
653
654         g_pToolWnd.connect( "delete_event", G_CALLBACK( close ), NULL );
655         g_pToolWnd.connect( "key_press_event",
656                                                 G_CALLBACK( keypress ), NULL );
657
658         return g_pToolWidget;
659 }
660 #endif
661
662 #if 0
663 static void DoPaint(){
664         if ( !CanProcess() ) {
665                 return;
666         }
667
668         if ( g_bTexViewReady ) {
669                 g_2DView.m_rect.bottom = g_pToolWnd->getHeight();
670                 g_2DView.m_rect.right = g_pToolWnd->getWidth();
671
672                 // set GL_PROJECTION
673                 g_2DView.PreparePaint();
674                 // render the objects
675                 // the master is not rendered the same way, draw over a unified texture background
676                 g_2DView.TextureBackground( g_DrawObjects[0].pObject->getTextureNumber() );
677                 if ( g_nDrawObjects >= 1 ) {
678                         int i;
679                         for ( i = 1; i < g_nDrawObjects; i++ )
680                         {
681                                 // we use a first step to the GL_MODELVIEW for the master object
682                                 // GL_MODELVIEW will be altered in RenderAuxiliary too
683                                 g_DrawObjects[0].pObject->PrepareModelView( g_DrawObjects[i].pTopo );
684                                 g_DrawObjects[i].pObject->RenderAuxiliary();
685                         }
686                 }
687                 // draw the polygon outline and control points
688                 g_DrawObjects[0].pObject->PrepareModelView( NULL );
689                 g_DrawObjects[0].pObject->RenderUI();
690         }
691 }
692 #endif
693
694 bool CWindowListener::OnLButtonDown( guint32 nFlags, double x, double y ){
695         if ( CanProcess() ) {
696                 g_pManager->OnLButtonDown( (int)x, (int)y );
697                 return true;
698         }
699         return false;
700 }
701
702 bool CWindowListener::OnRButtonDown( guint32 nFlags, double x, double y ){
703         if ( CanProcess() ) {
704                 g_2DView.OnRButtonDown( (int)x, (int)y );
705                 return true;
706         }
707         return false;
708 }
709
710 bool CWindowListener::OnLButtonUp( guint32 nFlags, double x, double y ){
711         if ( CanProcess() ) {
712                 g_pManager->OnLButtonUp( (int)x, (int)y );
713                 return true;
714         }
715         return false;
716 }
717
718 bool CWindowListener::OnRButtonUp( guint32 nFlags, double x, double y ){
719         if ( CanProcess() ) {
720                 g_2DView.OnRButtonUp( (int)x, (int)y );
721                 return true;
722         }
723         return false;
724 }
725
726 bool CWindowListener::OnMouseMove( guint32 nFlags, double x, double y ){
727         if ( CanProcess() ) {
728                 if ( g_2DView.OnMouseMove( (int)x, (int)y ) ) {
729                         return true;
730                 }
731
732                 g_pManager->OnMouseMove( (int)x, (int)y );
733                 return true;
734         }
735         return false;
736 }
737
738 // the widget is closing
739 void CWindowListener::Close(){
740         g_pToolWnd = NULL;
741 }
742
743 bool CWindowListener::Paint(){
744         if ( !CanProcess() ) {
745                 return false;
746         }
747
748         if ( g_bTexViewReady ) {
749                 DoExpose();
750         }
751
752         return true;
753 }
754
755 bool CWindowListener::OnKeyPressed( char *s ){
756         if ( !strcmp( s,"Escape" ) ) {
757                 Textool_Cancel();
758                 return TRUE;
759         }
760         if ( CanProcess() ) {
761                 if ( g_2DView.OnKeyDown( s ) ) {
762                         return TRUE;
763                 }
764
765                 if ( !strcmp( s,"Return" ) ) {
766                         Textool_Validate();
767                         return TRUE;
768                 }
769         }
770         return FALSE;
771 }
772
773 extern "C" void QERPlug_Dispatch( const char* p, vec3_t vMin, vec3_t vMax, bool bSingleBrush ){
774   #if 0
775         // if it's the first call, perhaps we need some additional init steps
776         if ( !g_bQglInitDone ) {
777                 g_QglTable.m_nSize = sizeof( OpenGLBinding );
778                 if ( g_FuncTable.m_pfnRequestInterface( QERQglTable_GUID, static_cast<LPVOID>( &g_QglTable ) ) ) {
779                         g_bQglInitDone = true;
780                 }
781                 else
782                 {
783                         Sys_Printf( "TexTool plugin: OpenGLBinding interface request failed\n" );
784                         return;
785                 }
786         }
787
788         if ( !g_bSelectedFaceInitDone ) {
789                 g_SelectedFaceTable.m_nSize = sizeof( _QERSelectedFaceTable );
790                 if ( g_FuncTable.m_pfnRequestInterface( QERSelectedFaceTable_GUID,
791                                                                                                 static_cast<LPVOID>( &g_SelectedFaceTable ) ) ) {
792                         g_bSelectedFaceInitDone = true;
793                 }
794                 else
795                 {
796                         Sys_Printf( "TexTool plugin: _QERSelectedFaceTable interface request failed\n" );
797                         return;
798                 }
799         }
800
801         if ( !g_bSurfaceTableInitDone ) {
802                 g_SurfaceTable.m_nSize = sizeof( _QERAppSurfaceTable );
803                 if ( g_FuncTable.m_pfnRequestInterface( QERAppSurfaceTable_GUID, static_cast<LPVOID>( &g_SurfaceTable ) ) ) {
804                         g_bSurfaceTableInitDone = true;
805                 }
806                 else
807                 {
808                         Sys_Printf( "TexTool plugin: _QERAppSurfaceTable interface request failed\n" );
809                         return;
810                 }
811         }
812
813         if ( !g_bUITable ) {
814                 g_UITable.m_nSize = sizeof( _QERUITable );
815                 if ( g_FuncTable.m_pfnRequestInterface( QERUI_GUID, static_cast<LPVOID>( &g_UITable ) ) ) {
816                         g_bUITable = true;
817                 }
818                 else
819                 {
820                         Sys_Printf( "TexTool plugin: _QERUITable interface request failed\n" );
821                         return;
822                 }
823         }
824   #endif
825
826         if ( !strcmp( p, "About..." ) ) {
827                 DoMessageBox( PLUGIN_ABOUT, "About ...", MB_OK );
828         }
829         else if ( !strcmp( p, "Go..." ) ) {
830                 if ( !g_pToolWnd ) {
831                         g_pToolWnd = g_UITable.m_pfnCreateGLWindow();
832                         g_pToolWnd->setSizeParm( 300,300 );
833                         g_pToolWnd->setName( "TexTool" );
834                         // g_Listener is a static class, we need to bump the refCount to avoid premature release problems
835                         g_Listen.IncRef();
836                         // setListener will incRef on the listener too
837                         g_pToolWnd->setListener( &g_Listen );
838                         if ( !g_pToolWnd->Show() ) {
839                                 DoMessageBox( "Error creating texture tools window!", "TexTool plugin", MB_ICONERROR | MB_OK );
840                                 return;
841                         }
842                 }
843
844                 g_bTexViewReady = false;
845                 g_bClosing = false;
846         }
847         else if ( !strcmp( p, "Help..." ) ) {
848                 if ( !g_bHelp ) {
849                         DoMessageBox( "Select a brush face (ctrl+shift+left mouse) or a patch, and hit Go...\n"
850                                                   "See tutorials for more", "TexTool plugin", MB_OK );
851                 }
852                 else{
853                         DoMessageBox( "Are you kidding me ?", "TexTool plugin", MB_OK );
854                 }
855                 g_bHelp = true;
856         }
857 }
858
859 // =============================================================================
860 // SYNAPSE
861
862 CSynapseServer* g_pSynapseServer = NULL;
863 CSynapseClientTexTool g_SynapseClient;
864
865 extern "C" CSynapseClient * SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces( const char *version, CSynapseServer *pServer ){
866         if ( strcmp( version, SYNAPSE_VERSION ) ) {
867                 Syn_Printf( "ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version );
868                 return NULL;
869         }
870         g_pSynapseServer = pServer;
871         g_pSynapseServer->IncRef();
872         Set_Syn_Printf( g_pSynapseServer->Get_Syn_Printf() );
873
874         g_SynapseClient.AddAPI( PLUGIN_MAJOR, "textool", sizeof( _QERPluginTable ) );
875         g_SynapseClient.AddAPI( RADIANT_MAJOR, NULL, sizeof( g_FuncTable ), SYN_REQUIRE, &g_FuncTable );
876         g_SynapseClient.AddAPI( QGL_MAJOR, NULL, sizeof( g_QglTable ), SYN_REQUIRE, &g_QglTable );
877         g_SynapseClient.AddAPI( SELECTEDFACE_MAJOR, NULL, sizeof( g_SelectedFaceTable ), SYN_REQUIRE, &g_SelectedFaceTable );
878
879         return &g_SynapseClient;
880 }
881
882 bool CSynapseClientTexTool::RequestAPI( APIDescriptor_t *pAPI ){
883         if ( !strcmp( pAPI->major_name, PLUGIN_MAJOR ) ) {
884                 _QERPluginTable *pTable = static_cast<_QERPluginTable*>( pAPI->mpTable );
885                 pTable->m_pfnQERPlug_Init = QERPlug_Init;
886                 pTable->m_pfnQERPlug_GetName = QERPlug_GetName;
887                 pTable->m_pfnQERPlug_GetCommandList = QERPlug_GetCommandList;
888                 pTable->m_pfnQERPlug_Dispatch = QERPlug_Dispatch;
889                 return true;
890         }
891
892         Syn_Printf( "ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo() );
893         return false;
894 }
895
896 #include "version.h"
897
898 const char* CSynapseClientTexTool::GetInfo(){
899         return "Texture Tools plugin built " __DATE__ " " RADIANT_VERSION;
900 }