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