//+----------------------------------------------------------------------------+
//| Description:  Magic Set Editor - Program to make Magic (tm) cards          |
//| Copyright:    (C) 2001 - 2010 Twan van Laarhoven and Sean Hunt             |
//| License:      GNU General Public License 2 or later (see file COPYING)     |
//+----------------------------------------------------------------------------+

// ----------------------------------------------------------------------------- : Includes

#include <util/prec.hpp>
#include <gui/set/window.hpp>
#include <gui/set/panel.hpp>
#include <gui/set/cards_panel.hpp>
#include <gui/set/set_info_panel.hpp>
#include <gui/set/style_panel.hpp>
#include <gui/set/keywords_panel.hpp>
#include <gui/set/stats_panel.hpp>
#include <gui/set/random_pack_panel.hpp>
#include <gui/set/console_panel.hpp>
#include <gui/control/card_list.hpp>
#include <gui/control/card_viewer.hpp>
#include <gui/control/gallery_list.hpp>
#include <gui/about_window.hpp>
#include <gui/update_checker.hpp>
#include <gui/packages_window.hpp>
#include <gui/new_window.hpp>
#include <gui/preferences_window.hpp>
#include <gui/print_window.hpp>
#include <gui/images_export_window.hpp>
#include <gui/html_export_window.hpp>
#include <gui/auto_replace_window.hpp>
#include <gui/icon_menu.hpp>
#include <gui/util.hpp>
#include <util/io/package_manager.hpp>
#include <util/window_id.hpp>
#include <data/game.hpp>
#include <data/set.hpp>
#include <data/card.hpp>
#include <data/settings.hpp>
#include <data/format/formats.hpp>
#include <data/action/value.hpp>
#include <data/action/set.hpp>

DECLARE_TYPEOF_COLLECTION(SetWindowPanel*);
DECLARE_TYPEOF_COLLECTION(SetWindow*);
DECLARE_TYPEOF_COLLECTION(String);

// ----------------------------------------------------------------------------- : Constructor

