+#include "texwindow.h"
+
+#include <gtk/gtk.h>
+
+#include "debugging/debugging.h"
+#include "warnings.h"
+
+#include "ifilesystem.h"
+#include "iundo.h"
+#include "igl.h"
+#include "iarchive.h"
+#include "moduleobserver.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <uilib/uilib.h>
+
+#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 "cmdlib.h"
+#include "texmanip.h"
+#include "textures.h"
+#include "convert.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 "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 "shaders.h"
+#include "commands.h"
+
+#define NOTEX_BASENAME "notex"
+#define SHADERNOTEX_BASENAME "shadernotex"
+
+bool TextureBrowser_showWads()
+{
+ return !string_empty(g_pGameDescription->getKeyValue("show_wads"));
+}
+
+void TextureBrowser_queueDraw(TextureBrowser &textureBrowser);
+
+bool string_equal_start(const char *string, StringRange start)
+{
+ return string_equal_n(string, start.first, start.last - start.first);
+}
+
+typedef std::set<CopiedString> TextureGroups;
+
+void TextureGroups_addWad(TextureGroups &groups, const char *archive)
+{
+ if (extension_equal(path_get_extension(archive), "wad")) {
+#if 1
+ groups.insert(archive);
+#else
+ CopiedString archiveBaseName( path_get_filename_start( archive ), path_get_filename_base_end( archive ) );
+ groups.insert( archiveBaseName );
+#endif
+ }
+}
+
+typedef ReferenceCaller<TextureGroups, void(const char *), TextureGroups_addWad> TextureGroupsAddWadCaller;
+
+namespace {
+ bool g_TextureBrowser_shaderlistOnly = false;
+ bool g_TextureBrowser_fixedSize = true;
+ bool g_TextureBrowser_filterMissing = false;
+ bool g_TextureBrowser_filterFallback = true;
+ bool g_TextureBrowser_enableAlpha = true;
+}
+
+CopiedString g_notex;
+CopiedString g_shadernotex;
+
+bool isMissing(const char *name);
+
+bool isNotex(const char *name);
+
+bool isMissing(const char *name)
+{
+ if (string_equal(g_notex.c_str(), name)) {
+ return true;
+ }
+ if (string_equal(g_shadernotex.c_str(), name)) {
+ return true;
+ }
+ return false;
+}
+
+bool isNotex(const char *name)
+{
+ if (string_equal_suffix(name, "/" NOTEX_BASENAME)) {
+ return true;
+ }
+ if (string_equal_suffix(name, "/" SHADERNOTEX_BASENAME)) {
+ return true;
+ }
+ return false;
+}
+
+void TextureGroups_addShader(TextureGroups &groups, const char *shaderName)
+{
+ const char *texture = path_make_relative(shaderName, "textures/");
+
+ // hide notex / shadernotex images
+ if (g_TextureBrowser_filterFallback) {
+ if (isNotex(shaderName)) {
+ return;
+ }
+ if (isNotex(texture)) {
+ return;
+ }
+ }
+
+ if (texture != shaderName) {
+ const char *last = path_remove_directory(texture);
+ if (!string_empty(last)) {
+ groups.insert(CopiedString(StringRange(texture, --last)));
+ }
+ }
+}
+
+typedef ReferenceCaller<TextureGroups, void(const char *), TextureGroups_addShader> TextureGroupsAddShaderCaller;
+
+void TextureGroups_addDirectory(TextureGroups &groups, const char *directory)
+{
+ groups.insert(directory);
+}
+
+typedef ReferenceCaller<TextureGroups, void(const char *), TextureGroups_addDirectory> TextureGroupsAddDirectoryCaller;
+
+class DeferredAdjustment {
+ gdouble m_value;
+ guint m_handler;
+
+ typedef void ( *ValueChangedFunction )(void *data, gdouble value);
+
+ ValueChangedFunction m_function;
+ void *m_data;
+
+ static gboolean deferred_value_changed(gpointer data)
+ {
+ reinterpret_cast<DeferredAdjustment *>( data )->m_function(
+ reinterpret_cast<DeferredAdjustment *>( data )->m_data,
+ reinterpret_cast<DeferredAdjustment *>( data )->m_value
+ );
+ reinterpret_cast<DeferredAdjustment *>( data )->m_handler = 0;
+ reinterpret_cast<DeferredAdjustment *>( data )->m_value = 0;
+ return FALSE;
+ }
+
+public:
+ DeferredAdjustment(ValueChangedFunction function, void *data) : m_value(0), m_handler(0), m_function(function),
+ m_data(data)
+ {
+ }
+
+ void flush()
+ {
+ if (m_handler != 0) {
+ g_source_remove(m_handler);
+ deferred_value_changed(this);
+ }
+ }
+
+ void value_changed(gdouble value)
+ {
+ m_value = value;
+ if (m_handler == 0) {
+ m_handler = g_idle_add(deferred_value_changed, this);
+ }
+ }
+
+ static void adjustment_value_changed(ui::Adjustment adjustment, DeferredAdjustment *self)
+ {
+ self->value_changed(gtk_adjustment_get_value(adjustment));
+ }
+};
+
+
+class TextureBrowser;
+
+typedef ReferenceCaller<TextureBrowser, void(), TextureBrowser_queueDraw> TextureBrowserQueueDrawCaller;
+
+void TextureBrowser_scrollChanged(void *data, gdouble value);
+
+
+enum StartupShaders {
+ STARTUPSHADERS_NONE = 0,
+ STARTUPSHADERS_COMMON,
+};
+
+void TextureBrowser_hideUnusedExport(const Callback<void(bool)> &importer);
+
+typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_hideUnusedExport> TextureBrowserHideUnusedExport;
+
+void TextureBrowser_showShadersExport(const Callback<void(bool)> &importer);
+
+typedef FreeCaller<void(
+ const Callback<void(bool)> &), TextureBrowser_showShadersExport> TextureBrowserShowShadersExport;
+
+void TextureBrowser_showShaderlistOnly(const Callback<void(bool)> &importer);
+
+typedef FreeCaller<void(
+ const Callback<void(bool)> &), TextureBrowser_showShaderlistOnly> TextureBrowserShowShaderlistOnlyExport;
+
+void TextureBrowser_fixedSize(const Callback<void(bool)> &importer);
+
+typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_fixedSize> TextureBrowserFixedSizeExport;
+
+void TextureBrowser_filterMissing(const Callback<void(bool)> &importer);
+
+typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_filterMissing> TextureBrowserFilterMissingExport;
+
+void TextureBrowser_filterFallback(const Callback<void(bool)> &importer);
+
+typedef FreeCaller<void(
+ const Callback<void(bool)> &), TextureBrowser_filterFallback> TextureBrowserFilterFallbackExport;
+
+void TextureBrowser_enableAlpha(const Callback<void(bool)> &importer);
+
+typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_enableAlpha> TextureBrowserEnableAlphaExport;
+
+class TextureBrowser {
+public:
+ int width, height;
+ int originy;
+ int m_nTotalHeight;
+
+ CopiedString shader;
+
+ ui::Window m_parent{ui::null};
+ ui::GLArea m_gl_widget{ui::null};
+ ui::Widget m_texture_scroll{ui::null};
+ ui::TreeView m_treeViewTree{ui::New};
+ ui::TreeView m_treeViewTags{ui::null};
+ ui::Frame m_tag_frame{ui::null};
+ ui::ListStore m_assigned_store{ui::null};
+ ui::ListStore m_available_store{ui::null};
+ ui::TreeView m_assigned_tree{ui::null};
+ ui::TreeView m_available_tree{ui::null};
+ ui::Widget m_scr_win_tree{ui::null};
+ ui::Widget m_scr_win_tags{ui::null};
+ ui::Widget m_tag_notebook{ui::null};
+ ui::Button m_search_button{ui::null};
+ ui::Widget m_shader_info_item{ui::null};
+
+ std::set<CopiedString> m_all_tags;
+ ui::ListStore m_all_tags_list{ui::null};
+ std::vector<CopiedString> m_copied_tags;
+ std::set<CopiedString> m_found_shaders;
+
+ ToggleItem m_hideunused_item;
+ ToggleItem m_hidenotex_item;
+ ToggleItem m_showshaders_item;
+ ToggleItem m_showshaderlistonly_item;
+ ToggleItem m_fixedsize_item;
+ ToggleItem m_filternotex_item;
+ ToggleItem m_enablealpha_item;
+
+ guint m_sizeHandler;
+ guint m_exposeHandler;
+
+ bool m_heightChanged;
+ bool m_originInvalid;
+
+ DeferredAdjustment m_scrollAdjustment;
+ FreezePointer m_freezePointer;
+
+ Vector3 color_textureback;
+// the increment step we use against the wheel mouse
+ std::size_t m_mouseWheelScrollIncrement;
+ std::size_t m_textureScale;
+// make the texture increments match the grid changes
+ bool m_showShaders;
+ 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_rmbSelected;
+ bool m_searchedTags;
+ bool m_tags;
+// The uniform size (in pixels) that textures are resized to when m_resizeTextures is true.
+ int m_uniformTextureSize;
+
+// Return the display width of a texture in the texture browser
+ int getTextureWidth(qtexture_t *tex)
+ {
+ int width;
+ if (!g_TextureBrowser_fixedSize) {
+ // Don't use uniform size
+ width = (int) (tex->width * ((float) m_textureScale / 100));
+ } else if
+ (tex->width >= tex->height) {
+ // Texture is square, or wider than it is tall
+ width = m_uniformTextureSize;
+ } else {
+ // Otherwise, preserve the texture's aspect ratio
+ width = (int) (m_uniformTextureSize * ((float) tex->width / tex->height));
+ }
+ return width;
+ }
+
+// Return the display height of a texture in the texture browser
+ int getTextureHeight(qtexture_t *tex)
+ {
+ int height;
+ if (!g_TextureBrowser_fixedSize) {
+ // Don't use uniform size
+ height = (int) (tex->height * ((float) m_textureScale / 100));
+ } else if (tex->height >= tex->width) {
+ // Texture is square, or taller than it is wide
+ height = m_uniformTextureSize;
+ } else {
+ // Otherwise, preserve the texture's aspect ratio
+ height = (int) (m_uniformTextureSize * ((float) tex->height / tex->width));
+ }
+ return height;
+ }
+
+ TextureBrowser() :
+ m_texture_scroll(ui::null),
+ m_hideunused_item(TextureBrowserHideUnusedExport()),
+ m_hidenotex_item(TextureBrowserFilterFallbackExport()),
+ m_showshaders_item(TextureBrowserShowShadersExport()),
+ m_showshaderlistonly_item(TextureBrowserShowShaderlistOnlyExport()),
+ m_fixedsize_item(TextureBrowserFixedSizeExport()),
+ m_filternotex_item(TextureBrowserFilterMissingExport()),
+ m_enablealpha_item(TextureBrowserEnableAlphaExport()),
+ m_heightChanged(true),
+ m_originInvalid(true),
+ m_scrollAdjustment(TextureBrowser_scrollChanged, this),
+ color_textureback(0.25f, 0.25f, 0.25f),
+ m_mouseWheelScrollIncrement(64),
+ m_textureScale(50),
+ m_showShaders(true),
+ m_showTextureScrollbar(true),
+ m_startupShaders(STARTUPSHADERS_NONE),
+ m_hideUnused(false),
+ m_rmbSelected(false),
+ m_searchedTags(false),
+ m_tags(false),
+ m_uniformTextureSize(96)
+ {
+ }
+};
+
+void ( *TextureBrowser_textureSelected )(const char *shader);
+
+
+void TextureBrowser_updateScroll(TextureBrowser &textureBrowser);
+
+
+const char *TextureBrowser_getComonShadersName()
+{
+ const char *value = g_pGameDescription->getKeyValue("common_shaders_name");
+ if (!string_empty(value)) {
+ return value;
+ }
+ return "Common";
+}
+
+const char *TextureBrowser_getComonShadersDir()
+{
+ const char *value = g_pGameDescription->getKeyValue("common_shaders_dir");
+ if (!string_empty(value)) {
+ return value;
+ }
+ return "common/";
+}
+
+inline int TextureBrowser_fontHeight(TextureBrowser &textureBrowser)
+{
+ return GlobalOpenGL().m_font->getPixelHeight();
+}
+
+const char *TextureBrowser_GetSelectedShader(TextureBrowser &textureBrowser)
+{
+ return textureBrowser.shader.c_str();
+}
+
+void TextureBrowser_SetStatus(TextureBrowser &textureBrowser, const char *name)
+{
+ IShader *shader = QERApp_Shader_ForName(name);
+ qtexture_t *q = shader->getTexture();
+ StringOutputStream strTex(256);
+ strTex << name << " W: " << Unsigned(q->width) << " H: " << Unsigned(q->height);
+ shader->DecRef();
+ g_pParentWnd->SetStatusText(g_pParentWnd->m_texture_status, strTex.c_str());
+}
+
+void TextureBrowser_Focus(TextureBrowser &textureBrowser, const char *name);
+
+void TextureBrowser_SetSelectedShader(TextureBrowser &textureBrowser, const char *shader)
+{
+ textureBrowser.shader = shader;
+ TextureBrowser_SetStatus(textureBrowser, shader);
+ TextureBrowser_Focus(textureBrowser, shader);
+
+ if (FindTextureDialog_isOpen()) {
+ FindTextureDialog_selectTexture(shader);
+ }
+
+ // disable the menu item "shader info" if no shader was selected
+ IShader *ishader = QERApp_Shader_ForName(shader);
+ CopiedString filename = ishader->getShaderFileName();
+
+ if (filename.empty()) {
+ if (textureBrowser.m_shader_info_item != NULL) {
+ gtk_widget_set_sensitive(textureBrowser.m_shader_info_item, FALSE);
+ }
+ } else {
+ gtk_widget_set_sensitive(textureBrowser.m_shader_info_item, TRUE);
+ }
+
+ ishader->DecRef();
+}
+
+
+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
+ ============================================================================
+ */
+
+class TextureLayout {
+public:
+// texture layout functions
+// TTimo: now based on shaders
+ int current_x, current_y, current_row;
+};
+
+void Texture_StartPos(TextureLayout &layout)
+{
+ layout.current_x = 8;
+ layout.current_y = -8;
+ layout.current_row = 0;
+}
+
+void Texture_NextPos(TextureBrowser &textureBrowser, TextureLayout &layout, qtexture_t *current_texture, int *x, int *y)
+{
+ qtexture_t *q = current_texture;
+
+ int nWidth = textureBrowser.getTextureWidth(q);
+ int nHeight = textureBrowser.getTextureHeight(q);
+ 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
+ layout.current_x = 8;
+ layout.current_y -= layout.current_row + TextureBrowser_fontHeight(textureBrowser) + 4;
+ layout.current_row = 0;
+ }
+
+ *x = layout.current_x;
+ *y = layout.current_y;
+
+ // Is our texture larger than the row? If so, grow the
+ // row height to match it
+
+ if (layout.current_row < nHeight) {
+ layout.current_row = nHeight;
+ }
+
+ // never go less than 96, or the names get all crunched up
+ layout.current_x += nWidth < 96 ? 96 : nWidth;
+ layout.current_x += 8;
+}
+
+bool TextureSearch_IsShown(const char *name)
+{
+ std::set<CopiedString>::iterator iter;
+
+ iter = GlobalTextureBrowser().m_found_shaders.find(name);
+
+ if (iter == GlobalTextureBrowser().m_found_shaders.end()) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+// if texture_showinuse jump over non in-use textures
+bool Texture_IsShown(IShader *shader, bool show_shaders, bool hideUnused)
+{
+ // filter missing shaders
+ // ugly: filter on built-in fallback name after substitution
+ if (g_TextureBrowser_filterMissing) {
+ if (isMissing(shader->getTexture()->name)) {
+ return false;
+ }
+ }
+ // filter the fallback (notex/shadernotex) for missing shaders or editor image
+ if (g_TextureBrowser_filterFallback) {
+ if (isNotex(shader->getName())) {
+ return false;
+ }
+ if (isNotex(shader->getTexture()->name)) {
+ return false;
+ }
+ }
+
+ if (g_TextureBrowser_currentDirectory == "Untagged") {
+ std::set<CopiedString>::iterator iter;
+
+ iter = GlobalTextureBrowser().m_found_shaders.find(shader->getName());
+
+ if (iter == GlobalTextureBrowser().m_found_shaders.end()) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ if (!shader_equal_prefix(shader->getName(), "textures/")) {
+ return false;
+ }
+
+ if (!show_shaders && !shader->IsDefault()) {
+ return false;
+ }
+
+ if (hideUnused && !shader->IsInUse()) {
+ return false;
+ }
+
+ if (GlobalTextureBrowser().m_searchedTags) {
+ if (!TextureSearch_IsShown(shader->getName())) {
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ if (!shader_equal_prefix(shader_get_textureName(shader->getName()),
+ g_TextureBrowser_currentDirectory.c_str())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void TextureBrowser_heightChanged(TextureBrowser &textureBrowser)
+{
+ textureBrowser.m_heightChanged = true;
+
+ TextureBrowser_updateScroll(textureBrowser);
+ TextureBrowser_queueDraw(textureBrowser);
+}
+
+void TextureBrowser_evaluateHeight(TextureBrowser &textureBrowser)
+{
+ if (textureBrowser.m_heightChanged) {
+ textureBrowser.m_heightChanged = false;
+
+ textureBrowser.m_nTotalHeight = 0;
+
+ TextureLayout layout;
+ Texture_StartPos(layout);
+ for (QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement()) {
+ IShader *shader = QERApp_ActiveShaders_IteratorCurrent();
+
+ if (!Texture_IsShown(shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused)) {
+ continue;
+ }
+
+ int x, y;
+ Texture_NextPos(textureBrowser, layout, shader->getTexture(), &x, &y);
+ textureBrowser.m_nTotalHeight = std::max(textureBrowser.m_nTotalHeight,
+ abs(layout.current_y) + TextureBrowser_fontHeight(textureBrowser) +
+ textureBrowser.getTextureHeight(shader->getTexture()) + 4);
+ }
+ }
+}
+
+int TextureBrowser_TotalHeight(TextureBrowser &textureBrowser)
+{
+ TextureBrowser_evaluateHeight(textureBrowser);
+ return textureBrowser.m_nTotalHeight;
+}
+
+inline const int &min_int(const int &left, const int &right)
+{
+ return std::min(left, right);
+}
+
+void TextureBrowser_clampOriginY(TextureBrowser &textureBrowser)
+{
+ if (textureBrowser.originy > 0) {
+ textureBrowser.originy = 0;
+ }
+ int lower = min_int(textureBrowser.height - TextureBrowser_TotalHeight(textureBrowser), 0);
+ if (textureBrowser.originy < lower) {
+ textureBrowser.originy = lower;
+ }
+}
+
+int TextureBrowser_getOriginY(TextureBrowser &textureBrowser)
+{
+ if (textureBrowser.m_originInvalid) {
+ textureBrowser.m_originInvalid = false;
+ TextureBrowser_clampOriginY(textureBrowser);
+ TextureBrowser_updateScroll(textureBrowser);
+ }
+ return textureBrowser.originy;
+}
+
+void TextureBrowser_setOriginY(TextureBrowser &textureBrowser, int originy)
+{
+ textureBrowser.originy = originy;
+ TextureBrowser_clampOriginY(textureBrowser);
+ TextureBrowser_updateScroll(textureBrowser);
+ TextureBrowser_queueDraw(textureBrowser);
+}
+
+
+Signal0 g_activeShadersChangedCallbacks;
+
+void TextureBrowser_addActiveShadersChangedCallback(const SignalHandler &handler)
+{
+ g_activeShadersChangedCallbacks.connectLast(handler);
+}
+
+void TextureBrowser_constructTreeStore();
+
+class ShadersObserver : public ModuleObserver {
+ Signal0 m_realiseCallbacks;
+public:
+ void realise()
+ {
+ m_realiseCallbacks();
+ TextureBrowser_constructTreeStore();
+ }
+
+ 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);
+ textureBrowser.m_originInvalid = true;
+
+ g_activeShadersChangedCallbacks();
+}
+
+struct TextureBrowser_ShowScrollbar {
+ static void Export(const TextureBrowser &self, const Callback<void(bool)> &returnz)
+ {
+ returnz(self.m_showTextureScrollbar);
+ }
+
+ static void Import(TextureBrowser &self, bool value)
+ {
+ self.m_showTextureScrollbar = value;
+ if (self.m_texture_scroll) {
+ self.m_texture_scroll.visible(self.m_showTextureScrollbar);
+ TextureBrowser_updateScroll(self);
+ }
+ }
+};
+
+
+/*
+ ==============
+ 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 overriden, 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)
+ ==============
+ */
+
+bool endswith(const char *haystack, const char *needle)
+{
+ size_t lh = strlen(haystack);
+ size_t ln = strlen(needle);
+ if (lh < ln) {
+ return false;
+ }
+ return !memcmp(haystack + (lh - ln), needle, ln);
+}
+
+bool texture_name_ignore(const char *name)
+{
+ StringOutputStream strTemp(string_length(name));
+ strTemp << LowerCase(name);
+
+ return
+ endswith(strTemp.c_str(), ".specular") ||
+ endswith(strTemp.c_str(), ".glow") ||
+ endswith(strTemp.c_str(), ".bump") ||
+ endswith(strTemp.c_str(), ".diffuse") ||
+ endswith(strTemp.c_str(), ".blend") ||
+ endswith(strTemp.c_str(), ".alpha") ||
+ endswith(strTemp.c_str(), "_norm") ||
+ endswith(strTemp.c_str(), "_bump") ||
+ endswith(strTemp.c_str(), "_glow") ||
+ endswith(strTemp.c_str(), "_gloss") ||
+ endswith(strTemp.c_str(), "_pants") ||
+ endswith(strTemp.c_str(), "_shirt") ||
+ endswith(strTemp.c_str(), "_reflect") ||
+ endswith(strTemp.c_str(), "_alpha") ||
+ 0;
+}
+
+class LoadShaderVisitor : public Archive::Visitor {
+public:
+ void visit(const char *name)
+ {
+ IShader *shader = QERApp_Shader_ForName(
+ CopiedString(StringRange(name, path_get_filename_base_end(name))).c_str());
+ shader->DecRef();
+ }
+};
+
+void TextureBrowser_SetHideUnused(TextureBrowser &textureBrowser, bool hideUnused);
+
+ui::Widget g_page_textures{ui::null};
+
+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:
+ using func = void(const char *);
+
+ 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 TextureDirectory_loadTexture(const char *directory, const char *texture)
+{
+ StringOutputStream name(256);
+ name << directory << StringRange(texture, path_get_filename_base_end(texture));
+
+ if (texture_name_ignore(name.c_str())) {
+ return;
+ }
+
+ if (!shader_valid(name.c_str())) {
+ globalOutputStream() << "Skipping invalid texture name: [" << name.c_str() << "]\n";
+ return;
+ }
+
+ // if a texture is already in use to represent a shader, ignore it
+ IShader *shader = QERApp_Shader_ForName(name.c_str());
+ shader->DecRef();
+}
+
+typedef ConstPointerCaller<char, void(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(TextureBrowser &textureBrowser, const char *directory)
+{
+ if (TextureBrowser_showWads()) {
+ Archive *archive = GlobalFileSystem().getArchive(directory);
+ ASSERT_NOTNULL(archive);
+ LoadShaderVisitor visitor;
+ archive->forEachFile(Archive::VisitorFunc(visitor, Archive::eFiles, 0), "textures/");
+ } else {
+ g_TextureBrowser_currentDirectory = directory;
+ TextureBrowser_heightChanged(textureBrowser);
+
+ std::size_t shaders_count;
+ GlobalShaderSystem().foreachShaderName(makeCallback(TextureCategoryLoadShader(directory, shaders_count)));
+ globalOutputStream() << "Showing " << Unsigned(shaders_count) << " shaders.\n";
+
+ if (g_pGameDescription->mGameType != "doom3") {
+ // load remaining texture files
+
+ StringOutputStream dirstring(64);
+ dirstring << "textures/" << directory;
+
+ Radiant_getImageModules().foreachModule(LoadTexturesByTypeVisitor(dirstring.c_str()));
+ }
+ }
+
+ // we'll display the newly loaded textures + all the ones already in use
+ TextureBrowser_SetHideUnused(textureBrowser, false);
+
+ TextureBrowser_updateTitle();
+}
+
+void TextureBrowser_ShowTagSearchResult(TextureBrowser &textureBrowser, const char *directory)
+{
+ g_TextureBrowser_currentDirectory = directory;
+ TextureBrowser_heightChanged(textureBrowser);
+
+ std::size_t shaders_count;
+ GlobalShaderSystem().foreachShaderName(makeCallback(TextureCategoryLoadShader(directory, shaders_count)));
+ globalOutputStream() << "Showing " << Unsigned(shaders_count) << " shaders.\n";
+
+ if (g_pGameDescription->mGameType != "doom3") {
+ // load remaining texture files
+ StringOutputStream dirstring(64);
+ dirstring << "textures/" << directory;
+
+ {
+ LoadTexturesByTypeVisitor visitor(dirstring.c_str());
+ Radiant_getImageModules().foreachModule(visitor);
+ }
+ }
+
+ // we'll display the newly loaded textures + all the ones already in use
+ TextureBrowser_SetHideUnused(textureBrowser, false);
+}
+
+
+bool TextureBrowser_hideUnused();
+
+void TextureBrowser_hideUnusedExport(const Callback<void(bool)> &importer)
+{
+ importer(TextureBrowser_hideUnused());
+}
+
+typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_hideUnusedExport> TextureBrowserHideUnusedExport;
+
+void TextureBrowser_showShadersExport(const Callback<void(bool)> &importer)
+{
+ importer(GlobalTextureBrowser().m_showShaders);
+}
+
+typedef FreeCaller<void(
+ const Callback<void(bool)> &), TextureBrowser_showShadersExport> TextureBrowserShowShadersExport;
+
+void TextureBrowser_showShaderlistOnly(const Callback<void(bool)> &importer)
+{
+ importer(g_TextureBrowser_shaderlistOnly);
+}
+
+typedef FreeCaller<void(
+ const Callback<void(bool)> &), TextureBrowser_showShaderlistOnly> TextureBrowserShowShaderlistOnlyExport;
+
+void TextureBrowser_fixedSize(const Callback<void(bool)> &importer)
+{
+ importer(g_TextureBrowser_fixedSize);
+}
+
+typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_fixedSize> TextureBrowser_FixedSizeExport;
+
+void TextureBrowser_filterMissing(const Callback<void(bool)> &importer)
+{
+ importer(g_TextureBrowser_filterMissing);
+}
+
+typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_filterMissing> TextureBrowser_filterMissingExport;
+
+void TextureBrowser_filterFallback(const Callback<void(bool)> &importer)
+{
+ importer(g_TextureBrowser_filterFallback);
+}
+
+typedef FreeCaller<void(
+ const Callback<void(bool)> &), TextureBrowser_filterFallback> TextureBrowser_filterFallbackExport;
+
+void TextureBrowser_enableAlpha(const Callback<void(bool)> &importer)
+{
+ importer(g_TextureBrowser_enableAlpha);
+}
+
+typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_enableAlpha> TextureBrowser_enableAlphaExport;
+
+void TextureBrowser_SetHideUnused(TextureBrowser &textureBrowser, bool hideUnused)
+{
+ if (hideUnused) {
+ textureBrowser.m_hideUnused = true;
+ } else {
+ textureBrowser.m_hideUnused = false;
+ }
+
+ textureBrowser.m_hideunused_item.update();
+
+ TextureBrowser_heightChanged(textureBrowser);
+ textureBrowser.m_originInvalid = true;
+}
+
+void TextureBrowser_ShowStartupShaders(TextureBrowser &textureBrowser)
+{
+ if (textureBrowser.m_startupShaders == STARTUPSHADERS_COMMON) {
+ TextureBrowser_ShowDirectory(textureBrowser, TextureBrowser_getComonShadersDir());
+ }
+}
+
+
+//++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
+ Texture_StartPos(layout);
+
+ for (QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement()) {
+ IShader *shader = QERApp_ActiveShaders_IteratorCurrent();
+
+ if (!Texture_IsShown(shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused)) {
+ continue;
+ }
+
+ int x, y;
+ Texture_NextPos(textureBrowser, layout, shader->getTexture(), &x, &y);
+ 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(textureBrowser);
+
+ int originy = TextureBrowser_getOriginY(textureBrowser);
+ if (y > originy) {
+ originy = y;
+ }
+
+ if (y - textureHeight < originy - textureBrowser.height) {
+ originy = (y - textureHeight) + textureBrowser.height;
+ }
+
+ TextureBrowser_setOriginY(textureBrowser, originy);
+ return;
+ }
+ }
+}
+
+IShader *Texture_At(TextureBrowser &textureBrowser, int mx, int my)
+{
+ my += TextureBrowser_getOriginY(textureBrowser) - textureBrowser.height;
+
+ TextureLayout layout;
+ Texture_StartPos(layout);
+ for (QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement()) {
+ IShader *shader = QERApp_ActiveShaders_IteratorCurrent();
+
+ if (!Texture_IsShown(shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused)) {
+ continue;
+ }
+
+ int x, y;
+ Texture_NextPos(textureBrowser, layout, shader->getTexture(), &x, &y);
+ qtexture_t *q = shader->getTexture();
+ if (!q) {
+ break;
+ }
+
+ int nWidth = textureBrowser.getTextureWidth(q);
+ int nHeight = textureBrowser.getTextureHeight(q);
+ if (mx > x && mx - x < nWidth
+ && my < y && y - my < nHeight + TextureBrowser_fontHeight(textureBrowser)) {
+ return shader;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ ==============
+ SelectTexture
+
+ By mouse click
+ ==============
+ */
+void SelectTexture(TextureBrowser &textureBrowser, int mx, int my, bool bShift)
+{
+ IShader *shader = Texture_At(textureBrowser, mx, my);
+ if (shader != 0) {
+ if (bShift) {
+ if (shader->IsDefault()) {
+ globalOutputStream() << "ERROR: " << shader->getName() << " is not a shader, it's a texture.\n";
+ } else {
+ ViewShader(shader->getShaderFileName(), shader->getName());
+ }
+ } else {
+ TextureBrowser_SetSelectedShader(textureBrowser, shader->getName());
+ TextureBrowser_textureSelected(shader->getName());
+
+ if (!FindTextureDialog_isOpen() && !textureBrowser.m_rmbSelected) {
+ UndoableCommand undo("textureNameSetSelected");
+ Select_SetShader(shader->getName());
+ }
+ }
+ }
+}
+
+/*
+ ============================================================================
+
+ MOUSE ACTIONS
+
+ ============================================================================
+ */
+
+void TextureBrowser_trackingDelta(int x, int y, unsigned int state, void *data)
+{
+ TextureBrowser &textureBrowser = *reinterpret_cast<TextureBrowser *>( data );
+ if (y != 0) {
+ int scale = 1;
+
+ if (state & GDK_SHIFT_MASK) {
+ scale = 4;
+ }
+
+ int originy = TextureBrowser_getOriginY(textureBrowser);
+ originy += y * scale;
+ TextureBrowser_setOriginY(textureBrowser, originy);
+ }
+}
+
+void TextureBrowser_Tracking_MouseDown(TextureBrowser &textureBrowser)
+{
+ textureBrowser.m_freezePointer.freeze_pointer(textureBrowser.m_parent, TextureBrowser_trackingDelta,
+ &textureBrowser);
+}
+
+void TextureBrowser_Tracking_MouseUp(TextureBrowser &textureBrowser)
+{
+ textureBrowser.m_freezePointer.unfreeze_pointer(textureBrowser.m_parent);
+}
+
+void TextureBrowser_Selection_MouseDown(TextureBrowser &textureBrowser, guint32 flags, int pointx, int pointy)
+{
+ SelectTexture(textureBrowser, pointx, textureBrowser.height - 1 - pointy, (flags & GDK_SHIFT_MASK) != 0);
+}
+
+/*
+ ============================================================================
+
+ DRAWING
+
+ ============================================================================
+ */
+
+/*
+ ============
+ Texture_Draw
+ 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 Texture_Draw(TextureBrowser &textureBrowser)
+{
+ int originy = TextureBrowser_getOriginY(textureBrowser);
+
+ glClearColor(textureBrowser.color_textureback[0],
+ textureBrowser.color_textureback[1],
+ textureBrowser.color_textureback[2],
+ 0);
+ glViewport(0, 0, textureBrowser.width, textureBrowser.height);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glDisable(GL_DEPTH_TEST);
+ if (g_TextureBrowser_enableAlpha) {
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ } else {
+ glDisable(GL_BLEND);
+ }
+ glOrtho(0, textureBrowser.width, originy - textureBrowser.height, originy, -100, 100);
+ glEnable(GL_TEXTURE_2D);
+
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+
+ int last_y = 0, last_height = 0;
+
+ TextureLayout layout;
+ Texture_StartPos(layout);
+ for (QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement()) {
+ IShader *shader = QERApp_ActiveShaders_IteratorCurrent();
+
+ if (!Texture_IsShown(shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused)) {
+ continue;
+ }
+
+ int x, y;
+ Texture_NextPos(textureBrowser, layout, shader->getTexture(), &x, &y);
+ qtexture_t *q = shader->getTexture();
+ if (!q) {
+ break;
+ }
+
+ int nWidth = textureBrowser.getTextureWidth(q);
+ int nHeight = textureBrowser.getTextureHeight(q);
+
+ if (y != last_y) {
+ last_y = y;
+ last_height = 0;
+ }
+ last_height = std::max(nHeight, last_height);
+
+ // Is this texture visible?
+ if ((y - nHeight - TextureBrowser_fontHeight(textureBrowser) < originy)
+ && (y > originy - textureBrowser.height)) {
+ // borders rules:
+ // if it's the current texture, draw a thick red line, else:
+ // shaders have a white border, simple textures don't
+ // if !texture_showinuse: (some textures displayed may not be in use)
+ // draw an additional square around with 0.5 1 0.5 color
+ if (shader_equal(TextureBrowser_GetSelectedShader(textureBrowser), shader->getName())) {
+ glLineWidth(3);
+ if (textureBrowser.m_rmbSelected) {
+ glColor3f(0, 0, 1);
+ } else {
+ glColor3f(1, 0, 0);
+ }
+ glDisable(GL_TEXTURE_2D);
+
+ glBegin(GL_LINE_LOOP);
+ glVertex2i(x - 4, y - TextureBrowser_fontHeight(textureBrowser) + 4);
+ glVertex2i(x - 4, y - TextureBrowser_fontHeight(textureBrowser) - nHeight - 4);
+ glVertex2i(x + 4 + nWidth, y - TextureBrowser_fontHeight(textureBrowser) - nHeight - 4);
+ glVertex2i(x + 4 + nWidth, y - TextureBrowser_fontHeight(textureBrowser) + 4);
+ glEnd();
+
+ glEnable(GL_TEXTURE_2D);
+ glLineWidth(1);
+ } else {
+ glLineWidth(1);
+ // shader border:
+ if (!shader->IsDefault()) {
+ glColor3f(1, 1, 1);
+ glDisable(GL_TEXTURE_2D);
+
+ glBegin(GL_LINE_LOOP);
+ glVertex2i(x - 1, y + 1 - TextureBrowser_fontHeight(textureBrowser));
+ glVertex2i(x - 1, y - nHeight - 1 - TextureBrowser_fontHeight(textureBrowser));
+ glVertex2i(x + 1 + nWidth, y - nHeight - 1 - TextureBrowser_fontHeight(textureBrowser));
+ glVertex2i(x + 1 + nWidth, y + 1 - TextureBrowser_fontHeight(textureBrowser));
+ glEnd();
+ glEnable(GL_TEXTURE_2D);
+ }
+
+ // highlight in-use textures
+ if (!textureBrowser.m_hideUnused && shader->IsInUse()) {
+ glColor3f(0.5, 1, 0.5);
+ glDisable(GL_TEXTURE_2D);
+ glBegin(GL_LINE_LOOP);
+ glVertex2i(x - 3, y + 3 - TextureBrowser_fontHeight(textureBrowser));
+ glVertex2i(x - 3, y - nHeight - 3 - TextureBrowser_fontHeight(textureBrowser));
+ glVertex2i(x + 3 + nWidth, y - nHeight - 3 - TextureBrowser_fontHeight(textureBrowser));
+ glVertex2i(x + 3 + nWidth, y + 3 - TextureBrowser_fontHeight(textureBrowser));
+ glEnd();
+ glEnable(GL_TEXTURE_2D);
+ }
+ }
+
+ // draw checkerboard for transparent textures
+ if (g_TextureBrowser_enableAlpha) {
+ glDisable(GL_TEXTURE_2D);
+ glBegin(GL_QUADS);
+ int font_height = TextureBrowser_fontHeight(textureBrowser);
+ for (int i = 0; i < nHeight; i += 8) {
+ for (int j = 0; j < nWidth; j += 8) {
+ unsigned char color = (i + j) / 8 % 2 ? 0x66 : 0x99;
+ glColor3ub(color, color, color);
+ int left = j;
+ int right = std::min(j + 8, nWidth);
+ int top = i;
+ int bottom = std::min(i + 8, nHeight);
+ glVertex2i(x + right, y - nHeight - font_height + top);
+ glVertex2i(x + left, y - nHeight - font_height + top);
+ glVertex2i(x + left, y - nHeight - font_height + bottom);
+ glVertex2i(x + right, y - nHeight - font_height + bottom);
+ }
+ }
+ glEnd();
+ glEnable(GL_TEXTURE_2D);
+ }
+
+ // Draw the texture
+ glBindTexture(GL_TEXTURE_2D, q->texture_number);
+ GlobalOpenGL_debugAssertNoErrors();
+ glColor3f(1, 1, 1);
+ glBegin(GL_QUADS);
+ glTexCoord2i(0, 0);
+ glVertex2i(x, y - TextureBrowser_fontHeight(textureBrowser));
+ glTexCoord2i(1, 0);
+ glVertex2i(x + nWidth, y - TextureBrowser_fontHeight(textureBrowser));
+ glTexCoord2i(1, 1);
+ glVertex2i(x + nWidth, y - TextureBrowser_fontHeight(textureBrowser) - nHeight);
+ glTexCoord2i(0, 1);
+ glVertex2i(x, y - TextureBrowser_fontHeight(textureBrowser) - nHeight);
+ glEnd();
+
+ // draw the texture name
+ glDisable(GL_TEXTURE_2D);
+ glColor3f(1, 1, 1);
+
+ glRasterPos2i(x, y - TextureBrowser_fontHeight(textureBrowser) + 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);
+ glEnable(GL_TEXTURE_2D);
+ }
+
+ //int totalHeight = abs(y) + last_height + TextureBrowser_fontHeight(textureBrowser) + 4;
+ }
+
+
+ // reset the current texture
+ glBindTexture(GL_TEXTURE_2D, 0);
+ //qglFinish();
+}
+
+void TextureBrowser_queueDraw(TextureBrowser &textureBrowser)
+{
+ if (textureBrowser.m_gl_widget) {
+ gtk_widget_queue_draw(textureBrowser.m_gl_widget);
+ }
+}
+
+
+void TextureBrowser_setScale(TextureBrowser &textureBrowser, std::size_t scale)
+{
+ textureBrowser.m_textureScale = scale;
+
+ TextureBrowser_queueDraw(textureBrowser);
+}
+
+void TextureBrowser_setUniformSize(TextureBrowser &textureBrowser, std::size_t scale)
+{
+ textureBrowser.m_uniformTextureSize = scale;
+
+ TextureBrowser_queueDraw(textureBrowser);
+}
+
+
+void TextureBrowser_MouseWheel(TextureBrowser &textureBrowser, bool bUp)
+{
+ int originy = TextureBrowser_getOriginY(textureBrowser);
+
+ if (bUp) {
+ originy += int(textureBrowser.m_mouseWheelScrollIncrement);
+ } else {
+ originy -= int(textureBrowser.m_mouseWheelScrollIncrement);
+ }
+
+ TextureBrowser_setOriginY(textureBrowser, originy);
+}
+
+XmlTagBuilder TagBuilder;
+
+enum {
+ TAG_COLUMN,
+ N_COLUMNS
+};
+
+void BuildStoreAssignedTags(ui::ListStore store, const char *shader, TextureBrowser *textureBrowser)
+{
+ GtkTreeIter iter;
+
+ store.clear();
+
+ std::vector<CopiedString> assigned_tags;
+ TagBuilder.GetShaderTags(shader, assigned_tags);
+
+ for (size_t i = 0; i < assigned_tags.size(); i++) {
+ store.append(TAG_COLUMN, assigned_tags[i].c_str());
+ }
+}
+
+void BuildStoreAvailableTags(ui::ListStore storeAvailable,
+ ui::ListStore storeAssigned,
+ const std::set<CopiedString> &allTags,
+ TextureBrowser *textureBrowser)
+{
+ GtkTreeIter iterAssigned;
+ GtkTreeIter iterAvailable;
+ std::set<CopiedString>::const_iterator iterAll;
+ gchar *tag_assigned;
+
+ storeAvailable.clear();
+
+ bool row = gtk_tree_model_get_iter_first(storeAssigned, &iterAssigned) != 0;
+
+ if (!row) { // does the shader have tags assigned?
+ for (iterAll = allTags.begin(); iterAll != allTags.end(); ++iterAll) {
+ storeAvailable.append(TAG_COLUMN, (*iterAll).c_str());
+ }
+ } else {
+ while (row) // available tags = all tags - assigned tags
+ {
+ gtk_tree_model_get(storeAssigned, &iterAssigned, TAG_COLUMN, &tag_assigned, -1);
+
+ for (iterAll = allTags.begin(); iterAll != allTags.end(); ++iterAll) {
+ if (strcmp((char *) tag_assigned, (*iterAll).c_str()) != 0) {
+ storeAvailable.append(TAG_COLUMN, (*iterAll).c_str());
+ } else {
+ row = gtk_tree_model_iter_next(storeAssigned, &iterAssigned) != 0;
+
+ if (row) {
+ gtk_tree_model_get(storeAssigned, &iterAssigned, TAG_COLUMN, &tag_assigned, -1);
+ }
+ }
+ }
+ }
+ }
+}
+
+gboolean TextureBrowser_button_press(ui::Widget widget, GdkEventButton *event, TextureBrowser *textureBrowser)
+{
+ if (event->type == GDK_BUTTON_PRESS) {
+ if (event->button == 3) {
+ if (GlobalTextureBrowser().m_tags) {
+ textureBrowser->m_rmbSelected = true;
+ TextureBrowser_Selection_MouseDown(*textureBrowser, event->state, static_cast<int>( event->x ),
+ static_cast<int>( event->y ));
+
+ BuildStoreAssignedTags(textureBrowser->m_assigned_store, textureBrowser->shader.c_str(),
+ textureBrowser);
+ BuildStoreAvailableTags(textureBrowser->m_available_store, textureBrowser->m_assigned_store,
+ textureBrowser->m_all_tags, textureBrowser);
+ textureBrowser->m_heightChanged = true;
+ textureBrowser->m_tag_frame.show();
+
+ ui::process();
+
+ TextureBrowser_Focus(*textureBrowser, textureBrowser->shader.c_str());
+ } else {
+ TextureBrowser_Tracking_MouseDown(*textureBrowser);
+ }
+ } else if (event->button == 1) {
+ TextureBrowser_Selection_MouseDown(*textureBrowser, event->state, static_cast<int>( event->x ),
+ static_cast<int>( event->y ));
+
+ if (GlobalTextureBrowser().m_tags) {
+ textureBrowser->m_rmbSelected = false;
+ textureBrowser->m_tag_frame.hide();
+ }
+ }
+ }
+ return FALSE;
+}
+
+gboolean TextureBrowser_button_release(ui::Widget widget, GdkEventButton *event, TextureBrowser *textureBrowser)
+{
+ if (event->type == GDK_BUTTON_RELEASE) {
+ if (event->button == 3) {
+ if (!GlobalTextureBrowser().m_tags) {
+ TextureBrowser_Tracking_MouseUp(*textureBrowser);
+ }
+ }
+ }
+ return FALSE;
+}
+
+gboolean TextureBrowser_motion(ui::Widget widget, GdkEventMotion *event, TextureBrowser *textureBrowser)
+{
+ return FALSE;
+}
+
+gboolean TextureBrowser_scroll(ui::Widget widget, GdkEventScroll *event, TextureBrowser *textureBrowser)
+{
+ if (event->direction == GDK_SCROLL_UP) {
+ TextureBrowser_MouseWheel(*textureBrowser, true);
+ } else if (event->direction == GDK_SCROLL_DOWN) {
+ TextureBrowser_MouseWheel(*textureBrowser, false);
+ }
+ return FALSE;
+}
+
+void TextureBrowser_scrollChanged(void *data, gdouble value)
+{
+ //globalOutputStream() << "vertical scroll\n";
+ TextureBrowser_setOriginY(*reinterpret_cast<TextureBrowser *>( data ), -(int) value);
+}
+
+static void TextureBrowser_verticalScroll(ui::Adjustment adjustment, TextureBrowser *textureBrowser)
+{
+ textureBrowser->m_scrollAdjustment.value_changed(gtk_adjustment_get_value(adjustment));
+}
+
+void TextureBrowser_updateScroll(TextureBrowser &textureBrowser)
+{
+ if (textureBrowser.m_showTextureScrollbar) {
+ int totalHeight = TextureBrowser_TotalHeight(textureBrowser);
+
+ totalHeight = std::max(totalHeight, textureBrowser.height);
+
+ auto vadjustment = gtk_range_get_adjustment(GTK_RANGE(textureBrowser.m_texture_scroll));
+
+ gtk_adjustment_set_value(vadjustment, -TextureBrowser_getOriginY(textureBrowser));
+ gtk_adjustment_set_page_size(vadjustment, textureBrowser.height);
+ gtk_adjustment_set_page_increment(vadjustment, textureBrowser.height / 2);
+ gtk_adjustment_set_step_increment(vadjustment, 20);
+ gtk_adjustment_set_lower(vadjustment, 0);
+ gtk_adjustment_set_upper(vadjustment, totalHeight);
+
+ g_signal_emit_by_name(G_OBJECT(vadjustment), "changed");
+ }
+}
+
+gboolean TextureBrowser_size_allocate(ui::Widget widget, GtkAllocation *allocation, TextureBrowser *textureBrowser)
+{
+ textureBrowser->width = allocation->width;
+ textureBrowser->height = allocation->height;
+ TextureBrowser_heightChanged(*textureBrowser);
+ textureBrowser->m_originInvalid = true;
+ TextureBrowser_queueDraw(*textureBrowser);
+ return FALSE;
+}
+
+gboolean TextureBrowser_expose(ui::Widget widget, GdkEventExpose *event, TextureBrowser *textureBrowser)
+{
+ if (glwidget_make_current(textureBrowser->m_gl_widget) != FALSE) {
+ GlobalOpenGL_debugAssertNoErrors();
+ TextureBrowser_evaluateHeight(*textureBrowser);
+ Texture_Draw(*textureBrowser);
+ GlobalOpenGL_debugAssertNoErrors();
+ glwidget_swap_buffers(textureBrowser->m_gl_widget);
+ }
+ return FALSE;
+}
+
+
+TextureBrowser g_TextureBrowser;
+
+TextureBrowser &GlobalTextureBrowser()
+{
+ return g_TextureBrowser;
+}
+
+bool TextureBrowser_hideUnused()
+{
+ return g_TextureBrowser.m_hideUnused;
+}
+
+void TextureBrowser_ToggleHideUnused()
+{
+ if (g_TextureBrowser.m_hideUnused) {
+ TextureBrowser_SetHideUnused(g_TextureBrowser, false);
+ } else {
+ TextureBrowser_SetHideUnused(g_TextureBrowser, true);
+ }
+}
+
+void TextureGroups_constructTreeModel(TextureGroups groups, ui::TreeStore store)
+{
+ // put the information from the old textures menu into a treeview
+ GtkTreeIter iter, child;
+
+ 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 = i;
+ ++next;
+ if (firstUnderscore != 0
+ && next != groups.end()
+ && string_equal_start((*next).c_str(), dirRoot)) {
+ gtk_tree_store_append(store, &iter, NULL);
+ gtk_tree_store_set(store, &iter, 0, CopiedString(StringRange(dirName, firstUnderscore)).c_str(), -1);
+
+ // keep going...
+ while (i != groups.end() && string_equal_start((*i).c_str(), dirRoot)) {
+ gtk_tree_store_append(store, &child, &iter);
+ gtk_tree_store_set(store, &child, 0, (*i).c_str(), -1);
+ ++i;
+ }
+ } else {
+ gtk_tree_store_append(store, &iter, NULL);
+ gtk_tree_store_set(store, &iter, 0, dirName, -1);
+ ++i;
+ }
+ }
+}
+
+TextureGroups TextureGroups_constructTreeView()
+{
+ TextureGroups groups;
+
+ if (TextureBrowser_showWads()) {
+ 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));
+ }
+
+ return groups;
+}
+
+void TextureBrowser_constructTreeStore()
+{
+ TextureGroups groups = TextureGroups_constructTreeView();
+ auto store = ui::TreeStore::from(gtk_tree_store_new(1, G_TYPE_STRING));
+ TextureGroups_constructTreeModel(groups, store);
+
+ gtk_tree_view_set_model(g_TextureBrowser.m_treeViewTree, store);
+
+ g_object_unref(G_OBJECT(store));
+}
+
+void TextureBrowser_constructTreeStoreTags()
+{
+ TextureGroups groups;
+ auto store = ui::TreeStore::from(gtk_tree_store_new(1, G_TYPE_STRING));
+ auto model = g_TextureBrowser.m_all_tags_list;
+
+ gtk_tree_view_set_model(g_TextureBrowser.m_treeViewTags, model);
+
+ g_object_unref(G_OBJECT(store));
+}
+
+void TreeView_onRowActivated(ui::TreeView treeview, ui::TreePath path, ui::TreeViewColumn col, gpointer userdata)
+{
+ GtkTreeIter iter;
+
+ auto model = gtk_tree_view_get_model(treeview);
+
+ if (gtk_tree_model_get_iter(model, &iter, path)) {
+ gchar dirName[1024];
+
+ gchar *buffer;
+ gtk_tree_model_get(model, &iter, 0, &buffer, -1);
+ strcpy(dirName, buffer);
+ g_free(buffer);
+
+ g_TextureBrowser.m_searchedTags = false;
+
+ if (!TextureBrowser_showWads()) {
+ strcat(dirName, "/");
+ }
+
+ ScopeDisableScreenUpdates disableScreenUpdates(dirName, "Loading Textures");
+ TextureBrowser_ShowDirectory(GlobalTextureBrowser(), dirName);
+ TextureBrowser_queueDraw(GlobalTextureBrowser());
+ }
+}
+
+void TextureBrowser_createTreeViewTree()
+{
+ gtk_tree_view_set_enable_search(g_TextureBrowser.m_treeViewTree, FALSE);
+
+ gtk_tree_view_set_headers_visible(g_TextureBrowser.m_treeViewTree, FALSE);
+ g_TextureBrowser.m_treeViewTree.connect("row-activated", (GCallback) TreeView_onRowActivated, NULL);
+
+ auto renderer = ui::CellRendererText(ui::New);
+ gtk_tree_view_insert_column_with_attributes(g_TextureBrowser.m_treeViewTree, -1, "", renderer, "text", 0, NULL);
+
+ TextureBrowser_constructTreeStore();
+}