]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/texwindow.cpp
Merge commit '0fb65a91c7530dc2215dddd13e7accf059c6f453' into garux-merge
[xonotic/netradiant.git] / radiant / texwindow.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 // Texture Window
24 //
25 // Leonardo Zide (leo@lokigames.com)
26 //
27
28 #include "texwindow.h"
29
30 #include <gtk/gtk.h>
31
32 #include "debugging/debugging.h"
33 #include "warnings.h"
34
35 #include "defaults.h"
36 #include "ifilesystem.h"
37 #include "iundo.h"
38 #include "igl.h"
39 #include "iarchive.h"
40 #include "moduleobserver.h"
41
42 #include <set>
43 #include <string>
44 #include <vector>
45
46 #include <uilib/uilib.h>
47
48 #include "signal/signal.h"
49 #include "math/vector.h"
50 #include "texturelib.h"
51 #include "string/string.h"
52 #include "shaderlib.h"
53 #include "os/file.h"
54 #include "os/path.h"
55 #include "stream/memstream.h"
56 #include "stream/textfilestream.h"
57 #include "stream/stringstream.h"
58 #include "cmdlib.h"
59 #include "texmanip.h"
60 #include "textures.h"
61 #include "convert.h"
62
63 #include "gtkutil/menu.h"
64 #include "gtkutil/nonmodal.h"
65 #include "gtkutil/cursor.h"
66 #include "gtkutil/widget.h"
67 #include "gtkutil/glwidget.h"
68 #include "gtkutil/messagebox.h"
69
70 #include "error.h"
71 #include "map.h"
72 #include "qgl.h"
73 #include "select.h"
74 #include "brush_primit.h"
75 #include "brushmanip.h"
76 #include "patchmanip.h"
77 #include "plugin.h"
78 #include "qe3.h"
79 #include "gtkdlgs.h"
80 #include "gtkmisc.h"
81 #include "mainframe.h"
82 #include "findtexturedialog.h"
83 #include "surfacedialog.h"
84 #include "patchdialog.h"
85 #include "groupdialog.h"
86 #include "preferences.h"
87 #include "shaders.h"
88 #include "commands.h"
89
90 bool TextureBrowser_showWads(){
91         return !string_empty( g_pGameDescription->getKeyValue( "show_wads" ) );
92 }
93
94 void TextureBrowser_queueDraw( TextureBrowser& textureBrowser );
95
96 bool string_equal_start( const char* string, StringRange start ){
97         return string_equal_n( string, start.first, start.last - start.first );
98 }
99
100 typedef std::set<CopiedString> TextureGroups;
101
102 void TextureGroups_addWad( TextureGroups& groups, const char* archive ){
103         if ( extension_equal( path_get_extension( archive ), "wad" ) ) {
104 #if 1
105                 groups.insert( archive );
106 #else
107                 CopiedString archiveBaseName( path_get_filename_start( archive ), path_get_filename_base_end( archive ) );
108                 groups.insert( archiveBaseName );
109 #endif
110         }
111 }
112
113 typedef ReferenceCaller<TextureGroups, void(const char*), TextureGroups_addWad> TextureGroupsAddWadCaller;
114
115 namespace
116 {
117 bool g_TextureBrowser_shaderlistOnly = false;
118 bool g_TextureBrowser_fixedSize = true;
119 bool g_TextureBrowser_filterMissing = false;
120 bool g_TextureBrowser_filterFallback = true;
121 bool g_TextureBrowser_enableAlpha = true;
122 }
123
124 CopiedString g_notex;
125 CopiedString g_shadernotex;
126
127 bool isMissing(const char* name);
128
129 bool isNotex(const char* name);
130
131 bool isMissing(const char* name){
132         if ( string_equal( g_notex.c_str(), name ) ) {
133                 return true;
134         }
135         if ( string_equal( g_shadernotex.c_str(), name ) ) {
136                 return true;
137         }
138         return false;
139 }
140
141 bool isNotex(const char* name){
142         if ( string_equal_suffix( name, "/" DEFAULT_NOTEX_BASENAME ) ) {
143                 return true;
144         }
145         if ( string_equal_suffix( name, "/" DEFAULT_SHADERNOTEX_BASENAME ) ) {
146                 return true;
147         }
148         return false;
149 }
150
151 void TextureGroups_addShader( TextureGroups& groups, const char* shaderName ){
152         const char* texture = path_make_relative( shaderName, "textures/" );
153
154         // hide notex / shadernotex images
155         if ( g_TextureBrowser_filterFallback ) {
156                 if ( isNotex( shaderName ) ) {
157                         return;
158                 }
159                 if ( isNotex( texture ) ) {
160                         return;
161                 }
162         }
163
164         if ( texture != shaderName ) {
165                 const char* last = path_remove_directory( texture );
166                 if ( !string_empty( last ) ) {
167                         groups.insert( CopiedString( StringRange( texture, --last ) ) );
168                 }
169         }
170 }
171
172 typedef ReferenceCaller<TextureGroups, void(const char*), TextureGroups_addShader> TextureGroupsAddShaderCaller;
173
174 void TextureGroups_addDirectory( TextureGroups& groups, const char* directory ){
175         groups.insert( directory );
176 }
177
178 typedef ReferenceCaller<TextureGroups, void(const char*), TextureGroups_addDirectory> TextureGroupsAddDirectoryCaller;
179
180 class DeferredAdjustment
181 {
182 gdouble m_value;
183 guint m_handler;
184
185 typedef void ( *ValueChangedFunction )( void* data, gdouble value );
186
187 ValueChangedFunction m_function;
188 void* m_data;
189
190 static gboolean deferred_value_changed( gpointer data ){
191         reinterpret_cast<DeferredAdjustment*>( data )->m_function(
192                 reinterpret_cast<DeferredAdjustment*>( data )->m_data,
193                 reinterpret_cast<DeferredAdjustment*>( data )->m_value
194                 );
195         reinterpret_cast<DeferredAdjustment*>( data )->m_handler = 0;
196         reinterpret_cast<DeferredAdjustment*>( data )->m_value = 0;
197         return FALSE;
198 }
199
200 public:
201 DeferredAdjustment( ValueChangedFunction function, void* data ) : m_value( 0 ), m_handler( 0 ), m_function( function ), m_data( data ){
202 }
203
204 void flush(){
205         if ( m_handler != 0 ) {
206                 g_source_remove( m_handler );
207                 deferred_value_changed( this );
208         }
209 }
210
211 void value_changed( gdouble value ){
212         m_value = value;
213         if ( m_handler == 0 ) {
214                 m_handler = g_idle_add( deferred_value_changed, this );
215         }
216 }
217
218 static void adjustment_value_changed(ui::Adjustment adjustment, DeferredAdjustment* self ){
219         self->value_changed( gtk_adjustment_get_value(adjustment) );
220 }
221 };
222
223
224 class TextureBrowser;
225
226 typedef ReferenceCaller<TextureBrowser, void(), TextureBrowser_queueDraw> TextureBrowserQueueDrawCaller;
227
228 void TextureBrowser_scrollChanged( void* data, gdouble value );
229
230
231 enum StartupShaders
232 {
233         STARTUPSHADERS_NONE = 0,
234         STARTUPSHADERS_COMMON,
235 };
236
237 void TextureBrowser_hideUnusedExport( const Callback<void(bool)> & importer );
238
239 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_hideUnusedExport> TextureBrowserHideUnusedExport;
240
241 void TextureBrowser_showShadersExport( const Callback<void(bool)> & importer );
242
243 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShadersExport> TextureBrowserShowShadersExport;
244
245 void TextureBrowser_showShaderlistOnly( const Callback<void(bool)> & importer );
246
247 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShaderlistOnly> TextureBrowserShowShaderlistOnlyExport;
248
249 void TextureBrowser_fixedSize( const Callback<void(bool)> & importer );
250
251 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_fixedSize> TextureBrowserFixedSizeExport;
252
253 void TextureBrowser_filterMissing( const Callback<void(bool)> & importer );
254
255 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_filterMissing> TextureBrowserFilterMissingExport;
256
257 void TextureBrowser_filterFallback( const Callback<void(bool)> & importer );
258
259 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_filterFallback> TextureBrowserFilterFallbackExport;
260
261 void TextureBrowser_enableAlpha( const Callback<void(bool)> & importer );
262
263 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_enableAlpha> TextureBrowserEnableAlphaExport;
264
265 class TextureBrowser
266 {
267 public:
268 int width, height;
269 int originy;
270 int m_nTotalHeight;
271
272 CopiedString shader;
273
274 ui::Window m_parent{ui::null};
275 ui::GLArea m_gl_widget{ui::null};
276 ui::Widget m_texture_scroll{ui::null};
277 ui::TreeView m_treeViewTree{ui::New};
278 ui::TreeView m_treeViewTags{ui::null};
279 ui::Frame m_tag_frame{ui::null};
280 ui::ListStore m_assigned_store{ui::null};
281 ui::ListStore m_available_store{ui::null};
282 ui::TreeView m_assigned_tree{ui::null};
283 ui::TreeView m_available_tree{ui::null};
284 ui::Widget m_scr_win_tree{ui::null};
285 ui::Widget m_scr_win_tags{ui::null};
286 ui::Widget m_tag_notebook{ui::null};
287 ui::Button m_search_button{ui::null};
288 ui::Widget m_shader_info_item{ui::null};
289
290 std::set<CopiedString> m_all_tags;
291 ui::ListStore m_all_tags_list{ui::null};
292 std::vector<CopiedString> m_copied_tags;
293 std::set<CopiedString> m_found_shaders;
294
295 ToggleItem m_hideunused_item;
296 ToggleItem m_hidenotex_item;
297 ToggleItem m_showshaders_item;
298 ToggleItem m_showshaderlistonly_item;
299 ToggleItem m_fixedsize_item;
300 ToggleItem m_filternotex_item;
301 ToggleItem m_enablealpha_item;
302
303 guint m_sizeHandler;
304 guint m_exposeHandler;
305
306 bool m_heightChanged;
307 bool m_originInvalid;
308
309 DeferredAdjustment m_scrollAdjustment;
310 FreezePointer m_freezePointer;
311
312 Vector3 color_textureback;
313 // the increment step we use against the wheel mouse
314 std::size_t m_mouseWheelScrollIncrement;
315 std::size_t m_textureScale;
316 // make the texture increments match the grid changes
317 bool m_showShaders;
318 bool m_showTextureScrollbar;
319 StartupShaders m_startupShaders;
320 // if true, the texture window will only display in-use shaders
321 // if false, all the shaders in memory are displayed
322 bool m_hideUnused;
323 bool m_rmbSelected;
324 bool m_searchedTags;
325 bool m_tags;
326 // The uniform size (in pixels) that textures are resized to when m_resizeTextures is true.
327 int m_uniformTextureSize;
328 int m_uniformTextureMinSize;
329
330 // Return the display width of a texture in the texture browser
331 /*void getTextureWH( qtexture_t* tex, int *width, int *height ){
332         if ( !g_TextureBrowser_fixedSize ) {
333                 // Don't use uniform size
334                 *width = (int)( tex->width * ( (float)m_textureScale / 100 ) );
335                 *height = (int)( tex->height * ( (float)m_textureScale / 100 ) );
336
337         }
338         else if ( tex->width >= tex->height ) {
339                 // Texture is square, or wider than it is tall
340                 if ( tex->width >= m_uniformTextureSize ){
341                         *width = m_uniformTextureSize;
342                         *height = (int)( m_uniformTextureSize * ( (float)tex->height / tex->width ) );
343                 }
344                 else if ( tex->width <= m_uniformTextureMinSize ){
345                         *width = m_uniformTextureMinSize;
346                         *height = (int)( m_uniformTextureMinSize * ( (float)tex->height / tex->width ) );
347                 }
348                 else {
349                         *width = tex->width;
350                         *height = tex->height;
351                 }
352         }
353         else {
354                 // Texture taller than it is wide
355                 if ( tex->height >= m_uniformTextureSize ){
356                         *height = m_uniformTextureSize;
357                         *width = (int)( m_uniformTextureSize * ( (float)tex->width / tex->height ) );
358                 }
359                 else if ( tex->height <= m_uniformTextureMinSize ){
360                         *height = m_uniformTextureMinSize;
361                         *width = (int)( m_uniformTextureMinSize * ( (float)tex->width / tex->height ) );
362                 }
363                 else {
364                         *width = tex->width;
365                         *height = tex->height;
366                 }
367         }
368 }
369
370 */
371 void getTextureWH( qtexture_t* tex, int *width, int *height ){
372                 // Don't use uniform size
373                 *width = (int)( tex->width * ( (float)m_textureScale / 100 ) );
374                 *height = (int)( tex->height * ( (float)m_textureScale / 100 ) );
375
376         if ( g_TextureBrowser_fixedSize ){
377                 int W = *width;
378                 int H = *height;
379                 if      ( W >= H ) {
380                         // Texture is square, or wider than it is tall
381                         if ( W >= m_uniformTextureSize ){
382                                 *width = m_uniformTextureSize;
383                                 *height = m_uniformTextureSize * H / W;
384                         }
385                         else if ( W <= m_uniformTextureMinSize ){
386                                 *width = m_uniformTextureMinSize;
387                                 *height = m_uniformTextureMinSize * H / W;
388                         }
389                 }
390                 else {
391                         // Texture taller than it is wide
392                         if ( H >= m_uniformTextureSize ){
393                                 *height = m_uniformTextureSize;
394                                 *width = m_uniformTextureSize * W / H;
395                         }
396                         else if ( H <= m_uniformTextureMinSize ){
397                                 *height = m_uniformTextureMinSize;
398                                 *width = m_uniformTextureMinSize * W / H;
399                         }
400                 }
401         }
402 }
403
404 TextureBrowser() :
405         m_texture_scroll( ui::null ),
406         m_hideunused_item( TextureBrowserHideUnusedExport() ),
407         m_hidenotex_item( TextureBrowserFilterFallbackExport() ),
408         m_showshaders_item( TextureBrowserShowShadersExport() ),
409         m_showshaderlistonly_item( TextureBrowserShowShaderlistOnlyExport() ),
410         m_fixedsize_item( TextureBrowserFixedSizeExport() ),
411         m_filternotex_item( TextureBrowserFilterMissingExport() ),
412         m_enablealpha_item( TextureBrowserEnableAlphaExport() ),
413         m_heightChanged( true ),
414         m_originInvalid( true ),
415         m_scrollAdjustment( TextureBrowser_scrollChanged, this ),
416         color_textureback( 0.25f, 0.25f, 0.25f ),
417         m_mouseWheelScrollIncrement( 64 ),
418         m_textureScale( 50 ),
419         m_showShaders( true ),
420         m_showTextureScrollbar( true ),
421         m_startupShaders( STARTUPSHADERS_NONE ),
422         m_hideUnused( false ),
423         m_rmbSelected( false ),
424         m_searchedTags( false ),
425         m_tags( false ),
426         m_uniformTextureSize( 160 ),
427         m_uniformTextureMinSize( 48 ){
428 }
429 };
430
431 void ( *TextureBrowser_textureSelected )( const char* shader );
432
433
434 void TextureBrowser_updateScroll( TextureBrowser& textureBrowser );
435
436
437 const char* TextureBrowser_getComonShadersName(){
438         const char* value = g_pGameDescription->getKeyValue( "common_shaders_name" );
439         if ( !string_empty( value ) ) {
440                 return value;
441         }
442         return "Common";
443 }
444
445 const char* TextureBrowser_getComonShadersDir(){
446         const char* value = g_pGameDescription->getKeyValue( "common_shaders_dir" );
447         if ( !string_empty( value ) ) {
448                 return value;
449         }
450         return "common/";
451 }
452
453 inline int TextureBrowser_fontHeight( TextureBrowser& textureBrowser ){
454         return GlobalOpenGL().m_font->getPixelHeight();
455 }
456
457 const char* TextureBrowser_GetSelectedShader( TextureBrowser& textureBrowser ){
458         return textureBrowser.shader.c_str();
459 }
460
461 void TextureBrowser_SetStatus( TextureBrowser& textureBrowser, const char* name ){
462         IShader* shader = QERApp_Shader_ForName( name );
463         qtexture_t* q = shader->getTexture();
464         StringOutputStream strTex( 256 );
465         strTex << name << " W: " << Unsigned( q->width ) << " H: " << Unsigned( q->height );
466         shader->DecRef();
467         g_pParentWnd->SetStatusText( g_pParentWnd->m_texture_status, strTex.c_str() );
468 }
469
470 void TextureBrowser_Focus( TextureBrowser& textureBrowser, const char* name );
471
472 void TextureBrowser_SetSelectedShader( TextureBrowser& textureBrowser, const char* shader ){
473         textureBrowser.shader = shader;
474         TextureBrowser_SetStatus( textureBrowser, shader );
475         TextureBrowser_Focus( textureBrowser, shader );
476
477         if ( FindTextureDialog_isOpen() ) {
478                 FindTextureDialog_selectTexture( shader );
479         }
480
481         // disable the menu item "shader info" if no shader was selected
482         IShader* ishader = QERApp_Shader_ForName( shader );
483         CopiedString filename = ishader->getShaderFileName();
484
485         if ( filename.empty() ) {
486                 if ( textureBrowser.m_shader_info_item != NULL ) {
487                         gtk_widget_set_sensitive( textureBrowser.m_shader_info_item, FALSE );
488                 }
489         }
490         else {
491                 gtk_widget_set_sensitive( textureBrowser.m_shader_info_item, TRUE );
492         }
493
494         ishader->DecRef();
495 }
496
497
498 CopiedString g_TextureBrowser_currentDirectory;
499
500 /*
501    ============================================================================
502
503    TEXTURE LAYOUT
504
505    TTimo: now based on a rundown through all the shaders
506    NOTE: we expect the Active shaders count doesn't change during a Texture_StartPos .. Texture_NextPos cycle
507    otherwise we may need to rely on a list instead of an array storage
508    ============================================================================
509  */
510
511 class TextureLayout
512 {
513 public:
514 // texture layout functions
515 // TTimo: now based on shaders
516 int current_x, current_y, current_row;
517 };
518
519 void Texture_StartPos( TextureLayout& layout ){
520         layout.current_x = 8;
521         layout.current_y = -8;
522         layout.current_row = 0;
523 }
524
525 void Texture_NextPos( TextureBrowser& textureBrowser, TextureLayout& layout, qtexture_t* current_texture, int *x, int *y ){
526         qtexture_t* q = current_texture;
527
528         int nWidth, nHeight;
529         textureBrowser.getTextureWH( q, &nWidth, &nHeight );
530         if ( layout.current_x + nWidth > textureBrowser.width - 8 && layout.current_row ) { // go to the next row unless the texture is the first on the row
531                 layout.current_x = 8;
532                 layout.current_y -= layout.current_row + TextureBrowser_fontHeight( textureBrowser ) + 4;
533                 layout.current_row = 0;
534         }
535
536         *x = layout.current_x;
537         *y = layout.current_y;
538
539         // Is our texture larger than the row? If so, grow the
540         // row height to match it
541
542         if ( layout.current_row < nHeight ) {
543                 layout.current_row = nHeight;
544         }
545
546         // never go less than 96, or the names get all crunched up
547         layout.current_x += nWidth < 96 ? 96 : nWidth;
548         layout.current_x += 8;
549 }
550
551 bool TextureSearch_IsShown( const char* name ){
552         std::set<CopiedString>::iterator iter;
553
554         iter = GlobalTextureBrowser().m_found_shaders.find( name );
555
556         if ( iter == GlobalTextureBrowser().m_found_shaders.end() ) {
557                 return false;
558         }
559         else {
560                 return true;
561         }
562 }
563
564 // if texture_showinuse jump over non in-use textures
565 bool Texture_IsShown( IShader* shader, bool show_shaders, bool hideUnused ){
566         // filter missing shaders
567         // ugly: filter on built-in fallback name after substitution
568         if ( g_TextureBrowser_filterMissing ) {
569                 if ( isMissing( shader->getTexture()->name ) ) {
570                         return false;
571                 }
572         }
573         // filter the fallback (notex/shadernotex) for missing shaders or editor image
574         if ( g_TextureBrowser_filterFallback ) {
575                 if ( isNotex( shader->getName() ) ) {
576                         return false;
577                 }
578                 if ( isNotex( shader->getTexture()->name ) ) {
579                         return false;
580                 }
581         }
582
583         if ( g_TextureBrowser_currentDirectory == "Untagged" ) {
584                 std::set<CopiedString>::iterator iter;
585
586                 iter = GlobalTextureBrowser().m_found_shaders.find( shader->getName() );
587
588                 if ( iter == GlobalTextureBrowser().m_found_shaders.end() ) {
589                         return false;
590                 }
591                 else {
592                         return true;
593                 }
594         }
595
596         if ( !shader_equal_prefix( shader->getName(), "textures/" ) ) {
597                 return false;
598         }
599
600         if ( !show_shaders && !shader->IsDefault() ) {
601                 return false;
602         }
603
604         if ( hideUnused && !shader->IsInUse() ) {
605                 return false;
606         }
607
608         if ( GlobalTextureBrowser().m_searchedTags ) {
609                 if ( !TextureSearch_IsShown( shader->getName() ) ) {
610                         return false;
611                 }
612                 else {
613                         return true;
614                 }
615         }
616         else {
617                 if ( !shader_equal_prefix( shader_get_textureName( shader->getName() ), g_TextureBrowser_currentDirectory.c_str() ) ) {
618                         return false;
619                 }
620         }
621
622         return true;
623 }
624
625 void TextureBrowser_heightChanged( TextureBrowser& textureBrowser ){
626         textureBrowser.m_heightChanged = true;
627
628         TextureBrowser_updateScroll( textureBrowser );
629         TextureBrowser_queueDraw( textureBrowser );
630 }
631
632 void TextureBrowser_evaluateHeight( TextureBrowser& textureBrowser ){
633         if ( textureBrowser.m_heightChanged ) {
634                 textureBrowser.m_heightChanged = false;
635
636                 textureBrowser.m_nTotalHeight = 0;
637
638                 TextureLayout layout;
639                 Texture_StartPos( layout );
640                 for ( QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement() )
641                 {
642                         IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
643
644                         if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused ) ) {
645                                 continue;
646                         }
647
648                         int x, y;
649                         Texture_NextPos( textureBrowser, layout, shader->getTexture(), &x, &y );
650                         int nWidth, nHeight;
651                         textureBrowser.getTextureWH( shader->getTexture(), &nWidth, &nHeight );
652                         textureBrowser.m_nTotalHeight = std::max( textureBrowser.m_nTotalHeight, abs( layout.current_y ) + TextureBrowser_fontHeight( textureBrowser ) + nHeight + 4 );
653                 }
654         }
655 }
656
657 int TextureBrowser_TotalHeight( TextureBrowser& textureBrowser ){
658         TextureBrowser_evaluateHeight( textureBrowser );
659         return textureBrowser.m_nTotalHeight;
660 }
661
662 inline const int& min_int( const int& left, const int& right ){
663         return std::min( left, right );
664 }
665
666 void TextureBrowser_clampOriginY( TextureBrowser& textureBrowser ){
667         if ( textureBrowser.originy > 0 ) {
668                 textureBrowser.originy = 0;
669         }
670         int lower = min_int( textureBrowser.height - TextureBrowser_TotalHeight( textureBrowser ), 0 );
671         if ( textureBrowser.originy < lower ) {
672                 textureBrowser.originy = lower;
673         }
674 }
675
676 int TextureBrowser_getOriginY( TextureBrowser& textureBrowser ){
677         if ( textureBrowser.m_originInvalid ) {
678                 textureBrowser.m_originInvalid = false;
679                 TextureBrowser_clampOriginY( textureBrowser );
680                 TextureBrowser_updateScroll( textureBrowser );
681         }
682         return textureBrowser.originy;
683 }
684
685 void TextureBrowser_setOriginY( TextureBrowser& textureBrowser, int originy ){
686         textureBrowser.originy = originy;
687         TextureBrowser_clampOriginY( textureBrowser );
688         TextureBrowser_updateScroll( textureBrowser );
689         TextureBrowser_queueDraw( textureBrowser );
690 }
691
692
693 Signal0 g_activeShadersChangedCallbacks;
694
695 void TextureBrowser_addActiveShadersChangedCallback( const SignalHandler& handler ){
696         g_activeShadersChangedCallbacks.connectLast( handler );
697 }
698
699 void TextureBrowser_constructTreeStore();
700
701 class ShadersObserver : public ModuleObserver
702 {
703 Signal0 m_realiseCallbacks;
704 public:
705 void realise(){
706         m_realiseCallbacks();
707         TextureBrowser_constructTreeStore();
708 }
709
710 void unrealise(){
711 }
712
713 void insert( const SignalHandler& handler ){
714         m_realiseCallbacks.connectLast( handler );
715 }
716 };
717
718 namespace
719 {
720 ShadersObserver g_ShadersObserver;
721 }
722
723 void TextureBrowser_addShadersRealiseCallback( const SignalHandler& handler ){
724         g_ShadersObserver.insert( handler );
725 }
726
727 void TextureBrowser_activeShadersChanged( TextureBrowser& textureBrowser ){
728         TextureBrowser_heightChanged( textureBrowser );
729         textureBrowser.m_originInvalid = true;
730
731         g_activeShadersChangedCallbacks();
732 }
733
734 struct TextureBrowser_ShowScrollbar {
735         static void Export(const TextureBrowser &self, const Callback<void(bool)> &returnz) {
736                 returnz(self.m_showTextureScrollbar);
737         }
738
739         static void Import(TextureBrowser &self, bool value) {
740                 self.m_showTextureScrollbar = value;
741                 if (self.m_texture_scroll) {
742                         self.m_texture_scroll.visible(self.m_showTextureScrollbar);
743                         TextureBrowser_updateScroll(self);
744                 }
745         }
746 };
747
748
749 /*
750    ==============
751    TextureBrowser_ShowDirectory
752    relies on texture_directory global for the directory to use
753    1) Load the shaders for the given directory
754    2) Scan the remaining texture, load them and assign them a default shader (the "noshader" shader)
755    NOTE: when writing a texture plugin, or some texture extensions, this function may need to be overriden, and made
756    available through the IShaders interface
757    NOTE: for texture window layout:
758    all shaders are stored with alphabetical order after load
759    previously loaded and displayed stuff is hidden, only in-use and newly loaded is shown
760    ( the GL textures are not flushed though)
761    ==============
762  */
763
764 bool endswith( const char *haystack, const char *needle ){
765         size_t lh = strlen( haystack );
766         size_t ln = strlen( needle );
767         if ( lh < ln ) {
768                 return false;
769         }
770         return !memcmp( haystack + ( lh - ln ), needle, ln );
771 }
772
773 bool texture_name_ignore( const char* name ){
774         StringOutputStream strTemp( string_length( name ) );
775         strTemp << LowerCase( name );
776
777         return
778                 endswith( strTemp.c_str(), ".specular" ) ||
779                 endswith( strTemp.c_str(), ".glow" ) ||
780                 endswith( strTemp.c_str(), ".bump" ) ||
781                 endswith( strTemp.c_str(), ".diffuse" ) ||
782                 endswith( strTemp.c_str(), ".blend" ) ||
783                 endswith( strTemp.c_str(), ".alpha" ) ||
784                 endswith( strTemp.c_str(), "_alpha" ) ||
785                 /* Quetoo */
786                 endswith( strTemp.c_str(), "_h" ) ||
787                 endswith( strTemp.c_str(), "_local" ) ||
788                 endswith( strTemp.c_str(), "_nm" ) ||
789                 endswith( strTemp.c_str(), "_s" ) ||
790                 /* DarkPlaces */
791                 endswith( strTemp.c_str(), "_bump" ) ||
792                 endswith( strTemp.c_str(), "_glow" ) ||
793                 endswith( strTemp.c_str(), "_gloss" ) ||
794                 endswith( strTemp.c_str(), "_luma" ) ||
795                 endswith( strTemp.c_str(), "_norm" ) ||
796                 endswith( strTemp.c_str(), "_pants" ) ||
797                 endswith( strTemp.c_str(), "_shirt" ) ||
798                 endswith( strTemp.c_str(), "_reflect" ) ||
799                 /* Unvanquished */
800                 endswith( strTemp.c_str(), "_d" ) ||
801                 endswith( strTemp.c_str(), "_n" ) ||
802                 endswith( strTemp.c_str(), "_p" ) ||
803                 endswith( strTemp.c_str(), "_g" ) ||
804                 endswith( strTemp.c_str(), "_a" ) ||
805                 0;
806 }
807
808 class LoadShaderVisitor : public Archive::Visitor
809 {
810 public:
811 void visit( const char* name ){
812         IShader* shader = QERApp_Shader_ForName( CopiedString( StringRange( name, path_get_filename_base_end( name ) ) ).c_str() );
813         shader->DecRef();
814 }
815 };
816
817 void TextureBrowser_SetHideUnused( TextureBrowser& textureBrowser, bool hideUnused );
818
819 ui::Widget g_page_textures{ui::null};
820
821 void TextureBrowser_toggleShow(){
822         GroupDialog_showPage( g_page_textures );
823 }
824
825
826 void TextureBrowser_updateTitle(){
827         GroupDialog_updatePageTitle( g_page_textures );
828 }
829
830
831 class TextureCategoryLoadShader
832 {
833 const char* m_directory;
834 std::size_t& m_count;
835 public:
836 using func = void(const char *);
837
838 TextureCategoryLoadShader( const char* directory, std::size_t& count )
839         : m_directory( directory ), m_count( count ){
840         m_count = 0;
841 }
842
843 void operator()( const char* name ) const {
844         if ( shader_equal_prefix( name, "textures/" )
845                  && shader_equal_prefix( name + string_length( "textures/" ), m_directory ) ) {
846                 ++m_count;
847                 // request the shader, this will load the texture if needed
848                 // this Shader_ForName call is a kind of hack
849                 IShader *pFoo = QERApp_Shader_ForName( name );
850                 pFoo->DecRef();
851         }
852 }
853 };
854
855 void TextureDirectory_loadTexture( const char* directory, const char* texture ){
856         StringOutputStream name( 256 );
857         name << directory << StringRange( texture, path_get_filename_base_end( texture ) );
858
859         if ( texture_name_ignore( name.c_str() ) ) {
860                 return;
861         }
862
863         if ( !shader_valid( name.c_str() ) ) {
864                 globalOutputStream() << "Skipping invalid texture name: [" << name.c_str() << "]\n";
865                 return;
866         }
867
868         // if a texture is already in use to represent a shader, ignore it
869         IShader* shader = QERApp_Shader_ForName( name.c_str() );
870         shader->DecRef();
871 }
872
873 typedef ConstPointerCaller<char, void(const char*), TextureDirectory_loadTexture> TextureDirectoryLoadTextureCaller;
874
875 class LoadTexturesByTypeVisitor : public ImageModules::Visitor
876 {
877 const char* m_dirstring;
878 public:
879 LoadTexturesByTypeVisitor( const char* dirstring )
880         : m_dirstring( dirstring ){
881 }
882
883 void visit( const char* minor, const _QERPlugImageTable& table ) const {
884         GlobalFileSystem().forEachFile( m_dirstring, minor, TextureDirectoryLoadTextureCaller( m_dirstring ) );
885 }
886 };
887
888 void TextureBrowser_ShowDirectory( TextureBrowser& textureBrowser, const char* directory ){
889         if ( TextureBrowser_showWads() ) {
890                 Archive* archive = GlobalFileSystem().getArchive( directory );
891                 ASSERT_NOTNULL( archive );
892                 LoadShaderVisitor visitor;
893                 archive->forEachFile( Archive::VisitorFunc( visitor, Archive::eFiles, 0 ), "textures/" );
894         }
895         else
896         {
897                 g_TextureBrowser_currentDirectory = directory;
898                 TextureBrowser_heightChanged( textureBrowser );
899
900                 std::size_t shaders_count;
901                 GlobalShaderSystem().foreachShaderName(makeCallback( TextureCategoryLoadShader( directory, shaders_count ) ) );
902                 globalOutputStream() << "Showing " << Unsigned( shaders_count ) << " shaders.\n";
903
904                 if ( g_pGameDescription->mGameType != "doom3" ) {
905                         // load remaining texture files
906
907                         StringOutputStream dirstring( 64 );
908                         dirstring << "textures/" << directory;
909
910                         Radiant_getImageModules().foreachModule( LoadTexturesByTypeVisitor( dirstring.c_str() ) );
911                 }
912         }
913
914         // we'll display the newly loaded textures + all the ones already in use
915         TextureBrowser_SetHideUnused( textureBrowser, false );
916
917         TextureBrowser_updateTitle();
918 }
919
920 void TextureBrowser_ShowTagSearchResult( TextureBrowser& textureBrowser, const char* directory ){
921         g_TextureBrowser_currentDirectory = directory;
922         TextureBrowser_heightChanged( textureBrowser );
923
924         std::size_t shaders_count;
925         GlobalShaderSystem().foreachShaderName(makeCallback( TextureCategoryLoadShader( directory, shaders_count ) ) );
926         globalOutputStream() << "Showing " << Unsigned( shaders_count ) << " shaders.\n";
927
928         if ( g_pGameDescription->mGameType != "doom3" ) {
929                 // load remaining texture files
930                 StringOutputStream dirstring( 64 );
931                 dirstring << "textures/" << directory;
932
933                 {
934                         LoadTexturesByTypeVisitor visitor( dirstring.c_str() );
935                         Radiant_getImageModules().foreachModule( visitor );
936                 }
937         }
938
939         // we'll display the newly loaded textures + all the ones already in use
940         TextureBrowser_SetHideUnused( textureBrowser, false );
941 }
942
943
944 bool TextureBrowser_hideUnused();
945
946 void TextureBrowser_hideUnusedExport( const Callback<void(bool)> & importer ){
947         importer( TextureBrowser_hideUnused() );
948 }
949
950 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_hideUnusedExport> TextureBrowserHideUnusedExport;
951
952 void TextureBrowser_showShadersExport( const Callback<void(bool)> & importer ){
953         importer( GlobalTextureBrowser().m_showShaders );
954 }
955
956 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShadersExport> TextureBrowserShowShadersExport;
957
958 void TextureBrowser_showShaderlistOnly( const Callback<void(bool)> & importer ){
959         importer( g_TextureBrowser_shaderlistOnly );
960 }
961
962 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShaderlistOnly> TextureBrowserShowShaderlistOnlyExport;
963
964 void TextureBrowser_fixedSize( const Callback<void(bool)> & importer ){
965         importer( g_TextureBrowser_fixedSize );
966 }
967
968 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_fixedSize> TextureBrowser_FixedSizeExport;
969
970 void TextureBrowser_filterMissing( const Callback<void(bool)> & importer ){
971         importer( g_TextureBrowser_filterMissing );
972 }
973
974 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_filterMissing> TextureBrowser_filterMissingExport;
975
976 void TextureBrowser_filterFallback( const Callback<void(bool)> & importer ){
977         importer( g_TextureBrowser_filterFallback );
978 }
979
980 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_filterFallback> TextureBrowser_filterFallbackExport;
981
982 void TextureBrowser_enableAlpha( const Callback<void(bool)> & importer ){
983         importer( g_TextureBrowser_enableAlpha );
984 }
985
986 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_enableAlpha> TextureBrowser_enableAlphaExport;
987
988 void TextureBrowser_SetHideUnused( TextureBrowser& textureBrowser, bool hideUnused ){
989         if ( hideUnused ) {
990                 textureBrowser.m_hideUnused = true;
991         }
992         else
993         {
994                 textureBrowser.m_hideUnused = false;
995         }
996
997         textureBrowser.m_hideunused_item.update();
998
999         TextureBrowser_heightChanged( textureBrowser );
1000         textureBrowser.m_originInvalid = true;
1001 }
1002
1003 void TextureBrowser_ShowStartupShaders( TextureBrowser& textureBrowser ){
1004         if ( textureBrowser.m_startupShaders == STARTUPSHADERS_COMMON ) {
1005                 TextureBrowser_ShowDirectory( textureBrowser, TextureBrowser_getComonShadersDir() );
1006         }
1007 }
1008
1009
1010 //++timo NOTE: this is a mix of Shader module stuff and texture explorer
1011 // it might need to be split in parts or moved out .. dunno
1012 // scroll origin so the specified texture is completely on screen
1013 // if current texture is not displayed, nothing is changed
1014 void TextureBrowser_Focus( TextureBrowser& textureBrowser, const char* name ){
1015         TextureLayout layout;
1016         // scroll origin so the texture is completely on screen
1017         Texture_StartPos( layout );
1018
1019         for ( QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement() )
1020         {
1021                 IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
1022
1023                 if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused ) ) {
1024                         continue;
1025                 }
1026
1027                 int x, y;
1028                 Texture_NextPos( textureBrowser, layout, shader->getTexture(), &x, &y );
1029                 qtexture_t* q = shader->getTexture();
1030                 if ( !q ) {
1031                         break;
1032                 }
1033
1034                 // we have found when texdef->name and the shader name match
1035                 // NOTE: as everywhere else for our comparisons, we are not case sensitive
1036                 if ( shader_equal( name, shader->getName() ) ) {
1037                         int textureHeight = (int)( q->height * ( (float)textureBrowser.m_textureScale / 100 ) )
1038                                                                 + 2 * TextureBrowser_fontHeight( textureBrowser );
1039
1040                         int originy = TextureBrowser_getOriginY( textureBrowser );
1041                         if ( y > originy ) {
1042                                 originy = y;
1043                         }
1044
1045                         if ( y - textureHeight < originy - textureBrowser.height ) {
1046                                 originy = ( y - textureHeight ) + textureBrowser.height;
1047                         }
1048
1049                         TextureBrowser_setOriginY( textureBrowser, originy );
1050                         return;
1051                 }
1052         }
1053 }
1054
1055 IShader* Texture_At( TextureBrowser& textureBrowser, int mx, int my ){
1056         my += TextureBrowser_getOriginY( textureBrowser ) - textureBrowser.height;
1057
1058         TextureLayout layout;
1059         Texture_StartPos( layout );
1060         for ( QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement() )
1061         {
1062                 IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
1063
1064                 if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused ) ) {
1065                         continue;
1066                 }
1067
1068                 int x, y;
1069                 Texture_NextPos( textureBrowser, layout, shader->getTexture(), &x, &y );
1070                 qtexture_t  *q = shader->getTexture();
1071                 if ( !q ) {
1072                         break;
1073                 }
1074
1075                 int nWidth, nHeight;
1076                 textureBrowser.getTextureWH( q, &nWidth, &nHeight );
1077                 if ( mx > x && mx - x < nWidth
1078                          && my < y && y - my < nHeight + TextureBrowser_fontHeight( textureBrowser ) ) {
1079                         return shader;
1080                 }
1081         }
1082
1083         return 0;
1084 }
1085
1086 /*
1087    ==============
1088    SelectTexture
1089
1090    By mouse click
1091    ==============
1092  */
1093 void SelectTexture( TextureBrowser& textureBrowser, int mx, int my, bool bShift ){
1094         IShader* shader = Texture_At( textureBrowser, mx, my );
1095         if ( shader != 0 ) {
1096                 if ( bShift ) {
1097                         if ( shader->IsDefault() ) {
1098                                 globalOutputStream() << "ERROR: " << shader->getName() << " is not a shader, it's a texture.\n";
1099                         }
1100                         else{
1101                                 ViewShader( shader->getShaderFileName(), shader->getName() );
1102                         }
1103                 }
1104                 else
1105                 {
1106                         TextureBrowser_SetSelectedShader( textureBrowser, shader->getName() );
1107                         TextureBrowser_textureSelected( shader->getName() );
1108
1109                         if ( !FindTextureDialog_isOpen() && !textureBrowser.m_rmbSelected ) {
1110                                 UndoableCommand undo( "textureNameSetSelected" );
1111                                 Select_SetShader( shader->getName() );
1112                         }
1113                 }
1114         }
1115 }
1116
1117 /*
1118    ============================================================================
1119
1120    MOUSE ACTIONS
1121
1122    ============================================================================
1123  */
1124
1125 void TextureBrowser_trackingDelta( int x, int y, unsigned int state, void* data ){
1126         TextureBrowser& textureBrowser = *reinterpret_cast<TextureBrowser*>( data );
1127         if ( y != 0 ) {
1128                 int scale = 1;
1129
1130                 if ( state & GDK_SHIFT_MASK ) {
1131                         scale = 4;
1132                 }
1133
1134                 int originy = TextureBrowser_getOriginY( textureBrowser );
1135                 originy += y * scale;
1136                 TextureBrowser_setOriginY( textureBrowser, originy );
1137         }
1138 }
1139
1140 void TextureBrowser_Tracking_MouseDown( TextureBrowser& textureBrowser ){
1141         textureBrowser.m_freezePointer.freeze_pointer( textureBrowser.m_parent, TextureBrowser_trackingDelta, &textureBrowser );
1142 }
1143
1144 void TextureBrowser_Tracking_MouseUp( TextureBrowser& textureBrowser ){
1145         textureBrowser.m_freezePointer.unfreeze_pointer( textureBrowser.m_parent );
1146 }
1147
1148 void TextureBrowser_Selection_MouseDown( TextureBrowser& textureBrowser, guint32 flags, int pointx, int pointy ){
1149         SelectTexture( textureBrowser, pointx, textureBrowser.height - 1 - pointy, ( flags & GDK_SHIFT_MASK ) != 0 );
1150 }
1151
1152 /*
1153    ============================================================================
1154
1155    DRAWING
1156
1157    ============================================================================
1158  */
1159
1160 /*
1161    ============
1162    Texture_Draw
1163    TTimo: relying on the shaders list to display the textures
1164    we must query all qtexture_t* to manage and display through the IShaders interface
1165    this allows a plugin to completely override the texture system
1166    ============
1167  */
1168 void Texture_Draw( TextureBrowser& textureBrowser ){
1169         int originy = TextureBrowser_getOriginY( textureBrowser );
1170
1171         glClearColor( textureBrowser.color_textureback[0],
1172                                   textureBrowser.color_textureback[1],
1173                                   textureBrowser.color_textureback[2],
1174                                   0 );
1175         glViewport( 0, 0, textureBrowser.width, textureBrowser.height );
1176         glMatrixMode( GL_PROJECTION );
1177         glLoadIdentity();
1178
1179         glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
1180         glDisable( GL_DEPTH_TEST );
1181         if ( g_TextureBrowser_enableAlpha ) {
1182                 glEnable( GL_BLEND );
1183                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1184         }
1185         else {
1186                 glDisable( GL_BLEND );
1187         }
1188         glOrtho( 0, textureBrowser.width, originy - textureBrowser.height, originy, -100, 100 );
1189         glEnable( GL_TEXTURE_2D );
1190
1191         glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
1192
1193         int last_y = 0, last_height = 0;
1194
1195         TextureLayout layout;
1196         Texture_StartPos( layout );
1197         for ( QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement() )
1198         {
1199                 IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
1200
1201                 if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused ) ) {
1202                         continue;
1203                 }
1204
1205                 int x, y;
1206                 Texture_NextPos( textureBrowser, layout, shader->getTexture(), &x, &y );
1207                 qtexture_t *q = shader->getTexture();
1208                 if ( !q ) {
1209                         break;
1210                 }
1211
1212                 int nWidth, nHeight;
1213                 textureBrowser.getTextureWH( q, &nWidth, &nHeight );
1214
1215                 if ( y != last_y ) {
1216                         last_y = y;
1217                         last_height = 0;
1218                 }
1219                 last_height = std::max( nHeight, last_height );
1220
1221                 // Is this texture visible?
1222                 if ( ( y - nHeight - TextureBrowser_fontHeight( textureBrowser ) < originy )
1223                          && ( y > originy - textureBrowser.height ) ) {
1224                         // borders rules:
1225                         // if it's the current texture, draw a thick red line, else:
1226                         // shaders have a white border, simple textures don't
1227                         // if !texture_showinuse: (some textures displayed may not be in use)
1228                         // draw an additional square around with 0.5 1 0.5 color
1229                         if ( shader_equal( TextureBrowser_GetSelectedShader( textureBrowser ), shader->getName() ) ) {
1230                                 glLineWidth( 3 );
1231                                 if ( textureBrowser.m_rmbSelected ) {
1232                                         glColor3f( 0,0,1 );
1233                                 }
1234                                 else {
1235                                         glColor3f( 1,0,0 );
1236                                 }
1237                                 glDisable( GL_TEXTURE_2D );
1238
1239                                 glBegin( GL_LINE_LOOP );
1240                                 glVertex2i( x - 4,y - TextureBrowser_fontHeight( textureBrowser ) + 4 );
1241                                 glVertex2i( x - 4,y - TextureBrowser_fontHeight( textureBrowser ) - nHeight - 4 );
1242                                 glVertex2i( x + 4 + nWidth,y - TextureBrowser_fontHeight( textureBrowser ) - nHeight - 4 );
1243                                 glVertex2i( x + 4 + nWidth,y - TextureBrowser_fontHeight( textureBrowser ) + 4 );
1244                                 glEnd();
1245
1246                                 glEnable( GL_TEXTURE_2D );
1247                                 glLineWidth( 1 );
1248                         }
1249                         else
1250                         {
1251                                 glLineWidth( 1 );
1252                                 // shader border:
1253                                 if ( !shader->IsDefault() ) {
1254                                         glColor3f( 1,1,1 );
1255                                         glDisable( GL_TEXTURE_2D );
1256
1257                                         glBegin( GL_LINE_LOOP );
1258                                         glVertex2i( x - 1,y + 1 - TextureBrowser_fontHeight( textureBrowser ) );
1259                                         glVertex2i( x - 1,y - nHeight - 1 - TextureBrowser_fontHeight( textureBrowser ) );
1260                                         glVertex2i( x + 1 + nWidth,y - nHeight - 1 - TextureBrowser_fontHeight( textureBrowser ) );
1261                                         glVertex2i( x + 1 + nWidth,y + 1 - TextureBrowser_fontHeight( textureBrowser ) );
1262                                         glEnd();
1263                                         glEnable( GL_TEXTURE_2D );
1264                                 }
1265
1266                                 // highlight in-use textures
1267                                 if ( !textureBrowser.m_hideUnused && shader->IsInUse() ) {
1268                                         glColor3f( 0.5,1,0.5 );
1269                                         glDisable( GL_TEXTURE_2D );
1270                                         glBegin( GL_LINE_LOOP );
1271                                         glVertex2i( x - 3,y + 3 - TextureBrowser_fontHeight( textureBrowser ) );
1272                                         glVertex2i( x - 3,y - nHeight - 3 - TextureBrowser_fontHeight( textureBrowser ) );
1273                                         glVertex2i( x + 3 + nWidth,y - nHeight - 3 - TextureBrowser_fontHeight( textureBrowser ) );
1274                                         glVertex2i( x + 3 + nWidth,y + 3 - TextureBrowser_fontHeight( textureBrowser ) );
1275                                         glEnd();
1276                                         glEnable( GL_TEXTURE_2D );
1277                                 }
1278                         }
1279
1280                         // draw checkerboard for transparent textures
1281                         if ( g_TextureBrowser_enableAlpha )
1282                         {
1283                                 glDisable( GL_TEXTURE_2D );
1284                                 glBegin( GL_QUADS );
1285                                 int font_height = TextureBrowser_fontHeight( textureBrowser );
1286                                 for ( int i = 0; i < nHeight; i += 8 )
1287                                 {
1288                                         for ( int j = 0; j < nWidth; j += 8 )
1289                                         {
1290                                                 unsigned char color = (i + j) / 8 % 2 ? 0x66 : 0x99;
1291                                                 glColor3ub( color, color, color );
1292                                                 int left = j;
1293                                                 int right = std::min(j+8, nWidth);
1294                                                 int top = i;
1295                                                 int bottom = std::min(i+8, nHeight);
1296                                                 glVertex2i(x + right, y - nHeight - font_height + top);
1297                                                 glVertex2i(x + left,  y - nHeight - font_height + top);
1298                                                 glVertex2i(x + left,  y - nHeight - font_height + bottom);
1299                                                 glVertex2i(x + right, y - nHeight - font_height + bottom);
1300                                         }
1301                                 }
1302                                 glEnd();
1303                                 glEnable( GL_TEXTURE_2D );
1304                         }
1305
1306                         // Draw the texture
1307                         glBindTexture( GL_TEXTURE_2D, q->texture_number );
1308                         GlobalOpenGL_debugAssertNoErrors();
1309                         glColor3f( 1,1,1 );
1310                         glBegin( GL_QUADS );
1311                         glTexCoord2i( 0,0 );
1312                         glVertex2i( x,y - TextureBrowser_fontHeight( textureBrowser ) );
1313                         glTexCoord2i( 1,0 );
1314                         glVertex2i( x + nWidth,y - TextureBrowser_fontHeight( textureBrowser ) );
1315                         glTexCoord2i( 1,1 );
1316                         glVertex2i( x + nWidth,y - TextureBrowser_fontHeight( textureBrowser ) - nHeight );
1317                         glTexCoord2i( 0,1 );
1318                         glVertex2i( x,y - TextureBrowser_fontHeight( textureBrowser ) - nHeight );
1319                         glEnd();
1320
1321                         // draw the texture name
1322                         glDisable( GL_TEXTURE_2D );
1323                         glColor3f( 1,1,1 );
1324
1325                         glRasterPos2i( x, y - TextureBrowser_fontHeight( textureBrowser ) + 5 );
1326
1327                         // don't draw the directory name
1328                         const char* name = shader->getName();
1329                         name += strlen( name );
1330                         while ( name != shader->getName() && *( name - 1 ) != '/' && *( name - 1 ) != '\\' )
1331                                 name--;
1332
1333                         GlobalOpenGL().drawString( name );
1334                         glEnable( GL_TEXTURE_2D );
1335                 }
1336
1337                 //int totalHeight = abs(y) + last_height + TextureBrowser_fontHeight(textureBrowser) + 4;
1338         }
1339
1340
1341         // reset the current texture
1342         glBindTexture( GL_TEXTURE_2D, 0 );
1343         //qglFinish();
1344 }
1345
1346 void TextureBrowser_queueDraw( TextureBrowser& textureBrowser ){
1347         if ( textureBrowser.m_gl_widget ) {
1348                 gtk_widget_queue_draw( textureBrowser.m_gl_widget );
1349         }
1350 }
1351
1352
1353 void TextureBrowser_setScale( TextureBrowser& textureBrowser, std::size_t scale ){
1354         textureBrowser.m_textureScale = scale;
1355
1356         TextureBrowser_queueDraw( textureBrowser );
1357 }
1358
1359 void TextureBrowser_setUniformSize( TextureBrowser& textureBrowser, std::size_t scale ){
1360         textureBrowser.m_uniformTextureSize = scale;
1361
1362         TextureBrowser_queueDraw( textureBrowser );
1363 }
1364
1365 void TextureBrowser_setUniformMinSize( TextureBrowser& textureBrowser, std::size_t scale ){
1366         textureBrowser.m_uniformTextureMinSize = scale;
1367
1368         TextureBrowser_queueDraw( textureBrowser );
1369 }
1370
1371 void TextureBrowser_MouseWheel( TextureBrowser& textureBrowser, bool bUp ){
1372         int originy = TextureBrowser_getOriginY( textureBrowser );
1373
1374         if ( bUp ) {
1375                 originy += int(textureBrowser.m_mouseWheelScrollIncrement);
1376         }
1377         else
1378         {
1379                 originy -= int(textureBrowser.m_mouseWheelScrollIncrement);
1380         }
1381
1382         TextureBrowser_setOriginY( textureBrowser, originy );
1383 }
1384
1385 XmlTagBuilder TagBuilder;
1386
1387 enum
1388 {
1389         TAG_COLUMN,
1390         N_COLUMNS
1391 };
1392
1393 void BuildStoreAssignedTags( ui::ListStore store, const char* shader, TextureBrowser* textureBrowser ){
1394         GtkTreeIter iter;
1395
1396         store.clear();
1397
1398         std::vector<CopiedString> assigned_tags;
1399         TagBuilder.GetShaderTags( shader, assigned_tags );
1400
1401         for ( size_t i = 0; i < assigned_tags.size(); i++ )
1402         {
1403                 store.append(TAG_COLUMN, assigned_tags[i].c_str());
1404         }
1405 }
1406
1407 void BuildStoreAvailableTags(   ui::ListStore storeAvailable,
1408                                                                 ui::ListStore storeAssigned,
1409                                                                 const std::set<CopiedString>& allTags,
1410                                                                 TextureBrowser* textureBrowser ){
1411         GtkTreeIter iterAssigned;
1412         GtkTreeIter iterAvailable;
1413         std::set<CopiedString>::const_iterator iterAll;
1414         gchar* tag_assigned;
1415
1416         storeAvailable.clear();
1417
1418         bool row = gtk_tree_model_get_iter_first(storeAssigned, &iterAssigned ) != 0;
1419
1420         if ( !row ) { // does the shader have tags assigned?
1421                 for ( iterAll = allTags.begin(); iterAll != allTags.end(); ++iterAll )
1422                 {
1423                         storeAvailable.append(TAG_COLUMN, (*iterAll).c_str());
1424                 }
1425         }
1426         else
1427         {
1428                 while ( row ) // available tags = all tags - assigned tags
1429                 {
1430                         gtk_tree_model_get(storeAssigned, &iterAssigned, TAG_COLUMN, &tag_assigned, -1 );
1431
1432                         for ( iterAll = allTags.begin(); iterAll != allTags.end(); ++iterAll )
1433                         {
1434                                 if ( strcmp( (char*)tag_assigned, ( *iterAll ).c_str() ) != 0 ) {
1435                                         storeAvailable.append(TAG_COLUMN, (*iterAll).c_str());
1436                                 }
1437                                 else
1438                                 {
1439                                         row = gtk_tree_model_iter_next(storeAssigned, &iterAssigned ) != 0;
1440
1441                                         if ( row ) {
1442                                                 gtk_tree_model_get(storeAssigned, &iterAssigned, TAG_COLUMN, &tag_assigned, -1 );
1443                                         }
1444                                 }
1445                         }
1446                 }
1447         }
1448 }
1449
1450 gboolean TextureBrowser_button_press( ui::Widget widget, GdkEventButton* event, TextureBrowser* textureBrowser ){
1451         if ( event->type == GDK_BUTTON_PRESS ) {
1452                 if ( event->button == 3 ) {
1453                         if ( GlobalTextureBrowser().m_tags ) {
1454                                 textureBrowser->m_rmbSelected = true;
1455                                 TextureBrowser_Selection_MouseDown( *textureBrowser, event->state, static_cast<int>( event->x ), static_cast<int>( event->y ) );
1456
1457                                 BuildStoreAssignedTags( textureBrowser->m_assigned_store, textureBrowser->shader.c_str(), textureBrowser );
1458                                 BuildStoreAvailableTags( textureBrowser->m_available_store, textureBrowser->m_assigned_store, textureBrowser->m_all_tags, textureBrowser );
1459                                 textureBrowser->m_heightChanged = true;
1460                                 textureBrowser->m_tag_frame.show();
1461
1462                 ui::process();
1463
1464                                 TextureBrowser_Focus( *textureBrowser, textureBrowser->shader.c_str() );
1465                         }
1466                         else
1467                         {
1468                                 TextureBrowser_Tracking_MouseDown( *textureBrowser );
1469                         }
1470                 }
1471                 else if ( event->button == 1 ) {
1472                         TextureBrowser_Selection_MouseDown( *textureBrowser, event->state, static_cast<int>( event->x ), static_cast<int>( event->y ) );
1473
1474                         if ( GlobalTextureBrowser().m_tags ) {
1475                                 textureBrowser->m_rmbSelected = false;
1476                                 textureBrowser->m_tag_frame.hide();
1477                         }
1478                 }
1479         }
1480         return FALSE;
1481 }
1482
1483 gboolean TextureBrowser_button_release( ui::Widget widget, GdkEventButton* event, TextureBrowser* textureBrowser ){
1484         if ( event->type == GDK_BUTTON_RELEASE ) {
1485                 if ( event->button == 3 ) {
1486                         if ( !GlobalTextureBrowser().m_tags ) {
1487                                 TextureBrowser_Tracking_MouseUp( *textureBrowser );
1488                         }
1489                 }
1490         }
1491         return FALSE;
1492 }
1493
1494 gboolean TextureBrowser_motion( ui::Widget widget, GdkEventMotion *event, TextureBrowser* textureBrowser ){
1495         return FALSE;
1496 }
1497
1498 gboolean TextureBrowser_scroll( ui::Widget widget, GdkEventScroll* event, TextureBrowser* textureBrowser ){
1499         if ( event->direction == GDK_SCROLL_UP ) {
1500                 TextureBrowser_MouseWheel( *textureBrowser, true );
1501         }
1502         else if ( event->direction == GDK_SCROLL_DOWN ) {
1503                 TextureBrowser_MouseWheel( *textureBrowser, false );
1504         }
1505         return FALSE;
1506 }
1507
1508 void TextureBrowser_scrollChanged( void* data, gdouble value ){
1509         //globalOutputStream() << "vertical scroll\n";
1510         TextureBrowser_setOriginY( *reinterpret_cast<TextureBrowser*>( data ), -(int)value );
1511 }
1512
1513 static void TextureBrowser_verticalScroll(ui::Adjustment adjustment, TextureBrowser* textureBrowser ){
1514         textureBrowser->m_scrollAdjustment.value_changed( gtk_adjustment_get_value(adjustment) );
1515 }
1516
1517 void TextureBrowser_updateScroll( TextureBrowser& textureBrowser ){
1518         if ( textureBrowser.m_showTextureScrollbar ) {
1519                 int totalHeight = TextureBrowser_TotalHeight( textureBrowser );
1520
1521                 totalHeight = std::max( totalHeight, textureBrowser.height );
1522
1523         auto vadjustment = gtk_range_get_adjustment( GTK_RANGE( textureBrowser.m_texture_scroll ) );
1524
1525                 gtk_adjustment_set_value(vadjustment, -TextureBrowser_getOriginY( textureBrowser ));
1526                 gtk_adjustment_set_page_size(vadjustment, textureBrowser.height);
1527                 gtk_adjustment_set_page_increment(vadjustment, textureBrowser.height / 2);
1528                 gtk_adjustment_set_step_increment(vadjustment, 20);
1529                 gtk_adjustment_set_lower(vadjustment, 0);
1530                 gtk_adjustment_set_upper(vadjustment, totalHeight);
1531
1532                 g_signal_emit_by_name( G_OBJECT( vadjustment ), "changed" );
1533         }
1534 }
1535
1536 gboolean TextureBrowser_size_allocate( ui::Widget widget, GtkAllocation* allocation, TextureBrowser* textureBrowser ){
1537         textureBrowser->width = allocation->width;
1538         textureBrowser->height = allocation->height;
1539         TextureBrowser_heightChanged( *textureBrowser );
1540         textureBrowser->m_originInvalid = true;
1541         TextureBrowser_queueDraw( *textureBrowser );
1542         return FALSE;
1543 }
1544
1545 gboolean TextureBrowser_expose( ui::Widget widget, GdkEventExpose* event, TextureBrowser* textureBrowser ){
1546         if ( glwidget_make_current( textureBrowser->m_gl_widget ) != FALSE ) {
1547                 GlobalOpenGL_debugAssertNoErrors();
1548                 TextureBrowser_evaluateHeight( *textureBrowser );
1549                 Texture_Draw( *textureBrowser );
1550                 GlobalOpenGL_debugAssertNoErrors();
1551                 glwidget_swap_buffers( textureBrowser->m_gl_widget );
1552         }
1553         return FALSE;
1554 }
1555
1556
1557 TextureBrowser g_TextureBrowser;
1558
1559 TextureBrowser& GlobalTextureBrowser(){
1560         return g_TextureBrowser;
1561 }
1562
1563 bool TextureBrowser_hideUnused(){
1564         return g_TextureBrowser.m_hideUnused;
1565 }
1566
1567 void TextureBrowser_ToggleHideUnused(){
1568         if ( g_TextureBrowser.m_hideUnused ) {
1569                 TextureBrowser_SetHideUnused( g_TextureBrowser, false );
1570         }
1571         else
1572         {
1573                 TextureBrowser_SetHideUnused( g_TextureBrowser, true );
1574         }
1575 }
1576
1577 void TextureGroups_constructTreeModel( TextureGroups groups, ui::TreeStore store ){
1578         // put the information from the old textures menu into a treeview
1579         GtkTreeIter iter, child;
1580
1581         TextureGroups::const_iterator i = groups.begin();
1582         while ( i != groups.end() )
1583         {
1584                 const char* dirName = ( *i ).c_str();
1585                 const char* firstUnderscore = strchr( dirName, '_' );
1586                 StringRange dirRoot( dirName, ( firstUnderscore == 0 ) ? dirName : firstUnderscore + 1 );
1587
1588                 TextureGroups::const_iterator next = i;
1589                 ++next;
1590                 if ( firstUnderscore != 0
1591                          && next != groups.end()
1592                          && string_equal_start( ( *next ).c_str(), dirRoot ) ) {
1593                         gtk_tree_store_append( store, &iter, NULL );
1594                         gtk_tree_store_set( store, &iter, 0, CopiedString( StringRange( dirName, firstUnderscore ) ).c_str(), -1 );
1595
1596                         // keep going...
1597                         while ( i != groups.end() && string_equal_start( ( *i ).c_str(), dirRoot ) )
1598                         {
1599                                 gtk_tree_store_append( store, &child, &iter );
1600                                 gtk_tree_store_set( store, &child, 0, ( *i ).c_str(), -1 );
1601                                 ++i;
1602                         }
1603                 }
1604                 else
1605                 {
1606                         gtk_tree_store_append( store, &iter, NULL );
1607                         gtk_tree_store_set( store, &iter, 0, dirName, -1 );
1608                         ++i;
1609                 }
1610         }
1611 }
1612
1613 TextureGroups TextureGroups_constructTreeView(){
1614         TextureGroups groups;
1615
1616         if ( TextureBrowser_showWads() ) {
1617                 GlobalFileSystem().forEachArchive( TextureGroupsAddWadCaller( groups ) );
1618         }
1619         else
1620         {
1621                 // scan texture dirs and pak files only if not restricting to shaderlist
1622                 if ( g_pGameDescription->mGameType != "doom3" && !g_TextureBrowser_shaderlistOnly ) {
1623                         GlobalFileSystem().forEachDirectory( "textures/", TextureGroupsAddDirectoryCaller( groups ) );
1624                 }
1625
1626                 GlobalShaderSystem().foreachShaderName( TextureGroupsAddShaderCaller( groups ) );
1627         }
1628
1629         return groups;
1630 }
1631
1632 void TextureBrowser_constructTreeStore(){
1633         TextureGroups groups = TextureGroups_constructTreeView();
1634         auto store = ui::TreeStore::from(gtk_tree_store_new( 1, G_TYPE_STRING ));
1635         TextureGroups_constructTreeModel( groups, store );
1636
1637         gtk_tree_view_set_model(g_TextureBrowser.m_treeViewTree, store);
1638
1639         g_object_unref( G_OBJECT( store ) );
1640 }
1641
1642 void TextureBrowser_constructTreeStoreTags(){
1643         TextureGroups groups;
1644         auto store = ui::TreeStore::from(gtk_tree_store_new( 1, G_TYPE_STRING ));
1645     auto model = g_TextureBrowser.m_all_tags_list;
1646
1647         gtk_tree_view_set_model(g_TextureBrowser.m_treeViewTags, model );
1648
1649         g_object_unref( G_OBJECT( store ) );
1650 }
1651
1652 void TreeView_onRowActivated( ui::TreeView treeview, ui::TreePath path, ui::TreeViewColumn col, gpointer userdata ){
1653         GtkTreeIter iter;
1654
1655     auto model = gtk_tree_view_get_model(treeview );
1656
1657         if ( gtk_tree_model_get_iter( model, &iter, path ) ) {
1658                 gchar dirName[1024];
1659
1660                 gchar* buffer;
1661                 gtk_tree_model_get( model, &iter, 0, &buffer, -1 );
1662                 strcpy( dirName, buffer );
1663                 g_free( buffer );
1664
1665                 g_TextureBrowser.m_searchedTags = false;
1666
1667                 if ( !TextureBrowser_showWads() ) {
1668                         strcat( dirName, "/" );
1669                 }
1670
1671                 ScopeDisableScreenUpdates disableScreenUpdates( dirName, "Loading Textures" );
1672                 TextureBrowser_ShowDirectory( GlobalTextureBrowser(), dirName );
1673                 TextureBrowser_queueDraw( GlobalTextureBrowser() );
1674                 //deactivate, so SPACE and RETURN wont be broken for 2d
1675                 gtk_window_set_focus( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( treeview ) ) ), NULL );
1676         }
1677 }
1678
1679 void TextureBrowser_createTreeViewTree(){
1680         gtk_tree_view_set_enable_search(g_TextureBrowser.m_treeViewTree, FALSE );
1681
1682         gtk_tree_view_set_headers_visible(g_TextureBrowser.m_treeViewTree, FALSE );
1683         g_TextureBrowser.m_treeViewTree.connect( "row-activated", (GCallback) TreeView_onRowActivated, NULL );
1684
1685         auto renderer = ui::CellRendererText(ui::New);
1686         gtk_tree_view_insert_column_with_attributes(g_TextureBrowser.m_treeViewTree, -1, "", renderer, "text", 0, NULL );
1687
1688         TextureBrowser_constructTreeStore();
1689 }
1690
1691 void TextureBrowser_addTag();
1692
1693 void TextureBrowser_renameTag();
1694
1695 void TextureBrowser_deleteTag();
1696
1697 void TextureBrowser_createContextMenu( ui::Widget treeview, GdkEventButton *event ){
1698         ui::Widget menu = ui::Menu(ui::New);
1699
1700         ui::Widget menuitem = ui::MenuItem( "Add tag" );
1701         menuitem.connect( "activate", (GCallback)TextureBrowser_addTag, treeview );
1702         gtk_menu_shell_append( GTK_MENU_SHELL( menu ), menuitem );
1703
1704         menuitem = ui::MenuItem( "Rename tag" );
1705         menuitem.connect( "activate", (GCallback)TextureBrowser_renameTag, treeview );
1706         gtk_menu_shell_append( GTK_MENU_SHELL( menu ), menuitem );
1707
1708         menuitem = ui::MenuItem( "Delete tag" );
1709         menuitem.connect( "activate", (GCallback)TextureBrowser_deleteTag, treeview );
1710         gtk_menu_shell_append( GTK_MENU_SHELL( menu ), menuitem );
1711
1712         gtk_widget_show_all( menu );
1713
1714         gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL,
1715                                         ( event != NULL ) ? event->button : 0,
1716                                         gdk_event_get_time( (GdkEvent*)event ) );
1717 }
1718
1719 gboolean TreeViewTags_onButtonPressed( ui::TreeView treeview, GdkEventButton *event ){
1720         if ( event->type == GDK_BUTTON_PRESS && event->button == 3 ) {
1721                 GtkTreePath *path;
1722         auto selection = gtk_tree_view_get_selection(treeview );
1723
1724                 if ( gtk_tree_view_get_path_at_pos(treeview, event->x, event->y, &path, NULL, NULL, NULL ) ) {
1725                         gtk_tree_selection_unselect_all( selection );
1726                         gtk_tree_selection_select_path( selection, path );
1727                         gtk_tree_path_free( path );
1728                 }
1729
1730                 TextureBrowser_createContextMenu( treeview, event );
1731                 return TRUE;
1732         }
1733         return FALSE;
1734 }
1735
1736 void TextureBrowser_createTreeViewTags(){
1737         g_TextureBrowser.m_treeViewTags = ui::TreeView(ui::New);
1738         gtk_tree_view_set_enable_search(g_TextureBrowser.m_treeViewTags, FALSE );
1739
1740         g_TextureBrowser.m_treeViewTags.connect( "button-press-event", (GCallback)TreeViewTags_onButtonPressed, NULL );
1741
1742         gtk_tree_view_set_headers_visible(g_TextureBrowser.m_treeViewTags, FALSE );
1743
1744         auto renderer = ui::CellRendererText(ui::New);
1745         gtk_tree_view_insert_column_with_attributes(g_TextureBrowser.m_treeViewTags, -1, "", renderer, "text", 0, NULL );
1746
1747         TextureBrowser_constructTreeStoreTags();
1748 }
1749
1750 ui::MenuItem TextureBrowser_constructViewMenu( ui::Menu menu ){
1751         ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic( "_View" ));
1752
1753         if ( g_Layout_enableDetachableMenus.m_value ) {
1754                 menu_tearoff( menu );
1755         }
1756
1757         create_check_menu_item_with_mnemonic( menu, "Hide _Unused", "ShowInUse" );
1758         if ( string_empty( g_pGameDescription->getKeyValue( "show_wads" ) ) ) {
1759                 create_check_menu_item_with_mnemonic( menu, "Hide Image Missing", "FilterMissing" );
1760         }
1761
1762         // hide notex and shadernotex on texture browser: no one wants to apply them
1763         create_check_menu_item_with_mnemonic( menu, "Hide Fallback", "FilterFallback" );
1764
1765         menu_separator( menu );
1766
1767         create_menu_item_with_mnemonic( menu, "Show All", "ShowAllTextures" );
1768
1769         // we always want to show shaders but don't want a "Show Shaders" menu for doom3 and .wad file games
1770         if ( g_pGameDescription->mGameType == "doom3" || !string_empty( g_pGameDescription->getKeyValue( "show_wads" ) ) ) {
1771                 g_TextureBrowser.m_showShaders = true;
1772         }
1773         else
1774         {
1775                 create_check_menu_item_with_mnemonic( menu, "Show shaders", "ToggleShowShaders" );
1776         }
1777
1778         if ( g_pGameDescription->mGameType != "doom3" && string_empty( g_pGameDescription->getKeyValue( "show_wads" ) ) ) {
1779                 create_check_menu_item_with_mnemonic( menu, "Shaders Only", "ToggleShowShaderlistOnly" );
1780         }
1781         if ( g_TextureBrowser.m_tags ) {
1782                 create_menu_item_with_mnemonic( menu, "Show Untagged", "ShowUntagged" );
1783         }
1784
1785         menu_separator( menu );
1786         create_check_menu_item_with_mnemonic( menu, "Fixed Size", "FixedSize" );
1787         create_check_menu_item_with_mnemonic( menu, "Transparency", "EnableAlpha" );
1788
1789         if ( string_empty( g_pGameDescription->getKeyValue( "show_wads" ) ) ) {
1790                 menu_separator( menu );
1791                 g_TextureBrowser.m_shader_info_item = ui::Widget(create_menu_item_with_mnemonic( menu, "Shader Info", "ShaderInfo"  ));
1792                 gtk_widget_set_sensitive( g_TextureBrowser.m_shader_info_item, FALSE );
1793         }
1794
1795
1796         return textures_menu_item;
1797 }
1798
1799 ui::MenuItem TextureBrowser_constructToolsMenu( ui::Menu menu ){
1800         ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic( "_Tools" ));
1801
1802         if ( g_Layout_enableDetachableMenus.m_value ) {
1803                 menu_tearoff( menu );
1804         }
1805
1806         create_menu_item_with_mnemonic( menu, "Flush & Reload Shaders", "RefreshShaders" );
1807         create_menu_item_with_mnemonic( menu, "Find / Replace...", "FindReplaceTextures" );
1808
1809         return textures_menu_item;
1810 }
1811
1812 ui::MenuItem TextureBrowser_constructTagsMenu( ui::Menu menu ){
1813         ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic( "T_ags" ));
1814
1815         if ( g_Layout_enableDetachableMenus.m_value ) {
1816                 menu_tearoff( menu );
1817         }
1818
1819         create_menu_item_with_mnemonic( menu, "Add tag", "AddTag" );
1820         create_menu_item_with_mnemonic( menu, "Rename tag", "RenameTag" );
1821         create_menu_item_with_mnemonic( menu, "Delete tag", "DeleteTag" );
1822         menu_separator( menu );
1823         create_menu_item_with_mnemonic( menu, "Copy tags from selected", "CopyTag" );
1824         create_menu_item_with_mnemonic( menu, "Paste tags to selected", "PasteTag" );
1825
1826         return textures_menu_item;
1827 }
1828
1829 gboolean TextureBrowser_tagMoveHelper( ui::TreeModel model, ui::TreePath path, GtkTreeIter iter, GSList** selected ){
1830         g_assert( selected != NULL );
1831
1832     auto rowref = gtk_tree_row_reference_new( model, path );
1833         *selected = g_slist_append( *selected, rowref );
1834
1835         return FALSE;
1836 }
1837
1838 void TextureBrowser_assignTags(){
1839         GSList* selected = NULL;
1840         GSList* node;
1841         gchar* tag_assigned;
1842
1843     auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_available_tree );
1844
1845         gtk_tree_selection_selected_foreach( selection, (GtkTreeSelectionForeachFunc)TextureBrowser_tagMoveHelper, &selected );
1846
1847         if ( selected != NULL ) {
1848                 for ( node = selected; node != NULL; node = node->next )
1849                 {
1850             auto path = gtk_tree_row_reference_get_path( (GtkTreeRowReference*)node->data );
1851
1852                         if ( path ) {
1853                                 GtkTreeIter iter;
1854
1855                                 if ( gtk_tree_model_get_iter(g_TextureBrowser.m_available_store, &iter, path ) ) {
1856                                         gtk_tree_model_get(g_TextureBrowser.m_available_store, &iter, TAG_COLUMN, &tag_assigned, -1 );
1857                                         if ( !TagBuilder.CheckShaderTag( g_TextureBrowser.shader.c_str() ) ) {
1858                                                 // create a custom shader/texture entry
1859                                                 IShader* ishader = QERApp_Shader_ForName( g_TextureBrowser.shader.c_str() );
1860                                                 CopiedString filename = ishader->getShaderFileName();
1861
1862                                                 if ( filename.empty() ) {
1863                                                         // it's a texture
1864                                                         TagBuilder.AddShaderNode( g_TextureBrowser.shader.c_str(), CUSTOM, TEXTURE );
1865                                                 }
1866                                                 else {
1867                                                         // it's a shader
1868                                                         TagBuilder.AddShaderNode( g_TextureBrowser.shader.c_str(), CUSTOM, SHADER );
1869                                                 }
1870                                                 ishader->DecRef();
1871                                         }
1872                                         TagBuilder.AddShaderTag( g_TextureBrowser.shader.c_str(), (char*)tag_assigned, TAG );
1873
1874                                         gtk_list_store_remove( g_TextureBrowser.m_available_store, &iter );
1875                                         g_TextureBrowser.m_assigned_store.append(TAG_COLUMN, tag_assigned);
1876                                 }
1877                         }
1878                 }
1879
1880                 g_slist_foreach( selected, (GFunc)gtk_tree_row_reference_free, NULL );
1881
1882                 // Save changes
1883                 TagBuilder.SaveXmlDoc();
1884         }
1885         g_slist_free( selected );
1886 }
1887
1888 void TextureBrowser_removeTags(){
1889         GSList* selected = NULL;
1890         GSList* node;
1891         gchar* tag;
1892
1893     auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_assigned_tree );
1894
1895         gtk_tree_selection_selected_foreach( selection, (GtkTreeSelectionForeachFunc)TextureBrowser_tagMoveHelper, &selected );
1896
1897         if ( selected != NULL ) {
1898                 for ( node = selected; node != NULL; node = node->next )
1899                 {
1900             auto path = gtk_tree_row_reference_get_path( (GtkTreeRowReference*)node->data );
1901
1902                         if ( path ) {
1903                                 GtkTreeIter iter;
1904
1905                                 if ( gtk_tree_model_get_iter(g_TextureBrowser.m_assigned_store, &iter, path ) ) {
1906                                         gtk_tree_model_get(g_TextureBrowser.m_assigned_store, &iter, TAG_COLUMN, &tag, -1 );
1907                                         TagBuilder.DeleteShaderTag( g_TextureBrowser.shader.c_str(), tag );
1908                                         gtk_list_store_remove( g_TextureBrowser.m_assigned_store, &iter );
1909                                 }
1910                         }
1911                 }
1912
1913                 g_slist_foreach( selected, (GFunc)gtk_tree_row_reference_free, NULL );
1914
1915                 // Update the "available tags list"
1916                 BuildStoreAvailableTags( g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store, g_TextureBrowser.m_all_tags, &g_TextureBrowser );
1917
1918                 // Save changes
1919                 TagBuilder.SaveXmlDoc();
1920         }
1921         g_slist_free( selected );
1922 }
1923
1924 void TextureBrowser_buildTagList(){
1925         g_TextureBrowser.m_all_tags_list.clear();
1926
1927         std::set<CopiedString>::iterator iter;
1928
1929         for ( iter = g_TextureBrowser.m_all_tags.begin(); iter != g_TextureBrowser.m_all_tags.end(); ++iter )
1930         {
1931                 g_TextureBrowser.m_all_tags_list.append(TAG_COLUMN, (*iter).c_str());
1932         }
1933 }
1934
1935 void TextureBrowser_searchTags(){
1936         GSList* selected = NULL;
1937         GSList* node;
1938         gchar* tag;
1939         char buffer[256];
1940         char tags_searched[256];
1941
1942     auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_treeViewTags );
1943
1944         gtk_tree_selection_selected_foreach( selection, (GtkTreeSelectionForeachFunc)TextureBrowser_tagMoveHelper, &selected );
1945
1946         if ( selected != NULL ) {
1947                 strcpy( buffer, "/root/*/*[tag='" );
1948                 strcpy( tags_searched, "[TAGS] " );
1949
1950                 for ( node = selected; node != NULL; node = node->next )
1951                 {
1952             auto path = gtk_tree_row_reference_get_path( (GtkTreeRowReference*)node->data );
1953
1954                         if ( path ) {
1955                                 GtkTreeIter iter;
1956
1957                                 if ( gtk_tree_model_get_iter(g_TextureBrowser.m_all_tags_list, &iter, path ) ) {
1958                                         gtk_tree_model_get(g_TextureBrowser.m_all_tags_list, &iter, TAG_COLUMN, &tag, -1 );
1959
1960                                         strcat( buffer, tag );
1961                                         strcat( tags_searched, tag );
1962                                         if ( node != g_slist_last( node ) ) {
1963                                                 strcat( buffer, "' and tag='" );
1964                                                 strcat( tags_searched, ", " );
1965                                         }
1966                                 }
1967                         }
1968                 }
1969
1970                 strcat( buffer, "']" );
1971
1972                 g_slist_foreach( selected, (GFunc)gtk_tree_row_reference_free, NULL );
1973
1974                 g_TextureBrowser.m_found_shaders.clear(); // delete old list
1975                 TagBuilder.TagSearch( buffer, g_TextureBrowser.m_found_shaders );
1976
1977                 if ( !g_TextureBrowser.m_found_shaders.empty() ) { // found something
1978                         size_t shaders_found = g_TextureBrowser.m_found_shaders.size();
1979
1980                         globalOutputStream() << "Found " << (unsigned int)shaders_found << " textures and shaders with " << tags_searched << "\n";
1981                         ScopeDisableScreenUpdates disableScreenUpdates( "Searching...", "Loading Textures" );
1982
1983                         std::set<CopiedString>::iterator iter;
1984
1985                         for ( iter = g_TextureBrowser.m_found_shaders.begin(); iter != g_TextureBrowser.m_found_shaders.end(); iter++ )
1986                         {
1987                                 std::string path = ( *iter ).c_str();
1988                                 size_t pos = path.find_last_of( "/", path.size() );
1989                                 std::string name = path.substr( pos + 1, path.size() );
1990                                 path = path.substr( 0, pos + 1 );
1991                                 TextureDirectory_loadTexture( path.c_str(), name.c_str() );
1992                         }
1993                 }
1994                 g_TextureBrowser.m_searchedTags = true;
1995                 g_TextureBrowser_currentDirectory = tags_searched;
1996
1997                 g_TextureBrowser.m_nTotalHeight = 0;
1998                 TextureBrowser_setOriginY( g_TextureBrowser, 0 );
1999                 TextureBrowser_heightChanged( g_TextureBrowser );
2000                 TextureBrowser_updateTitle();
2001         }
2002         g_slist_free( selected );
2003 }
2004
2005 void TextureBrowser_toggleSearchButton(){
2006         gint page = gtk_notebook_get_current_page( GTK_NOTEBOOK( g_TextureBrowser.m_tag_notebook ) );
2007
2008         if ( page == 0 ) { // tag page
2009                 gtk_widget_show_all( g_TextureBrowser.m_search_button );
2010         }
2011         else {
2012                 g_TextureBrowser.m_search_button.hide();
2013         }
2014 }
2015
2016 void TextureBrowser_constructTagNotebook(){
2017         g_TextureBrowser.m_tag_notebook = ui::Widget::from(gtk_notebook_new());
2018         ui::Widget labelTags = ui::Label( "Tags" );
2019         ui::Widget labelTextures = ui::Label( "Textures" );
2020
2021         gtk_notebook_append_page( GTK_NOTEBOOK( g_TextureBrowser.m_tag_notebook ), g_TextureBrowser.m_scr_win_tree, labelTextures );
2022         gtk_notebook_append_page( GTK_NOTEBOOK( g_TextureBrowser.m_tag_notebook ), g_TextureBrowser.m_scr_win_tags, labelTags );
2023
2024         g_TextureBrowser.m_tag_notebook.connect( "switch-page", G_CALLBACK( TextureBrowser_toggleSearchButton ), NULL );
2025
2026         gtk_widget_show_all( g_TextureBrowser.m_tag_notebook );
2027 }
2028
2029 void TextureBrowser_constructSearchButton(){
2030         auto image = ui::Widget::from(gtk_image_new_from_stock( GTK_STOCK_FIND, GTK_ICON_SIZE_SMALL_TOOLBAR ));
2031         g_TextureBrowser.m_search_button = ui::Button(ui::New);
2032         g_TextureBrowser.m_search_button.connect( "clicked", G_CALLBACK( TextureBrowser_searchTags ), NULL );
2033         gtk_widget_set_tooltip_text(g_TextureBrowser.m_search_button, "Search with selected tags");
2034         g_TextureBrowser.m_search_button.add(image);
2035 }
2036
2037 void TextureBrowser_checkTagFile(){
2038         const char SHADERTAG_FILE[] = "shadertags.xml";
2039         CopiedString default_filename, rc_filename;
2040         StringOutputStream stream( 256 );
2041
2042         stream << LocalRcPath_get();
2043         stream << SHADERTAG_FILE;
2044         rc_filename = stream.c_str();
2045
2046         if ( file_exists( rc_filename.c_str() ) ) {
2047                 g_TextureBrowser.m_tags = TagBuilder.OpenXmlDoc( rc_filename.c_str() );
2048
2049                 if ( g_TextureBrowser.m_tags ) {
2050                         globalOutputStream() << "Loading tag file " << rc_filename.c_str() << ".\n";
2051                 }
2052         }
2053         else
2054         {
2055                 // load default tagfile
2056                 stream.clear();
2057                 stream << g_pGameDescription->mGameToolsPath.c_str();
2058                 stream << SHADERTAG_FILE;
2059                 default_filename = stream.c_str();
2060
2061                 if ( file_exists( default_filename.c_str() ) ) {
2062                         g_TextureBrowser.m_tags = TagBuilder.OpenXmlDoc( default_filename.c_str(), rc_filename.c_str() );
2063
2064                         if ( g_TextureBrowser.m_tags ) {
2065                                 globalOutputStream() << "Loading default tag file " << default_filename.c_str() << ".\n";
2066                         }
2067                 }
2068                 else
2069                 {
2070                         globalErrorStream() << "Unable to find default tag file " << default_filename.c_str() << ". No tag support.\n";
2071                 }
2072         }
2073 }
2074
2075 void TextureBrowser_SetNotex(){
2076         IShader* notex = QERApp_Shader_ForName( DEFAULT_NOTEX_NAME );
2077         IShader* shadernotex = QERApp_Shader_ForName( DEFAULT_SHADERNOTEX_NAME );
2078
2079         g_notex = notex->getTexture()->name;
2080         g_shadernotex = shadernotex->getTexture()->name;
2081
2082         notex->DecRef();
2083         shadernotex->DecRef();
2084 }
2085
2086 ui::Widget TextureBrowser_constructWindow( ui::Window toplevel ){
2087         // The gl_widget and the tag assignment frame should be packed into a GtkVPaned with the slider
2088         // position stored in local.pref. gtk_paned_get_position() and gtk_paned_set_position() don't
2089         // seem to work in gtk 2.4 and the arrow buttons don't handle GTK_FILL, so here's another thing
2090         // for the "once-the-gtk-libs-are-updated-TODO-list" :x
2091
2092         TextureBrowser_checkTagFile();
2093         TextureBrowser_SetNotex();
2094
2095         GlobalShaderSystem().setActiveShadersChangedNotify( ReferenceCaller<TextureBrowser, void(), TextureBrowser_activeShadersChanged>( g_TextureBrowser ) );
2096
2097         g_TextureBrowser.m_parent = toplevel;
2098
2099         auto table = ui::Table(3, 3, FALSE);
2100         auto vbox = ui::VBox(FALSE, 0);
2101         table.attach(vbox, {0, 1, 1, 3}, {GTK_FILL, GTK_FILL});
2102         vbox.show();
2103
2104         ui::Widget menu_bar{ui::null};
2105
2106         { // menu bar
2107                 menu_bar = ui::Widget::from(gtk_menu_bar_new());
2108                 auto menu_view = ui::Menu(ui::New);
2109                 auto view_item = TextureBrowser_constructViewMenu( menu_view );
2110                 gtk_menu_item_set_submenu( GTK_MENU_ITEM( view_item ), menu_view );
2111                 gtk_menu_shell_append( GTK_MENU_SHELL( menu_bar ), view_item );
2112
2113                 auto menu_tools = ui::Menu(ui::New);
2114                 auto tools_item = TextureBrowser_constructToolsMenu( menu_tools );
2115                 gtk_menu_item_set_submenu( GTK_MENU_ITEM( tools_item ), menu_tools );
2116                 gtk_menu_shell_append( GTK_MENU_SHELL( menu_bar ), tools_item );
2117
2118                 table.attach(menu_bar, {0, 3, 0, 1}, {GTK_FILL, GTK_SHRINK});
2119                 menu_bar.show();
2120         }
2121         { // Texture TreeView
2122                 g_TextureBrowser.m_scr_win_tree = ui::ScrolledWindow(ui::New);
2123                 gtk_container_set_border_width( GTK_CONTAINER( g_TextureBrowser.m_scr_win_tree ), 0 );
2124
2125                 // vertical only scrolling for treeview
2126                 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( g_TextureBrowser.m_scr_win_tree ), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS );
2127
2128                 g_TextureBrowser.m_scr_win_tree.show();
2129
2130                 TextureBrowser_createTreeViewTree();
2131
2132                 gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( g_TextureBrowser.m_scr_win_tree ), g_TextureBrowser.m_treeViewTree  );
2133                 g_TextureBrowser.m_treeViewTree.show();
2134         }
2135         { // gl_widget scrollbar
2136                 auto w = ui::Widget::from(gtk_vscrollbar_new( ui::Adjustment( 0,0,0,1,1,0 ) ));
2137                 table.attach(w, {2, 3, 1, 2}, {GTK_SHRINK, GTK_FILL});
2138                 w.show();
2139                 g_TextureBrowser.m_texture_scroll = w;
2140
2141                 auto vadjustment = ui::Adjustment::from(gtk_range_get_adjustment( GTK_RANGE( g_TextureBrowser.m_texture_scroll ) ));
2142                 vadjustment.connect( "value_changed", G_CALLBACK( TextureBrowser_verticalScroll ), &g_TextureBrowser );
2143
2144                 g_TextureBrowser.m_texture_scroll.visible(g_TextureBrowser.m_showTextureScrollbar);
2145         }
2146         { // gl_widget
2147                 g_TextureBrowser.m_gl_widget = glwidget_new( FALSE );
2148                 g_object_ref( g_TextureBrowser.m_gl_widget._handle );
2149
2150                 gtk_widget_set_events( g_TextureBrowser.m_gl_widget, GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK );
2151                 gtk_widget_set_can_focus( g_TextureBrowser.m_gl_widget, true );
2152
2153                 table.attach(g_TextureBrowser.m_gl_widget, {1, 2, 1, 2});
2154                 g_TextureBrowser.m_gl_widget.show();
2155
2156                 g_TextureBrowser.m_sizeHandler = g_TextureBrowser.m_gl_widget.connect( "size_allocate", G_CALLBACK( TextureBrowser_size_allocate ), &g_TextureBrowser );
2157                 g_TextureBrowser.m_exposeHandler = g_TextureBrowser.m_gl_widget.on_render( G_CALLBACK( TextureBrowser_expose ), &g_TextureBrowser );
2158
2159                 g_TextureBrowser.m_gl_widget.connect( "button_press_event", G_CALLBACK( TextureBrowser_button_press ), &g_TextureBrowser );
2160                 g_TextureBrowser.m_gl_widget.connect( "button_release_event", G_CALLBACK( TextureBrowser_button_release ), &g_TextureBrowser );
2161                 g_TextureBrowser.m_gl_widget.connect( "motion_notify_event", G_CALLBACK( TextureBrowser_motion ), &g_TextureBrowser );
2162                 g_TextureBrowser.m_gl_widget.connect( "scroll_event", G_CALLBACK( TextureBrowser_scroll ), &g_TextureBrowser );
2163         }
2164
2165         // tag stuff
2166         if ( g_TextureBrowser.m_tags ) {
2167                 { // fill tag GtkListStore
2168                         g_TextureBrowser.m_all_tags_list = ui::ListStore::from(gtk_list_store_new( N_COLUMNS, G_TYPE_STRING ));
2169             auto sortable = GTK_TREE_SORTABLE( g_TextureBrowser.m_all_tags_list );
2170                         gtk_tree_sortable_set_sort_column_id( sortable, TAG_COLUMN, GTK_SORT_ASCENDING );
2171
2172                         TagBuilder.GetAllTags( g_TextureBrowser.m_all_tags );
2173                         TextureBrowser_buildTagList();
2174                 }
2175                 { // tag menu bar
2176                         auto menu_tags = ui::Menu(ui::New);
2177                         auto tags_item = TextureBrowser_constructTagsMenu( menu_tags );
2178                         gtk_menu_item_set_submenu( GTK_MENU_ITEM( tags_item ), menu_tags );
2179                         gtk_menu_shell_append( GTK_MENU_SHELL( menu_bar ), tags_item );
2180                 }
2181                 { // Tag TreeView
2182                         g_TextureBrowser.m_scr_win_tags = ui::ScrolledWindow(ui::New);
2183                         gtk_container_set_border_width( GTK_CONTAINER( g_TextureBrowser.m_scr_win_tags ), 0 );
2184
2185                         // vertical only scrolling for treeview
2186                         gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( g_TextureBrowser.m_scr_win_tags ), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS );
2187
2188                         TextureBrowser_createTreeViewTags();
2189
2190             auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_treeViewTags );
2191                         gtk_tree_selection_set_mode( selection, GTK_SELECTION_MULTIPLE );
2192
2193                         gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( g_TextureBrowser.m_scr_win_tags ), g_TextureBrowser.m_treeViewTags  );
2194                         g_TextureBrowser.m_treeViewTags.show();
2195                 }
2196                 { // Texture/Tag notebook
2197                         TextureBrowser_constructTagNotebook();
2198                         vbox.pack_start( g_TextureBrowser.m_tag_notebook, TRUE, TRUE, 0 );
2199                 }
2200                 { // Tag search button
2201                         TextureBrowser_constructSearchButton();
2202                         vbox.pack_end(g_TextureBrowser.m_search_button, FALSE, FALSE, 0);
2203                 }
2204                 auto frame_table = ui::Table(3, 3, FALSE);
2205                 { // Tag frame
2206
2207                         g_TextureBrowser.m_tag_frame = ui::Frame( "Tag assignment" );
2208                         gtk_frame_set_label_align( GTK_FRAME( g_TextureBrowser.m_tag_frame ), 0.5, 0.5 );
2209                         gtk_frame_set_shadow_type( GTK_FRAME( g_TextureBrowser.m_tag_frame ), GTK_SHADOW_NONE );
2210
2211                         table.attach(g_TextureBrowser.m_tag_frame, {1, 3, 2, 3}, {GTK_FILL, GTK_SHRINK});
2212
2213                         frame_table.show();
2214
2215                         g_TextureBrowser.m_tag_frame.add(frame_table);
2216                 }
2217                 { // assigned tag list
2218                         ui::Widget scrolled_win = ui::ScrolledWindow(ui::New);
2219                         gtk_container_set_border_width( GTK_CONTAINER( scrolled_win ), 0 );
2220                         gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolled_win ), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS );
2221
2222                         g_TextureBrowser.m_assigned_store = ui::ListStore::from(gtk_list_store_new( N_COLUMNS, G_TYPE_STRING ));
2223
2224             auto sortable = GTK_TREE_SORTABLE( g_TextureBrowser.m_assigned_store );
2225                         gtk_tree_sortable_set_sort_column_id( sortable, TAG_COLUMN, GTK_SORT_ASCENDING );
2226
2227                         auto renderer = ui::CellRendererText(ui::New);
2228
2229                         g_TextureBrowser.m_assigned_tree = ui::TreeView(ui::TreeModel::from(g_TextureBrowser.m_assigned_store._handle));
2230                         g_TextureBrowser.m_assigned_store.unref();
2231                         g_TextureBrowser.m_assigned_tree.connect( "row-activated", (GCallback) TextureBrowser_removeTags, NULL );
2232                         gtk_tree_view_set_headers_visible(g_TextureBrowser.m_assigned_tree, FALSE );
2233
2234             auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_assigned_tree );
2235                         gtk_tree_selection_set_mode( selection, GTK_SELECTION_MULTIPLE );
2236
2237             auto column = ui::TreeViewColumn( "", renderer, {{"text", TAG_COLUMN}} );
2238                         gtk_tree_view_append_column(g_TextureBrowser.m_assigned_tree, column );
2239                         g_TextureBrowser.m_assigned_tree.show();
2240
2241                         scrolled_win.show();
2242                         gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( scrolled_win ), g_TextureBrowser.m_assigned_tree  );
2243
2244                         frame_table.attach(scrolled_win, {0, 1, 1, 3}, {GTK_FILL, GTK_FILL});
2245                 }
2246                 { // available tag list
2247                         ui::Widget scrolled_win = ui::ScrolledWindow(ui::New);
2248                         gtk_container_set_border_width( GTK_CONTAINER( scrolled_win ), 0 );
2249                         gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolled_win ), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS );
2250
2251                         g_TextureBrowser.m_available_store = ui::ListStore::from(gtk_list_store_new( N_COLUMNS, G_TYPE_STRING ));
2252             auto sortable = GTK_TREE_SORTABLE( g_TextureBrowser.m_available_store );
2253                         gtk_tree_sortable_set_sort_column_id( sortable, TAG_COLUMN, GTK_SORT_ASCENDING );
2254
2255                         auto renderer = ui::CellRendererText(ui::New);
2256
2257                         g_TextureBrowser.m_available_tree = ui::TreeView(ui::TreeModel::from(g_TextureBrowser.m_available_store._handle));
2258                         g_TextureBrowser.m_available_store.unref();
2259                         g_TextureBrowser.m_available_tree.connect( "row-activated", (GCallback) TextureBrowser_assignTags, NULL );
2260                         gtk_tree_view_set_headers_visible(g_TextureBrowser.m_available_tree, FALSE );
2261
2262             auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_available_tree );
2263                         gtk_tree_selection_set_mode( selection, GTK_SELECTION_MULTIPLE );
2264
2265             auto column = ui::TreeViewColumn( "", renderer, {{"text", TAG_COLUMN}} );
2266                         gtk_tree_view_append_column(g_TextureBrowser.m_available_tree, column );
2267                         g_TextureBrowser.m_available_tree.show();
2268
2269                         scrolled_win.show();
2270                         gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( scrolled_win ), g_TextureBrowser.m_available_tree  );
2271
2272                         frame_table.attach(scrolled_win, {2, 3, 1, 3}, {GTK_FILL, GTK_FILL});
2273                 }
2274                 { // tag arrow buttons
2275                         auto m_btn_left = ui::Button(ui::New);
2276                         auto m_btn_right = ui::Button(ui::New);
2277                         auto m_arrow_left = ui::Widget::from(gtk_arrow_new( GTK_ARROW_LEFT, GTK_SHADOW_OUT ));
2278                         auto m_arrow_right = ui::Widget::from(gtk_arrow_new( GTK_ARROW_RIGHT, GTK_SHADOW_OUT ));
2279                         m_btn_left.add(m_arrow_left);
2280                         m_btn_right.add(m_arrow_right);
2281
2282                         // workaround. the size of the tag frame depends of the requested size of the arrow buttons.
2283                         m_arrow_left.dimensions(-1, 68);
2284                         m_arrow_right.dimensions(-1, 68);
2285
2286                         frame_table.attach(m_btn_left, {1, 2, 1, 2}, {GTK_SHRINK, GTK_EXPAND});
2287                         frame_table.attach(m_btn_right, {1, 2, 2, 3}, {GTK_SHRINK, GTK_EXPAND});
2288
2289                         m_btn_left.connect( "clicked", G_CALLBACK( TextureBrowser_assignTags ), NULL );
2290                         m_btn_right.connect( "clicked", G_CALLBACK( TextureBrowser_removeTags ), NULL );
2291
2292                         m_btn_left.show();
2293                         m_btn_right.show();
2294                         m_arrow_left.show();
2295                         m_arrow_right.show();
2296                 }
2297                 { // tag fram labels
2298                         ui::Widget m_lbl_assigned = ui::Label( "Assigned" );
2299                         ui::Widget m_lbl_unassigned = ui::Label( "Available" );
2300
2301                         frame_table.attach(m_lbl_assigned, {0, 1, 0, 1}, {GTK_EXPAND, GTK_SHRINK});
2302                         frame_table.attach(m_lbl_unassigned, {2, 3, 0, 1}, {GTK_EXPAND, GTK_SHRINK});
2303
2304                         m_lbl_assigned.show();
2305                         m_lbl_unassigned.show();
2306                 }
2307         }
2308         else { // no tag support, show the texture tree only
2309                 vbox.pack_start( g_TextureBrowser.m_scr_win_tree, TRUE, TRUE, 0 );
2310         }
2311
2312         // TODO do we need this?
2313         //gtk_container_set_focus_chain(GTK_CONTAINER(hbox_table), NULL);
2314
2315         return table;
2316 }
2317
2318 void TextureBrowser_destroyWindow(){
2319         GlobalShaderSystem().setActiveShadersChangedNotify( Callback<void()>() );
2320
2321         g_signal_handler_disconnect( G_OBJECT( g_TextureBrowser.m_gl_widget ), g_TextureBrowser.m_sizeHandler );
2322         g_signal_handler_disconnect( G_OBJECT( g_TextureBrowser.m_gl_widget ), g_TextureBrowser.m_exposeHandler );
2323
2324         g_TextureBrowser.m_gl_widget.unref();
2325 }
2326
2327 const Vector3& TextureBrowser_getBackgroundColour( TextureBrowser& textureBrowser ){
2328         return textureBrowser.color_textureback;
2329 }
2330
2331 void TextureBrowser_setBackgroundColour( TextureBrowser& textureBrowser, const Vector3& colour ){
2332         textureBrowser.color_textureback = colour;
2333         TextureBrowser_queueDraw( textureBrowser );
2334 }
2335
2336 void TextureBrowser_selectionHelper( ui::TreeModel model, ui::TreePath path, GtkTreeIter* iter, GSList** selected ){
2337         g_assert( selected != NULL );
2338
2339         gchar* name;
2340         gtk_tree_model_get( model, iter, TAG_COLUMN, &name, -1 );
2341         *selected = g_slist_append( *selected, name );
2342 }
2343
2344 void TextureBrowser_shaderInfo(){
2345         const char* name = TextureBrowser_GetSelectedShader( g_TextureBrowser );
2346         IShader* shader = QERApp_Shader_ForName( name );
2347
2348         DoShaderInfoDlg( name, shader->getShaderFileName(), "Shader Info" );
2349
2350         shader->DecRef();
2351 }
2352
2353 void TextureBrowser_addTag(){
2354         CopiedString tag;
2355
2356         EMessageBoxReturn result = DoShaderTagDlg( &tag, "Add shader tag" );
2357
2358         if ( result == eIDOK && !tag.empty() ) {
2359                 GtkTreeIter iter;
2360                 g_TextureBrowser.m_all_tags.insert( tag.c_str() );
2361                 gtk_list_store_append( g_TextureBrowser.m_available_store, &iter );
2362                 gtk_list_store_set( g_TextureBrowser.m_available_store, &iter, TAG_COLUMN, tag.c_str(), -1 );
2363
2364                 // Select the currently added tag in the available list
2365         auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_available_tree );
2366                 gtk_tree_selection_select_iter( selection, &iter );
2367
2368                 g_TextureBrowser.m_all_tags_list.append(TAG_COLUMN, tag.c_str());
2369         }
2370 }
2371
2372 void TextureBrowser_renameTag(){
2373         /* WORKAROUND: The tag treeview is set to GTK_SELECTION_MULTIPLE. Because
2374            gtk_tree_selection_get_selected() doesn't work with GTK_SELECTION_MULTIPLE,
2375            we need to count the number of selected rows first and use
2376            gtk_tree_selection_selected_foreach() then to go through the list of selected
2377            rows (which always containins a single row).
2378          */
2379
2380         GSList* selected = NULL;
2381
2382     auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_treeViewTags );
2383         gtk_tree_selection_selected_foreach( selection, GtkTreeSelectionForeachFunc( TextureBrowser_selectionHelper ), &selected );
2384
2385         if ( g_slist_length( selected ) == 1 ) { // we only rename a single tag
2386                 CopiedString newTag;
2387                 EMessageBoxReturn result = DoShaderTagDlg( &newTag, "Rename shader tag" );
2388
2389                 if ( result == eIDOK && !newTag.empty() ) {
2390                         GtkTreeIter iterList;
2391                         gchar* rowTag;
2392                         gchar* oldTag = (char*)selected->data;
2393
2394                         bool row = gtk_tree_model_get_iter_first(g_TextureBrowser.m_all_tags_list, &iterList ) != 0;
2395
2396                         while ( row )
2397                         {
2398                                 gtk_tree_model_get(g_TextureBrowser.m_all_tags_list, &iterList, TAG_COLUMN, &rowTag, -1 );
2399
2400                                 if ( strcmp( rowTag, oldTag ) == 0 ) {
2401                                         gtk_list_store_set( g_TextureBrowser.m_all_tags_list, &iterList, TAG_COLUMN, newTag.c_str(), -1 );
2402                                 }
2403                                 row = gtk_tree_model_iter_next(g_TextureBrowser.m_all_tags_list, &iterList ) != 0;
2404                         }
2405
2406                         TagBuilder.RenameShaderTag( oldTag, newTag.c_str() );
2407
2408                         g_TextureBrowser.m_all_tags.erase( (CopiedString)oldTag );
2409                         g_TextureBrowser.m_all_tags.insert( newTag );
2410
2411                         BuildStoreAssignedTags( g_TextureBrowser.m_assigned_store, g_TextureBrowser.shader.c_str(), &g_TextureBrowser );
2412                         BuildStoreAvailableTags( g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store, g_TextureBrowser.m_all_tags, &g_TextureBrowser );
2413                 }
2414         }
2415         else
2416         {
2417                 ui::alert( g_TextureBrowser.m_parent, "Select a single tag for renaming." );
2418         }
2419 }
2420
2421 void TextureBrowser_deleteTag(){
2422         GSList* selected = NULL;
2423
2424     auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_treeViewTags );
2425         gtk_tree_selection_selected_foreach( selection, GtkTreeSelectionForeachFunc( TextureBrowser_selectionHelper ), &selected );
2426
2427         if ( g_slist_length( selected ) == 1 ) { // we only delete a single tag
2428                 auto result = ui::alert( g_TextureBrowser.m_parent, "Are you sure you want to delete the selected tag?", "Delete Tag", ui::alert_type::YESNO, ui::alert_icon::Question );
2429
2430                 if ( result == ui::alert_response::YES ) {
2431                         GtkTreeIter iterSelected;
2432                         gchar *rowTag;
2433
2434                         gchar* tagSelected = (char*)selected->data;
2435
2436                         bool row = gtk_tree_model_get_iter_first(g_TextureBrowser.m_all_tags_list, &iterSelected ) != 0;
2437
2438                         while ( row )
2439                         {
2440                                 gtk_tree_model_get(g_TextureBrowser.m_all_tags_list, &iterSelected, TAG_COLUMN, &rowTag, -1 );
2441
2442                                 if ( strcmp( rowTag, tagSelected ) == 0 ) {
2443                                         gtk_list_store_remove( g_TextureBrowser.m_all_tags_list, &iterSelected );
2444                                         break;
2445                                 }
2446                                 row = gtk_tree_model_iter_next(g_TextureBrowser.m_all_tags_list, &iterSelected ) != 0;
2447                         }
2448
2449                         TagBuilder.DeleteTag( tagSelected );
2450                         g_TextureBrowser.m_all_tags.erase( (CopiedString)tagSelected );
2451
2452                         BuildStoreAssignedTags( g_TextureBrowser.m_assigned_store, g_TextureBrowser.shader.c_str(), &g_TextureBrowser );
2453                         BuildStoreAvailableTags( g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store, g_TextureBrowser.m_all_tags, &g_TextureBrowser );
2454                 }
2455         }
2456         else {
2457                 ui::alert( g_TextureBrowser.m_parent, "Select a single tag for deletion." );
2458         }
2459 }
2460
2461 void TextureBrowser_copyTag(){
2462         g_TextureBrowser.m_copied_tags.clear();
2463         TagBuilder.GetShaderTags( g_TextureBrowser.shader.c_str(), g_TextureBrowser.m_copied_tags );
2464 }
2465
2466 void TextureBrowser_pasteTag(){
2467         IShader* ishader = QERApp_Shader_ForName( g_TextureBrowser.shader.c_str() );
2468         CopiedString shader = g_TextureBrowser.shader.c_str();
2469
2470         if ( !TagBuilder.CheckShaderTag( shader.c_str() ) ) {
2471                 CopiedString shaderFile = ishader->getShaderFileName();
2472                 if ( shaderFile.empty() ) {
2473                         // it's a texture
2474                         TagBuilder.AddShaderNode( shader.c_str(), CUSTOM, TEXTURE );
2475                 }
2476                 else
2477                 {
2478                         // it's a shader
2479                         TagBuilder.AddShaderNode( shader.c_str(), CUSTOM, SHADER );
2480                 }
2481
2482                 for ( size_t i = 0; i < g_TextureBrowser.m_copied_tags.size(); ++i )
2483                 {
2484                         TagBuilder.AddShaderTag( shader.c_str(), g_TextureBrowser.m_copied_tags[i].c_str(), TAG );
2485                 }
2486         }
2487         else
2488         {
2489                 for ( size_t i = 0; i < g_TextureBrowser.m_copied_tags.size(); ++i )
2490                 {
2491                         if ( !TagBuilder.CheckShaderTag( shader.c_str(), g_TextureBrowser.m_copied_tags[i].c_str() ) ) {
2492                                 // the tag doesn't exist - let's add it
2493                                 TagBuilder.AddShaderTag( shader.c_str(), g_TextureBrowser.m_copied_tags[i].c_str(), TAG );
2494                         }
2495                 }
2496         }
2497
2498         ishader->DecRef();
2499
2500         TagBuilder.SaveXmlDoc();
2501         BuildStoreAssignedTags( g_TextureBrowser.m_assigned_store, shader.c_str(), &g_TextureBrowser );
2502         BuildStoreAvailableTags( g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store, g_TextureBrowser.m_all_tags, &g_TextureBrowser );
2503 }
2504
2505 void TextureBrowser_RefreshShaders(){
2506         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Shaders" );
2507         GlobalShaderSystem().refresh();
2508         UpdateAllWindows();
2509     auto selection = gtk_tree_view_get_selection(GlobalTextureBrowser().m_treeViewTree);
2510         GtkTreeModel* model = NULL;
2511         GtkTreeIter iter;
2512         if ( gtk_tree_selection_get_selected (selection, &model, &iter) )
2513         {
2514                 gchar dirName[1024];
2515
2516                 gchar* buffer;
2517                 gtk_tree_model_get( model, &iter, 0, &buffer, -1 );
2518                 strcpy( dirName, buffer );
2519                 g_free( buffer );
2520                 if ( !TextureBrowser_showWads() ) {
2521                         strcat( dirName, "/" );
2522                 }
2523                 TextureBrowser_ShowDirectory( GlobalTextureBrowser(), dirName );
2524                 TextureBrowser_queueDraw( GlobalTextureBrowser() );
2525         }
2526 }
2527
2528 void TextureBrowser_ToggleShowShaders(){
2529         g_TextureBrowser.m_showShaders ^= 1;
2530         g_TextureBrowser.m_showshaders_item.update();
2531         TextureBrowser_queueDraw( g_TextureBrowser );
2532 }
2533
2534 void TextureBrowser_ToggleShowShaderListOnly(){
2535         g_TextureBrowser_shaderlistOnly ^= 1;
2536         g_TextureBrowser.m_showshaderlistonly_item.update();
2537
2538         TextureBrowser_constructTreeStore();
2539 }
2540
2541 void TextureBrowser_showAll(){
2542         g_TextureBrowser_currentDirectory = "";
2543         g_TextureBrowser.m_searchedTags = false;
2544         TextureBrowser_heightChanged( g_TextureBrowser );
2545         TextureBrowser_updateTitle();
2546 }
2547
2548 void TextureBrowser_showUntagged(){
2549         auto result = ui::alert( g_TextureBrowser.m_parent, "WARNING! This function might need a lot of memory and time. Are you sure you want to use it?", "Show Untagged", ui::alert_type::YESNO, ui::alert_icon::Warning );
2550
2551         if ( result == ui::alert_response::YES ) {
2552                 g_TextureBrowser.m_found_shaders.clear();
2553                 TagBuilder.GetUntagged( g_TextureBrowser.m_found_shaders );
2554                 std::set<CopiedString>::iterator iter;
2555
2556                 ScopeDisableScreenUpdates disableScreenUpdates( "Searching untagged textures...", "Loading Textures" );
2557
2558                 for ( iter = g_TextureBrowser.m_found_shaders.begin(); iter != g_TextureBrowser.m_found_shaders.end(); iter++ )
2559                 {
2560                         std::string path = ( *iter ).c_str();
2561                         size_t pos = path.find_last_of( "/", path.size() );
2562                         std::string name = path.substr( pos + 1, path.size() );
2563                         path = path.substr( 0, pos + 1 );
2564                         TextureDirectory_loadTexture( path.c_str(), name.c_str() );
2565                         globalErrorStream() << path.c_str() << name.c_str() << "\n";
2566                 }
2567
2568                 g_TextureBrowser_currentDirectory = "Untagged";
2569                 TextureBrowser_queueDraw( GlobalTextureBrowser() );
2570                 TextureBrowser_heightChanged( g_TextureBrowser );
2571                 TextureBrowser_updateTitle();
2572         }
2573 }
2574
2575 void TextureBrowser_FixedSize(){
2576         g_TextureBrowser_fixedSize ^= 1;
2577         GlobalTextureBrowser().m_fixedsize_item.update();
2578         TextureBrowser_activeShadersChanged( GlobalTextureBrowser() );
2579 }
2580
2581 void TextureBrowser_FilterMissing(){
2582         g_TextureBrowser_filterMissing ^= 1;
2583         GlobalTextureBrowser().m_filternotex_item.update();
2584         TextureBrowser_activeShadersChanged( GlobalTextureBrowser() );
2585         TextureBrowser_RefreshShaders();
2586 }
2587
2588 void TextureBrowser_FilterFallback(){
2589         g_TextureBrowser_filterFallback ^= 1;
2590         GlobalTextureBrowser().m_hidenotex_item.update();
2591         TextureBrowser_activeShadersChanged( GlobalTextureBrowser() );
2592         TextureBrowser_RefreshShaders();
2593 }
2594
2595 void TextureBrowser_EnableAlpha(){
2596         g_TextureBrowser_enableAlpha ^= 1;
2597         GlobalTextureBrowser().m_enablealpha_item.update();
2598         TextureBrowser_activeShadersChanged( GlobalTextureBrowser() );
2599 }
2600
2601 void TextureBrowser_exportTitle( const Callback<void(const char *)> & importer ){
2602         StringOutputStream buffer( 64 );
2603         buffer << "Textures: ";
2604         if ( !string_empty( g_TextureBrowser_currentDirectory.c_str() ) ) {
2605                 buffer << g_TextureBrowser_currentDirectory.c_str();
2606         }
2607         else
2608         {
2609                 buffer << "all";
2610         }
2611         importer( buffer.c_str() );
2612 }
2613
2614 struct TextureScale {
2615         static void Export(const TextureBrowser &self, const Callback<void(int)> &returnz) {
2616                 switch (self.m_textureScale) {
2617                         case 10:
2618                                 returnz(0);
2619                                 break;
2620                         case 25:
2621                                 returnz(1);
2622                                 break;
2623                         case 50:
2624                                 returnz(2);
2625                                 break;
2626                         case 100:
2627                                 returnz(3);
2628                                 break;
2629                         case 200:
2630                                 returnz(4);
2631                                 break;
2632                 }
2633         }
2634
2635         static void Import(TextureBrowser &self, int value) {
2636                 switch (value) {
2637                         case 0:
2638                                 TextureBrowser_setScale(self, 10);
2639                                 break;
2640                         case 1:
2641                                 TextureBrowser_setScale(self, 25);
2642                                 break;
2643                         case 2:
2644                                 TextureBrowser_setScale(self, 50);
2645                                 break;
2646                         case 3:
2647                                 TextureBrowser_setScale(self, 100);
2648                                 break;
2649                         case 4:
2650                                 TextureBrowser_setScale(self, 200);
2651                                 break;
2652                 }
2653         }
2654 };
2655
2656 struct UniformTextureSize {
2657         static void Export(const TextureBrowser &self, const Callback<void(int)> &returnz) {
2658                 returnz(g_TextureBrowser.m_uniformTextureSize);
2659         }
2660
2661         static void Import(TextureBrowser &self, int value) {
2662                 if (value > 16)
2663                         TextureBrowser_setUniformSize(self, value);
2664         }
2665 };
2666
2667 struct UniformTextureMinSize {
2668         static void Export(const TextureBrowser &self, const Callback<void(int)> &returnz) {
2669                 returnz(g_TextureBrowser.m_uniformTextureMinSize);
2670         }
2671
2672         static void Import(TextureBrowser &self, int value) {
2673                 if (value > 16)
2674                         TextureBrowser_setUniformSize(self, value);
2675         }
2676 };
2677
2678 void TextureBrowser_constructPreferences( PreferencesPage& page ){
2679         page.appendCheckBox(
2680                 "", "Texture scrollbar",
2681                 make_property<TextureBrowser_ShowScrollbar>(GlobalTextureBrowser())
2682                 );
2683         {
2684                 const char* texture_scale[] = { "10%", "25%", "50%", "100%", "200%" };
2685                 page.appendCombo(
2686                         "Texture Thumbnail Scale",
2687                         STRING_ARRAY_RANGE( texture_scale ),
2688                         make_property<TextureScale>(GlobalTextureBrowser())
2689                         );
2690         }
2691         page.appendSpinner( "Thumbnails Max Size", GlobalTextureBrowser().m_uniformTextureSize, GlobalTextureBrowser().m_uniformTextureSize, 16, 8192 );
2692         page.appendSpinner( "Thumbnails Min Size", GlobalTextureBrowser().m_uniformTextureMinSize, GlobalTextureBrowser().m_uniformTextureMinSize, 16, 8192 );
2693         page.appendEntry( "Mousewheel Increment", GlobalTextureBrowser().m_mouseWheelScrollIncrement );
2694         {
2695                 const char* startup_shaders[] = { "None", TextureBrowser_getComonShadersName() };
2696                 page.appendCombo( "Load Shaders at Startup", reinterpret_cast<int&>( GlobalTextureBrowser().m_startupShaders ), STRING_ARRAY_RANGE( startup_shaders ) );
2697         }
2698 }
2699
2700 void TextureBrowser_constructPage( PreferenceGroup& group ){
2701         PreferencesPage page( group.createPage( "Texture Browser", "Texture Browser Preferences" ) );
2702         TextureBrowser_constructPreferences( page );
2703 }
2704
2705 void TextureBrowser_registerPreferencesPage(){
2706         PreferencesDialog_addSettingsPage( makeCallbackF(TextureBrowser_constructPage) );
2707 }
2708
2709
2710 #include "preferencesystem.h"
2711 #include "stringio.h"
2712
2713
2714 void TextureClipboard_textureSelected( const char* shader );
2715
2716 void TextureBrowser_Construct(){
2717         GlobalCommands_insert( "ShaderInfo", makeCallbackF(TextureBrowser_shaderInfo) );
2718         GlobalCommands_insert( "ShowUntagged", makeCallbackF(TextureBrowser_showUntagged) );
2719         GlobalCommands_insert( "AddTag", makeCallbackF(TextureBrowser_addTag) );
2720         GlobalCommands_insert( "RenameTag", makeCallbackF(TextureBrowser_renameTag) );
2721         GlobalCommands_insert( "DeleteTag", makeCallbackF(TextureBrowser_deleteTag) );
2722         GlobalCommands_insert( "CopyTag", makeCallbackF(TextureBrowser_copyTag) );
2723         GlobalCommands_insert( "PasteTag", makeCallbackF(TextureBrowser_pasteTag) );
2724         GlobalCommands_insert( "RefreshShaders", makeCallbackF(VFS_Refresh) );
2725         GlobalToggles_insert( "ShowInUse", makeCallbackF(TextureBrowser_ToggleHideUnused), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_hideunused_item ), Accelerator( 'U' ) );
2726         GlobalCommands_insert( "ShowAllTextures", makeCallbackF(TextureBrowser_showAll), Accelerator( 'A', (GdkModifierType)GDK_CONTROL_MASK ) );
2727         GlobalCommands_insert( "ToggleTextures", makeCallbackF(TextureBrowser_toggleShow), Accelerator( 'T' ) );
2728         GlobalToggles_insert( "ToggleShowShaders", makeCallbackF(TextureBrowser_ToggleShowShaders), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showshaders_item ) );
2729         GlobalToggles_insert( "ToggleShowShaderlistOnly", makeCallbackF(TextureBrowser_ToggleShowShaderListOnly), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_showshaderlistonly_item ) );
2730         GlobalToggles_insert( "FixedSize", makeCallbackF(TextureBrowser_FixedSize), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_fixedsize_item ) );
2731         GlobalToggles_insert( "FilterMissing", makeCallbackF(TextureBrowser_FilterMissing), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_filternotex_item ) );
2732         GlobalToggles_insert( "FilterFallback", makeCallbackF(TextureBrowser_FilterFallback), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_hidenotex_item ) );
2733         GlobalToggles_insert( "EnableAlpha", makeCallbackF(TextureBrowser_EnableAlpha), ToggleItem::AddCallbackCaller( g_TextureBrowser.m_enablealpha_item ) );
2734
2735         GlobalPreferenceSystem().registerPreference( "TextureScale", make_property_string<TextureScale>(g_TextureBrowser) );
2736         GlobalPreferenceSystem().registerPreference( "UniformTextureSize", make_property_string<UniformTextureSize>(g_TextureBrowser) );
2737         GlobalPreferenceSystem().registerPreference( "UniformTextureMinSize", make_property_string<UniformTextureMinSize>(g_TextureBrowser) );
2738         GlobalPreferenceSystem().registerPreference( "TextureScrollbar", make_property_string<TextureBrowser_ShowScrollbar>(GlobalTextureBrowser()));
2739         GlobalPreferenceSystem().registerPreference( "ShowShaders", make_property_string( GlobalTextureBrowser().m_showShaders ) );
2740         GlobalPreferenceSystem().registerPreference( "ShowShaderlistOnly", make_property_string( g_TextureBrowser_shaderlistOnly ) );
2741         GlobalPreferenceSystem().registerPreference( "FixedSize", make_property_string( g_TextureBrowser_fixedSize ) );
2742         GlobalPreferenceSystem().registerPreference( "FilterMissing", make_property_string( g_TextureBrowser_filterMissing ) );
2743         GlobalPreferenceSystem().registerPreference( "EnableAlpha", make_property_string( g_TextureBrowser_enableAlpha ) );
2744         GlobalPreferenceSystem().registerPreference( "LoadShaders", make_property_string( reinterpret_cast<int&>( GlobalTextureBrowser().m_startupShaders ) ) );
2745         GlobalPreferenceSystem().registerPreference( "WheelMouseInc", make_property_string( GlobalTextureBrowser().m_mouseWheelScrollIncrement ) );
2746         GlobalPreferenceSystem().registerPreference( "SI_Colors0", make_property_string( GlobalTextureBrowser().color_textureback ) );
2747
2748         g_TextureBrowser.shader = texdef_name_default();
2749
2750         Textures_setModeChangedNotify( ReferenceCaller<TextureBrowser, void(), TextureBrowser_queueDraw>( g_TextureBrowser ) );
2751
2752         TextureBrowser_registerPreferencesPage();
2753
2754         GlobalShaderSystem().attach( g_ShadersObserver );
2755
2756         TextureBrowser_textureSelected = TextureClipboard_textureSelected;
2757 }
2758
2759 void TextureBrowser_Destroy(){
2760         GlobalShaderSystem().detach( g_ShadersObserver );
2761
2762         Textures_setModeChangedNotify( Callback<void()>() );
2763 }