netradiant-custom/radiant/texwindow.cpp
Garux df02774ff5 tweak StringOutputStream use
auto str = StringOutputStream()(bla) use form was not doing copy elision or move, but copy
2024-01-29 16:54:08 +06:00

2114 lines
70 KiB
C++

/*
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.
This file is part of GtkRadiant.
GtkRadiant is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
GtkRadiant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
//
// Texture Window
//
// Leonardo Zide (leo@lokigames.com)
//
#include "texwindow.h"
#include "debugging/debugging.h"
#include "ifilesystem.h"
#include "iundo.h"
#include "igl.h"
#include "iarchive.h"
#include "moduleobserver.h"
#include <set>
#include <vector>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QApplication>
#include <QStyle>
#include <QTreeView>
#include <QHeaderView>
#include <QStandardItemModel>
#include <QListWidget>
#include <QListWidgetItem>
#include <QItemDelegate>
#include <QMetaProperty>
#include <QScrollBar>
#include <QSplitter>
#include <QOpenGLWidget>
#include <QTabWidget>
#include "signal/signal.h"
#include "math/vector.h"
#include "texturelib.h"
#include "string/string.h"
#include "shaderlib.h"
#include "os/file.h"
#include "os/path.h"
#include "stream/memstream.h"
#include "stream/textfilestream.h"
#include "stream/stringstream.h"
#include "commandlib.h"
#include "texmanip.h"
#include "textures.h"
#include "gtkutil/menu.h"
#include "gtkutil/nonmodal.h"
#include "gtkutil/cursor.h"
#include "gtkutil/widget.h"
#include "gtkutil/glwidget.h"
#include "gtkutil/messagebox.h"
#include "gtkutil/toolbar.h"
#include "gtkutil/mousepresses.h"
#include "gtkutil/guisettings.h"
#include "error.h"
#include "map.h"
#include "qgl.h"
#include "select.h"
#include "brush_primit.h"
#include "brushmanip.h"
#include "patchmanip.h"
#include "plugin.h"
#include "qe3.h"
#include "gtkdlgs.h"
#include "gtkmisc.h"
#include "mainframe.h"
#include "findtexturedialog.h"
#include "surfacedialog.h"
#include "patchdialog.h"
#include "groupdialog.h"
#include "preferences.h"
#include "commands.h"
bool string_equal_start( const char* string, StringRange start ){
return string_equal_n( string, start.data(), start.size() );
}
// sort case insensitively, as it is user friendly
// still preserve unequal names, as it is needed for linux case sensitive FS
struct TextureGroups_compare
{
bool operator()( const CopiedString& a, const CopiedString& b ) const {
const int cmp_nocase = string_compare_nocase( a.c_str(), b.c_str() );
if( cmp_nocase != 0 )
return cmp_nocase < 0;
else
return string_less( a.c_str(), b.c_str() );
}
};
typedef std::set<CopiedString, TextureGroups_compare> TextureGroups;
void TextureGroups_addWad( TextureGroups& groups, const char* archive ){
if ( path_extension_is( archive, "wad" ) ) {
#if 1
groups.insert( archive );
#else
CopiedString archiveBaseName( PathFilename( archive ) );
groups.insert( archiveBaseName );
#endif
}
}
typedef ReferenceCaller1<TextureGroups, const char*, TextureGroups_addWad> TextureGroupsAddWadCaller;
void TextureGroups_addShader( TextureGroups& groups, const char* shaderName ){
const char* texture = path_make_relative( shaderName, "textures/" );
if ( texture != shaderName ) {
const char* last = path_remove_directory( texture );
if ( !string_empty( last ) ) {
groups.insert( CopiedString( StringRange( texture, --last ) ) );
}
}
}
typedef ReferenceCaller1<TextureGroups, const char*, TextureGroups_addShader> TextureGroupsAddShaderCaller;
void TextureGroups_addDirectory( TextureGroups& groups, const char* directory ){
groups.insert( directory );
}
typedef ReferenceCaller1<TextureGroups, const char*, TextureGroups_addDirectory> TextureGroupsAddDirectoryCaller;
namespace
{
bool g_TextureBrowser_shaderlistOnly = false;
bool g_TextureBrowser_fixedSize = true;
bool g_TextureBrowser_filterNotex = false;
bool g_TextureBrowser_enableAlpha = false;
bool g_TextureBrowser_filter_searchFromStart = false;
}
enum StartupShaders
{
STARTUPSHADERS_NONE = 0,
STARTUPSHADERS_COMMON,
};
class TextureBrowser
{
int m_originy;
int m_nTotalHeight;
public:
int m_width, m_height;
CopiedString m_shader; // current shader
QWidget* m_parent;
QOpenGLWidget* m_gl_widget;
QScrollBar* m_texture_scroll;
QTabWidget* m_tabs;
QTreeView* m_treeView;
QStandardItemModel* m_treeViewModel;
QListWidget* m_tagsListWidget;
QMenu* m_tagsMenu;
QAction* m_shader_info_item{};
QLineEdit* m_filter_entry;
QAction* m_filter_action;
CopiedString m_filter_string;
std::set<CopiedString> m_all_tags;
std::vector<CopiedString> m_copied_tags;
std::set<CopiedString> m_found_shaders;
ToggleItem m_hideunused_item;
ToggleItem m_showshaders_item;
ToggleItem m_showtextures_item;
ToggleItem m_showshaderlistonly_item;
ToggleItem m_fixedsize_item;
ToggleItem m_filternotex_item;
ToggleItem m_enablealpha_item;
ToggleItem m_tags_item;
ToggleItem m_filter_searchFromStart_item;
bool m_heightChanged;
bool m_originInvalid;
DeferredAdjustment m_scrollAdjustment;
FreezePointer m_freezePointer;
Vector3 m_color_textureback;
// the increment step we use against the wheel mouse
int m_mouseWheelScrollIncrement;
std::size_t m_textureScale;
bool m_showShaders;
bool m_showTextures;
bool m_showTextureScrollbar;
StartupShaders m_startupShaders;
// if true, the texture window will only display in-use shaders
// if false, all the shaders in memory are displayed
bool m_hideUnused;
bool m_searchedTags; // flag to show m_found_shaders
bool m_tags; // whether to show tags gui
bool m_move_started;
// The uniform size (in pixels) that textures are resized to when m_resizeTextures is true.
int m_uniformTextureSize;
int m_uniformTextureMinSize;
bool m_hideNonShadersInCommon;
static bool wads;
TextureBrowser() :
m_texture_scroll( 0 ),
m_hideunused_item( BoolExportCaller( m_hideUnused ) ),
m_showshaders_item( BoolExportCaller( m_showShaders ) ),
m_showtextures_item( BoolExportCaller( m_showTextures ) ),
m_showshaderlistonly_item( BoolExportCaller( g_TextureBrowser_shaderlistOnly ) ),
m_fixedsize_item( BoolExportCaller( g_TextureBrowser_fixedSize ) ),
m_filternotex_item( BoolExportCaller( g_TextureBrowser_filterNotex ) ),
m_enablealpha_item( BoolExportCaller( g_TextureBrowser_enableAlpha ) ),
m_tags_item( BoolExportCaller( m_tags ) ),
m_filter_searchFromStart_item( BoolExportCaller( g_TextureBrowser_filter_searchFromStart ) ),
m_heightChanged( true ),
m_originInvalid( true ),
m_scrollAdjustment( [this]( int value ){
//globalOutputStream() << "vertical scroll\n";
setOriginY( -value );
} ),
m_color_textureback( 0.25f, 0.25f, 0.25f ),
m_mouseWheelScrollIncrement( 64 ),
m_textureScale( 50 ),
m_showShaders( true ),
m_showTextures( true ),
m_showTextureScrollbar( true ),
m_startupShaders( STARTUPSHADERS_NONE ),
m_hideUnused( false ),
m_searchedTags( false ),
m_tags( false ),
m_move_started( false ),
m_uniformTextureSize( 160 ),
m_uniformTextureMinSize( 48 ),
m_hideNonShadersInCommon( true ){
}
void queueDraw() const {
if ( m_gl_widget != nullptr )
widget_queue_draw( *m_gl_widget );
}
void draw();
void setOriginY( int originy ){
m_originy = originy;
clampOriginY();
updateScroll();
queueDraw();
}
private:
void clampOriginY(){
m_originy = std::clamp( m_originy, m_height - totalHeight(), 0 );
}
void evaluateHeight();
int totalHeight(){
evaluateHeight();
return m_nTotalHeight;
}
public:
int getOriginY(){
if ( m_originInvalid ) {
m_originInvalid = false;
clampOriginY();
updateScroll();
}
return m_originy;
}
void heightChanged(){
m_heightChanged = true;
updateScroll();
queueDraw();
}
void updateScroll(){
if ( m_showTextureScrollbar ) {
const int total_height = std::max( totalHeight(), m_height );
QScrollBar* s = m_texture_scroll;
s->setMinimum( 0 );
s->setMaximum( total_height - m_height );
s->setValue( - getOriginY() );
s->setPageStep( m_height );
s->setSingleStep( 20 );
}
}
// Return the display width of a texture in the texture browser
auto getTextureWH( const qtexture_t* tex ) const {
// Don't use uniform size
int W = std::max( std::size_t( 1 ), tex->width * m_textureScale / 100 );
int H = std::max( std::size_t( 1 ), tex->height * m_textureScale / 100 );
if ( g_TextureBrowser_fixedSize ){
if ( W >= H ) {
// Texture is square, or wider than it is tall
if ( W > m_uniformTextureSize ){
H = m_uniformTextureSize * H / W;
W = m_uniformTextureSize;
}
else if ( W < m_uniformTextureMinSize ){
H = m_uniformTextureMinSize * H / W;
W = m_uniformTextureMinSize;
}
}
else {
// Texture taller than it is wide
if ( H > m_uniformTextureSize ){
W = m_uniformTextureSize * W / H;
H = m_uniformTextureSize;
}
else if ( H < m_uniformTextureMinSize ){
W = m_uniformTextureMinSize * W / H;
H = m_uniformTextureMinSize;
}
}
}
return std::pair( W, H );
}
};
bool TextureBrowser::wads = false;
static TextureBrowser g_TexBro;
void ( *TextureBrowser_textureSelected )( const char* shader );
inline const char* TextureBrowser_getCommonShadersName(){
static const char* const value = string_empty( g_pGameDescription->getKeyValue( "common_shaders_name" ) )
? "Common"
: g_pGameDescription->getKeyValue( "common_shaders_name" );
return value;
}
inline const char* TextureBrowser_getCommonShadersDir(){
static const char* const value = string_empty( g_pGameDescription->getKeyValue( "common_shaders_dir" ) )
? "common/"
: g_pGameDescription->getKeyValue( "common_shaders_dir" );
return value;
}
inline int TextureBrowser_fontHeight(){
return GlobalOpenGL().m_font->getPixelHeight();
}
const char* TextureBrowser_GetSelectedShader(){
return g_TexBro.m_shader.c_str();
}
void TextureBrowser_SetStatus( const char* name ){
IShader* shader = QERApp_Shader_ForName( name );
qtexture_t* q = shader->getTexture();
const auto strTex = StringStream( ( string_equal_prefix_nocase( name, "textures/" )? name + 9 : name ),
" W: ", q->width,
" H: ", q->height );
shader->DecRef();
g_pParentWnd->SetStatusText( c_status_texture, strTex );
}
void TextureBrowser_Focus( TextureBrowser& textureBrowser, const char* name );
void TextureBrowser_tagsSetCheckboxesForShader( const char *shader );
void TextureBrowser_SetSelectedShader( TextureBrowser& textureBrowser, const char* shader ){
textureBrowser.m_shader = shader;
TextureBrowser_SetStatus( shader );
TextureBrowser_Focus( textureBrowser, shader );
if ( FindTextureDialog_isOpen() ) {
FindTextureDialog_selectTexture( shader );
}
// disable the menu item "shader info" if no shader was selected
if ( textureBrowser.m_shader_info_item != nullptr ){
IShader* ishader = QERApp_Shader_ForName( shader );
CopiedString filename = ishader->getShaderFileName();
textureBrowser.m_shader_info_item->setDisabled( filename.empty() );
ishader->DecRef();
}
if( textureBrowser.m_tabs->currentIndex() == 1 )
TextureBrowser_tagsSetCheckboxesForShader( shader );
}
void TextureBrowser_SetSelectedShader( const char* shader ){
TextureBrowser_SetSelectedShader( g_TexBro, shader );
}
CopiedString g_TextureBrowser_currentDirectory;
/*
============================================================================
TEXTURE LAYOUT
TTimo: now based on a rundown through all the shaders
NOTE: we expect the Active shaders count doesn't change during a Texture_StartPos .. Texture_NextPos cycle
otherwise we may need to rely on a list instead of an array storage
============================================================================
*/
struct TextureLayout
{
// texture layout functions
// TTimo: now based on shaders
int current_x = 8;
int current_y = -4;
int current_row = 0;
auto nextPos( const TextureBrowser& textureBrowser, qtexture_t* current_texture ){
const auto [nWidth, nHeight] = textureBrowser.getTextureWH( current_texture );
if ( current_x + nWidth > textureBrowser.m_width - 8 && current_row ) { // go to the next row unless the texture is the first on the row
current_x = 8;
current_y -= current_row + TextureBrowser_fontHeight() + 1;//+4
current_row = 0;
}
const int x = current_x;
const int y = current_y;
// Is our texture larger than the row? If so, grow the
// row height to match it
if ( current_row < nHeight ) {
current_row = nHeight;
}
// never go less than 96, or the names get all crunched up
current_x += std::max( nWidth, 96 ) + 8;
return std::pair( x, y );
}
};
bool Texture_filtered( const char* name, const TextureBrowser& textureBrowser ){
const char* filter = textureBrowser.m_filter_string.c_str();
if( string_empty( filter ) ){
return false;
}
if( g_TextureBrowser_filter_searchFromStart ){
if( string_equal_prefix_nocase( name, filter ) ){
return false;
}
}
else{
if( string_in_string_nocase( name, filter ) != 0 ){
return false;
}
}
return true;
}
CopiedString g_notex;
CopiedString g_shadernotex;
// if texture_showinuse jump over non in-use textures
/*
bool show_shaders, bool show_textures, bool hideUnused, bool hideNonShadersInCommon
textureBrowser.m_showShaders, textureBrowser.m_showTextures, textureBrowser.m_hideUnused, textureBrowser.m_hideNonShadersInCommon
*/
bool Texture_IsShown( IShader* shader, const TextureBrowser& textureBrowser ){
// filter notex / shadernotex images
if ( g_TextureBrowser_filterNotex && ( string_equal( g_notex.c_str(), shader->getTexture()->name ) || string_equal( g_shadernotex.c_str(), shader->getTexture()->name ) ) ) {
return false;
}
if ( !shader_equal_prefix( shader->getName(), "textures/" ) ) {
return false;
}
if ( !textureBrowser.m_showShaders && !shader->IsDefault() ) {
return false;
}
if ( !textureBrowser.m_showTextures && shader->IsDefault() ) {
return false;
}
if ( textureBrowser.m_hideUnused && !shader->IsInUse() ) {
return false;
}
if( textureBrowser.m_hideNonShadersInCommon && shader->IsDefault() && !shader->IsInUse() //&& g_TextureBrowser_currentDirectory != ""
&& shader_equal_prefix( shader_get_textureName( shader->getName() ), TextureBrowser_getCommonShadersDir() ) ){
return false;
}
if ( textureBrowser.m_searchedTags ) {
return textureBrowser.m_found_shaders.find( shader->getName() ) != textureBrowser.m_found_shaders.cend();
}
else {
if ( !shader_equal_prefix( shader_get_textureName( shader->getName() ), g_TextureBrowser_currentDirectory.c_str() ) ) {
return false;
}
}
if( Texture_filtered( path_get_filename_start( shader->getName() ), textureBrowser ) ){
return false;
}
return true;
}
void TextureBrowser::evaluateHeight(){
if ( m_heightChanged ) {
m_heightChanged = false;
m_nTotalHeight = 0;
TextureLayout layout;
for ( QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement() )
{
IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
if ( Texture_IsShown( shader, *this ) ) {
layout.nextPos( *this, shader->getTexture() );
const auto [nWidth, nHeight] = getTextureWH( shader->getTexture() );
m_nTotalHeight = std::max( m_nTotalHeight, abs( layout.current_y ) + TextureBrowser_fontHeight() + nHeight + 4 );
}
}
}
}
Signal0 g_activeShadersChangedCallbacks;
void TextureBrowser_addActiveShadersChangedCallback( const SignalHandler& handler ){
g_activeShadersChangedCallbacks.connectLast( handler );
}
class ShadersObserver : public ModuleObserver
{
Signal0 m_realiseCallbacks;
public:
void realise(){
m_realiseCallbacks();
}
void unrealise(){
}
void insert( const SignalHandler& handler ){
m_realiseCallbacks.connectLast( handler );
}
};
namespace
{
ShadersObserver g_ShadersObserver;
}
void TextureBrowser_addShadersRealiseCallback( const SignalHandler& handler ){
g_ShadersObserver.insert( handler );
}
void TextureBrowser_activeShadersChanged( TextureBrowser& textureBrowser ){
textureBrowser.heightChanged();
textureBrowser.m_originInvalid = true;
g_activeShadersChangedCallbacks();
}
void TextureBrowser_importShowScrollbar( TextureBrowser& textureBrowser, bool value ){
textureBrowser.m_showTextureScrollbar = value;
if ( textureBrowser.m_texture_scroll != 0 ) {
textureBrowser.m_texture_scroll->setVisible( textureBrowser.m_showTextureScrollbar );
textureBrowser.updateScroll();
}
}
typedef ReferenceCaller1<TextureBrowser, bool, TextureBrowser_importShowScrollbar> TextureBrowserImportShowScrollbarCaller;
/*
==============
TextureBrowser_ShowDirectory
relies on texture_directory global for the directory to use
1) Load the shaders for the given directory
2) Scan the remaining texture, load them and assign them a default shader (the "noshader" shader)
NOTE: when writing a texture plugin, or some texture extensions, this function may need to be overridden, and made
available through the IShaders interface
NOTE: for texture window layout:
all shaders are stored with alphabetical order after load
previously loaded and displayed stuff is hidden, only in-use and newly loaded is shown
( the GL textures are not flushed though)
==============
*/
inline bool texture_name_ignore( const char* name ){
const auto temp = StringStream<64>( LowerCase( name ) );
return
string_equal_suffix( temp, ".specular" ) ||
string_equal_suffix( temp, ".glow" ) ||
string_equal_suffix( temp, ".bump" ) ||
string_equal_suffix( temp, ".diffuse" ) ||
string_equal_suffix( temp, ".blend" ) ||
string_equal_suffix( temp, ".alpha" ) ||
string_equal_suffix( temp, "_norm" ) ||
string_equal_suffix( temp, "_bump" ) ||
string_equal_suffix( temp, "_glow" ) ||
string_equal_suffix( temp, "_gloss" ) ||
string_equal_suffix( temp, "_pants" ) ||
string_equal_suffix( temp, "_shirt" ) ||
string_equal_suffix( temp, "_reflect" ) ||
string_equal_suffix( temp, "_alpha" ) ||
0;
}
class LoadShaderVisitor : public Archive::Visitor
{
public:
void visit( const char* name ){
IShader* shader = QERApp_Shader_ForName( CopiedString( PathExtensionless( name ) ).c_str() );
shader->DecRef();
}
};
void TextureBrowser_SetHideUnused( TextureBrowser& textureBrowser, bool hideUnused );
QWidget* g_page_textures;
void TextureBrowser_toggleShow(){
GroupDialog_showPage( g_page_textures );
}
void TextureBrowser_updateTitle(){
GroupDialog_updatePageTitle( g_page_textures );
}
class TextureCategoryLoadShader
{
const char* m_directory;
std::size_t& m_count;
public:
typedef const char* first_argument_type;
TextureCategoryLoadShader( const char* directory, std::size_t& count )
: m_directory( directory ), m_count( count ){
m_count = 0;
}
void operator()( const char* name ) const {
if ( shader_equal_prefix( name, "textures/" )
&& shader_equal_prefix( name + string_length( "textures/" ), m_directory ) ) {
++m_count;
// request the shader, this will load the texture if needed
// this Shader_ForName call is a kind of hack
IShader *pFoo = QERApp_Shader_ForName( name );
pFoo->DecRef();
}
}
};
void TexturePath_loadTexture( const char* name ){
if ( texture_name_ignore( name ) ) {
return;
}
if ( !shader_valid( name ) ) {
globalWarningStream() << "Skipping invalid texture name: [" << name << "]\n";
return;
}
// if a texture is already in use to represent a shader, ignore it
IShader* shader = QERApp_Shader_ForName( name );
shader->DecRef();
}
void TextureDirectory_loadTexture( const char* directory, const char* texture ){
TexturePath_loadTexture( StringStream<64>( directory, PathExtensionless( texture ) ) );
}
typedef ConstPointerCaller1<char, const char*, TextureDirectory_loadTexture> TextureDirectoryLoadTextureCaller;
class LoadTexturesByTypeVisitor : public ImageModules::Visitor
{
const char* m_dirstring;
public:
LoadTexturesByTypeVisitor( const char* dirstring )
: m_dirstring( dirstring ){
}
void visit( const char* minor, const _QERPlugImageTable& table ) const {
GlobalFileSystem().forEachFile( m_dirstring, minor, TextureDirectoryLoadTextureCaller( m_dirstring ) );
}
};
void TextureBrowser_ShowDirectory( const char* directory ){
g_TexBro.m_searchedTags = false;
if ( TextureBrowser::wads ) {
Archive* archive = GlobalFileSystem().getArchive( directory );
//ASSERT_NOTNULL( archive );
if( archive ){
globalOutputStream() << "Loading " << makeQuoted( directory ) << " wad file.\n";
LoadShaderVisitor visitor;
archive->forEachFile( Archive::VisitorFunc( visitor, Archive::eFiles, 0 ), "textures/" );
}
else{
globalErrorStream() << "Attempted to load " << makeQuoted( directory ) << " wad file.\n";
}
}
else
{
g_TextureBrowser_currentDirectory = directory;
std::size_t shaders_count;
GlobalShaderSystem().foreachShaderName( makeCallback1( TextureCategoryLoadShader( directory, shaders_count ) ) );
globalOutputStream() << "Showing " << shaders_count << " shaders.\n";
if ( g_pGameDescription->mGameType != "doom3" ) {
// load remaining texture files
Radiant_getImageModules().foreachModule( LoadTexturesByTypeVisitor( StringStream<64>( "textures/", directory ) ) );
}
}
TextureBrowser_SetHideUnused( g_TexBro, false );
g_TexBro.setOriginY( 0 );
TextureBrowser_updateTitle();
}
void TextureBrowser_SetHideUnused( TextureBrowser& textureBrowser, bool hideUnused ){
textureBrowser.m_hideUnused = hideUnused;
textureBrowser.m_hideunused_item.update();
textureBrowser.heightChanged();
textureBrowser.m_originInvalid = true;
}
void TextureBrowser_ShowStartupShaders(){
if ( g_TexBro.m_startupShaders == STARTUPSHADERS_COMMON ) {
TextureBrowser_ShowDirectory( TextureBrowser_getCommonShadersDir() );
}
}
//++timo NOTE: this is a mix of Shader module stuff and texture explorer
// it might need to be split in parts or moved out .. dunno
// scroll origin so the specified texture is completely on screen
// if current texture is not displayed, nothing is changed
void TextureBrowser_Focus( TextureBrowser& textureBrowser, const char* name ){
TextureLayout layout;
// scroll origin so the texture is completely on screen
for ( QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement() )
{
IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
if ( !Texture_IsShown( shader, textureBrowser ) ) {
continue;
}
const auto [ x, y ] = layout.nextPos( textureBrowser, shader->getTexture() );
qtexture_t* q = shader->getTexture();
if ( !q ) {
break;
}
// we have found when texdef->name and the shader name match
// NOTE: as everywhere else for our comparisons, we are not case sensitive
if ( shader_equal( name, shader->getName() ) ) {
//int textureHeight = (int)( q->height * ( (float)textureBrowser.m_textureScale / 100 ) ) + 2 * TextureBrowser_fontHeight();
auto [textureWidth, textureHeight] = textureBrowser.getTextureWH( q );
textureHeight += 2 * TextureBrowser_fontHeight();
int originy = textureBrowser.getOriginY();
if ( y > originy ) {
originy = y + 4;
}
if ( y - textureHeight < originy - textureBrowser.m_height ) {
originy = ( y - textureHeight ) + textureBrowser.m_height;
}
textureBrowser.setOriginY( originy );
return;
}
}
}
IShader* Texture_At( TextureBrowser& textureBrowser, int mx, int my ){
my = textureBrowser.getOriginY() - my - 1;
TextureLayout layout;
for ( QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement() )
{
IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
if ( !Texture_IsShown( shader, textureBrowser ) ) {
continue;
}
const auto [ x, y ] = layout.nextPos( textureBrowser, shader->getTexture() );
qtexture_t *q = shader->getTexture();
if ( !q ) {
break;
}
const auto [nWidth, nHeight] = textureBrowser.getTextureWH( q );
if ( mx > x && mx - x < nWidth
&& my < y && y - my < nHeight + TextureBrowser_fontHeight() ) {
return shader;
}
}
return 0;
}
/*
==============
SelectTexture
By mouse click
==============
*/
void SelectTexture( TextureBrowser& textureBrowser, int mx, int my, bool texturizeSelection ){
IShader* shader = Texture_At( textureBrowser, mx, my );
if ( shader != 0 ) {
TextureBrowser_SetSelectedShader( textureBrowser, shader->getName() );
TextureBrowser_textureSelected( shader->getName() );
if ( !FindTextureDialog_isOpen() && !texturizeSelection ) {
Select_SetShader_Undo( shader->getName() );
}
}
}
/*
============================================================================
MOUSE ACTIONS
============================================================================
*/
void TextureBrowser_Tracking_MouseUp( TextureBrowser& textureBrowser ){
if( textureBrowser.m_move_started ){
textureBrowser.m_move_started = false;
textureBrowser.m_freezePointer.unfreeze_pointer( false );
}
}
void TextureBrowser_Tracking_MouseDown( TextureBrowser& textureBrowser ){
TextureBrowser_Tracking_MouseUp( textureBrowser );
textureBrowser.m_move_started = true;
textureBrowser.m_freezePointer.freeze_pointer( textureBrowser.m_gl_widget,
[&textureBrowser]( int x, int y, const QMouseEvent *event ){
if ( y != 0 ) {
const int scale = event->modifiers().testFlag( Qt::KeyboardModifier::ShiftModifier )? 4 : 1;
const int originy = textureBrowser.getOriginY() + y * scale;
textureBrowser.setOriginY( originy );
}
},
[&textureBrowser](){
TextureBrowser_Tracking_MouseUp( textureBrowser );
} );
}
void TextureBrowser_ViewShader( TextureBrowser& textureBrowser, Qt::KeyboardModifiers modifiers, int pointx, int pointy ){
IShader* shader = Texture_At( textureBrowser, pointx, pointy );
if ( shader != 0 ) {
if ( shader->IsDefault() ) {
globalWarningStream() << shader->getName() << " is not a shader, it's a texture.\n";
}
else{
DoShaderView( shader->getShaderFileName(), shader->getName(), modifiers.testFlag( Qt::KeyboardModifier::ControlModifier ) );
}
}
}
/*
============================================================================
DRAWING
============================================================================
*/
/*
============
TTimo: relying on the shaders list to display the textures
we must query all qtexture_t* to manage and display through the IShaders interface
this allows a plugin to completely override the texture system
============
*/
void TextureBrowser::draw(){
evaluateHeight();
const int fontHeight = TextureBrowser_fontHeight();
const int fontDescent = GlobalOpenGL().m_font->getPixelDescent();
const int originy = getOriginY();
gl().glClearColor( m_color_textureback[0],
m_color_textureback[1],
m_color_textureback[2],
0 );
gl().glViewport( 0, 0, m_width, m_height );
gl().glMatrixMode( GL_PROJECTION );
gl().glLoadIdentity();
gl().glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
gl().glDisable( GL_DEPTH_TEST );
gl().glDisable( GL_MULTISAMPLE );
if ( g_TextureBrowser_enableAlpha ) {
gl().glEnable( GL_BLEND );
gl().glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
}
else {
gl().glDisable( GL_BLEND );
}
gl().glOrtho( 0, m_width, originy - m_height, originy, -100, 100 );
gl().glEnable( GL_TEXTURE_2D );
gl().glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
TextureLayout layout;
for ( QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement() )
{
IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
if ( !Texture_IsShown( shader, *this ) ) {
continue;
}
const auto [ x, y ] = layout.nextPos( *this, shader->getTexture() );
qtexture_t *q = shader->getTexture();
if ( !q ) {
break;
}
const auto [nWidth, nHeight] = getTextureWH( q );
// Is this texture visible?
if ( ( y - nHeight - fontHeight < originy )
&& ( y > originy - m_height ) ) {
gl().glLineWidth( 1 );
gl().glDisable( GL_TEXTURE_2D );
const float xf = x;
const float yf = y - fontHeight;
float xfMax = xf + 1.5 + nWidth;
float xfMin = xf - 1.5;
float yfMax = yf + 1.5;
float yfMin = yf - nHeight - 1.5;
#define TEXBRO_RENDER_BORDER \
gl().glBegin( GL_LINE_LOOP ); \
gl().glVertex2f( xfMin, yfMax ); \
gl().glVertex2f( xfMin, yfMin ); \
gl().glVertex2f( xfMax, yfMin ); \
gl().glVertex2f( xfMax, yfMax ); \
gl().glEnd();
//selected texture
if ( shader_equal( m_shader.c_str(), shader->getName() ) ) {
gl().glLineWidth( 2 );
gl().glColor3f( 1, 0, 0 );
xfMax += .5;
xfMin -= .5;
yfMax += .5;
yfMin -= .5;
TEXBRO_RENDER_BORDER
}
// highlight in-use textures
else if ( !m_hideUnused && shader->IsInUse() ) {
gl().glColor3f( 0.5, 1, 0.5 );
TEXBRO_RENDER_BORDER
}
// shader white border:
else if ( !shader->IsDefault() ) {
gl().glColor3f( 1, 1, 1 );
TEXBRO_RENDER_BORDER
}
// shader stipple:
if ( !shader->IsDefault() ) {
gl().glEnable( GL_LINE_STIPPLE );
gl().glLineStipple( 1, 0xF000 );
gl().glColor3f( 0, 0, 0 );
TEXBRO_RENDER_BORDER
gl().glDisable( GL_LINE_STIPPLE );
}
// draw checkerboard for transparent textures
if ( g_TextureBrowser_enableAlpha )
{
gl().glBegin( GL_QUADS );
for ( int i = 0; i < nHeight; i += 8 )
for ( int j = 0; j < nWidth; j += 8 )
{
const unsigned char color = ( i + j ) / 8 % 2 ? 0x66 : 0x99;
gl().glColor3ub( color, color, color );
const int left = j;
const int right = std::min( j + 8, nWidth );
const int top = i;
const int bottom = std::min( i + 8, nHeight );
gl().glVertex2i( x + right, y - nHeight - fontHeight + top );
gl().glVertex2i( x + left, y - nHeight - fontHeight + top );
gl().glVertex2i( x + left, y - nHeight - fontHeight + bottom );
gl().glVertex2i( x + right, y - nHeight - fontHeight + bottom );
}
gl().glEnd();
}
// Draw the texture
gl().glEnable( GL_TEXTURE_2D );
gl().glBindTexture( GL_TEXTURE_2D, q->texture_number );
GlobalOpenGL_debugAssertNoErrors();
gl().glColor3f( 1, 1, 1 );
gl().glBegin( GL_QUADS );
gl().glTexCoord2i( 0, 0 );
gl().glVertex2i( x, y - fontHeight );
gl().glTexCoord2i( 1, 0 );
gl().glVertex2i( x + nWidth, y - fontHeight );
gl().glTexCoord2i( 1, 1 );
gl().glVertex2i( x + nWidth, y - fontHeight - nHeight );
gl().glTexCoord2i( 0, 1 );
gl().glVertex2i( x, y - fontHeight - nHeight );
gl().glEnd();
// draw the texture name
// gl().glDisable( GL_TEXTURE_2D );
// gl().glColor3f( 1, 1, 1 ); //already set
gl().glRasterPos2i( x, y - fontHeight - fontDescent + 3 );//+5
// don't draw the directory name
const char* name = shader->getName();
name += strlen( name );
while ( name != shader->getName() && *( name - 1 ) != '/' && *( name - 1 ) != '\\' )
name--;
GlobalOpenGL().drawString( name );
}
}
// reset the current texture
gl().glBindTexture( GL_TEXTURE_2D, 0 );
gl().glDisable( GL_BLEND );
//qglFinish();
}
void TextureBrowser_setScale( TextureBrowser& textureBrowser, std::size_t scale ){
textureBrowser.m_textureScale = scale;
textureBrowser.m_heightChanged = true;
textureBrowser.m_originInvalid = true;
g_activeShadersChangedCallbacks();
textureBrowser.queueDraw();
}
void TextureBrowser_setUniformSize( TextureBrowser& textureBrowser, std::size_t scale ){
textureBrowser.m_uniformTextureSize = scale;
textureBrowser.m_heightChanged = true;
textureBrowser.m_originInvalid = true;
g_activeShadersChangedCallbacks();
textureBrowser.queueDraw();
}
void TextureBrowser_setUniformMinSize( TextureBrowser& textureBrowser, std::size_t scale ){
textureBrowser.m_uniformTextureMinSize = scale;
textureBrowser.m_heightChanged = true;
textureBrowser.m_originInvalid = true;
g_activeShadersChangedCallbacks();
textureBrowser.queueDraw();
}
void TextureBrowser_ToggleHideUnused(){
TextureBrowser_SetHideUnused( g_TexBro, !g_TexBro.m_hideUnused );
}
void TextureGroups_constructTreeModel( TextureGroups groups, QStandardItemModel* model ){
auto root = model->invisibleRootItem();
TextureGroups::const_iterator i = groups.begin();
while ( i != groups.end() )
{
const char* dirName = ( *i ).c_str();
const char* firstUnderscore = strchr( dirName, '_' );
StringRange dirRoot( dirName, ( firstUnderscore == 0 ) ? dirName : firstUnderscore + 1 );
TextureGroups::const_iterator next = std::next( i );
if ( firstUnderscore != 0
&& next != groups.end()
&& string_equal_start( ( *next ).c_str(), dirRoot ) ) {
auto subroot = new QStandardItem( CopiedString( StringRange( dirName, firstUnderscore ) ).c_str() );
root->appendRow( subroot );
// keep going...
while ( i != groups.end() && string_equal_start( ( *i ).c_str(), dirRoot ) )
{
auto item = new QStandardItem( ( *i ).c_str() );
item->setData( ( *i ).c_str(), Qt::ItemDataRole::ToolTipRole );
subroot->appendRow( item );
++i;
}
}
else
{
auto item = new QStandardItem( dirName );
item->setData( dirName, Qt::ItemDataRole::ToolTipRole );
root->appendRow( item );
++i;
}
}
}
void TextureGroups_constructTreeModel_childless( TextureGroups groups, QStandardItemModel* model ){
auto root = model->invisibleRootItem();
TextureGroups::const_iterator i = groups.begin();
while ( i != groups.end() )
{
const char* dirName = ( *i ).c_str();
const char* pakName = strrchr( dirName, '/' );
const char* pakNameEnd = strrchr( dirName, '.' );
ASSERT_MESSAGE( pakName != 0 && pakNameEnd != 0 && pakNameEnd > pakName, "interesting wad path" );
{
auto item = new QStandardItem( CopiedString( StringRange( pakName + 1, pakNameEnd ) ).c_str() );
item->setData( dirName, Qt::ItemDataRole::ToolTipRole );
root->appendRow( item );
++i;
}
}
}
void TextureGroups_constructTreeView( TextureGroups& groups ){
if ( TextureBrowser::wads ) {
GlobalFileSystem().forEachArchive( TextureGroupsAddWadCaller( groups ) );
}
else
{
// scan texture dirs and pak files only if not restricting to shaderlist
if ( g_pGameDescription->mGameType != "doom3" && !g_TextureBrowser_shaderlistOnly ) {
GlobalFileSystem().forEachDirectory( "textures/", TextureGroupsAddDirectoryCaller( groups ) );
}
GlobalShaderSystem().foreachShaderName( TextureGroupsAddShaderCaller( groups ) );
}
}
void TextureBrowser_constructTreeStore(){
TextureGroups groups;
TextureGroups_constructTreeView( groups );
auto model = new QStandardItemModel( g_TexBro.m_treeView ); //. ? delete old or clear() & reuse
// store display name in column #0 and load path in data( Qt::ItemDataRole::ToolTipRole )
// tooltips are only wanted for TextureBrowser::wads, but well
if( !TextureBrowser::wads )
TextureGroups_constructTreeModel( groups, model );
else
TextureGroups_constructTreeModel_childless( groups, model );
g_TexBro.m_treeView->setModel( model );
}
void TreeView_onRowActivated( const QModelIndex& index ){
auto dirName = index.data( Qt::ItemDataRole::ToolTipRole ).toByteArray();
if( !dirName.isEmpty() ){ // empty = directory group root
g_TexBro.m_searchedTags = false;
if ( !TextureBrowser::wads ) {
dirName.append( '/' );
}
ScopeDisableScreenUpdates disableScreenUpdates( dirName, "Loading Textures" );
TextureBrowser_ShowDirectory( dirName.data() );
g_TexBro.queueDraw();
//deactivate, so SPACE and RETURN wont be broken for 2d
g_TexBro.m_treeView->clearFocus();
}
}
class TexBro_QTreeView : public QTreeView
{
protected:
bool event( QEvent *event ) override {
if( event->type() == QEvent::ShortcutOverride ){
event->accept();
return true;
}
return QTreeView::event( event );
}
};
void TextureBrowser_createTreeViewTree(){
g_TexBro.m_treeView = new TexBro_QTreeView;
g_TexBro.m_treeView->setHeaderHidden( true );
g_TexBro.m_treeView->setEditTriggers( QAbstractItemView::EditTrigger::NoEditTriggers );
g_TexBro.m_treeView->setUniformRowHeights( true ); // optimization
g_TexBro.m_treeView->setFocusPolicy( Qt::FocusPolicy::ClickFocus );
g_TexBro.m_treeView->setExpandsOnDoubleClick( false );
g_TexBro.m_treeView->header()->setStretchLastSection( false ); // non greedy column sizing; + QHeaderView::ResizeMode::ResizeToContents = no text elision 🤷‍♀️
g_TexBro.m_treeView->header()->setSectionResizeMode( QHeaderView::ResizeMode::ResizeToContents );
QObject::connect( g_TexBro.m_treeView, &QAbstractItemView::activated, TreeView_onRowActivated );
TextureBrowser_constructTreeStore();
}
static QMenu* TextureBrowser_constructViewMenu(){
QMenu *menu = new QMenu( "View" );
menu->setTearOffEnabled( g_Layout_enableDetachableMenus.m_value );
create_check_menu_item_with_mnemonic( menu, "Hide Unused", "ShowInUse" );
create_menu_item_with_mnemonic( menu, "Show All", "ShowAllTextures" );
menu->addSeparator();
// we always want to show shaders but don't want a "Show Shaders" menu for doom3 and .wad file games
if ( g_pGameDescription->mGameType == "doom3" || TextureBrowser::wads ) {
g_TexBro.m_showShaders = true;
}
else
{
create_check_menu_item_with_mnemonic( menu, "Show Shaders", "ToggleShowShaders" );
create_check_menu_item_with_mnemonic( menu, "Show Textures", "ToggleShowTextures" );
menu->addSeparator();
}
if ( g_pGameDescription->mGameType != "doom3" && !TextureBrowser::wads ) {
create_check_menu_item_with_mnemonic( menu, "ShaderList Only", "ToggleShowShaderlistOnly" );
}
if ( !TextureBrowser::wads ) {
create_check_menu_item_with_mnemonic( menu, "Hide Image Missing", "FilterNotex" );
menu->addSeparator();
}
create_check_menu_item_with_mnemonic( menu, "Fixed Size", "FixedSize" );
create_check_menu_item_with_mnemonic( menu, "Transparency", "EnableAlpha" );
menu->addSeparator();
create_check_menu_item_with_mnemonic( menu, "Tags GUI", "TagsToggleGui" );
if ( !TextureBrowser::wads ) {
menu->addSeparator();
g_TexBro.m_shader_info_item = create_menu_item_with_mnemonic( menu, "Shader Info", "ShaderInfo" );
g_TexBro.m_shader_info_item->setDisabled( true );
}
return menu;
}
void Popup_View_Menu( QMenu *menu ){
menu->popup( QCursor::pos() );
}
#include "xml/xmltextags.h"
XmlTagBuilder TagBuilder;
static QMenu* TextureBrowser_constructTagsMenu(){
auto menu = new QMenu( "Tags" );
menu->setTearOffEnabled( g_Layout_enableDetachableMenus.m_value );
create_menu_item_with_mnemonic( menu, "Add tag", "TagAdd" );
create_menu_item_with_mnemonic( menu, "Rename tag", "TagRename" );
create_menu_item_with_mnemonic( menu, "Delete tag", "TagDelete" );
menu->addSeparator();
create_menu_item_with_mnemonic( menu, "Copy tags from selected", "TagCopy" );
create_menu_item_with_mnemonic( menu, "Paste tags to selected", "TagPaste" );
menu->addSeparator();
create_menu_item_with_mnemonic( menu, "Search tag", "TagSearch" );
create_menu_item_with_mnemonic( menu, "Search Untagged", "TagSearchUntagged" );
return menu;
}
inline void TextureBrowser_tagsEnableGui( bool enable ){
if( enable )
g_TexBro.m_tabs->addTab( g_TexBro.m_tagsListWidget, "Tags" );
else
g_TexBro.m_tabs->removeTab( 1 );
}
class Tags_QListWidget : public QListWidget
{
using QListWidget::QListWidget;
protected:
bool event( QEvent *event ) override {
if( event->type() == QEvent::ShortcutOverride ){
event->accept();
return true;
}
return QListWidget::event( event );
}
void contextMenuEvent( QContextMenuEvent *event ) override {
g_TexBro.m_tagsMenu->popup( event->globalPos() );
}
};
void TextureBrowser_tagsSetCheckboxesForShader( const char *shader ){
std::vector<CopiedString> assigned_tags;
TagBuilder.GetShaderTags( shader, assigned_tags );
const auto contains = [&assigned_tags]( const char *tag )->bool {
for( auto it = assigned_tags.cbegin(); it != assigned_tags.cend(); ++it )
if( string_equal( tag, it->c_str() ) ){
assigned_tags.erase( it ); // assuming / hoping that tag names are unique, thus can run faster
return true;
}
return false;
};
for( int i = 0; i < g_TexBro.m_tagsListWidget->count(); ++i )
{
auto item = g_TexBro.m_tagsListWidget->item( i );
item->setCheckState( contains( item->data( Qt::ItemDataRole::DisplayRole ).toByteArray() )? Qt::CheckState::Checked : Qt::CheckState::Unchecked );
}
}
void TextureBrowser_tagAssignmentChanged( const QModelIndex &index ){
const bool assigned = Qt::CheckState::Checked == static_cast<Qt::CheckState>( index.data( Qt::ItemDataRole::CheckStateRole ).toInt() );
const auto tag = index.data( Qt::ItemDataRole::DisplayRole ).toByteArray();
if( assigned ){
if ( !TagBuilder.CheckShaderTag( g_TexBro.m_shader.c_str() ) ) {
// create a custom shader/texture entry
IShader* ishader = QERApp_Shader_ForName( g_TexBro.m_shader.c_str() );
if ( ishader->IsDefault() ) {
// it's a texture
TagBuilder.AddShaderNode( g_TexBro.m_shader.c_str(), TextureType::CUSTOM, NodeShaderType::TEXTURE );
}
else {
// it's a shader
TagBuilder.AddShaderNode( g_TexBro.m_shader.c_str(), TextureType::CUSTOM, NodeShaderType::SHADER );
}
ishader->DecRef();
}
TagBuilder.AddShaderTag( g_TexBro.m_shader.c_str(), tag, NodeTagType::TAG );
}
else{
TagBuilder.DeleteShaderTag( g_TexBro.m_shader.c_str(), tag );
}
}
class Tags_QItemDelegate : public QItemDelegate
{
using QItemDelegate::QItemDelegate;
protected:
/* track user edit of tag name */
void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const override {
const QByteArray propname = editor->metaObject()->userProperty().name();
const auto newName = propname.isEmpty()? QByteArray() : editor->property( propname ).toByteArray();
if ( newName.isEmpty() ){
qt_MessageBox( g_TexBro.m_parent, "New tag name is empty :0", ":o", EMessageBoxType::Error );
}
else{
const auto oldName = index.data( Qt::ItemDataRole::DisplayRole ).toByteArray();
if( oldName != newName // is changed
&& g_TexBro.m_all_tags.find( newName.constData() ) != g_TexBro.m_all_tags.cend() ){ // & found in existing names
qt_MessageBox( g_TexBro.m_parent, "New tag name is already taken :0", newName.constData(), EMessageBoxType::Error );
}
else{
TagBuilder.RenameShaderTag( oldName, newName.constData() );
g_TexBro.m_all_tags.erase( oldName.constData() );
g_TexBro.m_all_tags.insert( newName.constData() );
QItemDelegate::setModelData( editor, model, index ); // normal processing
}
}
}
bool editorEvent( QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index ) override {
/* let's do some infamous juggling to track user unduced CheckState change */
if( event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::KeyPress ){
if( const QVariant value = index.data( Qt::ItemDataRole::CheckStateRole ); value.isValid() ){
const bool ret = QItemDelegate::editorEvent( event, model, option, index );
if( ret && value != index.data( Qt::ItemDataRole::CheckStateRole ) ){
TextureBrowser_tagAssignmentChanged( index );
}
return ret;
}
}
return QItemDelegate::editorEvent( event, model, option, index );
}
};
void TextureBrowser_searchTags(){
const auto selected = g_TexBro.m_tagsListWidget->selectedItems();
if ( !selected.empty() ) {
auto buffer = StringStream( "/root/*/*[tag='" );
auto tags_searched = StringStream( "[TAGS] " );
for ( auto it = selected.begin(); it != selected.end(); ++it )
{
const auto tag = ( *it )->text().toLatin1();
buffer << tag.constData();
tags_searched << tag.constData();
if ( it + 1 != selected.end() ) {
buffer << "' and tag='";
tags_searched << ", ";
}
}
buffer << "']";
g_TexBro.m_found_shaders.clear(); // delete old list
TagBuilder.TagSearch( buffer, g_TexBro.m_found_shaders );
if ( !g_TexBro.m_found_shaders.empty() ) { // found something
globalOutputStream() << "Found " << g_TexBro.m_found_shaders.size() << " textures and shaders with " << tags_searched << '\n';
ScopeDisableScreenUpdates disableScreenUpdates( "Searching...", "Loading Textures" );
for ( const CopiedString& shader : g_TexBro.m_found_shaders )
{
TexturePath_loadTexture( shader.c_str() );
}
}
TextureBrowser_SetHideUnused( g_TexBro, false );
g_TexBro.m_searchedTags = true;
g_TextureBrowser_currentDirectory = tags_searched;
g_TexBro.heightChanged();
g_TexBro.m_originInvalid = true;
TextureBrowser_updateTitle();
//deactivate, so SPACE and RETURN wont be broken for 2d
g_TexBro.m_tagsListWidget->clearFocus();
}
}
void TextureBrowser_showUntagged(){
EMessageBoxReturn result = qt_MessageBox( g_TexBro.m_parent,
"WARNING! This function might need <b>a lot</b> of memory and time.<br>"
"It shows all textures & shaders indexed by ShaderPlug plugin, but having no tag.<br>"
"Are you sure you want to use it?",
"Show Untagged", EMessageBoxType::Warning, eIDYES | eIDNO );
if ( result == eIDYES ) {
g_TexBro.m_found_shaders.clear();
TagBuilder.GetUntagged( g_TexBro.m_found_shaders );
ScopeDisableScreenUpdates disableScreenUpdates( "Searching untagged textures...", "Loading Textures" );
for ( const CopiedString& shader : g_TexBro.m_found_shaders )
{
TexturePath_loadTexture( shader.c_str() );
}
TextureBrowser_SetHideUnused( g_TexBro, false );
g_TexBro.m_searchedTags = true;
g_TextureBrowser_currentDirectory = "Untagged";
g_TexBro.heightChanged();
g_TexBro.m_originInvalid = true;
TextureBrowser_updateTitle();
}
}
void TextureBrowser_checkTagFile(){
const auto rc_filename = StringStream( LocalRcPath_get(), SHADERTAG_FILE );
const auto default_filename = StringStream( g_pGameDescription->mGameToolsPath, SHADERTAG_FILE );
if ( file_exists( rc_filename ) && TagBuilder.OpenXmlDoc( rc_filename ) )
{
globalOutputStream() << "Loaded tag file " << rc_filename << ".\n";
}
else if ( file_exists( default_filename ) && TagBuilder.OpenXmlDoc( default_filename, rc_filename ) ) // load default tagfile
{
globalOutputStream() << "Loaded default tag file " << default_filename << ".\n";
}
else
{
// globalWarningStream() << "Unable to find default tag file " << default_filename << ". No tag support. Plugins -> ShaderPlug -> Create tag file: to start using texture tags\n";
const bool ok = TagBuilder.CreateXmlDocument( rc_filename );
ASSERT_MESSAGE( ok, "empty tag document was not created" );
globalOutputStream() << "Created empty tag file " << rc_filename << ". Plugins -> ShaderPlug -> Create tag file: to index all textures and shaders, if needed.\n";
}
}
void TextureBrowser_addTag(){
auto tag = StringStream<64>( "NewTag" );
int index = 0;
while( g_TexBro.m_all_tags.find( tag.c_str() ) != g_TexBro.m_all_tags.cend() )
tag( "NewTag", ++index );
auto item = new QListWidgetItem( tag.c_str() );
item->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren );
item->setCheckState( Qt::CheckState::Unchecked ); // is needed to see checkbox
g_TexBro.m_tagsListWidget->addItem( item );
g_TexBro.m_all_tags.insert( tag.c_str() );
g_TexBro.m_tagsListWidget->scrollToItem( item );
g_TexBro.m_tagsListWidget->editItem( item );
}
void TextureBrowser_renameTag(){
const auto selected = g_TexBro.m_tagsListWidget->selectedItems();
if ( !selected.empty() ) {
g_TexBro.m_tagsListWidget->editItem( selected.front() );
}
}
void TextureBrowser_deleteTag(){
const auto selected = g_TexBro.m_tagsListWidget->selectedItems();
if ( !selected.empty() ) {
if ( eIDYES == qt_MessageBox( g_TexBro.m_parent, "Are you sure you want to delete the selected tags?", "Delete Tag", EMessageBoxType::Question ) ) {
for( auto item : selected ){
auto tag = item->text().toLatin1();
delete item;
TagBuilder.DeleteTag( tag.constData() );
g_TexBro.m_all_tags.erase( tag.constData() );
}
}
}
}
void TextureBrowser_copyTag(){
g_TexBro.m_copied_tags.clear();
TagBuilder.GetShaderTags( g_TexBro.m_shader.c_str(), g_TexBro.m_copied_tags );
}
void TextureBrowser_pasteTag(){
const CopiedString shader = g_TexBro.m_shader;
if ( !TagBuilder.CheckShaderTag( shader.c_str() ) ) {
IShader* ishader = QERApp_Shader_ForName( shader.c_str() );
if ( ishader->IsDefault() ) {
// it's a texture
TagBuilder.AddShaderNode( shader.c_str(), TextureType::CUSTOM, NodeShaderType::TEXTURE );
}
else
{
// it's a shader
TagBuilder.AddShaderNode( shader.c_str(), TextureType::CUSTOM, NodeShaderType::SHADER );
}
ishader->DecRef();
for ( const CopiedString& tag : g_TexBro.m_copied_tags )
{
TagBuilder.AddShaderTag( shader.c_str(), tag.c_str(), NodeTagType::TAG );
}
}
else
{
for ( const CopiedString& tag : g_TexBro.m_copied_tags )
{
if ( !TagBuilder.CheckShaderTag( shader.c_str(), tag.c_str() ) ) {
// the tag doesn't exist - let's add it
TagBuilder.AddShaderTag( shader.c_str(), tag.c_str(), NodeTagType::TAG );
}
}
}
TextureBrowser_tagsSetCheckboxesForShader( shader.c_str() );
}
void TextureBrowser_SetNotex(){
g_notex = StringStream( GlobalRadiant().getAppPath(), "bitmaps/notex.png" );
g_shadernotex = StringStream( GlobalRadiant().getAppPath(), "bitmaps/shadernotex.png" );
}
class Filter_QLineEdit : public QLineEdit
{
protected:
void enterEvent( QEvent *event ) override {
setFocus();
}
void leaveEvent( QEvent *event ) override {
clearFocus();
}
bool event( QEvent *event ) override {
if( event->type() == QEvent::ShortcutOverride ){
QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event );
if( keyEvent->key() == Qt::Key_Escape ){
clear();
event->accept();
}
}
return QLineEdit::event( event );
}
};
void TextureBrowser_filterSetModeIcon( QAction *action ){
action->setIcon( QApplication::style()->standardIcon(
g_TextureBrowser_filter_searchFromStart
? QStyle::StandardPixmap::SP_CommandLink
: QStyle::StandardPixmap::SP_FileDialogContentsView ) );
}
#include "timer.h"
class TexWndGLWidget : public QOpenGLWidget
{
TextureBrowser& m_texBro;
qreal m_scale;
MousePresses m_mouse;
public:
TexWndGLWidget( TextureBrowser& textureBrowser ) : QOpenGLWidget(), m_texBro( textureBrowser )
{
}
~TexWndGLWidget() override {
glwidget_context_destroyed();
}
protected:
void initializeGL() override
{
glwidget_context_created( *this );
// show definitely after gl init, otherwise crash
TextureBrowser_ShowStartupShaders();
}
void resizeGL( int w, int h ) override
{
m_scale = devicePixelRatioF();
m_texBro.m_width = float_to_integer( w * m_scale );
m_texBro.m_height = float_to_integer( h * m_scale );
m_texBro.heightChanged();
m_texBro.m_originInvalid = true;
}
void paintGL() override
{
GlobalOpenGL_debugAssertNoErrors();
m_texBro.draw();
GlobalOpenGL_debugAssertNoErrors();
}
void mousePressEvent( QMouseEvent *event ) override {
setFocus();
const auto press = m_mouse.press( event );
if( press == MousePresses::Left2x || press == MousePresses::Right2x ){
mouseDoubleClick( press );
}
else if ( press == MousePresses::Right ) {
TextureBrowser_Tracking_MouseDown( m_texBro );
}
else if ( press == MousePresses::Left || press == MousePresses::Middle ) {
if ( !event->modifiers().testFlag( Qt::KeyboardModifier::ShiftModifier ) )
SelectTexture( m_texBro, event->x() * m_scale, event->y() * m_scale, press == MousePresses::Middle );
}
}
void mouseDoubleClick( MousePresses::Result press ){
/* loads directory, containing active shader + focuses on it */
if ( press == MousePresses::Left2x && !TextureBrowser::wads ) {
const StringRange range( strchr( m_texBro.m_shader.c_str(), '/' ) + 1, strrchr( m_texBro.m_shader.c_str(), '/' ) + 1 );
if( !range.empty() ){
const CopiedString dir = range;
ScopeDisableScreenUpdates disableScreenUpdates( dir.c_str(), "Loading Textures" );
TextureBrowser_ShowDirectory( dir.c_str() );
TextureBrowser_Focus( m_texBro, m_texBro.m_shader.c_str() );
m_texBro.queueDraw();
}
}
else if ( press == MousePresses::Right2x ) {
ScopeDisableScreenUpdates disableScreenUpdates( TextureBrowser_getCommonShadersDir(), "Loading Textures" );
TextureBrowser_ShowDirectory( TextureBrowser_getCommonShadersDir() );
m_texBro.queueDraw();
}
}
void mouseReleaseEvent( QMouseEvent *event ) override {
const auto release = m_mouse.release( event );
if ( release == MousePresses::Right ) {
TextureBrowser_Tracking_MouseUp( m_texBro );
}
else if ( release == MousePresses::Left && event->modifiers().testFlag( Qt::KeyboardModifier::ShiftModifier ) ) {
TextureBrowser_ViewShader( m_texBro, event->modifiers(), event->x() * m_scale, event->y() * m_scale );
}
}
void wheelEvent( QWheelEvent *event ) override {
setFocus();
if( !m_texBro.m_parent->isActiveWindow() ){
m_texBro.m_parent->activateWindow();
m_texBro.m_parent->raise();
}
const int originy = m_texBro.getOriginY() + std::copysign( m_texBro.m_mouseWheelScrollIncrement, event->angleDelta().y() );
m_texBro.setOriginY( originy );
}
};
QWidget* TextureBrowser_constructWindow( QWidget* toplevel ){
TextureBrowser_checkTagFile();
TextureBrowser_SetNotex();
GlobalShaderSystem().setActiveShadersChangedNotify( ReferenceCaller<TextureBrowser, TextureBrowser_activeShadersChanged>( g_TexBro ) );
g_TexBro.m_parent = toplevel;
QSplitter *splitter = new QSplitter;
QWidget *containerWidgetLeft = new QWidget; // Adding a QLayout to a QSplitter is not supported, use proxy widget
QWidget *containerWidgetRight = new QWidget; // Adding a QLayout to a QSplitter is not supported, use proxy widget
splitter->addWidget( containerWidgetLeft );
splitter->addWidget( containerWidgetRight );
QVBoxLayout *vbox = new QVBoxLayout( containerWidgetLeft );
QHBoxLayout *hbox = new QHBoxLayout( containerWidgetRight );
hbox->setContentsMargins( 0, 0, 0, 0 );
vbox->setContentsMargins( 0, 0, 0, 0 );
hbox->setSpacing( 0 );
vbox->setSpacing( 0 );
{ // menu bar
QToolBar *toolbar = new QToolBar;
vbox->addWidget( toolbar );
QMenu* menu_view = TextureBrowser_constructViewMenu();
//show detached menu over floating tex bro and main wnd...
menu_view->setParent( toolbar, menu_view->windowFlags() ); //don't reset windowFlags
//view menu button
toolbar_append_button( toolbar, "View", "texbro_view.png", PointerCaller<QMenu, Popup_View_Menu>( menu_view ) );
toolbar_append_button( toolbar, "Find / Replace...", "texbro_gtk-find-and-replace.png", "FindReplaceTextures" );
toolbar_append_button( toolbar, "Flush & Reload Shaders", "texbro_refresh.png", "RefreshShaders" );
}
{ // filter entry
QLineEdit *entry = g_TexBro.m_filter_entry = new Filter_QLineEdit;
vbox->addWidget( entry );
entry->setClearButtonEnabled( true );
entry->setFocusPolicy( Qt::FocusPolicy::ClickFocus );
QAction *action = g_TexBro.m_filter_action = entry->addAction( QApplication::style()->standardIcon( QStyle::StandardPixmap::SP_CommandLink ), QLineEdit::LeadingPosition );
TextureBrowser_filterSetModeIcon( action );
action->setToolTip( "toggle match mode ( start / any position )" );
QObject::connect( entry, &QLineEdit::textChanged, []( const QString& text ){
g_TexBro.m_filter_string = text.toLatin1().constData();
g_TexBro.heightChanged();
g_TexBro.m_originInvalid = true;
} );
QObject::connect( action, &QAction::triggered, GlobalToggles_find( "SearchFromStart" ).m_command.m_callback );
}
{ // Texture TreeView
TextureBrowser_createTreeViewTree();
}
{ // gl_widget
g_TexBro.m_gl_widget = new TexWndGLWidget( g_TexBro );
hbox->addWidget( g_TexBro.m_gl_widget );
}
{ // gl_widget scrollbar
auto scroll = g_TexBro.m_texture_scroll = new QScrollBar;
hbox->addWidget( scroll );
QObject::connect( scroll, &QAbstractSlider::valueChanged, []( int value ){
g_TexBro.m_scrollAdjustment.value_changed( value );
} );
scroll->setVisible( g_TexBro.m_showTextureScrollbar );
}
{ // tag stuff
g_TexBro.m_tagsListWidget = new Tags_QListWidget;
g_TexBro.m_tagsListWidget->setSortingEnabled( true );
g_TexBro.m_tagsListWidget->setSelectionMode( QAbstractItemView::SelectionMode::ExtendedSelection );
g_TexBro.m_tagsListWidget->setEditTriggers( QAbstractItemView::EditTrigger::SelectedClicked | QAbstractItemView::EditTrigger::EditKeyPressed );
g_TexBro.m_tagsListWidget->setUniformItemSizes( true ); // optimization
g_TexBro.m_tagsListWidget->setFocusPolicy( Qt::FocusPolicy::ClickFocus );
g_TexBro.m_tagsListWidget->setItemDelegate( new Tags_QItemDelegate( g_TexBro.m_tagsListWidget ) );
QObject::connect( g_TexBro.m_tagsListWidget, &QListWidget::activated, TextureBrowser_searchTags );
TagBuilder.GetAllTags( g_TexBro.m_all_tags );
for ( const CopiedString& tag : g_TexBro.m_all_tags ){
auto item = new QListWidgetItem( tag.c_str() );
item->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren );
item->setCheckState( Qt::CheckState::Unchecked ); // is needed to see checkbox
g_TexBro.m_tagsListWidget->addItem( item );
}
}
{ // tag context menu
g_TexBro.m_tagsMenu = TextureBrowser_constructTagsMenu();
//show detached menu over floating tex bro and main wnd...
g_TexBro.m_tagsMenu->setParent( g_TexBro.m_tagsListWidget, g_TexBro.m_tagsMenu->windowFlags() ); //don't reset windowFlags
}
{ // Texture/Tag notebook
g_TexBro.m_tabs = new QTabWidget;
g_TexBro.m_tabs->setFocusPolicy( Qt::FocusPolicy::ClickFocus );
g_TexBro.m_tabs->setDocumentMode( true );
g_TexBro.m_tabs->setTabBarAutoHide( true );
g_TexBro.m_tabs->addTab( g_TexBro.m_treeView, "Textures" );
static_cast<QObject*>( g_TexBro.m_tagsListWidget )->setParent( g_TexBro.m_tabs );
TextureBrowser_tagsEnableGui( g_TexBro.m_tags );
vbox->addWidget( g_TexBro.m_tabs );
QObject::connect( g_TexBro.m_tabs, &QTabWidget::currentChanged, []( int index ){
if( index == 1 )
TextureBrowser_tagsSetCheckboxesForShader( g_TexBro.m_shader.c_str() );
} );
}
splitter->setStretchFactor( 0, 0 ); // consistent treeview side sizing on resizes
splitter->setStretchFactor( 1, 1 );
g_guiSettings.addSplitter( splitter, "TextureBrowser/splitter", { 100, 800 } );
return splitter;
}
void TextureBrowser_destroyWindow(){
GlobalShaderSystem().setActiveShadersChangedNotify( Callback() );
}
const Vector3& TextureBrowser_getBackgroundColour(){
return g_TexBro.m_color_textureback;
}
void TextureBrowser_setBackgroundColour( const Vector3& colour ){
g_TexBro.m_color_textureback = colour;
g_TexBro.queueDraw();
}
void TextureBrowser_shaderInfo(){
const char* name = TextureBrowser_GetSelectedShader();
IShader* shader = QERApp_Shader_ForName( name );
DoShaderInfoDlg( name, shader->getShaderFileName(), "Shader Info" );
shader->DecRef();
}
void RefreshShaders(){
g_TextureBrowser_currentDirectory = "";
g_TexBro.m_searchedTags = false;
TextureBrowser_updateTitle();
ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Shaders" );
GlobalShaderSystem().refresh();
TextureBrowser_constructTreeStore(); /* texturebrowser tree update on vfs restart */
UpdateAllWindows();
}
void TextureBrowser_ToggleShowShaders(){
g_TexBro.m_showShaders ^= 1;
g_TexBro.m_showshaders_item.update();
g_TexBro.m_heightChanged = true;
g_TexBro.m_originInvalid = true;
g_activeShadersChangedCallbacks();
g_TexBro.queueDraw();
}
void TextureBrowser_ToggleShowTextures(){
g_TexBro.m_showTextures ^= 1;
g_TexBro.m_showtextures_item.update();
g_TexBro.m_heightChanged = true;
g_TexBro.m_originInvalid = true;
g_activeShadersChangedCallbacks();
g_TexBro.queueDraw();
}
void TextureBrowser_ToggleShowShaderListOnly(){
g_TextureBrowser_shaderlistOnly ^= 1;
g_TexBro.m_showshaderlistonly_item.update();
TextureBrowser_constructTreeStore();
}
void TextureBrowser_showAll(){
g_TextureBrowser_currentDirectory = "";
g_TexBro.m_searchedTags = false;
// TextureBrowser_SetHideUnused( g_TexBro, false );
TextureBrowser_ToggleHideUnused(); //toggle to show all used on the first hit and all on the second
TextureBrowser_updateTitle();
}
void TextureBrowser_FixedSize(){
g_TextureBrowser_fixedSize ^= 1;
g_TexBro.m_fixedsize_item.update();
TextureBrowser_activeShadersChanged( g_TexBro );
}
void TextureBrowser_FilterNotex(){
g_TextureBrowser_filterNotex ^= 1;
g_TexBro.m_filternotex_item.update();
TextureBrowser_activeShadersChanged( g_TexBro );
}
void TextureBrowser_EnableAlpha(){
g_TextureBrowser_enableAlpha ^= 1;
g_TexBro.m_enablealpha_item.update();
TextureBrowser_activeShadersChanged( g_TexBro );
}
void TextureBrowser_tagsToggleGui(){
g_TexBro.m_tags ^= 1;
g_TexBro.m_tags_item.update();
TextureBrowser_tagsEnableGui( g_TexBro.m_tags );
}
void TextureBrowser_filter_searchFromStart(){
g_TextureBrowser_filter_searchFromStart ^= 1;
g_TexBro.m_filter_searchFromStart_item.update();
TextureBrowser_activeShadersChanged( g_TexBro );
TextureBrowser_filterSetModeIcon( g_TexBro.m_filter_action );
}
void TextureBrowser_exportTitle( const StringImportCallback& importer ){
const auto buffer = StringStream<64>( "Textures: ", !g_TextureBrowser_currentDirectory.empty()
? g_TextureBrowser_currentDirectory.c_str()
: "all" );
importer( buffer );
}
void TextureScaleImport( TextureBrowser& textureBrowser, int value ){
switch ( value )
{
case 0:
TextureBrowser_setScale( textureBrowser, 10 );
break;
case 1:
TextureBrowser_setScale( textureBrowser, 25 );
break;
case 2:
TextureBrowser_setScale( textureBrowser, 50 );
break;
case 3:
TextureBrowser_setScale( textureBrowser, 100 );
break;
case 4:
TextureBrowser_setScale( textureBrowser, 200 );
break;
}
}
typedef ReferenceCaller1<TextureBrowser, int, TextureScaleImport> TextureScaleImportCaller;
void TextureScaleExport( TextureBrowser& textureBrowser, const IntImportCallback& importer ){
switch ( textureBrowser.m_textureScale )
{
case 10:
importer( 0 );
break;
case 25:
importer( 1 );
break;
case 50:
importer( 2 );
break;
case 100:
importer( 3 );
break;
case 200:
importer( 4 );
break;
}
}
typedef ReferenceCaller1<TextureBrowser, const IntImportCallback&, TextureScaleExport> TextureScaleExportCaller;
void UniformTextureSizeImport( TextureBrowser& textureBrowser, int value ){
if ( value >= 16 )
TextureBrowser_setUniformSize( textureBrowser, value );
}
typedef ReferenceCaller1<TextureBrowser, int, UniformTextureSizeImport> UniformTextureSizeImportCaller;
void UniformTextureMinSizeImport( TextureBrowser& textureBrowser, int value ){
if ( value >= 16 )
TextureBrowser_setUniformMinSize( textureBrowser, value );
}
typedef ReferenceCaller1<TextureBrowser, int, UniformTextureMinSizeImport> UniformTextureMinSizeImportCaller;
void TextureBrowser_constructPreferences( PreferencesPage& page ){
page.appendCheckBox(
"", "Texture scrollbar",
TextureBrowserImportShowScrollbarCaller( g_TexBro ),
BoolExportCaller( g_TexBro.m_showTextureScrollbar )
);
{
const char* texture_scale[] = { "10%", "25%", "50%", "100%", "200%" };
page.appendCombo(
"Texture Thumbnail Scale",
StringArrayRange( texture_scale ),
IntImportCallback( TextureScaleImportCaller( g_TexBro ) ),
IntExportCallback( TextureScaleExportCaller( g_TexBro ) )
);
}
page.appendSpinner( "Thumbnails Max Size", g_TexBro.m_uniformTextureSize, 16, 8192 );
page.appendSpinner( "Thumbnails Min Size", g_TexBro.m_uniformTextureMinSize, 16, 8192 );
page.appendSpinner( "Mousewheel Increment", g_TexBro.m_mouseWheelScrollIncrement, 0, 8192 );
{
const char* startup_shaders[] = { "None", TextureBrowser_getCommonShadersName() };
page.appendCombo( "Load Shaders at Startup", reinterpret_cast<int&>( g_TexBro.m_startupShaders ), StringArrayRange( startup_shaders ) );
}
{
const auto str = StringStream<64>( "Hide nonShaders in ", TextureBrowser_getCommonShadersDir(), " folder" );
page.appendCheckBox(
"", str,
g_TexBro.m_hideNonShadersInCommon
);
}
}
void TextureBrowser_constructPage( PreferenceGroup& group ){
PreferencesPage page( group.createPage( "Texture Browser", "Texture Browser Preferences" ) );
TextureBrowser_constructPreferences( page );
}
void TextureBrowser_registerPreferencesPage(){
PreferencesDialog_addSettingsPage( FreeCaller1<PreferenceGroup&, TextureBrowser_constructPage>() );
}
#include "preferencesystem.h"
#include "stringio.h"
typedef ReferenceCaller1<TextureBrowser, std::size_t, TextureBrowser_setScale> TextureBrowserSetScaleCaller;
void TextureClipboard_textureSelected( const char* shader );
void TextureBrowser_Construct(){
GlobalCommands_insert( "ShaderInfo", FreeCaller<TextureBrowser_shaderInfo>() );
GlobalCommands_insert( "TagSearchUntagged", FreeCaller<TextureBrowser_showUntagged>() );
GlobalCommands_insert( "TagSearch", FreeCaller<TextureBrowser_searchTags>() );
GlobalCommands_insert( "TagAdd", FreeCaller<TextureBrowser_addTag>() );
GlobalCommands_insert( "TagRename", FreeCaller<TextureBrowser_renameTag>() );
GlobalCommands_insert( "TagDelete", FreeCaller<TextureBrowser_deleteTag>() );
GlobalCommands_insert( "TagCopy", FreeCaller<TextureBrowser_copyTag>() );
GlobalCommands_insert( "TagPaste", FreeCaller<TextureBrowser_pasteTag>() );
GlobalCommands_insert( "RefreshShaders", FreeCaller<RefreshShaders>() );
GlobalToggles_insert( "ShowInUse", FreeCaller<TextureBrowser_ToggleHideUnused>(), ToggleItem::AddCallbackCaller( g_TexBro.m_hideunused_item ), QKeySequence( "U" ) );
GlobalCommands_insert( "ShowAllTextures", FreeCaller<TextureBrowser_showAll>(), QKeySequence( "Ctrl+A" ) );
GlobalCommands_insert( "ToggleTextures", FreeCaller<TextureBrowser_toggleShow>(), QKeySequence( "T" ) );
GlobalToggles_insert( "ToggleShowShaders", FreeCaller<TextureBrowser_ToggleShowShaders>(), ToggleItem::AddCallbackCaller( g_TexBro.m_showshaders_item ) );
GlobalToggles_insert( "ToggleShowTextures", FreeCaller<TextureBrowser_ToggleShowTextures>(), ToggleItem::AddCallbackCaller( g_TexBro.m_showtextures_item ) );
GlobalToggles_insert( "ToggleShowShaderlistOnly", FreeCaller<TextureBrowser_ToggleShowShaderListOnly>(), ToggleItem::AddCallbackCaller( g_TexBro.m_showshaderlistonly_item ) );
GlobalToggles_insert( "FixedSize", FreeCaller<TextureBrowser_FixedSize>(), ToggleItem::AddCallbackCaller( g_TexBro.m_fixedsize_item ) );
GlobalToggles_insert( "FilterNotex", FreeCaller<TextureBrowser_FilterNotex>(), ToggleItem::AddCallbackCaller( g_TexBro.m_filternotex_item ) );
GlobalToggles_insert( "EnableAlpha", FreeCaller<TextureBrowser_EnableAlpha>(), ToggleItem::AddCallbackCaller( g_TexBro.m_enablealpha_item ) );
GlobalToggles_insert( "TagsToggleGui", FreeCaller<TextureBrowser_tagsToggleGui>(), ToggleItem::AddCallbackCaller( g_TexBro.m_tags_item ) );
GlobalToggles_insert( "SearchFromStart", FreeCaller<TextureBrowser_filter_searchFromStart>(), ToggleItem::AddCallbackCaller( g_TexBro.m_filter_searchFromStart_item ) );
GlobalPreferenceSystem().registerPreference( "TextureScale",
makeSizeStringImportCallback( TextureBrowserSetScaleCaller( g_TexBro ) ),
SizeExportStringCaller( g_TexBro.m_textureScale )
);
GlobalPreferenceSystem().registerPreference( "UniformTextureSize",
makeIntStringImportCallback(UniformTextureSizeImportCaller(g_TexBro)),
IntExportStringCaller(g_TexBro.m_uniformTextureSize) );
GlobalPreferenceSystem().registerPreference( "UniformTextureMinSize",
makeIntStringImportCallback(UniformTextureMinSizeImportCaller(g_TexBro)),
IntExportStringCaller(g_TexBro.m_uniformTextureMinSize) );
GlobalPreferenceSystem().registerPreference( "TextureScrollbar",
makeBoolStringImportCallback( TextureBrowserImportShowScrollbarCaller( g_TexBro ) ),
BoolExportStringCaller( g_TexBro.m_showTextureScrollbar )
);
GlobalPreferenceSystem().registerPreference( "ShowShaders", BoolImportStringCaller( g_TexBro.m_showShaders ), BoolExportStringCaller( g_TexBro.m_showShaders ) );
GlobalPreferenceSystem().registerPreference( "ShowTextures", BoolImportStringCaller( g_TexBro.m_showTextures ), BoolExportStringCaller( g_TexBro.m_showTextures ) );
GlobalPreferenceSystem().registerPreference( "ShowShaderlistOnly", BoolImportStringCaller( g_TextureBrowser_shaderlistOnly ), BoolExportStringCaller( g_TextureBrowser_shaderlistOnly ) );
GlobalPreferenceSystem().registerPreference( "FixedSize", BoolImportStringCaller( g_TextureBrowser_fixedSize ), BoolExportStringCaller( g_TextureBrowser_fixedSize ) );
GlobalPreferenceSystem().registerPreference( "FilterNotex", BoolImportStringCaller( g_TextureBrowser_filterNotex ), BoolExportStringCaller( g_TextureBrowser_filterNotex ) );
GlobalPreferenceSystem().registerPreference( "EnableAlpha", BoolImportStringCaller( g_TextureBrowser_enableAlpha ), BoolExportStringCaller( g_TextureBrowser_enableAlpha ) );
GlobalPreferenceSystem().registerPreference( "TagsShowGui", BoolImportStringCaller( g_TexBro.m_tags ), BoolExportStringCaller( g_TexBro.m_tags ) );
GlobalPreferenceSystem().registerPreference( "SearchFromStart", BoolImportStringCaller( g_TextureBrowser_filter_searchFromStart ), BoolExportStringCaller( g_TextureBrowser_filter_searchFromStart ) );
GlobalPreferenceSystem().registerPreference( "LoadShaders", IntImportStringCaller( reinterpret_cast<int&>( g_TexBro.m_startupShaders ) ), IntExportStringCaller( reinterpret_cast<int&>( g_TexBro.m_startupShaders ) ) );
GlobalPreferenceSystem().registerPreference( "WheelMouseInc", IntImportStringCaller( g_TexBro.m_mouseWheelScrollIncrement ), IntExportStringCaller( g_TexBro.m_mouseWheelScrollIncrement ) );
GlobalPreferenceSystem().registerPreference( "SI_Colors0", Vector3ImportStringCaller( g_TexBro.m_color_textureback ), Vector3ExportStringCaller( g_TexBro.m_color_textureback ) );
GlobalPreferenceSystem().registerPreference( "HideNonShadersInCommon", BoolImportStringCaller( g_TexBro.m_hideNonShadersInCommon ), BoolExportStringCaller( g_TexBro.m_hideNonShadersInCommon ) );
g_TexBro.m_shader = texdef_name_default();
TextureBrowser::wads = !string_empty( g_pGameDescription->getKeyValue( "show_wads" ) );
Textures_setModeChangedNotify( ConstMemberCaller<TextureBrowser, &TextureBrowser::queueDraw>( g_TexBro ) );
TextureBrowser_registerPreferencesPage();
GlobalShaderSystem().attach( g_ShadersObserver );
TextureBrowser_textureSelected = TextureClipboard_textureSelected;
}
void TextureBrowser_Destroy(){
TagBuilder.SaveXmlDoc();
GlobalShaderSystem().detach( g_ShadersObserver );
Textures_setModeChangedNotify( Callback() );
}