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