1 /**
2  * @file
3  * @brief Texture Window
4  * @author Leonardo Zide (leo@lokigames.com)
5  */
6 
7 /*
8  Copyright (C) 1999-2006 Id Software, Inc. and contributors.
9  For a list of contributors, see the accompanying CONTRIBUTORS file.
10 
11  This file is part of GtkRadiant.
12 
13  GtkRadiant is free software; you can redistribute it and/or modify
14  it under the terms of the GNU General Public License as published by
15  the Free Software Foundation; either version 2 of the License, or
16  (at your option) any later version.
17 
18  GtkRadiant is distributed in the hope that it will be useful,
19  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  GNU General Public License for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with GtkRadiant; if not, write to the Free Software
25  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
26  */
27 
28 #include "texturebrowser.h"
29 #include "radiant_i18n.h"
30 
31 #include "debugging/debugging.h"
32 
33 #include "ifilesystem.h"
34 #include "iundo.h"
35 #include "igl.h"
36 #include "iarchive.h"
37 #include "moduleobserver.h"
38 #include "ieventmanager.h"
39 
40 #include <set>
41 #include <string>
42 #include <vector>
43 
44 #include "math/Vector3.h"
45 #include "texturelib.h"
46 #include "string/string.h"
47 #include "shaderlib.h"
48 #include "os/file.h"
49 #include "os/path.h"
50 #include "stream/memstream.h"
51 #include "stream/textfilestream.h"
52 #include "stream/stringstream.h"
53 #include "imaterial.h"
54 
55 #include "gtkutil/image.h"
56 #include "gtkutil/menu.h"
57 #include "gtkutil/cursor.h"
58 #include "gtkutil/widget.h"
59 #include "gtkutil/glwidget.h"
60 #include "gtkutil/GLWidgetSentry.h"
61 #include "gtkutil/messagebox.h"
62 #include "gtkutil/TreeModel.h"
63 #include "gtkutil/ModalProgressDialog.h"
64 
65 #include "../ui/common/ToolbarCreator.h"
66 #include "../ui/mainframe/mainframe.h"
67 #include "../map/map.h"
68 #include "../render/OpenGLRenderSystem.h"
69 #include "../select.h"
70 #include "../brush/TextureProjection.h"
71 #include "../brush/brushmanip.h"
72 #include "../plugin.h"
73 
74 namespace {
75 const std::string RKEY_TEXTURES_HIDE_UNUSED = "user/ui/textures/browser/hideUnused";
76 const std::string RKEY_TEXTURES_HIDE_INVALID = "user/ui/textures/browser/hideInvalid";
77 const std::string RKEY_TEXTURES_UNIFORM_SIZE = "user/ui/textures/browser/uniformSize";
78 const std::string RKEY_TEXTURES_THUMBNAIL_SCALE = "user/ui/textures/browser/thumbnailScale";
79 const std::string RKEY_TEXTURES_LICENSE_PATH = "user/ui/textures/browser/licensepath";
80 const std::string RKEY_TEXTURES_SKIPCOMMON = "user/ui/textures/skipCommon";
81 }
82 
83 static void TextureBrowser_scrollChanged (void* data, gdouble value);
84 
TextureBrowser()85 TextureBrowser::TextureBrowser () :
86 	_glWidget(false), m_texture_scroll(NULL), m_heightChanged(true), m_originInvalid(true), m_scrollAdjustment(
87 			TextureBrowser_scrollChanged, this), m_rmbSelected(false), m_resizeTextures(true)
88 {
89 	GlobalRegistry().addKeyObserver(this, RKEY_TEXTURES_HIDE_UNUSED);
90 	GlobalRegistry().addKeyObserver(this, RKEY_TEXTURES_HIDE_INVALID);
91 	GlobalRegistry().addKeyObserver(this, RKEY_TEXTURES_UNIFORM_SIZE);
92 	GlobalRegistry().addKeyObserver(this, RKEY_TEXTURES_THUMBNAIL_SCALE);
93 
94 	GlobalShaderSystem().setActiveShadersChangedNotify(MemberCaller<TextureBrowser,
95 			&TextureBrowser::activeShadersChanged> (*this));
96 
97 	GlobalPreferenceSystem().addConstructor(this);
98 	keyChanged("", "");
99 	setSelectedShader("");
100 }
101 
createWidget()102 void TextureBrowser::createWidget() {
103 	_widget = gtk_vbox_new(FALSE, 0);
104 
105 	gtk_box_pack_start(GTK_BOX(_widget), createMenuBar(), false, true, 0);
106 	gtk_box_pack_start(GTK_BOX(_widget), createToolBar(), false, true, 0);
107 
108 	GtkWidget* texhbox = gtk_hbox_new(FALSE, 0);
109 	gtk_box_pack_end(GTK_BOX(_widget), texhbox, true, true, 0);
110 
111 	m_texture_scroll = gtk_vscrollbar_new(GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 0, 1, 1, 1)));
112 
113 	GtkAdjustment *vadjustment = gtk_range_get_adjustment(GTK_RANGE(m_texture_scroll));
114 	g_signal_connect(G_OBJECT(vadjustment), "value_changed", G_CALLBACK(onVerticalScroll),
115 			this);
116 
117 	widget_set_visible(m_texture_scroll, true);
118 
119 	gtk_box_pack_end(GTK_BOX(texhbox), m_texture_scroll, false, true, 0);
120 
121 	{ // gl_widget
122 		GtkWidget *glWidget = _glWidget;
123 
124 		gtk_widget_set_events(glWidget, GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
125 				| GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK);
126 		GTK_WIDGET_SET_FLAGS(glWidget, GTK_CAN_FOCUS);
127 		gtk_widget_show(glWidget);
128 
129 		g_signal_connect(G_OBJECT(glWidget), "size_allocate",
130 				G_CALLBACK(onSizeAllocate), this);
131 		g_signal_connect(G_OBJECT(glWidget), "expose_event",
132 				G_CALLBACK(onExpose), this);
133 		g_signal_connect(G_OBJECT(glWidget), "button_press_event", G_CALLBACK(
134 						onButtonPress), this);
135 		g_signal_connect(G_OBJECT(glWidget), "button_release_event", G_CALLBACK(
136 						onButtonRelease), this);
137 		g_signal_connect(G_OBJECT(glWidget), "motion_notify_event", G_CALLBACK(
138 						onMouseMotion), this);
139 		g_signal_connect(G_OBJECT(glWidget), "scroll_event", G_CALLBACK(onMouseScroll),
140 				this);
141 		gtk_box_pack_start(GTK_BOX(texhbox), glWidget, true, true, 0);
142 	}
143 
144 	gtk_container_unset_focus_chain(GTK_CONTAINER(_widget));
145 
146 	gtk_widget_show_all(_widget);
147 }
148 
textureScaleImport(int value)149 void TextureBrowser::textureScaleImport (int value)
150 {
151 	int val;
152 	switch (value) {
153 	case 0:
154 		val = 10;
155 		break;
156 	case 1:
157 		val = 25;
158 		break;
159 	case 2:
160 		val = 50;
161 		break;
162 	case 3:
163 		val = 100;
164 		break;
165 	case 4:
166 		val = 200;
167 		break;
168 	default:
169 		return;
170 	}
171 	m_textureScale = val;
172 
173 	queueDraw();
174 }
175 
keyChanged(const std::string & changedKey,const std::string & newValue)176 void TextureBrowser::keyChanged (const std::string& changedKey, const std::string& newValue)
177 {
178 	m_hideUnused = GlobalRegistry().getBool(RKEY_TEXTURES_HIDE_UNUSED);
179 	m_hideInvalid = GlobalRegistry().getBool(RKEY_TEXTURES_HIDE_INVALID);
180 	m_uniformTextureSize = GlobalRegistry().getInt(RKEY_TEXTURES_UNIFORM_SIZE);
181 
182 	textureScaleImport(GlobalRegistry().getInt(RKEY_TEXTURES_THUMBNAIL_SCALE));
183 
184 	heightChanged();
185 	m_originInvalid = true;
186 }
187 
getFontHeight()188 int TextureBrowser::getFontHeight ()
189 {
190 	return GlobalOpenGL().m_fontHeight;
191 }
192 
getSelectedShader() const193 const std::string& TextureBrowser::getSelectedShader () const
194 {
195 	return shader;
196 }
197 
198 /**
199  * @brief Updates statusbar with texture information
200  */
setStatusText(const std::string & name)201 void TextureBrowser::setStatusText (const std::string& name)
202 {
203 	if (g_pParentWnd == 0)
204 		return;
205 	IShader* shaderPtr = GlobalShaderSystem().getShaderForName(name);
206 	GLTexture* q = shaderPtr->getTexture();
207 	StringOutputStream strTex(256);
208 	strTex << name << " W: " << string::toString(q->width) << " H: " << string::toString(q->height);
209 	shaderPtr->DecRef();
210 	g_pParentWnd->SetStatusText(g_pParentWnd->m_texture_status, shader_get_textureName(strTex.toString()));
211 }
212 
setSelectedShader(const std::string & _shader)213 void TextureBrowser::setSelectedShader (const std::string& _shader)
214 {
215 	shader = _shader;
216 	if (shader.empty())
217 		shader = GlobalTexturePrefix_get() + "tex_common/nodraw";
218 	setStatusText(shader);
219 	focus(shader);
220 }
221 
222 // Return the display width of a texture in the texture browser
getTextureWidth(const GLTexture * tex)223 int TextureBrowser::getTextureWidth(const GLTexture* tex) {
224 	int width;
225 	if (!m_resizeTextures) {
226 		// Don't use uniform size
227 		width = (int) (tex->width * ((float) m_textureScale / 100));
228 	} else if (tex->width >= tex->height) {
229 		// Texture is square, or wider than it is tall
230 		width = m_uniformTextureSize;
231 	} else {
232 		// Otherwise, preserve the texture's aspect ratio
233 		width = (int) (m_uniformTextureSize * ((float) tex->width / tex->height));
234 	}
235 	return width;
236 }
237 // Return the display height of a texture in the texture browser
getTextureHeight(const GLTexture * tex)238 int TextureBrowser::getTextureHeight(const GLTexture* tex) {
239 	int height;
240 	if (!m_resizeTextures) {
241 		// Don't use uniform size
242 		height = (int) (tex->height * ((float) m_textureScale / 100));
243 	} else if (tex->height >= tex->width) {
244 		// Texture is square, or taller than it is wide
245 		height = m_uniformTextureSize;
246 	} else {
247 		// Otherwise, preserve the texture's aspect ratio
248 		height = (int) (m_uniformTextureSize * ((float) tex->height / tex->width));
249 	}
250 	return height;
251 }
252 
getNextTexturePosition(TextureLayout & layout,const GLTexture * q,int * x,int * y)253 void TextureBrowser::getNextTexturePosition (TextureLayout& layout, const GLTexture* q, int* x, int* y)
254 {
255 	const int nWidth = getTextureWidth(q);
256 	const int nHeight = getTextureHeight(q);
257 	// go to the next row unless the texture is the first on the row
258 	if (layout.current_x + nWidth > width - 8 && layout.current_row) {
259 		layout.current_x = 8;
260 		layout.current_y -= layout.current_row + getFontHeight() + 4;
261 		layout.current_row = 0;
262 	}
263 
264 	*x = layout.current_x;
265 	*y = layout.current_y;
266 
267 	// Is our texture larger than the row? If so, grow the
268 	// row height to match it
269 
270 	if (layout.current_row < nHeight)
271 		layout.current_row = nHeight;
272 
273 	// never go less than 96, or the names get all crunched up
274 	layout.current_x += nWidth < 96 ? 96 : nWidth;
275 	layout.current_x += 8;
276 }
277 
278 // if texture_showinuse jump over non in-use textures
isTextureShown(const IShader * shader) const279 bool TextureBrowser::isTextureShown (const IShader* shader) const
280 {
281 	if (!shader_equal_prefix(shader->getName(), GlobalTexturePrefix_get()))
282 		return false;
283 
284 	if (m_hideUnused && !shader->isInUse())
285 		return false;
286 
287 	if (m_hideInvalid && !shader->isValid())
288 		return false;
289 
290 	if (!shader_equal_prefix(shader_get_textureName(shader->getName()), currentDirectory))
291 		return false;
292 
293 	return true;
294 }
295 
heightChanged()296 void TextureBrowser::heightChanged ()
297 {
298 	m_heightChanged = true;
299 
300 	updateScroll();
301 	queueDraw();
302 }
303 
evaluateHeight()304 void TextureBrowser::evaluateHeight ()
305 {
306 	if (!m_heightChanged)
307 		return;
308 
309 	m_heightChanged = false;
310 	m_nTotalHeight = 0;
311 
312 	TextureLayout layout;
313 	ShaderSystem &s = GlobalShaderSystem();
314 	for (s.beginActiveShadersIterator(); !s.endActiveShadersIterator(); s.incrementActiveShadersIterator()) {
315 		const IShader* shader = s.dereferenceActiveShadersIterator();
316 
317 		if (!isTextureShown(shader))
318 			continue;
319 
320 		int x, y;
321 		getNextTexturePosition(layout, shader->getTexture(), &x, &y);
322 		m_nTotalHeight = std::max(m_nTotalHeight, abs(layout.current_y) + getFontHeight() + getTextureHeight(
323 				shader->getTexture()) + 4);
324 	}
325 }
326 
getTotalHeight()327 int TextureBrowser::getTotalHeight ()
328 {
329 	evaluateHeight();
330 	return m_nTotalHeight;
331 }
332 
clampOriginY()333 void TextureBrowser::clampOriginY ()
334 {
335 	if (originy > 0) {
336 		originy = 0;
337 	}
338 	const int lower = std::min(height - getTotalHeight(), 0);
339 	if (originy < lower) {
340 		originy = lower;
341 	}
342 }
343 
getOriginY()344 int TextureBrowser::getOriginY ()
345 {
346 	if (m_originInvalid) {
347 		m_originInvalid = false;
348 		clampOriginY();
349 		updateScroll();
350 	}
351 	return originy;
352 }
353 
setOriginY(int _originy)354 void TextureBrowser::setOriginY (int _originy)
355 {
356 	originy = _originy;
357 	clampOriginY();
358 	updateScroll();
359 	queueDraw();
360 }
361 
addActiveShadersChangedCallback(const SignalHandler & handler)362 void TextureBrowser::addActiveShadersChangedCallback (const SignalHandler& handler)
363 {
364 	g_activeShadersChangedCallbacks.connectLast(handler);
365 }
366 
367 class ShadersObserver: public ModuleObserver
368 {
369 		Signal0 m_realiseCallbacks;
370 	public:
realise()371 		void realise ()
372 		{
373 			m_realiseCallbacks();
374 		}
unrealise()375 		void unrealise ()
376 		{
377 		}
insert(const SignalHandler & handler)378 		void insert (const SignalHandler& handler)
379 		{
380 			m_realiseCallbacks.connectLast(handler);
381 		}
382 };
383 
384 namespace {
385 ShadersObserver g_ShadersObserver;
386 }
387 
activeShadersChanged()388 void TextureBrowser::activeShadersChanged ()
389 {
390 	heightChanged();
391 	m_originInvalid = true;
392 
393 	g_activeShadersChangedCallbacks();
394 }
395 
396 /**
397  * @note relies on texture_directory global for the directory to use
398  * 1) Load the shaders for the given directory
399  * 2) Scan the remaining texture, load them and assign them a default shader (the "noshader" shader)
400  * NOTE: when writing a texture plugin, or some texture extensions, this function may need to be overriden, and made
401  * available through the IShaders interface
402  * NOTE: for texture window layout:
403  * all shaders are stored with alphabetical order after load
404  * previously loaded and displayed stuff is hidden, only in-use and newly loaded is shown
405  * ( the GL textures are not flushed though)
406  */
texture_name_ignore(const std::string & name)407 static bool texture_name_ignore (const std::string& name)
408 {
409 	if (string::contains(name, "_nm"))
410 		return true;
411 	if (string::contains(name, "_gm"))
412 		return true;
413 	if (string::contains(name, "_sm"))
414 		return true;
415 
416 	return string::contains(name, "tex_terrain") && !string::contains(name, "dummy");
417 }
418 
419 class LoadTexturesByTypeVisitor: public ImageModules::Visitor
420 {
421 	private:
422 		const std::string m_dirstring;
423 		// Modal dialog window to display progress
424 		gtkutil::ModalProgressDialog _dialog;
425 
426 		struct TextureDirectoryLoadTextureCaller
427 		{
428 				typedef const std::string& first_argument_type;
429 
430 				const std::string& _directory;
431 				const gtkutil::ModalProgressDialog& _dialog;
432 
433 				// Constructor
TextureDirectoryLoadTextureCallerLoadTexturesByTypeVisitor::TextureDirectoryLoadTextureCaller434 				TextureDirectoryLoadTextureCaller (const std::string& directory,
435 						const gtkutil::ModalProgressDialog& dialog) :
436 					_directory(directory), _dialog(dialog)
437 				{
438 				}
439 
440 				// Functor operator
operator ()LoadTexturesByTypeVisitor::TextureDirectoryLoadTextureCaller441 				void operator() (const std::string& texture)
442 				{
443 					std::string name = _directory + os::stripExtension(texture);
444 
445 					if (texture_name_ignore(name))
446 						return;
447 
448 					if (!shader_valid(name)) {
449 						g_warning("Skipping invalid texture name: [%s]\n", name.c_str());
450 						return;
451 					}
452 
453 					// Update the text in the dialog
454 					_dialog.setText(name);
455 
456 					// if a texture is already in use to represent a shader, ignore it
457 					IShader* shaderPtr = GlobalShaderSystem().getShaderForName(name);
458 					shaderPtr->DecRef();
459 				}
460 		};
461 
462 	public:
LoadTexturesByTypeVisitor(const std::string & dirstring)463 		LoadTexturesByTypeVisitor (const std::string& dirstring) :
464 			m_dirstring(dirstring), _dialog(MainFrame_getWindow(), _("Loading textures"))
465 		{
466 		}
visit(const std::string & minor,const IImageModule & table) const467 		void visit (const std::string& minor, const IImageModule& table) const
468 		{
469 			TextureDirectoryLoadTextureCaller functor(m_dirstring, _dialog);
470 			GlobalFileSystem().forEachFile(m_dirstring, minor, makeCallback1(functor));
471 		}
472 };
473 
showDirectory(const std::string & directory)474 void TextureBrowser::showDirectory (const std::string& directory)
475 {
476 	currentDirectory = directory;
477 	heightChanged();
478 
479 	// load remaining texture files
480 	Radiant_getImageModules().foreachModule(LoadTexturesByTypeVisitor(GlobalTexturePrefix_get() + directory));
481 
482 	// we'll display the newly loaded textures + all the ones already in use
483 	GlobalRegistry().set(RKEY_TEXTURES_HIDE_UNUSED, "0");
484 	GlobalRegistry().set(RKEY_TEXTURES_HIDE_INVALID, "0");
485 }
486 
showStartupShaders()487 void TextureBrowser::showStartupShaders ()
488 {
489 	showDirectory("tex_common/");
490 }
491 
492 //++timo NOTE: this is a mix of Shader module stuff and texture explorer
493 // it might need to be split in parts or moved out .. dunno
494 // scroll origin so the specified texture is completely on screen
495 // if current texture is not displayed, nothing is changed
focus(const std::string & name)496 void TextureBrowser::focus (const std::string& name)
497 {
498 	TextureLayout layout;
499 	// scroll origin so the texture is completely on screen
500 	for (GlobalShaderSystem().beginActiveShadersIterator(); !GlobalShaderSystem().endActiveShadersIterator(); GlobalShaderSystem().incrementActiveShadersIterator()) {
501 		const IShader* shader = GlobalShaderSystem().dereferenceActiveShadersIterator();
502 
503 		if (!isTextureShown(shader))
504 			continue;
505 
506 		int x, y;
507 		getNextTexturePosition(layout, shader->getTexture(), &x, &y);
508 		const GLTexture* q = shader->getTexture();
509 		if (!q)
510 			break;
511 
512 		// we have found when texdef->name and the shader name match
513 		// NOTE: as everywhere else for our comparisons, we are not case sensitive
514 		if (shader_equal(name, shader->getName())) {
515 			const int textureHeight = getTextureHeight(q);
516 
517 			int originy = getOriginY();
518 			if (y > originy) {
519 				originy = y;
520 			}
521 
522 			if (y - textureHeight < originy - height) {
523 				originy = (y - textureHeight) + height;
524 			}
525 
526 			setOriginY(originy);
527 			return;
528 		}
529 	}
530 }
531 
getTextureAt(int mx,int my)532 const IShader* TextureBrowser::getTextureAt (int mx, int my)
533 {
534 	my += getOriginY() - height;
535 
536 	TextureLayout layout;
537 	for (GlobalShaderSystem().beginActiveShadersIterator(); !GlobalShaderSystem().endActiveShadersIterator(); GlobalShaderSystem().incrementActiveShadersIterator()) {
538 		const IShader* shader = GlobalShaderSystem().dereferenceActiveShadersIterator();
539 
540 		if (!isTextureShown(shader))
541 			continue;
542 
543 		int x, y;
544 		getNextTexturePosition(layout, shader->getTexture(), &x, &y);
545 		GLTexture *q = shader->getTexture();
546 		if (!q)
547 			break;
548 
549 		int nWidth = getTextureWidth(q);
550 		int nHeight = getTextureHeight(q);
551 		if (mx > x && mx - x < nWidth && my < y && y - my < nHeight + getFontHeight()) {
552 			return shader;
553 		}
554 	}
555 
556 	return 0;
557 }
558 
selectTextureAt(int mx,int my)559 void TextureBrowser::selectTextureAt (int mx, int my)
560 {
561 	const IShader* shader = getTextureAt(mx, my);
562 	if (shader != 0) {
563 		setSelectedShader(shader->getName());
564 
565 		if (!m_rmbSelected) {
566 			UndoableCommand undo("textureNameSetSelected");
567 			Select_SetShader(shader->getName(), GlobalRegistry().getBool(RKEY_TEXTURES_SKIPCOMMON));
568 		}
569 	}
570 }
571 
572 /**
573  * @brief relying on the shaders list to display the textures
574  * we must query all GLTexture* to manage and display through the IShaders interface
575  * this allows a plugin to completely override the texture system
576  */
draw()577 void TextureBrowser::draw ()
578 {
579 	const int originy = getOriginY();
580 
581 	Vector3 colorBackground = ColourSchemes().getColourVector3("texture_background");
582 	glClearColor(colorBackground[0], colorBackground[1], colorBackground[2], 0);
583 	glViewport(0, 0, width, height);
584 	glMatrixMode(GL_PROJECTION);
585 	glLoadIdentity();
586 
587 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
588 	glDisable(GL_DEPTH_TEST);
589 	glDisable(GL_BLEND);
590 	glOrtho(0, width, originy - height, originy, -100, 100);
591 	glEnable(GL_TEXTURE_2D);
592 
593 	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
594 
595 	int last_y = 0, last_height = 0;
596 
597 	TextureLayout layout;
598 	for (GlobalShaderSystem().beginActiveShadersIterator(); !GlobalShaderSystem().endActiveShadersIterator(); GlobalShaderSystem().incrementActiveShadersIterator()) {
599 		const IShader* shader = GlobalShaderSystem().dereferenceActiveShadersIterator();
600 
601 		if (!isTextureShown(shader))
602 			continue;
603 
604 		int x, y;
605 		getNextTexturePosition(layout, shader->getTexture(), &x, &y);
606 		const GLTexture *q = shader->getTexture();
607 		if (!q)
608 			break;
609 
610 		const int nWidth = getTextureWidth(q);
611 		const int nHeight = getTextureHeight(q);
612 
613 		if (y != last_y) {
614 			last_y = y;
615 			last_height = 0;
616 		}
617 		last_height = std::max(nHeight, last_height);
618 
619 		// Is this texture visible?
620 		if ((y - nHeight - getFontHeight() < originy) && (y > originy - height)) {
621 			// borders rules:
622 			// if it's the current texture, draw a thick red line, else:
623 			// shaders have a white border, simple textures don't
624 			// if !texture_showinuse: (some textures displayed may not be in use)
625 			// draw an additional square around with 0.5 1 0.5 color
626 			if (shader_equal(getSelectedShader(), shader->getName())) {
627 				glLineWidth(3);
628 				if (m_rmbSelected) {
629 					glColor3f(0, 0, 1);
630 				} else {
631 					glColor3f(1, 0, 0);
632 				}
633 				glDisable(GL_TEXTURE_2D);
634 
635 				glBegin(GL_LINE_LOOP);
636 				glVertex2i(x - 4, y - getFontHeight() + 4);
637 				glVertex2i(x - 4, y - getFontHeight() - nHeight - 4);
638 				glVertex2i(x + 4 + nWidth, y - getFontHeight() - nHeight - 4);
639 				glVertex2i(x + 4 + nWidth, y - getFontHeight() + 4);
640 				glEnd();
641 
642 				glEnable(GL_TEXTURE_2D);
643 				glLineWidth(1);
644 			} else {
645 				glLineWidth(1);
646 				// shader border:
647 				// TODO: Highlight if material is available for this texture (mattn)
648 				if (!shader->isDefault()) {
649 					glColor3f(1, 1, 1);
650 					glDisable(GL_TEXTURE_2D);
651 
652 					glBegin(GL_LINE_LOOP);
653 					glVertex2i(x - 1, y + 1 - getFontHeight());
654 					glVertex2i(x - 1, y - nHeight - 1 - getFontHeight());
655 					glVertex2i(x + 1 + nWidth, y - nHeight - 1 - getFontHeight());
656 					glVertex2i(x + 1 + nWidth, y + 1 - getFontHeight());
657 					glEnd();
658 					glEnable(GL_TEXTURE_2D);
659 				}
660 
661 				// highlight in-use textures
662 				if (!m_hideUnused && shader->isInUse()) {
663 					glColor3f(0.5, 1, 0.5);
664 					glDisable(GL_TEXTURE_2D);
665 					glBegin(GL_LINE_LOOP);
666 					glVertex2i(x - 3, y + 3 - getFontHeight());
667 					glVertex2i(x - 3, y - nHeight - 3 - getFontHeight());
668 					glVertex2i(x + 3 + nWidth, y - nHeight - 3 - getFontHeight());
669 					glVertex2i(x + 3 + nWidth, y + 3 - getFontHeight());
670 					glEnd();
671 					glEnable(GL_TEXTURE_2D);
672 				}
673 				if (!m_hideInvalid && !shader->isValid()) {
674 					glColor3f(1, 0, 0);
675 					glDisable(GL_TEXTURE_2D);
676 					glBegin(GL_LINE_LOOP);
677 					glVertex2i(x - 3, y + 3 - getFontHeight());
678 					glVertex2i(x - 3, y - nHeight - 3 - getFontHeight());
679 					glVertex2i(x + 3 + nWidth, y - nHeight - 3 - getFontHeight());
680 					glVertex2i(x + 3 + nWidth, y + 3 - getFontHeight());
681 					glEnd();
682 					glEnable(GL_TEXTURE_2D);
683 				}
684 			}
685 
686 			// Draw the texture
687 			glBindTexture(GL_TEXTURE_2D, q->texture_number);
688 			glColor3f(1, 1, 1);
689 			glBegin(GL_QUADS);
690 			glTexCoord2i(0, 0);
691 			glVertex2i(x, y - getFontHeight());
692 			glTexCoord2i(1, 0);
693 			glVertex2i(x + nWidth, y - getFontHeight());
694 			glTexCoord2i(1, 1);
695 			glVertex2i(x + nWidth, y - getFontHeight() - nHeight);
696 			glTexCoord2i(0, 1);
697 			glVertex2i(x, y - getFontHeight() - nHeight);
698 			glEnd();
699 
700 			// draw the texture name
701 			glDisable(GL_TEXTURE_2D);
702 			glColor3f(1, 1, 1);
703 
704 			glRasterPos2i(x, y - getFontHeight() + 5);
705 
706 			// don't draw the directory name
707 			std::string name = std::string(shader->getName());
708 			name = name.substr(name.rfind("/") + 1);
709 
710 			// Ellipsize the name if it's too long
711 			unsigned int maxNameLength = 32;
712 			if (name.size() > maxNameLength) {
713 				name = name.substr(0, maxNameLength / 2) + "..." + name.substr(name.size() - maxNameLength / 2);
714 			}
715 
716 			GlobalOpenGL().drawString(name);
717 			glEnable(GL_TEXTURE_2D);
718 		}
719 
720 		//int totalHeight = abs(y) + last_height + fontHeight() + 4;
721 	}
722 
723 	// reset the current texture
724 	glBindTexture(GL_TEXTURE_2D, 0);
725 }
726 
queueDraw()727 void TextureBrowser::queueDraw ()
728 {
729 	GtkWidget *glWidget = _glWidget;
730 	gtk_widget_queue_draw(glWidget);
731 }
732 
onMouseWheel(bool bUp)733 void TextureBrowser::onMouseWheel (bool bUp)
734 {
735 	int originy = getOriginY();
736 
737 	if (bUp) {
738 		originy += 64;
739 	} else {
740 		originy -= 64;
741 	}
742 
743 	setOriginY(originy);
744 }
745 
746 // GTK callback for toggling uniform texture sizing
onToggleResizeTextures(GtkToggleToolButton * button,TextureBrowser * textureBrowser)747 void TextureBrowser::onToggleResizeTextures (GtkToggleToolButton* button, TextureBrowser* textureBrowser)
748 {
749 	if (gtk_toggle_tool_button_get_active(button) == TRUE) {
750 		textureBrowser->m_resizeTextures = true;
751 	} else {
752 		textureBrowser->m_resizeTextures = false;
753 	}
754 
755 	// Update texture browser
756 	textureBrowser->heightChanged();
757 }
758 
onButtonPress(GtkWidget * widget,GdkEventButton * event,TextureBrowser * textureBrowser)759 gboolean TextureBrowser::onButtonPress (GtkWidget* widget, GdkEventButton* event, TextureBrowser* textureBrowser)
760 {
761 	if (event->type == GDK_BUTTON_PRESS) {
762 		if (event->button == 1) {
763 			const int pointx = static_cast<int> (event->x);
764 			const int pointy = static_cast<int> (event->y);
765 			textureBrowser->selectTextureAt(pointx, textureBrowser->height - 1 - pointy);
766 		}
767 	}
768 	return FALSE;
769 }
770 
onButtonRelease(GtkWidget * widget,GdkEventButton * event,TextureBrowser * textureBrowser)771 gboolean TextureBrowser::onButtonRelease (GtkWidget* widget, GdkEventButton* event, TextureBrowser* textureBrowser)
772 {
773 	if (event->type == GDK_BUTTON_RELEASE) {
774 		if (event->button == 1) {
775 		}
776 	}
777 	return FALSE;
778 }
779 
onMouseMotion(GtkWidget * widget,GdkEventMotion * event,TextureBrowser * textureBrowser)780 gboolean TextureBrowser::onMouseMotion (GtkWidget *widget, GdkEventMotion *event, TextureBrowser* textureBrowser)
781 {
782 	return FALSE;
783 }
784 
onMouseScroll(GtkWidget * widget,GdkEventScroll * event,TextureBrowser * textureBrowser)785 gboolean TextureBrowser::onMouseScroll (GtkWidget* widget, GdkEventScroll* event, TextureBrowser* textureBrowser)
786 {
787 	if (event->direction == GDK_SCROLL_UP) {
788 		textureBrowser->onMouseWheel(true);
789 	} else if (event->direction == GDK_SCROLL_DOWN) {
790 		textureBrowser->onMouseWheel(false);
791 	}
792 	return FALSE;
793 }
794 
TextureBrowser_scrollChanged(void * data,gdouble value)795 void TextureBrowser_scrollChanged (void* data, gdouble value)
796 {
797 	TextureBrowser *textureBrowser = reinterpret_cast<TextureBrowser*> (data);
798 	textureBrowser->setOriginY(-(int) value);
799 }
800 
onVerticalScroll(GtkAdjustment * adjustment,TextureBrowser * textureBrowser)801 void TextureBrowser::onVerticalScroll (GtkAdjustment *adjustment, TextureBrowser* textureBrowser)
802 {
803 	textureBrowser->m_scrollAdjustment.value_changed(adjustment->value);
804 }
805 
updateScroll()806 void TextureBrowser::updateScroll ()
807 {
808 	if (!m_texture_scroll)
809 		return;
810 	int totalHeight = getTotalHeight();
811 
812 	totalHeight = std::max(totalHeight, height);
813 
814 	GtkAdjustment *vadjustment = gtk_range_get_adjustment(GTK_RANGE(m_texture_scroll));
815 	if (!vadjustment)
816 		return;
817 	vadjustment->value = -getOriginY();
818 	vadjustment->page_size = height;
819 	vadjustment->page_increment = height / 2;
820 	vadjustment->step_increment = 20;
821 	vadjustment->lower = 0;
822 	vadjustment->upper = totalHeight;
823 
824 	g_signal_emit_by_name(G_OBJECT (vadjustment), "changed");
825 }
826 
onSizeAllocate(GtkWidget * widget,GtkAllocation * allocation,TextureBrowser * textureBrowser)827 gboolean TextureBrowser::onSizeAllocate (GtkWidget* widget, GtkAllocation* allocation, TextureBrowser* textureBrowser)
828 {
829 	textureBrowser->width = allocation->width;
830 	textureBrowser->height = allocation->height;
831 	textureBrowser->heightChanged();
832 	textureBrowser->m_originInvalid = true;
833 	textureBrowser->queueDraw();
834 	return FALSE;
835 }
836 
onExpose(GtkWidget * widget,GdkEventExpose * event,TextureBrowser * textureBrowser)837 gboolean TextureBrowser::onExpose (GtkWidget* widget, GdkEventExpose* event, TextureBrowser* textureBrowser)
838 {
839 	GtkWidget* glWidget = textureBrowser->_glWidget;
840 	gtkutil::GLWidgetSentry sentry(glWidget);
841 	textureBrowser->evaluateHeight();
842 	textureBrowser->draw();
843 	return FALSE;
844 }
845 
846 namespace {
847 struct TextureFunctor
848 {
849 		typedef const std::string& first_argument_type;
850 
851 		// TextureGroups to populate
852 		TextureGroups& _groups;
853 
854 		// Constructor
TextureFunctor__anonbe4590f60311::TextureFunctor855 		TextureFunctor (TextureGroups& groups) :
856 			_groups(groups)
857 		{
858 		}
859 
860 		// Functor operator
operator ()__anonbe4590f60311::TextureFunctor861 		void operator() (const std::string& file)
862 		{
863 			const std::string texture = os::makeRelative(file, GlobalTexturePrefix_get());
864 			if (texture != file) {
865 				std::string filename = os::getFilenameFromPath(file);
866 				if (!filename.empty()) {
867 					std::string path = os::stripFilename(texture);
868 					_groups.insert(path);
869 				}
870 			}
871 		}
872 };
873 
874 struct TextureDirectoryFunctor
875 {
876 		typedef const std::string& first_argument_type;
877 
878 		// TextureGroups to populate
879 		TextureGroups& _groups;
880 
881 		// Constructor
TextureDirectoryFunctor__anonbe4590f60311::TextureDirectoryFunctor882 		TextureDirectoryFunctor (TextureGroups& groups) :
883 			_groups(groups)
884 		{
885 		}
886 
887 		// Functor operator
operator ()__anonbe4590f60311::TextureDirectoryFunctor888 		void operator() (const std::string& directory)
889 		{
890 			// skip none texture subdirs
891 			if (!string::startsWith(directory, "tex_"))
892 				return;
893 
894 			_groups.insert(directory);
895 		}
896 };
897 }
898 
constructTreeView()899 void TextureBrowser::constructTreeView ()
900 {
901 	TextureDirectoryFunctor functorDirs(groups);
902 	GlobalFileSystem().forEachDirectory(GlobalTexturePrefix_get(), makeCallback1(functorDirs));
903 	TextureFunctor functorTextures(groups);
904 	GlobalShaderSystem().foreachShaderName(makeCallback1(functorTextures));
905 }
906 
onActivateDirectoryChange(GtkMenuItem * item,TextureBrowser * textureBrowser)907 void TextureBrowser::onActivateDirectoryChange (GtkMenuItem* item, TextureBrowser* textureBrowser)
908 {
909 	std::string const& dirname = *static_cast<std::string const*>(g_object_get_data(G_OBJECT(item), "directory")) + "/";
910 	textureBrowser->showDirectory(dirname);
911 	textureBrowser->queueDraw();
912 }
913 
constructDirectoriesMenu(GtkMenu * menu)914 GtkMenuItem* TextureBrowser::constructDirectoriesMenu (GtkMenu* menu)
915 {
916 	GtkMenuItem* textures_menu_item = new_sub_menu_item_with_mnemonic(_("_Directories"));
917 
918 	constructTreeView();
919 
920 	TextureGroups::const_iterator i = groups.begin();
921 	while (i != groups.end()) {
922 		GtkWidget* const item = gtk_menu_item_new_with_label(i->c_str());
923 		g_object_set_data(G_OBJECT(item), "directory", const_cast<std::string*>(&*i));
924 		g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(onActivateDirectoryChange), this);
925 		gtk_container_add(GTK_CONTAINER(menu), item);
926 		++i;
927 	}
928 
929 	return textures_menu_item;
930 }
931 
createMenuBar()932 GtkWidget* TextureBrowser::createMenuBar ()
933 {
934 	GtkWidget* menu_bar = gtk_menu_bar_new();
935 
936 	GtkWidget* menu_directories = gtk_menu_new();
937 	GtkWidget* directories_item = (GtkWidget*) constructDirectoriesMenu(GTK_MENU(menu_directories));
938 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(directories_item), menu_directories);
939 	gtk_menu_bar_append(GTK_MENU_BAR(menu_bar), directories_item);
940 
941 	return menu_bar;
942 }
943 
createToolBar()944 GtkWidget* TextureBrowser::createToolBar ()
945 {
946 	// Load the texture toolbar from the registry
947 	ui::ToolbarCreator toolbarCreator;
948 	GtkToolbar* textureToolbar = toolbarCreator.getToolbar("texture");
949 	if (textureToolbar != NULL) {
950 		gtk_widget_show(GTK_WIDGET(textureToolbar));
951 
952 		// Button for toggling the resizing of textures
953 		GtkToolItem* sizeToggle = gtk_toggle_tool_button_new();
954 		GdkPixbuf* pixBuf = gtkutil::getLocalPixbuf("texwindow_uniformsize.png");
955 		GtkWidget* toggle_image = GTK_WIDGET(gtk_image_new_from_pixbuf(pixBuf));
956 
957 		GtkTooltips* barTips = gtk_tooltips_new();
958 		gtk_tool_item_set_tooltip(sizeToggle, barTips, _("Clamp texture thumbnails to constant size"), "");
959 
960 		gtk_tool_button_set_label(GTK_TOOL_BUTTON(sizeToggle), _("Constant size"));
961 		gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(sizeToggle), toggle_image);
962 		gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(sizeToggle), TRUE);
963 
964 		// Insert button and connect callback
965 		gtk_toolbar_insert(GTK_TOOLBAR(textureToolbar), sizeToggle, 0);
966 		g_signal_connect(G_OBJECT(sizeToggle),
967 				"toggled",
968 				G_CALLBACK(onToggleResizeTextures),
969 				this);
970 
971 		g_object_unref(pixBuf);
972 	}
973 
974 	return GTK_WIDGET(textureToolbar);
975 }
976 
getWidget() const977 GtkWidget* TextureBrowser::getWidget () const
978 {
979 	return _widget;
980 }
981 
getTitle() const982 const std::string TextureBrowser::getTitle() const
983 {
984 	return _("Textures");
985 }
986 
showAll()987 void TextureBrowser::showAll ()
988 {
989 	currentDirectory = "";
990 	heightChanged();
991 }
992 
refreshShaders()993 void TextureBrowser::refreshShaders ()
994 {
995 	ScopeDisableScreenUpdates disableScreenUpdates(_("Processing..."), _("Loading Shaders"));
996 	GlobalShaderSystem().refresh();
997 	showDirectory(currentDirectory);
998 	UpdateAllWindows();
999 }
1000 
constructPreferencePage(PreferenceGroup & group)1001 void TextureBrowser::constructPreferencePage(PreferenceGroup& group) {
1002 	// Add a page to the given group
1003 	PreferencesPage* page(group.createPage(_("Texture Browser"), _("Texture Browser Preferences")));
1004 
1005 	// Create the string list containing the texture scale options
1006 	std::list<std::string> textureScaleDescriptions;
1007 	textureScaleDescriptions.push_back("10%");
1008 	textureScaleDescriptions.push_back("25%");
1009 	textureScaleDescriptions.push_back("50%");
1010 	textureScaleDescriptions.push_back("100%");
1011 	textureScaleDescriptions.push_back("200%");
1012 
1013 	page->appendCombo(_("Texture thumbnail scale"), RKEY_TEXTURES_THUMBNAIL_SCALE, textureScaleDescriptions);
1014 	page->appendEntry(_("Uniform texture thumbnail size (pixels)"), RKEY_TEXTURES_UNIFORM_SIZE);
1015 	page->appendPathEntry(_("License Path"), RKEY_TEXTURES_LICENSE_PATH, false);
1016 }
1017 
registerCommands()1018 void TextureBrowser::registerCommands ()
1019 {
1020 	GlobalEventManager().addCommand("RefreshShaders", MemberCaller<TextureBrowser, &TextureBrowser::refreshShaders> (
1021 			*this));
1022 	GlobalEventManager().addCommand("ShowAllTextures", MemberCaller<TextureBrowser, &TextureBrowser::showAll> (*this));
1023 }
1024 
1025 #include "preferencesystem.h"
1026 #include "stringio.h"
1027 
TextureBrowser_Construct()1028 void TextureBrowser_Construct ()
1029 {
1030 	GlobalTextureBrowser().registerCommands();
1031 
1032 	GlobalEventManager().addRegistryToggle("ShowInUse", RKEY_TEXTURES_HIDE_UNUSED);
1033 	GlobalEventManager().addRegistryToggle("ShowInvalid", RKEY_TEXTURES_HIDE_INVALID);
1034 	GlobalEventManager().addRegistryToggle("SkipCommon", RKEY_TEXTURES_SKIPCOMMON);
1035 
1036 	GlobalShaderSystem().attach(g_ShadersObserver);
1037 }
1038 
TextureBrowser_Destroy()1039 void TextureBrowser_Destroy ()
1040 {
1041 	GlobalShaderSystem().detach(g_ShadersObserver);
1042 
1043 	GlobalShaderSystem().setActiveShadersChangedNotify(Callback());
1044 }
1045 
GlobalTextureBrowser()1046 TextureBrowser& GlobalTextureBrowser ()
1047 {
1048 	static TextureBrowser g_textureBrowser;
1049 	return g_textureBrowser;
1050 }
1051