Merge commit '515673c08f8718a237e90c2130a1f5294f966d6a'
[xonotic/netradiant.git] / contrib / hydratoolz / plugin.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 #include "plugin.h"
23 #include "version.h"
24
25 /*! \file plugin.cpp
26     \brief HydraToolz!
27
28     HydraToolz by Dominic Clifton - Hydra (Hydra@Hydras-World.com)
29
30     Overview
31     ========
32
33
34     Version History
35     ===============
36
37     v0.1 - 28/May/2002
38       - Initial version.
39
40
41     ToDo
42     ====
43
44     * Code it ? :)
45
46 */
47
48 // =============================================================================
49 // Globals
50
51 _QERFuncTable_1 g_FuncTable;
52 _QERFileSystemTable g_FileSystemTable;
53 _QEREntityTable g_EntityTable;
54
55 // cast to GtkWidget*
56 void *g_pMainWnd;
57
58 // =============================================================================
59 // Ripped from TexTool.cpp
60
61 static void dialog_button_callback (GtkWidget *widget, gpointer data)
62 {
63   GtkWidget *parent;
64   int *loop, *ret;
65
66   parent = gtk_widget_get_toplevel (widget);
67   loop = (int*)gtk_object_get_data (GTK_OBJECT (parent), "loop");
68   ret = (int*)gtk_object_get_data (GTK_OBJECT (parent), "ret");
69
70   *loop = 0;
71   *ret = gpointer_to_int (data);
72 }
73
74 static gint dialog_delete_callback (GtkWidget *widget, GdkEvent* event, gpointer data)
75 {
76   int *loop;
77
78   gtk_widget_hide (widget);
79   loop = (int*)gtk_object_get_data (GTK_OBJECT (widget), "loop");
80   *loop = 0;
81
82   return TRUE;
83 }
84
85 int DoMessageBox (const char* lpText, const char* lpCaption, guint32 uType)
86 {
87   GtkWidget *window, *w, *vbox, *hbox;
88   int mode = (uType & MB_TYPEMASK), ret, loop = 1;
89
90   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
91   gtk_signal_connect (GTK_OBJECT (window), "delete_event",
92                       GTK_SIGNAL_FUNC (dialog_delete_callback), NULL);
93   gtk_signal_connect (GTK_OBJECT (window), "destroy",
94                       GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL);
95   gtk_window_set_title (GTK_WINDOW (window), lpCaption);
96   gtk_container_border_width (GTK_CONTAINER (window), 10);
97   gtk_object_set_data (GTK_OBJECT (window), "loop", &loop);
98   gtk_object_set_data (GTK_OBJECT (window), "ret", &ret);
99   gtk_widget_realize (window);
100
101   vbox = gtk_vbox_new (FALSE, 10);
102   gtk_container_add (GTK_CONTAINER (window), vbox);
103   gtk_widget_show (vbox);
104
105   w = gtk_label_new (lpText);
106   gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2);
107   gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
108   gtk_widget_show (w);
109
110   w = gtk_hseparator_new ();
111   gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2);
112   gtk_widget_show (w);
113
114   hbox = gtk_hbox_new (FALSE, 10);
115   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2);
116   gtk_widget_show (hbox);
117
118   if (mode == MB_OK)
119   {
120     w = gtk_button_new_with_label ("Ok");
121     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
122     gtk_signal_connect (GTK_OBJECT (w), "clicked",
123                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK));
124     GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
125     gtk_widget_grab_default (w);
126     gtk_widget_show (w);
127     ret = IDOK;
128   }
129   else if (mode ==  MB_OKCANCEL)
130   {
131     w = gtk_button_new_with_label ("Ok");
132     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
133     gtk_signal_connect (GTK_OBJECT (w), "clicked",
134                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK));
135     GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
136     gtk_widget_grab_default (w);
137     gtk_widget_show (w);
138
139     w = gtk_button_new_with_label ("Cancel");
140     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
141     gtk_signal_connect (GTK_OBJECT (w), "clicked",
142                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL));
143     gtk_widget_show (w);
144     ret = IDCANCEL;
145   }
146   else if (mode == MB_YESNOCANCEL)
147   {
148     w = gtk_button_new_with_label ("Yes");
149     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
150     gtk_signal_connect (GTK_OBJECT (w), "clicked",
151                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDYES));
152     GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
153     gtk_widget_grab_default (w);
154     gtk_widget_show (w);
155
156     w = gtk_button_new_with_label ("No");
157     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
158     gtk_signal_connect (GTK_OBJECT (w), "clicked",
159                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDNO));
160     gtk_widget_show (w);
161
162     w = gtk_button_new_with_label ("Cancel");
163     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
164     gtk_signal_connect (GTK_OBJECT (w), "clicked",
165                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL));
166     gtk_widget_show (w);
167     ret = IDCANCEL;
168   }
169   else /* if (mode == MB_YESNO) */
170   {
171     w = gtk_button_new_with_label ("Yes");
172     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
173     gtk_signal_connect (GTK_OBJECT (w), "clicked",
174                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDYES));
175     GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
176     gtk_widget_grab_default (w);
177     gtk_widget_show (w);
178
179     w = gtk_button_new_with_label ("No");
180     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
181     gtk_signal_connect (GTK_OBJECT (w), "clicked",
182                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDNO));
183     gtk_widget_show (w);
184     ret = IDNO;
185   }
186
187   gtk_widget_show (window);
188   gtk_grab_add (window);
189
190   while (loop)
191     gtk_main_iteration ();
192
193   gtk_grab_remove (window);
194   gtk_widget_destroy (window);
195
196   return ret;
197 }
198
199 // End of rip from TexTool.cpp
200
201 // =============================================================================
202 // Ripped from cmdlib.cpp
203
204 /*
205 ====================
206 Extract file parts
207 ====================
208 */
209 void ExtractFilePath (const char *path, char *dest)
210 {
211   const char *src;
212
213   src = path + strlen(path) - 1;
214
215 //
216 // back up until a \ or the start
217 //
218   while (src != path && *(src-1) != '/' && *(src-1) != '\\')
219     src--;
220
221   memcpy (dest, path, src-path);
222   dest[src-path] = 0;
223 }
224
225 void ExtractFileName (const char *path, char *dest)
226 {
227   const char *src;
228
229   src = path + strlen(path) - 1;
230
231 //
232 // back up until a \ or the start
233 //
234   while (src != path && *(src-1) != '/'
235          && *(src-1) != '\\' )
236     src--;
237
238   while (*src)
239   {
240     *dest++ = *src++;
241   }
242   *dest = 0;
243 }
244
245 void ConvertDOSToUnixName( char *dst, const char *src )
246 {
247   while ( *src )
248   {
249     if ( *src == '\\' )
250       *dst = '/';
251     else
252       *dst = *src;
253     dst++; src++;
254   }
255   *dst = 0;
256 }
257
258 // End of rip from cmdlib.cpp
259
260 // =============================================================================
261 // Actual Plugin Code
262
263 // get the wad name from the shader name (or an actual wadname) and add to a list of wad names making
264 // sure we don't add duplicates.
265
266 GSList *AddToWadList(GSList *wadlist, const char *shadername, const char *wad)
267 {
268   char tmpstr[QER_MAX_NAMELEN];
269   char *wadname;
270   if (!shadername && !wad) return wadlist;
271
272   if (shadername)
273   {
274     if (strcmp(shadername,"color") == 0)
275       return wadlist;
276     ExtractFilePath(shadername,tmpstr);
277     // Sys_Printf("checking: %s\n",shadername);
278
279     int l = strlen(tmpstr) - 1;
280
281     if (tmpstr[l] == '/' || tmpstr[l] == '\\')
282       tmpstr[l] = 0;
283     else
284     {
285       Sys_Printf("WARNING: Unknown wad file for shader %s\n",shadername);
286       return wadlist;
287     }
288
289     ExtractFileName(tmpstr,tmpstr);
290
291     wadname = (char *)malloc(strlen(tmpstr) + 5);
292     sprintf(wadname,"%s.wad",tmpstr);
293   }
294   else
295   {
296     wadname=strdup(wad);
297   }
298
299   for (GSList *l = wadlist; l != NULL ; l = l->next)
300   {
301     if (string_equal_nocase((char *)l->data,wadname))
302     {
303       free( wadname );
304       return wadlist;
305     }
306   }
307
308   Sys_Printf("Adding Wad File to WAD list: %s (reason: ",wadname);
309   if (shadername)
310     Sys_Printf("see shader \"%s\")\n", shadername);
311   else
312     Sys_Printf("already in WAD key. )\n");
313   return ( g_slist_append (wadlist, wadname ) );
314 }
315
316 void UpdateWadKeyPair( void )
317 {
318   int i,nb;
319
320   char wads[2048]; // change to CString usage ?
321   wads[0] = 0;
322   char *p1,*p2;
323   entity_t *pEntity;
324   epair_t *pEpair;
325   GSList *wadlist = NULL;
326   face_t  *f;
327   brush_t *b;
328   char cleanwadname[QER_MAX_NAMELEN];
329   const char *actualwad;
330
331
332   pEntity = (entity_t *)g_FuncTable.m_pfnGetEntityHandle(0); // get the worldspawn ent
333
334   Sys_Printf("Searching for in-use wad files...\n");
335   for(pEpair = pEntity->epairs; pEpair != NULL; pEpair = pEpair->next)
336   {
337     if (string_equal_nocase(pEpair->key,"wad"))
338     {
339       strcpy(wads,pEpair->value);
340       ConvertDOSToUnixName(wads,wads);
341
342       // ok, we got the list of ; delimited wads, now split it into a GSList that contains
343       // just the wad names themselves.
344
345       p1 = wads;
346
347       do
348       {
349         p2 = strchr(p1,';');
350         if (p2)
351           *p2 = 0; // swap the ; with a null terminator
352
353         if (strchr(p1,'/') || strchr(p1,'\\'))
354         {
355           ExtractFileName(p1,cleanwadname);
356           wadlist = AddToWadList (wadlist, NULL, cleanwadname);
357         }
358         else
359         {
360           wadlist = AddToWadList (wadlist, NULL, p1);
361         }
362         if (p2)
363           p1 = p2+1; // point back to the remainder of the string
364         else
365           p1 = NULL; // make it so we exit the loop.
366
367       } while (p1);
368
369       // ok, now we have a list of wads in GSList.
370       // now we need to add any new wadfiles (with their paths) to this list
371       // so scan all brushes and see what wads are in use
372       // FIXME: scan brushes only in the region ?
373
374       break; // we don't need to process any more key/pairs.
375     }
376   }
377
378   nb = g_FuncTable.m_pfnAllocateActiveBrushHandles();
379   for( i = 0; i < nb; i++ )
380         {
381     b = (brush_t *)g_FuncTable.m_pfnGetActiveBrushHandle(i);
382     if (b->patchBrush) // patches in halflife ?
383     {
384       wadlist = AddToWadList(wadlist, b->pPatch->pShader->getName(),NULL);
385     } else
386     {
387       for (f=b->brush_faces ; f ; f=f->next)
388       {
389         wadlist = AddToWadList(wadlist, f->pShader->getName(),NULL);
390       }
391     }
392   }
393   g_FuncTable.m_pfnReleaseActiveBrushHandles();
394
395   nb = g_FuncTable.m_pfnAllocateSelectedBrushHandles();
396   for( i = 0; i < nb; i++ )
397         {
398     b = (brush_t *)g_FuncTable.m_pfnGetSelectedBrushHandle(i);
399     if (b->patchBrush) // patches in halflife ?
400     {
401       wadlist = AddToWadList(wadlist, b->pPatch->pShader->getName(),NULL);
402     } else
403     {
404       for (f=b->brush_faces ; f ; f=f->next)
405       {
406         wadlist = AddToWadList(wadlist, f->pShader->getName(),NULL);
407       }
408     }
409   }
410   g_FuncTable.m_pfnReleaseSelectedBrushHandles();
411
412   // Now we have a complete list of wadnames (without paths) so we just have to turn this
413   // back to a ; delimited list.
414
415   wads[0] = 0;
416   while (wadlist)
417   {
418     if (string_equal_nocase((char *)wadlist->data,"common-hydra.wad"))
419     {
420       Sys_Printf("Skipping radiant-supplied wad file %s\n",(char *)wadlist->data);
421     }
422     else
423     {
424       if (wads[0])
425         strcat(wads,";");
426
427       actualwad = vfsGetFullPath((char *)wadlist->data);
428
429       if (actualwad)
430       {
431         strcat(wads, actualwad);
432       }
433       else
434       {
435         Sys_Printf("WARNING: could not locate wad file %s\n",(char *)wadlist->data);
436         strcat(wads, (char *)wadlist->data);
437       }
438     }
439
440     free (wadlist->data);
441     wadlist = g_slist_remove (wadlist, wadlist->data);
442   }
443
444   // store the wad list back in the worldspawn.
445   if (wads[0])
446   {
447     //free(pEpair->value);
448     //pEpair->value = strdup(wads);
449     SetKeyValue(pEntity, "wad", wads);
450   }
451
452 }
453
454 // =============================================================================
455 // PLUGIN INTERFACE STUFF
456
457 // plugin name
458 const char *PLUGIN_NAME = "Q3 Texture Tools";
459
460 // commands in the menu
461 const char *PLUGIN_COMMANDS = "About...;Create/Update WAD keypair";
462
463 const char *PLUGIN_ABOUT = "HydraToolz for GTKRadiant\n\n"
464                            "By Hydra!";
465
466 extern "C" void* WINAPI QERPlug_GetFuncTable ()
467 {
468   return &g_FuncTable;
469 }
470
471 const char* QERPlug_Init (void* hApp, void *pWidget)
472 {
473   GtkWidget* pMainWidget = static_cast<GtkWidget*>(pWidget);
474
475   g_pMainWnd = pMainWidget;
476   memset(&g_FuncTable, 0, sizeof(_QERFuncTable_1));
477   g_FuncTable.m_nSize = sizeof(_QERFuncTable_1);
478   return "HydraToolz for GTKRadiant"; // do we need this ? hmmm
479 }
480
481 const char* QERPlug_GetName()
482 {
483   return (char*)PLUGIN_NAME;
484 }
485
486 const char* QERPlug_GetCommandList()
487 {
488   return PLUGIN_COMMANDS;
489 }
490
491 extern "C" void QERPlug_Dispatch(const char* p, vec3_t vMin, vec3_t vMax, bool bSingleBrush)
492 {
493         if(!strcmp(p, "Create/Update WAD keypair"))
494                 UpdateWadKeyPair();
495         else if(!strcmp(p, "About..."))
496                 g_FuncTable.m_pfnMessageBox((GtkWidget*)NULL, PLUGIN_ABOUT, "About", eMB_OK);
497 }
498
499 // =============================================================================
500 // SYNAPSE
501
502 CSynapseServer* g_pSynapseServer = NULL;
503 CSynapseClientHydraToolz g_SynapseClient;
504
505 extern "C" CSynapseClient* SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces (const char *version, CSynapseServer *pServer)
506 {
507   if (strcmp(version, SYNAPSE_VERSION))
508   {
509     Syn_Printf("ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version);
510     return NULL;
511   }
512   g_pSynapseServer = pServer;
513   g_pSynapseServer->IncRef();
514   Set_Syn_Printf(g_pSynapseServer->Get_Syn_Printf());
515
516   g_SynapseClient.AddAPI(PLUGIN_MAJOR, "HydraToolz", sizeof(_QERPluginTable));
517   g_SynapseClient.AddAPI(RADIANT_MAJOR, NULL, sizeof(g_FuncTable), SYN_REQUIRE, &g_FuncTable);
518   g_SynapseClient.AddAPI(VFS_MAJOR, "wad", sizeof(g_FileSystemTable), SYN_REQUIRE, &g_FileSystemTable);
519   g_SynapseClient.AddAPI(ENTITY_MAJOR, NULL, sizeof(g_EntityTable), SYN_REQUIRE, &g_EntityTable);
520   return &g_SynapseClient;
521 }
522
523 bool CSynapseClientHydraToolz::RequestAPI(APIDescriptor_t *pAPI)
524 {
525   if (!strcmp(pAPI->major_name, PLUGIN_MAJOR))
526   {
527     _QERPluginTable *pTable = static_cast<_QERPluginTable*>(pAPI->mpTable);
528     pTable->m_pfnQERPlug_Init = QERPlug_Init;
529     pTable->m_pfnQERPlug_GetName = QERPlug_GetName;
530     pTable->m_pfnQERPlug_GetCommandList = QERPlug_GetCommandList;
531     pTable->m_pfnQERPlug_Dispatch = QERPlug_Dispatch;
532     return true;
533   }
534
535   Syn_Printf("ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo());
536   return false;
537 }
538
539 const char* CSynapseClientHydraToolz::GetInfo()
540 {
541   return "HydraToolz plugin built " __DATE__ " " RADIANT_VERSION;
542 }