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