SetWindow::SetWindow(Window* parent, const SetP& set)
	: wxFrame(parent, wxID_ANY, _TITLE_("magic set editor"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxNO_FULL_REPAINT_ON_RESIZE)
	, current_panel(nullptr)
	, find_dialog(nullptr)
	, find_data(wxFR_DOWN)
	, number_of_recent_sets(0)
{
	SetIcon(load_resource_icon(_("app")));
	
	// initialize menu bar
	wxMenuBar* menuBar = new wxMenuBar();
	IconMenu* menuFile = new IconMenu();
		menuFile->Append(ID_FILE_NEW,		_("new"),		_MENU_("new set"),			_HELP_("new set"));
		menuFile->Append(ID_FILE_OPEN,		_("open"),		_MENU_("open set"),			_HELP_("open set"));
		menuFile->Append(ID_FILE_SAVE,		_("save"),		_MENU_("save set"),			_HELP_("save set"));
		menuFile->Append(ID_FILE_SAVE_AS,					_MENU_("save set as"),		_HELP_("save set as"));
		menuExport = new IconMenu();
			menuExport->Append(ID_FILE_EXPORT_HTML,		_("export_html"),		_MENU_("export html"),		_HELP_("export html"));
			menuExport->Append(ID_FILE_EXPORT_IMAGE,	_("export_image"),		_MENU_("export image"),		_HELP_("export image"));
			menuExport->Append(ID_FILE_EXPORT_IMAGES,	_("export_images"),		_MENU_("export images"),	_HELP_("export images"));
			menuExport->Append(ID_FILE_EXPORT_APPR,		_("export_apprentice"),	_MENU_("export apprentice"),_HELP_("export apprentice"));
			menuExport->Append(ID_FILE_EXPORT_MWS,		_("export_mws"),		_MENU_("export mws"),		_HELP_("export mws"));
		menuFile->Append(wxID_ANY,			_("export"),	_MENU_("export"),					_HELP_("export"), wxITEM_NORMAL, menuExport);
		menuFile->AppendSeparator();
		menuFile->Append(ID_FILE_CHECK_UPDATES,	_MENU_("check updates"),	_HELP_("check updates"));
		#if USE_SCRIPT_PROFILING
		menuFile->Append(ID_FILE_PROFILER,		_MENU_("show profiler"),	_HELP_("show profiler"));
		#endif
//		menuFile->Append(ID_FILE_INSPECT,					_("Inspect Internal Data..."),	_("Shows a the data in the set using a tree structure"));
//		menuFile->AppendSeparator();
		menuFile->Append(ID_FILE_RELOAD,					_MENU_("reload data"),		_HELP_("reload data"));
		menuFile->AppendSeparator();
		menuFile->Append(ID_FILE_PRINT_PREVIEW,	_("print_preview"),	_MENU_("print preview"),	_HELP_("print preview"));
		menuFile->Append(ID_FILE_PRINT,			_("print"),			_MENU_("print"),			_HELP_("print"));
		menuFile->AppendSeparator();
		// recent files go here
		menuFile->AppendSeparator();
		menuFile->Append(ID_FILE_EXIT,						_MENU_("exit"),				_HELP_("exit"));
	menuBar->Append(menuFile, _MENU_("file"));
	
	IconMenu* menuEdit = new IconMenu();
		menuEdit->Append(ID_EDIT_UNDO,		_("undo"),		_MENU_1_("undo",wxEmptyString),	_HELP_("undo"));
		menuEdit->Append(ID_EDIT_REDO,		_("redo"),		_MENU_1_("redo",wxEmptyString),	_HELP_("redo"));
		menuEdit->AppendSeparator();
		menuEdit->Append(ID_EDIT_CUT,		_("cut"),		_MENU_("cut"),				_HELP_("cut"));
		menuEdit->Append(ID_EDIT_COPY,		_("copy"),		_MENU_("copy"),				_HELP_("copy"));
		menuEdit->Append(ID_EDIT_PASTE,		_("paste"),		_MENU_("paste"),			_HELP_("paste"));
		menuEdit->AppendSeparator();
		menuEdit->Append(ID_EDIT_FIND,		_("find"),		_MENU_("find"),				_HELP_("find"));
		menuEdit->Append(ID_EDIT_FIND_NEXT,					_MENU_("find next"),		_HELP_("find next"));
		menuEdit->Append(ID_EDIT_REPLACE,					_MENU_("replace"),			_HELP_("replace"));
		menuEdit->Append(ID_EDIT_AUTO_REPLACE,				_MENU_("auto replace"),		_HELP_("auto replace"));
		menuEdit->AppendSeparator();
		menuEdit->Append(ID_EDIT_PREFERENCES,				_MENU_("preferences"),		_HELP_("preferences"));
	menuBar->Append(menuEdit, _MENU_("edit"));
	
	IconMenu* menuWindow = new IconMenu();
		menuWindow->Append(ID_WINDOW_NEW,					_MENU_("new window"),		_HELP_("new window"));
		menuWindow->AppendSeparator();
	menuBar->Append(menuWindow, _MENU_("window"));
	
	IconMenu* menuHelp = new IconMenu();
		menuHelp->Append(ID_HELP_INDEX,		_("help"),		_MENU_("index"),			_HELP_("index"));
		menuHelp->Append(ID_HELP_WEBSITE,					_MENU_("website"),			_HELP_("website"));
		menuHelp->AppendSeparator();
		menuHelp->Append(ID_HELP_ABOUT,						_MENU_("about"),			_HELP_("about"));
	menuBar->Append(menuHelp, _MENU_("help"));
	
	SetMenuBar(menuBar);
	
	// status bar
	CreateStatusBar();
	SetStatusText(_HELP_("welcome"));
	
	// tool bar
	wxToolBar* tb = CreateToolBar(wxTB_FLAT | wxNO_BORDER | wxTB_HORIZONTAL);
	tb->SetToolBitmapSize(wxSize(18,18));
	tb->AddTool(ID_FILE_NEW,	_(""),	load_resource_tool_image(_("new")),		wxNullBitmap, wxITEM_NORMAL, _TOOLTIP_("new set"),	_HELP_("new set"));
	tb->AddTool(ID_FILE_OPEN,	_(""),	load_resource_tool_image(_("open")),	wxNullBitmap, wxITEM_NORMAL, _TOOLTIP_("open set"),	_HELP_("open set"));
	tb->AddTool(ID_FILE_SAVE,	_(""),	load_resource_tool_image(_("save")),	wxNullBitmap, wxITEM_NORMAL, _TOOLTIP_("save set"),	_HELP_("save set"));
	tb->AddSeparator();
	tb->AddTool(ID_FILE_EXPORT,	_(""),	load_resource_tool_image(_("export")),	wxNullBitmap, wxITEM_CHECK,  _TOOLTIP_("export"),	_HELP_("export"));
	tb->AddSeparator();
	tb->AddTool(ID_EDIT_CUT,	_(""),	load_resource_tool_image(_("cut")),		wxNullBitmap, wxITEM_NORMAL, _TOOLTIP_("cut"),		_HELP_("cut"));
	tb->AddTool(ID_EDIT_COPY,	_(""),	load_resource_tool_image(_("copy")),	wxNullBitmap, wxITEM_NORMAL, _TOOLTIP_("copy"),		_HELP_("copy"));
	tb->AddTool(ID_EDIT_PASTE,	_(""),	load_resource_tool_image(_("paste")),	wxNullBitmap, wxITEM_NORMAL, _TOOLTIP_("paste"),	_HELP_("paste"));
	tb->AddSeparator();
	tb->AddTool(ID_EDIT_UNDO,	_(""),	load_resource_tool_image(_("undo")),	wxNullBitmap, wxITEM_NORMAL, _TOOLTIP_1_("undo",wxEmptyString));
	tb->AddTool(ID_EDIT_REDO,	_(""),	load_resource_tool_image(_("redo")),	wxNullBitmap, wxITEM_NORMAL, _TOOLTIP_1_("redo",wxEmptyString));
	tb->AddSeparator();
	tb->Realize();
	
	// tab bar, sizer
	wxToolBar* tabBar = new wxToolBar(this, ID_TAB_BAR, wxDefaultPosition, wxDefaultSize, wxTB_FLAT | wxNO_BORDER | wxTB_HORIZONTAL | wxTB_HORZ_TEXT | wxTB_NOALIGN);
	wxSizer* s = new wxBoxSizer(wxVERTICAL);
	s->Add(tabBar, 0, wxEXPAND | wxBOTTOM, 3);
	SetSizer(s);
	#if defined(__WXMSW__) && defined(TBSTYLE_EX_DOUBLEBUFFER)
		// Use double buffering
		HWND hWND = (HWND)tabBar->GetHWND();
		int style = ::SendMessage(hWND, TB_GETEXTENDEDSTYLE, 0, 0);
		::SendMessage(hWND, TB_SETEXTENDEDSTYLE, style | LVS_EX_DOUBLEBUFFER, 0);
	#endif
	
	// panels
	addPanel(menuWindow, tabBar, new CardsPanel     (this, wxID_ANY), 0, _("window_cards"),      _("cards tab"));
	addPanel(menuWindow, tabBar, new StylePanel     (this, wxID_ANY), 1, _("window_style"),      _("style tab"));
	addPanel(menuWindow, tabBar, new SetInfoPanel   (this, wxID_ANY), 2, _("window_set_info"),   _("set info tab"));
	addPanel(menuWindow, tabBar, new KeywordsPanel  (this, wxID_ANY), 3, _("window_keywords"),   _("keywords tab"));
	addPanel(menuWindow, tabBar, new StatsPanel     (this, wxID_ANY), 4, _("window_statistics"), _("stats tab"));
	addPanel(menuWindow, tabBar, new RandomPackPanel(this, wxID_ANY), 5, _("window_random_pack"),_("random pack tab"));
	addPanel(menuWindow, tabBar, new ConsolePanel   (this, wxID_ANY), 6, _("window_console"),    _("console tab"));
	selectPanel(ID_WINDOW_CARDS); // select cards panel
	
	// loose ends
	tabBar->Realize();
	SetSize(settings.set_window_width, settings.set_window_height);
	if (settings.set_window_maximized) {
		Maximize();
	}
	set_windows.push_back(this); // register this window
	// don't send update ui events to children
	// note: this still sends events for menu and toolbar items!
	wxUpdateUIEvent::SetMode(wxUPDATE_UI_PROCESS_SPECIFIED);
	SetExtraStyle(wxWS_EX_PROCESS_UI_UPDATES);
	tabBar->SetExtraStyle(wxWS_EX_PROCESS_UI_UPDATES);
	
	try {
		setSet(set);
	} catch (...) {
		// clean up!
		// if we don't destroy the panel we could crash in ~CardsPanel, since it expected
		// the insertSymbolMenu to be removed by destroyUI but not deleted.
		current_panel->destroyUI(GetToolBar(), GetMenuBar());
		delete find_dialog;
		set_windows.erase(remove(set_windows.begin(), set_windows.end(), this));
		throw;
	}
	current_panel->Layout();
}

SetWindow::~SetWindow() {
	// store window size in settings
	wxSize s = GetSize();
	settings.set_window_maximized = IsMaximized();
	if (!IsMaximized()) {
		settings.set_window_width  = s.GetWidth();
		settings.set_window_height = s.GetHeight();
	}
	// destroy ui of selected panel
	current_panel->destroyUI(GetToolBar(), GetMenuBar());
	// cleanup (see find stuff)
	delete find_dialog;
	// remove from list of set windows
	set_windows.erase(remove(set_windows.begin(), set_windows.end(), this));
}

// ----------------------------------------------------------------------------- : Panel managment

void SetWindow::addPanel(IconMenu* windowMenu, wxToolBar* tabBar, SetWindowPanel* panel, UInt pos, const String& image_name, const String& name) {
	// insert in list
	if (panels.size() <= pos) panels.resize(pos + 1);
	panels[pos] = panel;
	// names
	String menu_name   = tr(LOCALE_CAT_MENU,    name);
	String description = tr(LOCALE_CAT_HELP,    name);
	String tab_name    = tr(LOCALE_CAT_TOOL,    name);
	String tab_help    = tr(LOCALE_CAT_TOOLTIP, name);
	// add to tab bar
	int id = ID_WINDOW_MIN + pos;
	tabBar->AddTool(id, tab_name + _("   "), load_resource_tool_image(image_name), wxNullBitmap, wxITEM_CHECK, tab_help, description);
	tabBar->AddSeparator();
	// add to menu bar
	windowMenu->Append(id, image_name, menu_name, description, wxITEM_CHECK);
	// add to sizer
	GetSizer()->Add(panel, 1, wxEXPAND);
}

void SetWindow::selectPanel(int id) {
	SetWindowPanel* toSelect = panels.at(id - ID_WINDOW_MIN);
	if (current_panel == toSelect) {
		// don't change, but fix tab bar
		wxToolBar* tabBar  = (wxToolBar*)FindWindow(ID_TAB_BAR);
		int wid = ID_WINDOW_MIN;
		FOR_EACH(p, panels) {
			tabBar->ToggleTool(wid++, p == current_panel);
		}
		return;
	}
	// destroy & create menus
	if (current_panel) current_panel->destroyUI(GetToolBar(), GetMenuBar());
	current_panel = toSelect;
	current_panel->initUI(GetToolBar(), GetMenuBar());
	// show/hide panels and select tabs
	wxSizer*   sizer   = GetSizer();
	wxToolBar* tabBar  = (wxToolBar*)FindWindow(ID_TAB_BAR);
	wxMenuBar* menuBar = GetMenuBar();
	int wid = ID_WINDOW_MIN;
	FOR_EACH(p, panels) {
		sizer->Show       (p,   p == current_panel);
		tabBar->ToggleTool(wid, p == current_panel);
		menuBar->Check    (wid, p == current_panel);
		++wid;
	}
	// fix sizer stuff
	fixMinWindowSize();
	// select something
	current_panel->SetFocus();
}

void toolbar_SetToolNormalBitmap(wxToolBar* toolbar, int id, wxBitmap const& bitmap) {
	#if wxVERSION_NUMBER < 2800
		// copied from wx2.8.11, tbar95.cpp
		wxToolBarToolBase* tool = wx_static_cast(wxToolBarToolBase*, toolbar->FindById(id));
		if (tool) {
			tool->SetNormalBitmap(bitmap);
			toolbar->Realize();
		}
	#else
		toolbar->SetToolNormalBitmap(id, bitmap);
	#endif
}

void SetWindow::setPanelIcon(SetWindowPanel* panel, wxBitmap const& icon) {
	for (size_t i = 0 ; i < panels.size() ; ++i) {
		if (panels[i] == panel) {
			wxToolBar* tabBar = (wxToolBar*)FindWindow(ID_TAB_BAR);
			toolbar_SetToolNormalBitmap(tabBar, ID_WINDOW_MIN+(int)i, icon);
			#if wxVERSION_NUMBER < 2800
				// This is needed at least in wx2.6, other versions seem not to need it, but maybe they do
				Layout();
			#endif
			// TODO: this could be done better, wx requires a full new Realize of the toolbar, but on win32 a single message would do
			// if only we could set up the imagelist correctly.
			return;
		}
	}
}

// ----------------------------------------------------------------------------- : Window managment

vector<SetWindow*> SetWindow::set_windows;

bool SetWindow::isOnlyWithSet() {
	FOR_EACH(w, set_windows) {
		if (w != this && w->set == set) return false;
	}
	return true;
}

// ----------------------------------------------------------------------------- : Set actions

void SetWindow::onChangeSet() {
	// window title
	updateTitle();
	// make sure there is always at least one card
	// some things need this
	if (set->cards.empty()) set->cards.push_back(intrusive(new Card(*set->game)));
	// all panels view the same set
	FOR_EACH(p, panels) {
		p->setSet(set);
	}
	// only after setSet select a card
	FOR_EACH(p, panels) {
		p->selectFirstCard();
	}
	fixMinWindowSize();
}

void SetWindow::onAction(const Action& action, bool undone) {
	TYPE_CASE(action, ValueAction) {
		if (!action.card) {
			if (set->data.contains(action.valueP) && action.valueP->fieldP->identifying) {
				updateTitle();
			}
		}
	}
/*	TYPE_CASE_(action, DisplayChangeAction) {
		// The style changed, maybe also the size of card viewers
		if (current_panel) current_panel->Layout();
		fixMinWindowSize();
	}
*/
}

void SetWindow::updateTitle() {
	if (!set) {
		SetTitle(_TITLE_("magic set editor"));
	} else {
		String identification = set->identification();
		if (identification.empty()) identification = set->name();
		if (identification.empty()) identification = _TITLE_("untitled");
		set->short_name = identification;
		SetTitle(_TITLE_1_("%s - magic set editor",identification));
	}
}

void SetWindow::fixMinWindowSize() {
	current_panel->SetMinSize(current_panel->GetSizer()->GetMinSize());
	Layout();
	current_panel->Layout();
	wxSize s  = GetSizer()->GetMinSize();
	wxSize ws = GetSize();
	wxSize cs = GetClientSize();
	wxSize minSize = wxSize(s.x + ws.x - cs.x, s.y + ws.y - cs.y);
	if (ws.x < minSize.x) ws.x = minSize.x;
	if (ws.y < minSize.y) ws.y = minSize.y;
	SetSize(ws);
	SetMinSize(minSize);
}

void SetWindow::onSizeChange(wxCommandEvent&) {
	FOR_EACH(p, panels) {
		p->Layout();
	}
	fixMinWindowSize();
}


// ----------------------------------------------------------------------------- : Cards

void SetWindow::onCardSelect(CardSelectEvent& ev) {
	FOR_EACH(p, panels) {
		p->selectCard(ev.getCard());
	}
}
void SetWindow::onCardActivate(CardSelectEvent& ev) {
	selectPanel(ID_WINDOW_CARDS);
}

void SetWindow::selectionChoices(ExportCardSelectionChoices& out) {
	out.push_back(intrusive(new ExportCardSelectionChoice(*set))); // entire set
	FOR_EACH(p, panels) {
		p->selectionChoices(out);
	}
	out.push_back(intrusive(new ExportCardSelectionChoice())); // custom
}

// ----------------------------------------------------------------------------- : Window events - close

void SetWindow::onClose(wxCloseEvent& ev) {
	// only ask if we want to save is this is the only window that has the current set opened
	if (!isOnlyWithSet() || askSaveAndContinue()) {
		Destroy();
	} else {
		ev.Veto();
	}
}


int ask_save_changes_impl(wxWindow* parent, String const& message, String const& title) {
	#if defined(__WXMSW__) && defined(UNICODE) && defined(TD_WARNING_ICON) // the last one is a hack to test for precense of TASKDIALOG stuff
		// Do we have the TaskDialogIndirect function?
		HMODULE h = ::LoadLibrary(L"comctl32.dll");
		if (!h) return 0;
		typedef HRESULT (WINAPI *type_TaskDialogIndirect)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked);
		type_TaskDialogIndirect func_TaskDialogIndirect = !h ? nullptr : (type_TaskDialogIndirect)::GetProcAddress(h, "TaskDialogIndirect" );
		if (!func_TaskDialogIndirect) return 0;
		
		int nButtonPressed                  = 0;
		TASKDIALOGCONFIG config             = {0};
		const TASKDIALOG_BUTTON buttons[]   = { { IDYES, L"&Save" }, { IDNO, L"Do&n't Save" } };
		config.cbSize                       = sizeof(config);
		config.dwCommonButtons              = TDCBF_CANCEL_BUTTON;
		config.pszWindowTitle               = title.c_str();
		config.pszMainInstruction           = message.c_str();
		config.pButtons                     = buttons;
		config.cButtons                     = ARRAYSIZE(buttons);
		config.hwndParent                   = (HWND)(parent->GetHWND()); // without this the dialog is not modal
		
		func_TaskDialogIndirect(&config, &nButtonPressed, NULL, NULL);
		
		FreeLibrary(h);
		
		switch (nButtonPressed) {
			case IDYES: return wxYES;
			case IDNO:  return wxNO;
			default:    return wxCANCEL;
		}
	#else
		return 0;
	#endif
}
// Fancy save dialog
int ask_save_changes(wxWindow* parent, String const& message, String const& title) {
	int result = ask_save_changes_impl(parent, message,title);
	if (result) return result;
	// Use a normal message box
	return wxMessageBox(message, title, wxYES_NO | wxCANCEL | wxICON_EXCLAMATION, parent);
}


