From 8601c79647333079aab4d80f4dee681938108232 Mon Sep 17 00:00:00 2001 From: "Ignacio R. Morelle" Date: Thu, 19 Jun 2014 19:42:27 -0400 Subject: [PATCH 1/4] Cache the config cache files prefix No pun intended. This micro-refactoring step will be required for introducing functionality that requires to know the config cache files' naming pattern. --- src/config_cache.cpp | 14 ++++++++++---- src/config_cache.hpp | 2 ++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/config_cache.cpp b/src/config_cache.cpp index 0d575732abc4..5a98f61c5fbc 100644 --- a/src/config_cache.cpp +++ b/src/config_cache.cpp @@ -48,8 +48,15 @@ namespace game_config { force_valid_cache_(false), use_cache_(true), fake_invalid_cache_(false), - defines_map_() + defines_map_(), + cache_file_prefix_() { + cache_file_prefix_ + = "cache-v" + + boost::algorithm::replace_all_copy(game_config::revision, + ":", "_") + + "-"; + // To set-up initial defines map correctly clear_defines(); } @@ -177,9 +184,8 @@ namespace game_config { const std::string& cache = get_cache_dir(); if(cache != "") { sha1_hash sha(defines_string.str()); // use a hash for a shorter display of the defines - const std::string fname = cache + "/cache-v" + - boost::algorithm::replace_all_copy(game_config::revision, ":", "_") + - "-" + sha.display(); + const std::string fname = cache + "/" + + cache_file_prefix_ + sha.display(); const std::string fname_checksum = fname + ".checksum" + extension; file_tree_checksum dir_checksum; diff --git a/src/config_cache.hpp b/src/config_cache.hpp index cff4a2102cda..4e14cb1c5f23 100644 --- a/src/config_cache.hpp +++ b/src/config_cache.hpp @@ -83,6 +83,8 @@ namespace game_config { bool force_valid_cache_, use_cache_, fake_invalid_cache_; preproc_map defines_map_; + std::string cache_file_prefix_; + void read_file(const std::string& file, config& cfg); void write_file(std::string file, const config& cfg); void write_file(std::string file, const preproc_map&); From c5e81ca5f5c2a8d559b0fdf48c1cd5ee87055c6d Mon Sep 17 00:00:00 2001 From: "Ignacio R. Morelle" Date: Thu, 19 Jun 2014 19:43:57 -0400 Subject: [PATCH 2/4] fs: Add dir_size() function to obtain the total size of directory's contents --- src/filesystem.cpp | 21 +++++++++++++++++++++ src/filesystem.hpp | 3 +++ 2 files changed, 24 insertions(+) diff --git a/src/filesystem.cpp b/src/filesystem.cpp index 8edb3a863a0e..9b8f1fb38d84 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -914,6 +914,27 @@ int file_size(const std::string& fname) return buf.st_size; } +int dir_size(const std::string& path) +{ + std::vector files, dirs; + get_files_in_dir(path, &files, &dirs, ENTIRE_FILE_PATH); + + int res = 0; + + BOOST_FOREACH(const std::string& file_path, files) + { + res += file_size(file_path); + } + + BOOST_FOREACH(const std::string& dir_path, dirs) + { + // FIXME: this could result in infinite recursion with symlinks!! + res += dir_size(dir_path); + } + + return res; +} + std::string file_name(const std::string& file) // Analogous to POSIX basename(3), but for C++ string-object pathnames { diff --git a/src/filesystem.hpp b/src/filesystem.hpp index 492664e99682..6dc74efe44b7 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -159,6 +159,9 @@ const file_tree_checksum& data_tree_checksum(bool reset = false); /** Returns the size of a file, or -1 if the file doesn't exist. */ int file_size(const std::string& fname); +/** Returns the sum of the sizes of the files contained in a directory. */ +int dir_size(const std::string& path); + bool ends_with(const std::string& str, const std::string& suffix); /** From 3154af156a2318aae03e7c1bfcf4c23259ccf480 Mon Sep 17 00:00:00 2001 From: "Ignacio R. Morelle" Date: Thu, 19 Jun 2014 19:45:12 -0400 Subject: [PATCH 3/4] Add functionality to clean or purge the cache to game_config::config_cache Cleaning the cache removes all stale cache files (files that do not match the current cache's naming pattern), and purging removes *everything* in sight in the cache dir. Ideally we would clean the cache every once in a while without asking, but for now there will be a user-controlled option to allow doing this by hand. --- src/config_cache.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++ src/config_cache.hpp | 13 +++++++++ 2 files changed, 79 insertions(+) diff --git a/src/config_cache.cpp b/src/config_cache.cpp index 5a98f61c5fbc..4d422607d255 100644 --- a/src/config_cache.cpp +++ b/src/config_cache.cpp @@ -340,6 +340,72 @@ namespace game_config { } } + bool config_cache::clean_cache() + { + std::vector files, dirs; + get_files_in_dir(get_cache_dir(), &files, &dirs, ENTIRE_FILE_PATH); + + LOG_CACHE << "clean_cache(): " << files.size() << " files, " + << dirs.size() << " dirs to check\n"; + + const std::string& exclude_current = cache_file_prefix_ + "*"; + + bool status = true; + + status &= delete_cache_files(files, exclude_current); + status &= delete_cache_files(dirs, exclude_current); + + LOG_CACHE << "clean_cache(): done\n"; + + return status; + } + + bool config_cache::purge_cache() + { + std::vector files, dirs; + get_files_in_dir(get_cache_dir(), &files, &dirs, ENTIRE_FILE_PATH); + + LOG_CACHE << "purge_cache(): deleting " << files.size() << " files, " + << dirs.size() << " dirs\n"; + + bool status = true; + + status &= delete_cache_files(files); + status &= delete_cache_files(dirs); + + LOG_CACHE << "purge_cache(): done\n"; + return status; + } + + bool config_cache::delete_cache_files(const std::vector& paths, + const std::string& exclude_pattern) + { + const bool delete_everything = exclude_pattern.empty(); + bool status = true; + + BOOST_FOREACH(const std::string& path, paths) + { + if(!delete_everything) { + const std::string& fn = file_name(path); + + if(utils::wildcard_string_match(fn, exclude_pattern)) { + LOG_CACHE << "delete_cache_files(): skipping " << path + << " excluded by '" << exclude_pattern << "'\n"; + continue; + } + } + + LOG_CACHE << "delete_cache_files(): deleting " << path << '\n'; + if(!delete_directory(path)) { + ERR_CACHE << "delete_cache_files(): could not delete " + << path << '\n'; + status = false; + } + } + + return status; + } + config_cache_transaction::state config_cache_transaction::state_ = FREE; config_cache_transaction* config_cache_transaction::active_ = 0; diff --git a/src/config_cache.hpp b/src/config_cache.hpp index 4e14cb1c5f23..5cd8501fc23f 100644 --- a/src/config_cache.hpp +++ b/src/config_cache.hpp @@ -99,6 +99,9 @@ namespace game_config { preproc_map& make_copy_map(); void add_defines_map_diff(preproc_map&); + bool delete_cache_files(const std::vector& paths, + const std::string& exclude_pattern = ""); + // Protected to let test code access protected: config_cache(); @@ -146,6 +149,16 @@ namespace game_config { **/ void recheck_filetree_checksum(); + /** + * Deletes stale cache files not in use by the game. + */ + bool clean_cache(); + + /** + * Deletes all cache files. + */ + bool purge_cache(); + }; class fake_transaction; From 17c8d568d54067ddc0ffd94b3dd11796ca686253 Mon Sep 17 00:00:00 2001 From: "Ignacio R. Morelle" Date: Sat, 1 Mar 2014 07:18:54 -0300 Subject: [PATCH 4/4] gui2/tgame_cache_options: New Preferences subdialog for cache management It allows the user to browse to the cache (using a desktop API call), copy its path to clipboard, clean the cache contents (deleting stale files), or purge it entirely. This is ideal for people sticking to the same cache dir for very long and thus keeping content that was generated by previous versions of Wesnoth they might not even use anymore. Purging the cache might also aid troubleshooting (although there is a --nocache switch for that too). The dialog also makes Wesnoth slightly more transparent about its own disk usage, information which may not be readily accessible to people without the technical know-how to locate a hidden directory. For now it's accessed by pushing a button in Preferences -> General, even though this is not ideal since it is actually Advanced Preferences material given the nature of the cache. Unfortunately, the Advanced page's layout is currently monopolized by the advanced preferences options and I'm not interested right now in solving that UI design puzzle; it will have to wait, much like the Paths dialog has done and continues to do for 1.12. --- changelog | 1 + .../gui/default/window/game_cache_options.cfg | 267 ++++++++++++++++++ players_changelog | 1 + src/CMakeLists.txt | 1 + src/SConscript | 1 + src/game_preferences_display.cpp | 11 + src/gui/dialogs/game_cache_options.cpp | 155 ++++++++++ src/gui/dialogs/game_cache_options.hpp | 68 +++++ 8 files changed, 505 insertions(+) create mode 100644 data/gui/default/window/game_cache_options.cfg create mode 100644 src/gui/dialogs/game_cache_options.cpp create mode 100644 src/gui/dialogs/game_cache_options.hpp diff --git a/changelog b/changelog index f857175605ff..15109049cd77 100644 --- a/changelog +++ b/changelog @@ -75,6 +75,7 @@ Version 1.13.0-dev: * Fixed most of the minimap buttons and the End Turn button appearing without contents or in the wrong state during WML start events until they are interacted with or control is given to the player for the first time. + * Added a new subdialog in Preferences to clean or purge the WML cache. * WML engine: * Added customizable recall costs for unit types and individual units, using the new recall_cost attribute in [unit_type] and [unit]. diff --git a/data/gui/default/window/game_cache_options.cfg b/data/gui/default/window/game_cache_options.cfg new file mode 100644 index 000000000000..963814ec8e60 --- /dev/null +++ b/data/gui/default/window/game_cache_options.cfg @@ -0,0 +1,267 @@ +#textdomain wesnoth-lib + +[window] + id = "game_cache_options" + description = "Game data cache management dialog." + + [resolution] + definition = "default" + + click_dismiss = "true" + maximum_width = 600 + + [tooltip] + id = "tooltip_large" + [/tooltip] + + [helptip] + id = "tooltip_large" + [/helptip] + + [grid] + + [row] + grow_factor = 0 + + [column] + grow_factor = 1 + + border = "all" + border_size = 5 + horizontal_alignment = "left" + [label] + id = "title" + definition = "title" + + label = _ "WML Cache" + [/label] + + [/column] + + [/row] + + [row] + grow_factor = 0 + + [column] + border = "all" + border_size = 5 + horizontal_alignment = "left" + [label] + definition = "default" + + label = _ "Wesnoth maintains a cache of preprocessed WML data for campaigns, multiplayer scenarios, and other add-ons to speed up the loading process. The cache may be safely cleaned to free up disk space, thus removing stale files generated by older versions; or you may purge its entire contents if you are experiencing issues when loading the game data." + wrap = "true" + [/label] + + [/column] + + [/row] + + [row] + grow_factor = 1 + + [column] + horizontal_grow = "true" + + [grid] + + [row] + + [column] + grow_factor = 0 + + border = "all" + border_size = 5 + horizontal_alignment = "left" + + [label] + definition = "default" + + label = _ "Path:" + [/label] + + [/column] + + [column] + grow_factor = 1 + + border = "all" + border_size = 5 + horizontal_grow = "true" + + [text_box] + id = "path" + definition = "default" + label = "" + [/text_box] + + [/column] + + [column] + grow_factor = 0 + + border = "all" + border_size = 5 + horizontal_alignment = "left" + + [button] + id = "copy" + definition = "action_copy" + label = _ "filesystem^Copy" + tooltip = _ "Copy this path to clipboard" + [/button] + + [/column] + + [column] + grow_factor = 0 + + border = "all" + border_size = 5 + horizontal_alignment = "left" + + [button] + id = "browse" + definition = "action_go" + label = _ "filesystem^Browse" + tooltip = _ "Browse this location using a file manager" + [/button] + + [/column] + + [/row] + + [/grid] + + [/column] + + [/row] + + [row] + grow_factor = 1 + + [column] + horizontal_grow = "true" + + [grid] + + [row] + + [column] + grow_factor = 0 + + border = "all" + border_size = 5 + horizontal_alignment = "left" + + [label] + definition = "default" + + label = _ "Size:" + [/label] + + [/column] + + [column] + grow_factor = 1 + + border = "all" + border_size = 5 + horizontal_grow = "true" + + [label] + definition = "default" + + id = "size" + label = "" + [/label] + + [/column] + + [column] + horizontal_alignment = "right" + + [grid] + + [row] + + [column] + border = "all" + border_size = 5 + + [button] + id = "clean" + definition = "default" + label = _ "cache^Clean" + tooltip = _ "Clear stale and unused cache files" + [/button] + + [/column] + + [column] + border = "all" + border_size = 5 + + [button] + id = "purge" + definition = "default" + label = _ "cache^Purge" + tooltip = _ "Purge the entire contents of the cache" + [/button] + + [/column] + + [/row] + + [/grid] + + [/column] + + [/row] + + [/grid] + + [/column] + + [/row] + + [row] + grow_factor = 0 + + [column] + horizontal_alignment = "right" + + [grid] + + [row] + grow_factor = 0 + + [column] + border = "all" + border_size = 5 + horizontal_alignment = "right" + + [button] + id = "ok" + definition = "default" + + label = _ "OK" + [/button] + + [/column] + + [/row] + + [/grid] + + [/column] + + [/row] + + + [/grid] + + [/resolution] + +[/window] diff --git a/players_changelog b/players_changelog index faf4fd8a1510..7edbe29c77a9 100644 --- a/players_changelog +++ b/players_changelog @@ -22,6 +22,7 @@ Version 1.13.0-dev: * Fixed most of the minimap buttons and the End Turn button appearing without contents or in the wrong state during WML start events until they are interacted with or control is given to the player for the first time. + * Added a new subdialog in Preferences to clean or purge the WML cache. * Miscellaneous and bug fixes: * Fixed halos glitching through locations that become shrouded after the diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6b790241d91c..bcc6f3847388 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -784,6 +784,7 @@ set(wesnoth-main_SRC gui/dialogs/editor_set_starting_position.cpp gui/dialogs/folder_create.cpp gui/dialogs/formula_debugger.cpp + gui/dialogs/game_cache_options.cpp gui/dialogs/game_delete.cpp gui/dialogs/game_load.cpp gui/dialogs/game_paths.cpp diff --git a/src/SConscript b/src/SConscript index e3feffe61107..4ad80e33370d 100644 --- a/src/SConscript +++ b/src/SConscript @@ -379,6 +379,7 @@ wesnoth_sources = Split(""" gui/dialogs/editor_set_starting_position.cpp gui/dialogs/folder_create.cpp gui/dialogs/formula_debugger.cpp + gui/dialogs/game_cache_options.cpp gui/dialogs/game_delete.cpp gui/dialogs/game_load.cpp gui/dialogs/game_paths.cpp diff --git a/src/game_preferences_display.cpp b/src/game_preferences_display.cpp index b349a790a0d0..9562544d7f49 100644 --- a/src/game_preferences_display.cpp +++ b/src/game_preferences_display.cpp @@ -20,6 +20,7 @@ #include "filechooser.hpp" #include "game_preferences.hpp" #include "gettext.hpp" +#include "gui/dialogs/game_cache_options.hpp" #include "gui/dialogs/game_paths.hpp" #include "gui/dialogs/simple_item_selector.hpp" #include "gui/dialogs/theme_list.hpp" @@ -129,6 +130,7 @@ class preferences_dialog : public gui::preview_pane show_haloing_button_, video_mode_button_, theme_button_, hotkeys_button_, paths_button_, colors_button_, + cache_button_, advanced_button_, sound_button_, music_button_, chat_timestamp_button_, advanced_sound_button_, normal_sound_button_, @@ -222,6 +224,7 @@ preferences_dialog::preferences_dialog(display& disp, const config& game_cfg) hotkeys_button_(disp.video(), _("Hotkeys")), paths_button_(disp.video(), _("Paths")), colors_button_(disp.video(), _("Colors")), + cache_button_(disp.video(), _("Cache")), advanced_button_(disp.video(), "", gui::button::TYPE_CHECK), sound_button_(disp.video(), _("Sound effects"), gui::button::TYPE_CHECK), music_button_(disp.video(), _("Music"), gui::button::TYPE_CHECK), @@ -509,6 +512,7 @@ preferences_dialog::preferences_dialog(display& disp, const config& game_cfg) hotkeys_button_.set_help_string(_("View and configure keyboard shortcuts")); paths_button_.set_help_string(_("View game file paths")); colors_button_.set_help_string(_("Adjust orb colors")); + cache_button_.set_help_string(_("Manage the game WML cache")); set_advanced_menu(); set_friends_menu(); @@ -565,6 +569,7 @@ handler_vector preferences_dialog::handler_members() h.push_back(&hotkeys_button_); h.push_back(&paths_button_); h.push_back(&colors_button_); + h.push_back(&cache_button_); h.push_back(&advanced_button_); h.push_back(&sound_button_); h.push_back(&music_button_); @@ -659,6 +664,7 @@ void preferences_dialog::update_location(SDL_Rect const &rect) autosavemax_slider_.set_location(autosavemax_rect); hotkeys_button_.set_location(rect.x, bottom_row_y - hotkeys_button_.height()); paths_button_.set_location(rect.x + hotkeys_button_.width() + 10, bottom_row_y - paths_button_.height()); + cache_button_.set_location(paths_button_.location().x + paths_button_.width() + 10, bottom_row_y - cache_button_.height()); // Display tab ypos = rect.y + top_border; @@ -918,6 +924,10 @@ void preferences_dialog::process_event() show_paths_dialog(disp_); parent->clear_buttons(); } + if (cache_button_.pressed()) { + gui2::tgame_cache_options::display(disp_.video()); + parent->clear_buttons(); + } set_scroll_speed(scroll_slider_.value()); set_autosavemax(autosavemax_slider_.value()); @@ -1543,6 +1553,7 @@ void preferences_dialog::set_selection(int index) interrupt_when_ally_sighted_button_.hide(hide_general); hotkeys_button_.hide(hide_general); paths_button_.hide(hide_general); + cache_button_.hide(hide_general); save_replays_button_.hide(hide_general); delete_saves_button_.hide(hide_general); autosavemax_slider_label_.hide(hide_general); diff --git a/src/gui/dialogs/game_cache_options.cpp b/src/gui/dialogs/game_cache_options.cpp new file mode 100644 index 000000000000..a9f7a4e185d8 --- /dev/null +++ b/src/gui/dialogs/game_cache_options.cpp @@ -0,0 +1,155 @@ +/* + Copyright (C) 2014 by Ignacio Riquelme Morelle + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +#define GETTEXT_DOMAIN "wesnoth-lib" + +#include "gui/dialogs/game_cache_options.hpp" + +#include "clipboard.hpp" +#include "config_cache.hpp" +#include "desktop_util.hpp" +#include "filesystem.hpp" +#include "gui/auxiliary/find_widget.tpp" +#include "gui/dialogs/message.hpp" +#include "gui/widgets/button.hpp" +#include "gui/widgets/label.hpp" +#include "gui/widgets/settings.hpp" +#include "gui/widgets/text_box.hpp" +#include "gui/widgets/window.hpp" + +#include + +#include "gettext.hpp" + +namespace gui2 +{ + +// TODO: wikidoc! + +REGISTER_DIALOG(game_cache_options) + +tgame_cache_options::tgame_cache_options() + : cache_path_(get_cache_dir()) + , size_label_(NULL) +{ +} + +void tgame_cache_options::pre_show(CVideo& video, twindow& window) +{ + size_label_ = &find_widget(&window, "size", false); + update_cache_size_display(); + + ttext_& path_box = find_widget(&window, "path", false); + path_box.set_value(cache_path_); + path_box.set_active(false); + + tbutton& copy = find_widget(&window, "copy", false); + connect_signal_mouse_left_click(copy, + boost::bind(&tgame_cache_options::copy_to_clipboard_callback, + this)); + + tbutton& browse = find_widget(&window, "browse", false); + connect_signal_mouse_left_click(browse, + boost::bind(&tgame_cache_options::browse_cache_callback, + this)); + + tbutton& clean = find_widget(&window, "clean", false); + connect_signal_mouse_left_click(clean, + boost::bind(&tgame_cache_options::clean_cache_callback, + this, + boost::ref(video))); + + tbutton& purge = find_widget(&window, "purge", false); + connect_signal_mouse_left_click(purge, + boost::bind(&tgame_cache_options::purge_cache_callback, + this, + boost::ref(video))); +} + +void tgame_cache_options::post_show(twindow& /*window*/) +{ + size_label_ = NULL; +} + +void tgame_cache_options::update_cache_size_display() +{ + if(!size_label_) { + return; + } + + size_label_->set_label(utils::si_string(dir_size(cache_path_), + true, + _("unit_byte^B"))); +} + +void tgame_cache_options::copy_to_clipboard_callback() +{ + copy_to_clipboard(cache_path_, false); +} + +void tgame_cache_options::browse_cache_callback() +{ + desktop::open_object(cache_path_); +} + +void tgame_cache_options::clean_cache_callback(CVideo& video) +{ + if(clean_cache()) { + show_message(video, + _("Cache Cleaned"), + _("The game data cache has been cleaned.")); + } else { + show_error_message(video, + _("The game data cache could not be completely cleaned.")); + } + + update_cache_size_display(); +} + +bool tgame_cache_options::clean_cache() +{ + return game_config::config_cache::instance().clean_cache(); +} + +void tgame_cache_options::purge_cache_callback(CVideo& video) +{ + if(show_message(video, + _("Purge Cache"), + _("Are you sure you want to purge the game data cache? " + "All files in the cache directory will be deleted, and " + "the cache will be regenerated next time it is " + "required."), + gui2::tmessage::yes_no_buttons) != gui2::twindow::OK) + { + return; + } + + if(purge_cache()) { + show_message(video, + _("Cache Purged"), + _("The game data cache has been purged.")); + } else { + show_error_message(video, + _("The game data cache could not be purged.")); + } + + update_cache_size_display(); +} + +bool tgame_cache_options::purge_cache() +{ + return game_config::config_cache::instance().purge_cache(); +} + +} // end namespace gui2 diff --git a/src/gui/dialogs/game_cache_options.hpp b/src/gui/dialogs/game_cache_options.hpp new file mode 100644 index 000000000000..62624c2d4d15 --- /dev/null +++ b/src/gui/dialogs/game_cache_options.hpp @@ -0,0 +1,68 @@ +/* + Copyright (C) 2014 by Ignacio Riquelme Morelle + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +#ifndef GUI_DIALOGS_GAME_CACHE_OPTIONS_HPP_INCLUDED +#define GUI_DIALOGS_GAME_CACHE_OPTIONS_HPP_INCLUDED + +#include "gui/dialogs/dialog.hpp" + +namespace gui2 +{ +class tlabel; + +class tgame_cache_options : public tdialog +{ +public: + /** Constructor. */ + tgame_cache_options(); + + /** + * The display function. + * + * See @ref tdialog for more information. + */ + static void display(CVideo& video) + { + tgame_cache_options().show(video); + } + +private: + std::string cache_path_; + tlabel* size_label_; + + void clean_cache_callback(CVideo& video); + bool clean_cache(); + + void purge_cache_callback(CVideo& video); + bool purge_cache(); + + void copy_to_clipboard_callback(); + + void browse_cache_callback(); + + void update_cache_size_display(); + + /** Inherited from tdialog, implemented by REGISTER_DIALOG. */ + virtual const std::string& window_id() const; + + /** Inherited from tdialog. */ + void pre_show(CVideo& video, twindow& window); + + /** Inherited from tdialog. */ + void post_show(twindow& window); +}; + +} + +#endif