+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
+ (event != NULL) ? event->button : 0,
+ gdk_event_get_time((GdkEvent *) event));
+}
+
+gboolean TreeViewTags_onButtonPressed(ui::TreeView treeview, GdkEventButton *event)
+{
+ if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
+ GtkTreePath *path;
+ auto selection = gtk_tree_view_get_selection(treeview);
+
+ if (gtk_tree_view_get_path_at_pos(treeview, event->x, event->y, &path, NULL, NULL, NULL)) {
+ gtk_tree_selection_unselect_all(selection);
+ gtk_tree_selection_select_path(selection, path);
+ gtk_tree_path_free(path);
+ }
+
+ TextureBrowser_createContextMenu(treeview, event);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void TextureBrowser_createTreeViewTags()
+{
+ g_TextureBrowser.m_treeViewTags = ui::TreeView(ui::New);
+ gtk_tree_view_set_enable_search(g_TextureBrowser.m_treeViewTags, FALSE);
+
+ g_TextureBrowser.m_treeViewTags.connect("button-press-event", (GCallback) TreeViewTags_onButtonPressed, NULL);
+
+ gtk_tree_view_set_headers_visible(g_TextureBrowser.m_treeViewTags, FALSE);
+
+ auto renderer = ui::CellRendererText(ui::New);
+ gtk_tree_view_insert_column_with_attributes(g_TextureBrowser.m_treeViewTags, -1, "", renderer, "text", 0, NULL);
+
+ TextureBrowser_constructTreeStoreTags();
+}
+
+ui::MenuItem TextureBrowser_constructViewMenu(ui::Menu menu)
+{
+ ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic("_View"));
+
+ if (g_Layout_enableDetachableMenus.m_value) {
+ menu_tearoff(menu);
+ }
+
+ create_check_menu_item_with_mnemonic(menu, "Hide _Unused", "ShowInUse");
+ if (string_empty(g_pGameDescription->getKeyValue("show_wads"))) {
+ create_check_menu_item_with_mnemonic(menu, "Hide Image Missing", "FilterMissing");
+ }
+
+ // hide notex and shadernotex on texture browser: no one wants to apply them
+ create_check_menu_item_with_mnemonic(menu, "Hide Fallback", "FilterFallback");
+
+ menu_separator(menu);
+
+ create_menu_item_with_mnemonic(menu, "Show All", "ShowAllTextures");
+
+ // 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" || !string_empty(g_pGameDescription->getKeyValue("show_wads"))) {
+ g_TextureBrowser.m_showShaders = true;
+ } else {
+ create_check_menu_item_with_mnemonic(menu, "Show shaders", "ToggleShowShaders");
+ }
+
+ if (g_pGameDescription->mGameType != "doom3" && string_empty(g_pGameDescription->getKeyValue("show_wads"))) {
+ create_check_menu_item_with_mnemonic(menu, "Shaders Only", "ToggleShowShaderlistOnly");
+ }
+ if (g_TextureBrowser.m_tags) {
+ create_menu_item_with_mnemonic(menu, "Show Untagged", "ShowUntagged");
+ }
+
+ menu_separator(menu);
+ create_check_menu_item_with_mnemonic(menu, "Fixed Size", "FixedSize");
+ create_check_menu_item_with_mnemonic(menu, "Transparency", "EnableAlpha");
+
+ if (string_empty(g_pGameDescription->getKeyValue("show_wads"))) {
+ menu_separator(menu);
+ g_TextureBrowser.m_shader_info_item = ui::Widget(
+ create_menu_item_with_mnemonic(menu, "Shader Info", "ShaderInfo"));
+ gtk_widget_set_sensitive(g_TextureBrowser.m_shader_info_item, FALSE);
+ }
+
+
+ return textures_menu_item;
+}
+
+ui::MenuItem TextureBrowser_constructToolsMenu(ui::Menu menu)
+{
+ ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic("_Tools"));
+
+ if (g_Layout_enableDetachableMenus.m_value) {
+ menu_tearoff(menu);
+ }
+
+ create_menu_item_with_mnemonic(menu, "Flush & Reload Shaders", "RefreshShaders");
+ create_menu_item_with_mnemonic(menu, "Find / Replace...", "FindReplaceTextures");
+
+ return textures_menu_item;
+}
+
+ui::MenuItem TextureBrowser_constructTagsMenu(ui::Menu menu)
+{
+ ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic("T_ags"));
+
+ if (g_Layout_enableDetachableMenus.m_value) {
+ menu_tearoff(menu);
+ }
+
+ create_menu_item_with_mnemonic(menu, "Add tag", "AddTag");
+ create_menu_item_with_mnemonic(menu, "Rename tag", "RenameTag");
+ create_menu_item_with_mnemonic(menu, "Delete tag", "DeleteTag");
+ menu_separator(menu);
+ create_menu_item_with_mnemonic(menu, "Copy tags from selected", "CopyTag");
+ create_menu_item_with_mnemonic(menu, "Paste tags to selected", "PasteTag");
+
+ return textures_menu_item;
+}
+
+gboolean TextureBrowser_tagMoveHelper(ui::TreeModel model, ui::TreePath path, GtkTreeIter iter, GSList **selected)
+{
+ g_assert(selected != NULL);
+
+ auto rowref = gtk_tree_row_reference_new(model, path);
+ *selected = g_slist_append(*selected, rowref);
+
+ return FALSE;
+}
+
+void TextureBrowser_assignTags()
+{
+ GSList *selected = NULL;
+ GSList *node;
+ gchar *tag_assigned;
+
+ auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_available_tree);
+
+ gtk_tree_selection_selected_foreach(selection, (GtkTreeSelectionForeachFunc) TextureBrowser_tagMoveHelper,
+ &selected);
+
+ if (selected != NULL) {
+ for (node = selected; node != NULL; node = node->next) {
+ auto path = gtk_tree_row_reference_get_path((GtkTreeRowReference *) node->data);
+
+ if (path) {
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_get_iter(g_TextureBrowser.m_available_store, &iter, path)) {
+ gtk_tree_model_get(g_TextureBrowser.m_available_store, &iter, TAG_COLUMN, &tag_assigned, -1);
+ if (!TagBuilder.CheckShaderTag(g_TextureBrowser.shader.c_str())) {
+ // create a custom shader/texture entry
+ IShader *ishader = QERApp_Shader_ForName(g_TextureBrowser.shader.c_str());
+ CopiedString filename = ishader->getShaderFileName();
+
+ if (filename.empty()) {
+ // it's a texture
+ TagBuilder.AddShaderNode(g_TextureBrowser.shader.c_str(), CUSTOM, TEXTURE);
+ } else {
+ // it's a shader
+ TagBuilder.AddShaderNode(g_TextureBrowser.shader.c_str(), CUSTOM, SHADER);
+ }
+ ishader->DecRef();
+ }
+ TagBuilder.AddShaderTag(g_TextureBrowser.shader.c_str(), (char *) tag_assigned, TAG);
+
+ gtk_list_store_remove(g_TextureBrowser.m_available_store, &iter);
+ g_TextureBrowser.m_assigned_store.append(TAG_COLUMN, tag_assigned);
+ }
+ }
+ }
+
+ g_slist_foreach(selected, (GFunc) gtk_tree_row_reference_free, NULL);
+
+ // Save changes
+ TagBuilder.SaveXmlDoc();
+ }
+ g_slist_free(selected);
+}
+
+void TextureBrowser_removeTags()
+{
+ GSList *selected = NULL;
+ GSList *node;
+ gchar *tag;
+
+ auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_assigned_tree);
+
+ gtk_tree_selection_selected_foreach(selection, (GtkTreeSelectionForeachFunc) TextureBrowser_tagMoveHelper,
+ &selected);
+
+ if (selected != NULL) {
+ for (node = selected; node != NULL; node = node->next) {
+ auto path = gtk_tree_row_reference_get_path((GtkTreeRowReference *) node->data);
+
+ if (path) {
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_get_iter(g_TextureBrowser.m_assigned_store, &iter, path)) {
+ gtk_tree_model_get(g_TextureBrowser.m_assigned_store, &iter, TAG_COLUMN, &tag, -1);
+ TagBuilder.DeleteShaderTag(g_TextureBrowser.shader.c_str(), tag);
+ gtk_list_store_remove(g_TextureBrowser.m_assigned_store, &iter);
+ }
+ }
+ }
+
+ g_slist_foreach(selected, (GFunc) gtk_tree_row_reference_free, NULL);
+
+ // Update the "available tags list"
+ BuildStoreAvailableTags(g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store,
+ g_TextureBrowser.m_all_tags, &g_TextureBrowser);
+
+ // Save changes
+ TagBuilder.SaveXmlDoc();
+ }
+ g_slist_free(selected);
+}
+
+void TextureBrowser_buildTagList()
+{
+ g_TextureBrowser.m_all_tags_list.clear();
+
+ std::set<CopiedString>::iterator iter;
+
+ for (iter = g_TextureBrowser.m_all_tags.begin(); iter != g_TextureBrowser.m_all_tags.end(); ++iter) {
+ g_TextureBrowser.m_all_tags_list.append(TAG_COLUMN, (*iter).c_str());
+ }
+}
+
+void TextureBrowser_searchTags()
+{
+ GSList *selected = NULL;
+ GSList *node;
+ gchar *tag;
+ char buffer[256];
+ char tags_searched[256];
+
+ auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_treeViewTags);
+
+ gtk_tree_selection_selected_foreach(selection, (GtkTreeSelectionForeachFunc) TextureBrowser_tagMoveHelper,
+ &selected);
+
+ if (selected != NULL) {
+ strcpy(buffer, "/root/*/*[tag='");
+ strcpy(tags_searched, "[TAGS] ");
+
+ for (node = selected; node != NULL; node = node->next) {
+ auto path = gtk_tree_row_reference_get_path((GtkTreeRowReference *) node->data);
+
+ if (path) {
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_get_iter(g_TextureBrowser.m_all_tags_list, &iter, path)) {
+ gtk_tree_model_get(g_TextureBrowser.m_all_tags_list, &iter, TAG_COLUMN, &tag, -1);
+
+ strcat(buffer, tag);
+ strcat(tags_searched, tag);
+ if (node != g_slist_last(node)) {
+ strcat(buffer, "' and tag='");
+ strcat(tags_searched, ", ");
+ }
+ }
+ }
+ }
+
+ strcat(buffer, "']");
+
+ g_slist_foreach(selected, (GFunc) gtk_tree_row_reference_free, NULL);
+
+ g_TextureBrowser.m_found_shaders.clear(); // delete old list
+ TagBuilder.TagSearch(buffer, g_TextureBrowser.m_found_shaders);
+
+ if (!g_TextureBrowser.m_found_shaders.empty()) { // found something
+ size_t shaders_found = g_TextureBrowser.m_found_shaders.size();
+
+ globalOutputStream() << "Found " << (unsigned int) shaders_found << " textures and shaders with "
+ << tags_searched << "\n";
+ ScopeDisableScreenUpdates disableScreenUpdates("Searching...", "Loading Textures");
+
+ std::set<CopiedString>::iterator iter;
+
+ for (iter = g_TextureBrowser.m_found_shaders.begin();
+ iter != g_TextureBrowser.m_found_shaders.end(); iter++) {
+ std::string path = (*iter).c_str();
+ size_t pos = path.find_last_of("/", path.size());
+ std::string name = path.substr(pos + 1, path.size());
+ path = path.substr(0, pos + 1);
+ TextureDirectory_loadTexture(path.c_str(), name.c_str());
+ }
+ }
+ g_TextureBrowser.m_searchedTags = true;
+ g_TextureBrowser_currentDirectory = tags_searched;
+
+ g_TextureBrowser.m_nTotalHeight = 0;
+ TextureBrowser_setOriginY(g_TextureBrowser, 0);
+ TextureBrowser_heightChanged(g_TextureBrowser);
+ TextureBrowser_updateTitle();
+ }
+ g_slist_free(selected);
+}
+
+void TextureBrowser_toggleSearchButton()
+{
+ gint page = gtk_notebook_get_current_page(GTK_NOTEBOOK(g_TextureBrowser.m_tag_notebook));
+
+ if (page == 0) { // tag page
+ gtk_widget_show_all(g_TextureBrowser.m_search_button);
+ } else {
+ g_TextureBrowser.m_search_button.hide();
+ }
+}
+
+void TextureBrowser_constructTagNotebook()
+{
+ g_TextureBrowser.m_tag_notebook = ui::Widget::from(gtk_notebook_new());
+ ui::Widget labelTags = ui::Label("Tags");
+ ui::Widget labelTextures = ui::Label("Textures");
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(g_TextureBrowser.m_tag_notebook), g_TextureBrowser.m_scr_win_tree,
+ labelTextures);
+ gtk_notebook_append_page(GTK_NOTEBOOK(g_TextureBrowser.m_tag_notebook), g_TextureBrowser.m_scr_win_tags, labelTags);
+
+ g_TextureBrowser.m_tag_notebook.connect("switch-page", G_CALLBACK(TextureBrowser_toggleSearchButton), NULL);
+
+ gtk_widget_show_all(g_TextureBrowser.m_tag_notebook);
+}
+
+void TextureBrowser_constructSearchButton()
+{
+ auto image = ui::Widget::from(gtk_image_new_from_stock(GTK_STOCK_FIND, GTK_ICON_SIZE_SMALL_TOOLBAR));
+ g_TextureBrowser.m_search_button = ui::Button(ui::New);
+ g_TextureBrowser.m_search_button.connect("clicked", G_CALLBACK(TextureBrowser_searchTags), NULL);
+ gtk_widget_set_tooltip_text(g_TextureBrowser.m_search_button, "Search with selected tags");
+ g_TextureBrowser.m_search_button.add(image);
+}
+
+void TextureBrowser_checkTagFile()
+{
+ const char SHADERTAG_FILE[] = "shadertags.xml";
+ CopiedString default_filename, rc_filename;
+ StringOutputStream stream(256);
+
+ stream << LocalRcPath_get();
+ stream << SHADERTAG_FILE;
+ rc_filename = stream.c_str();
+
+ if (file_exists(rc_filename.c_str())) {
+ g_TextureBrowser.m_tags = TagBuilder.OpenXmlDoc(rc_filename.c_str());
+
+ if (g_TextureBrowser.m_tags) {
+ globalOutputStream() << "Loading tag file " << rc_filename.c_str() << ".\n";
+ }
+ } else {
+ // load default tagfile
+ stream.clear();
+ stream << g_pGameDescription->mGameToolsPath.c_str();
+ stream << SHADERTAG_FILE;
+ default_filename = stream.c_str();
+
+ if (file_exists(default_filename.c_str())) {
+ g_TextureBrowser.m_tags = TagBuilder.OpenXmlDoc(default_filename.c_str(), rc_filename.c_str());
+
+ if (g_TextureBrowser.m_tags) {
+ globalOutputStream() << "Loading default tag file " << default_filename.c_str() << ".\n";
+ }
+ } else {
+ globalErrorStream() << "Unable to find default tag file " << default_filename.c_str()
+ << ". No tag support.\n";
+ }
+ }
+}
+
+void TextureBrowser_SetNotex()
+{
+ StringOutputStream name(256);
+ name << GlobalRadiant().getAppPath() << "bitmaps/" NOTEX_BASENAME ".png";
+ g_notex = name.c_str();
+
+ name = StringOutputStream(256);
+ name << GlobalRadiant().getAppPath() << "bitmaps/" SHADERNOTEX_BASENAME " .png";
+ g_shadernotex = name.c_str();
+}
+
+ui::Widget TextureBrowser_constructWindow(ui::Window toplevel)
+{
+ // The gl_widget and the tag assignment frame should be packed into a GtkVPaned with the slider
+ // position stored in local.pref. gtk_paned_get_position() and gtk_paned_set_position() don't
+ // seem to work in gtk 2.4 and the arrow buttons don't handle GTK_FILL, so here's another thing
+ // for the "once-the-gtk-libs-are-updated-TODO-list" :x
+
+ TextureBrowser_checkTagFile();
+ TextureBrowser_SetNotex();
+
+ GlobalShaderSystem().setActiveShadersChangedNotify(
+ ReferenceCaller<TextureBrowser, void(), TextureBrowser_activeShadersChanged>(g_TextureBrowser));
+
+ g_TextureBrowser.m_parent = toplevel;
+
+ auto table = ui::Table(3, 3, FALSE);
+ auto vbox = ui::VBox(FALSE, 0);
+ table.attach(vbox, {0, 1, 1, 3}, {GTK_FILL, GTK_FILL});
+ vbox.show();
+
+ ui::Widget menu_bar{ui::null};
+
+ { // menu bar
+ menu_bar = ui::Widget::from(gtk_menu_bar_new());
+ auto menu_view = ui::Menu(ui::New);
+ auto view_item = TextureBrowser_constructViewMenu(menu_view);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(view_item), menu_view);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), view_item);
+
+ auto menu_tools = ui::Menu(ui::New);
+ auto tools_item = TextureBrowser_constructToolsMenu(menu_tools);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(tools_item), menu_tools);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), tools_item);
+
+ table.attach(menu_bar, {0, 3, 0, 1}, {GTK_FILL, GTK_SHRINK});
+ menu_bar.show();
+ }
+ { // Texture TreeView
+ g_TextureBrowser.m_scr_win_tree = ui::ScrolledWindow(ui::New);
+ gtk_container_set_border_width(GTK_CONTAINER(g_TextureBrowser.m_scr_win_tree), 0);
+
+ // vertical only scrolling for treeview
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(g_TextureBrowser.m_scr_win_tree), GTK_POLICY_NEVER,
+ GTK_POLICY_ALWAYS);
+
+ g_TextureBrowser.m_scr_win_tree.show();
+
+ TextureBrowser_createTreeViewTree();
+
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(g_TextureBrowser.m_scr_win_tree),
+ g_TextureBrowser.m_treeViewTree);
+ g_TextureBrowser.m_treeViewTree.show();
+ }
+ { // gl_widget scrollbar
+ auto w = ui::Widget::from(gtk_vscrollbar_new(ui::Adjustment(0, 0, 0, 1, 1, 0)));
+ table.attach(w, {2, 3, 1, 2}, {GTK_SHRINK, GTK_FILL});
+ w.show();
+ g_TextureBrowser.m_texture_scroll = w;
+
+ auto vadjustment = ui::Adjustment::from(gtk_range_get_adjustment(GTK_RANGE(g_TextureBrowser.m_texture_scroll)));
+ vadjustment.connect("value_changed", G_CALLBACK(TextureBrowser_verticalScroll), &g_TextureBrowser);
+
+ g_TextureBrowser.m_texture_scroll.visible(g_TextureBrowser.m_showTextureScrollbar);
+ }
+ { // gl_widget
+ g_TextureBrowser.m_gl_widget = glwidget_new(FALSE);
+ g_object_ref(g_TextureBrowser.m_gl_widget._handle);
+
+ gtk_widget_set_events(g_TextureBrowser.m_gl_widget,
+ GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK);
+ gtk_widget_set_can_focus(g_TextureBrowser.m_gl_widget, true);
+
+ table.attach(g_TextureBrowser.m_gl_widget, {1, 2, 1, 2});
+ g_TextureBrowser.m_gl_widget.show();
+
+ g_TextureBrowser.m_sizeHandler = g_TextureBrowser.m_gl_widget.connect("size_allocate",
+ G_CALLBACK(TextureBrowser_size_allocate),
+ &g_TextureBrowser);
+ g_TextureBrowser.m_exposeHandler = g_TextureBrowser.m_gl_widget.on_render(G_CALLBACK(TextureBrowser_expose),
+ &g_TextureBrowser);
+
+ g_TextureBrowser.m_gl_widget.connect("button_press_event", G_CALLBACK(TextureBrowser_button_press),
+ &g_TextureBrowser);
+ g_TextureBrowser.m_gl_widget.connect("button_release_event", G_CALLBACK(TextureBrowser_button_release),
+ &g_TextureBrowser);
+ g_TextureBrowser.m_gl_widget.connect("motion_notify_event", G_CALLBACK(TextureBrowser_motion),
+ &g_TextureBrowser);
+ g_TextureBrowser.m_gl_widget.connect("scroll_event", G_CALLBACK(TextureBrowser_scroll), &g_TextureBrowser);
+ }
+
+ // tag stuff
+ if (g_TextureBrowser.m_tags) {
+ { // fill tag GtkListStore
+ g_TextureBrowser.m_all_tags_list = ui::ListStore::from(gtk_list_store_new(N_COLUMNS, G_TYPE_STRING));
+ auto sortable = GTK_TREE_SORTABLE(g_TextureBrowser.m_all_tags_list);
+ gtk_tree_sortable_set_sort_column_id(sortable, TAG_COLUMN, GTK_SORT_ASCENDING);
+
+ TagBuilder.GetAllTags(g_TextureBrowser.m_all_tags);
+ TextureBrowser_buildTagList();
+ }
+ { // tag menu bar
+ auto menu_tags = ui::Menu(ui::New);
+ auto tags_item = TextureBrowser_constructTagsMenu(menu_tags);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(tags_item), menu_tags);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), tags_item);
+ }
+ { // Tag TreeView
+ g_TextureBrowser.m_scr_win_tags = ui::ScrolledWindow(ui::New);
+ gtk_container_set_border_width(GTK_CONTAINER(g_TextureBrowser.m_scr_win_tags), 0);
+
+ // vertical only scrolling for treeview
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(g_TextureBrowser.m_scr_win_tags), GTK_POLICY_NEVER,
+ GTK_POLICY_ALWAYS);
+
+ TextureBrowser_createTreeViewTags();
+
+ auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_treeViewTags);
+ gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
+
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(g_TextureBrowser.m_scr_win_tags),
+ g_TextureBrowser.m_treeViewTags);
+ g_TextureBrowser.m_treeViewTags.show();
+ }
+ { // Texture/Tag notebook
+ TextureBrowser_constructTagNotebook();
+ vbox.pack_start(g_TextureBrowser.m_tag_notebook, TRUE, TRUE, 0);
+ }
+ { // Tag search button
+ TextureBrowser_constructSearchButton();
+ vbox.pack_end(g_TextureBrowser.m_search_button, FALSE, FALSE, 0);
+ }
+ auto frame_table = ui::Table(3, 3, FALSE);
+ { // Tag frame
+
+ g_TextureBrowser.m_tag_frame = ui::Frame("Tag assignment");
+ gtk_frame_set_label_align(GTK_FRAME(g_TextureBrowser.m_tag_frame), 0.5, 0.5);
+ gtk_frame_set_shadow_type(GTK_FRAME(g_TextureBrowser.m_tag_frame), GTK_SHADOW_NONE);
+
+ table.attach(g_TextureBrowser.m_tag_frame, {1, 3, 2, 3}, {GTK_FILL, GTK_SHRINK});
+
+ frame_table.show();
+
+ g_TextureBrowser.m_tag_frame.add(frame_table);
+ }
+ { // assigned tag list
+ ui::Widget scrolled_win = ui::ScrolledWindow(ui::New);
+ gtk_container_set_border_width(GTK_CONTAINER(scrolled_win), 0);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_win), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+
+ g_TextureBrowser.m_assigned_store = ui::ListStore::from(gtk_list_store_new(N_COLUMNS, G_TYPE_STRING));
+
+ auto sortable = GTK_TREE_SORTABLE(g_TextureBrowser.m_assigned_store);
+ gtk_tree_sortable_set_sort_column_id(sortable, TAG_COLUMN, GTK_SORT_ASCENDING);
+
+ auto renderer = ui::CellRendererText(ui::New);
+
+ g_TextureBrowser.m_assigned_tree = ui::TreeView(
+ ui::TreeModel::from(g_TextureBrowser.m_assigned_store._handle));
+ g_TextureBrowser.m_assigned_store.unref();
+ g_TextureBrowser.m_assigned_tree.connect("row-activated", (GCallback) TextureBrowser_removeTags, NULL);
+ gtk_tree_view_set_headers_visible(g_TextureBrowser.m_assigned_tree, FALSE);
+
+ auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_assigned_tree);
+ gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
+
+ auto column = ui::TreeViewColumn("", renderer, {{"text", TAG_COLUMN}});
+ gtk_tree_view_append_column(g_TextureBrowser.m_assigned_tree, column);
+ g_TextureBrowser.m_assigned_tree.show();
+
+ scrolled_win.show();
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_win), g_TextureBrowser.m_assigned_tree);
+
+ frame_table.attach(scrolled_win, {0, 1, 1, 3}, {GTK_FILL, GTK_FILL});
+ }
+ { // available tag list
+ ui::Widget scrolled_win = ui::ScrolledWindow(ui::New);
+ gtk_container_set_border_width(GTK_CONTAINER(scrolled_win), 0);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_win), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+
+ g_TextureBrowser.m_available_store = ui::ListStore::from(gtk_list_store_new(N_COLUMNS, G_TYPE_STRING));
+ auto sortable = GTK_TREE_SORTABLE(g_TextureBrowser.m_available_store);
+ gtk_tree_sortable_set_sort_column_id(sortable, TAG_COLUMN, GTK_SORT_ASCENDING);
+
+ auto renderer = ui::CellRendererText(ui::New);
+
+ g_TextureBrowser.m_available_tree = ui::TreeView(
+ ui::TreeModel::from(g_TextureBrowser.m_available_store._handle));
+ g_TextureBrowser.m_available_store.unref();
+ g_TextureBrowser.m_available_tree.connect("row-activated", (GCallback) TextureBrowser_assignTags, NULL);
+ gtk_tree_view_set_headers_visible(g_TextureBrowser.m_available_tree, FALSE);
+
+ auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_available_tree);
+ gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
+
+ auto column = ui::TreeViewColumn("", renderer, {{"text", TAG_COLUMN}});
+ gtk_tree_view_append_column(g_TextureBrowser.m_available_tree, column);
+ g_TextureBrowser.m_available_tree.show();
+
+ scrolled_win.show();
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_win), g_TextureBrowser.m_available_tree);
+
+ frame_table.attach(scrolled_win, {2, 3, 1, 3}, {GTK_FILL, GTK_FILL});
+ }
+ { // tag arrow buttons
+ auto m_btn_left = ui::Button(ui::New);
+ auto m_btn_right = ui::Button(ui::New);
+ auto m_arrow_left = ui::Widget::from(gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_OUT));
+ auto m_arrow_right = ui::Widget::from(gtk_arrow_new(GTK_ARROW_RIGHT, GTK_SHADOW_OUT));
+ m_btn_left.add(m_arrow_left);
+ m_btn_right.add(m_arrow_right);
+
+ // workaround. the size of the tag frame depends of the requested size of the arrow buttons.
+ m_arrow_left.dimensions(-1, 68);
+ m_arrow_right.dimensions(-1, 68);
+
+ frame_table.attach(m_btn_left, {1, 2, 1, 2}, {GTK_SHRINK, GTK_EXPAND});
+ frame_table.attach(m_btn_right, {1, 2, 2, 3}, {GTK_SHRINK, GTK_EXPAND});
+
+ m_btn_left.connect("clicked", G_CALLBACK(TextureBrowser_assignTags), NULL);
+ m_btn_right.connect("clicked", G_CALLBACK(TextureBrowser_removeTags), NULL);
+
+ m_btn_left.show();
+ m_btn_right.show();
+ m_arrow_left.show();
+ m_arrow_right.show();
+ }
+ { // tag fram labels
+ ui::Widget m_lbl_assigned = ui::Label("Assigned");
+ ui::Widget m_lbl_unassigned = ui::Label("Available");
+
+ frame_table.attach(m_lbl_assigned, {0, 1, 0, 1}, {GTK_EXPAND, GTK_SHRINK});
+ frame_table.attach(m_lbl_unassigned, {2, 3, 0, 1}, {GTK_EXPAND, GTK_SHRINK});
+
+ m_lbl_assigned.show();
+ m_lbl_unassigned.show();
+ }
+ } else { // no tag support, show the texture tree only
+ vbox.pack_start(g_TextureBrowser.m_scr_win_tree, TRUE, TRUE, 0);
+ }
+
+ // TODO do we need this?
+ //gtk_container_set_focus_chain(GTK_CONTAINER(hbox_table), NULL);
+
+ return table;