bool SetWindow::askSaveAndContinue() {
	if (set->actions.atSavePoint()) return true;
	int save = ask_save_changes(this, _LABEL_1_("save changes", set->short_name), _TITLE_("save changes"));
	if (save == wxYES) {
		// save the set
		try {
			if (set->needSaveAs()) {
				// need save as
				wxFileDialog dlg(this, _TITLE_("save set"), settings.default_set_dir, set->short_name, export_formats(*set->game), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
				if (dlg.ShowModal() == wxID_OK) {
					settings.default_set_dir = dlg.GetDirectory();
					export_set(*set, dlg.GetPath(), dlg.GetFilterIndex());
					return true;
				} else {
					return false;
				}
			} else {
				set->save();
				set->actions.setSavePoint();
				return true;
			}
		} catch (Error e) {
			// something went wrong with saving, don't proceed
			handle_error(e);
			return false;
		}
	} else if (save == wxNO) {
		return true;
	} else { // wxCANCEL
		return false;
	}
}

void SetWindow::switchSet(const SetP& new_set) {
	if (new_set) {
		if (settings.open_sets_in_new_window) {
			(new SetWindow(nullptr, new_set))->Show();
		} else {
			setSet(new_set);
		}
	}
}

// ----------------------------------------------------------------------------- : Window events - update UI

void SetWindow::onUpdateUI(wxUpdateUIEvent& ev) {
	switch (ev.GetId()) {
		// file menu
		case ID_FILE_SAVE:         ev.Enable(!set->actions.atSavePoint() || set->needSaveAs());	break;
		case ID_FILE_EXPORT_IMAGE: ev.Enable(!!current_panel->selectedCard());					break;
		case ID_FILE_EXPORT_APPR:  ev.Enable(set->game->isMagic());								break;
		case ID_FILE_EXPORT_MWS:   ev.Enable(set->game->isMagic());								break;
		case ID_FILE_EXIT:
			// update for ID_FILE_RECENT done for a different id, because ID_FILE_RECENT may not be in the menu yet
			updateRecentSets();
			break;
		// undo/redo
		case ID_EDIT_UNDO: {
			ev.Enable(set->actions.canUndo());
			String label = set->actions.undoName();
			ev.SetText(_MENU_1_("undo", label));
			GetToolBar()->SetToolShortHelp(ID_EDIT_UNDO, _TOOLTIP_1_("undo", label));
			break;
		} case ID_EDIT_REDO: {
			ev.Enable(set->actions.canRedo());
			String label = set->actions.redoName();
			ev.SetText(_MENU_1_("redo", label));
			GetToolBar()->SetToolShortHelp(ID_EDIT_REDO, _TOOLTIP_1_("redo", label));
			break;
		}
		// copy & paste & find
		case ID_EDIT_CUT       : ev.Enable(current_panel->canCut());	break;
		case ID_EDIT_COPY      : ev.Enable(current_panel->canCopy());	break;
		case ID_EDIT_PASTE     : ev.Enable(current_panel->canPaste());	break;
		case ID_EDIT_FIND      : ev.Enable(current_panel->canFind());	break;
		case ID_EDIT_FIND_NEXT : ev.Enable(current_panel->canFind());	break;
		case ID_EDIT_REPLACE   : ev.Enable(current_panel->canReplace());break;
		// windows
		case ID_WINDOW_KEYWORDS: ev.Enable(set->game->has_keywords);	break;
		case ID_WINDOW_RANDOM_PACK: ev.Enable(!set->game->pack_types.empty());	break;
		// help
		case ID_HELP_INDEX     : ev.Enable(false);						break; // not implemented
		// other
		default:
			// items created by the panel, and cut/copy/paste and find/replace
			if(current_panel) current_panel->onUpdateUI(ev);
	}
}

static const int FILE_MENU_SIZE_BEFORE_RECENT_SETS = 12; // HACK; we should calculate the position to insert!
void SetWindow::updateRecentSets() {
	wxMenuBar* mb = GetMenuBar();
	assert(number_of_recent_sets <= (UInt)settings.recent_sets.size()); // the number of recent sets should only increase
	UInt i = 0;
	FOR_EACH(file, settings.recent_sets) {
		if (i >= settings.max_recent_sets) break;
		if (i < number_of_recent_sets) {
			// menu item already exists, update it
			mb->SetLabel(ID_FILE_RECENT + i, String(_("&")) << (i+1) << _(" ") << file);
		} else {
			// add new item
			int pos = i + FILE_MENU_SIZE_BEFORE_RECENT_SETS; // HUGE HACK, we should calculate the position to insert!
			IconMenu* file_menu = static_cast<IconMenu*>(mb->GetMenu(0));
			file_menu->Insert(pos, ID_FILE_RECENT + i, String(_("&")) << (i+1) << _(" ") << file, wxEmptyString);
		}
		i++;
	}
	number_of_recent_sets = (UInt)settings.recent_sets.size();
}

// ----------------------------------------------------------------------------- : Window events - menu - file


void SetWindow::onFileNew(wxCommandEvent&) {
	if (!settings.open_sets_in_new_window && isOnlyWithSet() && !askSaveAndContinue()) return;
	// new set?
	SetP new_set = new_set_window(this);
	switchSet(new_set);
}

void SetWindow::onFileOpen(wxCommandEvent&) {
	if (!settings.open_sets_in_new_window && isOnlyWithSet() && !askSaveAndContinue()) return;
	wxFileDialog dlg(this, _TITLE_("open set"), settings.default_set_dir, _(""), import_formats(), wxFD_OPEN);
	if (dlg.ShowModal() == wxID_OK) {
		settings.default_set_dir = dlg.GetDirectory();
		wxBusyCursor busy;
		SetP new_set = import_set(dlg.GetPath());
		switchSet(new_set);
	}
}

void SetWindow::onFileSave(wxCommandEvent& ev) {
	if (set->needSaveAs()) {
		onFileSaveAs(ev);
	} else {
		wxBusyCursor busy;
		settings.addRecentFile(set->absoluteFilename());
		set->save();
		set->actions.setSavePoint();
	}
}

void SetWindow::onFileSaveAs(wxCommandEvent&) {
	wxFileDialog dlg(this, _TITLE_("save set"), settings.default_set_dir, set->short_name, export_formats(*set->game), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
	if (dlg.ShowModal() == wxID_OK) {
		settings.default_set_dir = dlg.GetDirectory();
		export_set(*set, dlg.GetPath(), dlg.GetFilterIndex());
		updateTitle(); // title may depend on filename
	}
}

/*
void SetWindow::onFileInspect(wxCommandEvent&) {
	var wnd = new TreeGridWindow(&this, set);
	wnd.show();
}*/

#if defined(__WXMSW__)
#include "wx/msw/wrapcctl.h"
#endif

void SetWindow::onFileExportMenu(wxCommandEvent& ev) {
	// find position of export tool bar button
	wxToolBar* tb = GetToolBar();
	int pos = tb->GetToolPos(ev.GetId());
	wxRect tool_rect;
	#if defined(__WXMSW__)
		RECT r = {0,0,0,0};
		::SendMessage((HWND)tb->GetHWND(), TB_GETITEMRECT, pos, (LPARAM)&r);
		tool_rect.x      = r.left;
		tool_rect.y      = r.top;
		tool_rect.width  = r.right - r.left;
		tool_rect.height = r.bottom - r.top;
	#else
		// TODO: This is probably not correct!
		wxSize tool_size = tb->GetToolSize();
		wxSize tool_margin = tb->GetToolMargins();
		tool_rect.SetSize(tool_size);
		tool_rect.y = 0;
		tool_rect.x = tool_size.x * pos;
	#endif
	// tool down
	tb->ToggleTool(ev.GetId(), true);
	// pop up a menu of export options
	tb->PopupMenu(menuExport, tool_rect.x, tool_rect.GetBottom());
	// tool up
	tb->ToggleTool(ev.GetId(), false);
}

void SetWindow::onFileExportImage(wxCommandEvent&) {
	CardP card = current_panel->selectedCard();
	if (!card)  return; // no card selected
	String name = wxFileSelector(_TITLE_("save image"), settings.default_export_dir, clean_filename(card->identification()), _(""),
		                         _("JPEG images (*.jpg)|*.jpg|Windows bitmaps (*.bmp)|*.bmp|PNG images (*.png)|*.png|TIFF images (*.tif)|*.tif"),
		                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT, this);
	if (!name.empty()) {
		settings.default_export_dir = wxPathOnly(name);
		export_image(set, card, name);
	}
}

void SetWindow::onFileExportImages(wxCommandEvent&) {
	ExportCardSelectionChoices choices;
	selectionChoices(choices);
	ImagesExportWindow wnd(this, set, choices);
	wnd.ShowModal();
}

void SetWindow::onFileExportHTML(wxCommandEvent&) {
	ExportCardSelectionChoices choices;
	selectionChoices(choices);
	HtmlExportWindow wnd(this, set, choices);
	wnd.ShowModal();
}

void SetWindow::onFileExportApprentice(wxCommandEvent&) {
	export_apprentice(this, set);
}

void SetWindow::onFileExportMWS(wxCommandEvent&) {
	export_mws(this, set);
}

void SetWindow::onFileCheckUpdates(wxCommandEvent&) {
	if (!askSaveAndContinue()) return;
	(new PackagesWindow(this))->Show();
	//Destroy();
}

#if USE_SCRIPT_PROFILING
	void show_profiler_window(wxWindow* parent);
	void SetWindow::onFileProfiler(wxCommandEvent&) {
		show_profiler_window(this);
	}
#endif

void SetWindow::onFilePrint(wxCommandEvent&) {
	ExportCardSelectionChoices choices;
	selectionChoices(choices);
	print_set(this, set, choices);
}

void SetWindow::onFilePrintPreview(wxCommandEvent&) {
	ExportCardSelectionChoices choices;
	selectionChoices(choices);
	print_preview(this, set, choices);
}

void SetWindow::onFileReload(wxCommandEvent&) {
	if (!askSaveAndContinue()) return;
	String filename = set->absoluteFilename();
	if (filename.empty()) return;
	wxBusyCursor busy;
	settings.write();   // save settings
	// current card
	size_t card_pos = 0;
	{
		vector<CardP>::const_iterator card_it = find(set->cards.begin(), set->cards.end(), current_panel->selectedCard());
		if (card_it != set->cards.end()) card_pos = card_it - set->cards.begin();
	}
	package_manager.reset(); // unload all packages
	settings.read();         // reload settings
	setSet(import_set(filename));
	// reselect card
	if (card_pos < set->cards.size()) {
		FOR_EACH(p, panels) {
			p->selectCard(set->cards[card_pos]);
		}
	}
}

void SetWindow::onFileRecent(wxCommandEvent& ev) {
	wxBusyCursor busy;
	switchSet(import_set(settings.recent_sets.at(ev.GetId() - ID_FILE_RECENT)));
}

void SetWindow::onFileExit(wxCommandEvent&) {
	Close();
}

// ----------------------------------------------------------------------------- : Window events - menu - edit

void SetWindow::onEditUndo(wxCommandEvent&) {
	set->actions.undo();
}
void SetWindow::onEditRedo(wxCommandEvent&) {
	set->actions.redo();
}

void SetWindow::onEditCut  (wxCommandEvent&) {
	current_panel->doCut();
}
void SetWindow::onEditCopy (wxCommandEvent&) {
	current_panel->doCopy();
}
void SetWindow::onEditPaste(wxCommandEvent&) {
	current_panel->doPaste();
}

void SetWindow::onEditFind(wxCommandEvent&) {
	delete find_dialog;
	find_dialog = new wxFindReplaceDialog(this, &find_data, _("Find"));
	find_dialog->Show();
}
void SetWindow::onEditFindNext(wxCommandEvent&) {
	current_panel->doFind(find_data);
}
void SetWindow::onEditReplace(wxCommandEvent&) {
	delete find_dialog;
	find_dialog = new wxFindReplaceDialog(this, &find_data, _("Replace"), wxFR_REPLACEDIALOG);
	find_dialog->Show();
}

// find events
void SetWindow::onFind      (wxFindDialogEvent&) {
	current_panel->doFind(find_data);
}
void SetWindow::onFindNext  (wxFindDialogEvent&) {
	current_panel->doFind(find_data);
}
void SetWindow::onReplace   (wxFindDialogEvent&) {
	current_panel->doReplace(find_data);
}
void SetWindow::onReplaceAll(wxFindDialogEvent&) {
	current_panel->doReplaceAll(find_data);
}

void SetWindow::onEditAutoReplace(wxCommandEvent&) {
	(new AutoReplaceWindow(this, *set->game))->Show();
}

void SetWindow::onEditPreferences(wxCommandEvent&) {
	PreferencesWindow wnd(this);
	if (wnd.ShowModal() == wxID_OK) {
		// render settings may have changed, notify all windows
		set->actions.tellListeners(DisplayChangeAction(),true);
	}
}


// ----------------------------------------------------------------------------- : Window events - menu - window

void SetWindow::onWindowNewWindow(wxCommandEvent&) {
	SetWindow* newWindow = new SetWindow(nullptr, set);
	newWindow->Show();
}

void SetWindow::onWindowSelect(wxCommandEvent& ev) {
	selectPanel(ev.GetId());
}


// ----------------------------------------------------------------------------- : Window events - menu - help

void SetWindow::onHelpIndex(wxCommandEvent&) {
	// TODO : context sensitive
//	HelpWindow* wnd = new HelpWindow(this);
//	wnd->Show();
}

void SetWindow::onHelpWebsite(wxCommandEvent&) {
	wxLaunchDefaultBrowser(settings.website_url);
}

void SetWindow::onHelpAbout(wxCommandEvent&) {
	AboutWindow wnd(this);
	wnd.ShowModal();
}

// ----------------------------------------------------------------------------- : Window events - other

void SetWindow::onChildMenu(wxCommandEvent& ev) {
	current_panel->onCommand(ev.GetId());
}

void SetWindow::onMenuOpen(wxMenuEvent& ev) {
	current_panel->onMenuOpen(ev);
	wxFrame::OnMenuOpen(ev);
}

void SetWindow::onIdle(wxIdleEvent& ev) {
	// Stuff that must be done in the main thread
	show_update_dialog(this);
}

// ----------------------------------------------------------------------------- : Event table

BEGIN_EVENT_TABLE(SetWindow, wxFrame)
	EVT_MENU			(ID_FILE_NEW,			SetWindow::onFileNew)
	EVT_MENU			(ID_FILE_OPEN,			SetWindow::onFileOpen)
	EVT_MENU			(ID_FILE_SAVE,			SetWindow::onFileSave)
	EVT_MENU			(ID_FILE_SAVE_AS,		SetWindow::onFileSaveAs)
	EVT_MENU			(ID_FILE_EXPORT,		SetWindow::onFileExportMenu)
	EVT_MENU			(ID_FILE_EXPORT_IMAGE,	SetWindow::onFileExportImage)
	EVT_MENU			(ID_FILE_EXPORT_IMAGES,	SetWindow::onFileExportImages)
	EVT_MENU			(ID_FILE_EXPORT_HTML,	SetWindow::onFileExportHTML)
	EVT_MENU			(ID_FILE_EXPORT_APPR,	SetWindow::onFileExportApprentice)
	EVT_MENU			(ID_FILE_EXPORT_MWS,	SetWindow::onFileExportMWS)
	EVT_MENU			(ID_FILE_CHECK_UPDATES,	SetWindow::onFileCheckUpdates)
#if USE_SCRIPT_PROFILING
	EVT_MENU			(ID_FILE_PROFILER,		SetWindow::onFileProfiler)
#endif
//	EVT_MENU			(ID_FILE_INSPECT,		SetWindow::onFileInspect)
	EVT_MENU			(ID_FILE_PRINT,			SetWindow::onFilePrint)
	EVT_MENU			(ID_FILE_PRINT_PREVIEW,	SetWindow::onFilePrintPreview)
	EVT_MENU			(ID_FILE_RELOAD,		SetWindow::onFileReload)
	EVT_MENU_RANGE		(ID_FILE_RECENT, ID_FILE_RECENT_MAX, SetWindow::onFileRecent)
	EVT_MENU			(ID_FILE_EXIT,			SetWindow::onFileExit)
	EVT_MENU			(ID_EDIT_UNDO,			SetWindow::onEditUndo)
	EVT_MENU			(ID_EDIT_REDO,			SetWindow::onEditRedo)
	EVT_MENU			(ID_EDIT_CUT,			SetWindow::onEditCut)
	EVT_MENU			(ID_EDIT_COPY,			SetWindow::onEditCopy)
	EVT_MENU			(ID_EDIT_PASTE,			SetWindow::onEditPaste)
	EVT_MENU			(ID_EDIT_FIND,			SetWindow::onEditFind)
	EVT_MENU			(ID_EDIT_FIND_NEXT,		SetWindow::onEditFindNext)
	EVT_MENU			(ID_EDIT_REPLACE,		SetWindow::onEditReplace)
	EVT_MENU			(ID_EDIT_AUTO_REPLACE,	SetWindow::onEditAutoReplace)
	EVT_MENU			(ID_EDIT_PREFERENCES,	SetWindow::onEditPreferences)
	EVT_MENU			(ID_WINDOW_NEW,			SetWindow::onWindowNewWindow)
	EVT_TOOL_RANGE		(ID_WINDOW_MIN, ID_WINDOW_MAX, SetWindow::onWindowSelect)
	EVT_MENU			(ID_HELP_INDEX,			SetWindow::onHelpIndex)
	EVT_MENU			(ID_HELP_WEBSITE,		SetWindow::onHelpWebsite)
	EVT_MENU			(ID_HELP_ABOUT,			SetWindow::onHelpAbout)
	EVT_MENU_OPEN		(						SetWindow::onMenuOpen)
	EVT_TOOL_RANGE		(ID_CHILD_MIN, ID_CHILD_MAX,   SetWindow::onChildMenu)
	EVT_COMMAND_RANGE	(ID_CHILD_MIN, ID_CHILD_MAX, wxEVT_COMMAND_BUTTON_CLICKED, SetWindow::onChildMenu)
	EVT_COMMAND_RANGE	(ID_CHILD_MIN, ID_CHILD_MAX, wxEVT_COMMAND_SPINCTRL_UPDATED, SetWindow::onChildMenu)
	EVT_COMMAND_RANGE	(ID_CHILD_MIN, ID_CHILD_MAX, wxEVT_COMMAND_RADIOBUTTON_SELECTED, SetWindow::onChildMenu)
	EVT_COMMAND_RANGE	(ID_CHILD_MIN, ID_CHILD_MAX, wxEVT_COMMAND_TEXT_UPDATED, SetWindow::onChildMenu)
	EVT_GALLERY_SELECT  (ID_FIELD_LIST,                SetWindow::onChildMenu) // for StatsPanel, because it is not a EVT_TOOL
	
	EVT_UPDATE_UI		(wxID_ANY,				SetWindow::onUpdateUI)
	EVT_FIND			(wxID_ANY,				SetWindow::onFind)
	EVT_FIND_NEXT		(wxID_ANY,				SetWindow::onFindNext)
	EVT_FIND_REPLACE	(wxID_ANY,				SetWindow::onReplace)
	EVT_FIND_REPLACE_ALL(wxID_ANY,				SetWindow::onReplaceAll)
	EVT_CLOSE			(						SetWindow::onClose)
	EVT_IDLE			(						SetWindow::onIdle)
	EVT_CARD_SELECT		(wxID_ANY,				SetWindow::onCardSelect)
	EVT_CARD_ACTIVATE	(wxID_ANY,				SetWindow::onCardActivate)
	EVT_SIZE_CHANGE		(wxID_ANY,				SetWindow::onSizeChange)
	EVT_ERASE_BACKGROUND(						SetWindow::onEraseBackground)
END_EVENT_TABLE  ()
