//+----------------------------------------------------------------------------+
//| 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/cards_panel.hpp>
#include <gui/control/image_card_list.hpp>
#include <gui/control/card_editor.hpp>
#include <gui/control/text_ctrl.hpp>
#include <gui/about_window.hpp> // for HoverButton
#include <gui/update_checker.hpp>
#include <gui/icon_menu.hpp>
#include <gui/util.hpp>
#include <data/set.hpp>
#include <data/game.hpp>
#include <data/card.hpp>
#include <data/add_cards_script.hpp>
#include <data/action/set.hpp>
#include <data/settings.hpp>
#include <util/find_replace.hpp>
#include <util/tagged_string.hpp>
#include <util/window_id.hpp>
#include <wx/splitter.h>

DECLARE_TYPEOF_COLLECTION(AddCardsScriptP);

#ifdef EVT_TOOL_DROPDOWN
	// This is only available after patching wx or in version 2.10
	// see http://trac.wxwidgets.org/ticket/8556
	#define HAVE_TOOLBAR_DROPDOWN_MENU 1
#endif

// ----------------------------------------------------------------------------- : FilterControl

/// Text control that forwards focus events to the parent
class TextCtrlWithFocus : public wxTextCtrl {
  public:
	DECLARE_EVENT_TABLE();
	void forwardEvent(wxFocusEvent&);
};

/// A search/filter textbox
class FilterCtrl : public wxControl {
  public:
	FilterCtrl(wxWindow* parent, int id);
	/// Set the filter text
	void setFilter(const String& filter, bool event = false);
	void clearFilter()       { setFilter(String()); }
	bool hasFilter() const   { return !value.empty(); }
	String const& getFilter() const { return value; }
	
	//bool AcceptsFocus() const { return false; }
  private:
	DECLARE_EVENT_TABLE();
	bool changing;
	wxString value;
	TextCtrlWithFocus*  filter_ctrl;
	HoverButton* clear_button;
	
	void update();
	bool hasFocus();
        // wxWidgets appears to have developed an overload allergy
	void onChange();
	void onChangeEvent(wxCommandEvent&);
	void onClear(wxCommandEvent&);
	void onSizeEvent(wxSizeEvent&);
	void onSize();
  public:
	void onSetFocus(wxFocusEvent&);
	void onKillFocus(wxFocusEvent&);
};

FilterCtrl::FilterCtrl(wxWindow* parent, int id)
	: wxControl(parent, id, wxDefaultPosition, wxSize(160,41), wxSTATIC_BORDER)
	, changing(false)
{
	wxColour bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
	SetBackgroundColour(bg);
	SetCursor(wxCURSOR_IBEAM);
	filter_ctrl = new TextCtrlWithFocus();
	filter_ctrl->Create(this, wxID_ANY, _(""), wxDefaultPosition, wxSize(130,-1), wxNO_BORDER);
	clear_button = new HoverButton(this, wxID_ANY, _("btn_clear_filter"), bg, false);
	clear_button->SetCursor(*wxSTANDARD_CURSOR);
	onSize();
	update();
}

void FilterCtrl::setFilter(const String& new_value, bool event) {
	if (this->value == new_value) return;
	// update ui
	this->value = new_value;
	update();
	// send event
	if (event) {
		wxCommandEvent ev(wxEVT_COMMAND_TEXT_UPDATED, GetId());
		GetParent()->ProcessEvent(ev);
	}
}

void FilterCtrl::update() {
	changing = true;
	if (!value.empty() || hasFocus()) {
		filter_ctrl->SetValue(value);
		wxColour fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
		filter_ctrl->SetDefaultStyle(wxTextAttr(fg));
		filter_ctrl->SetForegroundColour(fg);
	} else {
		filter_ctrl->SetValue(_LABEL_("search cards"));
		wxColour fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
		wxColour bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
		filter_ctrl->SetDefaultStyle(wxTextAttr(lerp(fg,bg,0.5)));
		filter_ctrl->SetForegroundColour(lerp(fg,bg,0.5));
	}
	clear_button->Show(!value.empty());
	changing = false;
}

void FilterCtrl::onChangeEvent(wxCommandEvent&) {
	if (!changing) {
		setFilter(filter_ctrl->GetValue(),true);
	}
}

void FilterCtrl::onClear(wxCommandEvent&) {
	setFilter(String(),true);
}

void FilterCtrl::onSizeEvent(wxSizeEvent&) {
	onSize();
}
void FilterCtrl::onSize() {
	wxSize s = GetClientSize();
	wxSize fs = filter_ctrl->GetBestSize();
	wxSize cs = clear_button->GetBestSize();
	int margin = 2;
	filter_ctrl ->SetSize(margin, max(margin,(s.y-fs.y)/2), s.x - cs.x - 3*margin, fs.y);
	clear_button->SetSize(s.x - cs.x - margin, (s.y-cs.y)/2, cs.x, cs.y);
}

void FilterCtrl::onSetFocus(wxFocusEvent&) {
	filter_ctrl->SetFocus();
	update();
}
void FilterCtrl::onKillFocus(wxFocusEvent&) {
	update();
}

bool FilterCtrl::hasFocus() {
	wxWindow* focus = wxWindow::FindFocus();
	return focus == this || focus == filter_ctrl || focus == clear_button;
}

BEGIN_EVENT_TABLE(FilterCtrl, wxControl)
	EVT_BUTTON    (wxID_ANY, FilterCtrl::onClear)
	EVT_TEXT      (wxID_ANY, FilterCtrl::onChangeEvent)
	EVT_SIZE      (FilterCtrl::onSizeEvent)
	EVT_SET_FOCUS (FilterCtrl::onSetFocus)
	EVT_KILL_FOCUS(FilterCtrl::onKillFocus)
END_EVENT_TABLE()

void TextCtrlWithFocus::forwardEvent(wxFocusEvent& ev) {
	GetParent()->ProcessEvent(ev);
}

BEGIN_EVENT_TABLE(TextCtrlWithFocus, wxTextCtrl)
	EVT_SET_FOCUS (TextCtrlWithFocus::forwardEvent)
	EVT_KILL_FOCUS(TextCtrlWithFocus::forwardEvent)
END_EVENT_TABLE()

// ----------------------------------------------------------------------------- : CardsPanel

CardsPanel::CardsPanel(Window* parent, int id)
	: SetWindowPanel(parent, id)
{
	// init controls
	editor      = new CardEditor(this, ID_EDITOR);
	splitter    = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
	card_list   = new FilteredImageCardList(splitter, ID_CARD_LIST);
	nodes_panel = new Panel(splitter, wxID_ANY);
	notes       = new TextCtrl(nodes_panel, ID_NOTES, true);
	collapse_notes = new HoverButton(nodes_panel, ID_COLLAPSE_NOTES, _("btn_collapse"), wxNullColour, false);
	collapse_notes->SetExtraStyle(wxWS_EX_PROCESS_UI_UPDATES);
	filter    = nullptr;
	editor->next_in_tab_order = card_list;
	// init sizer for notes panel
	wxSizer* sn = new wxBoxSizer(wxVERTICAL);
		wxSizer* sc = new wxBoxSizer(wxHORIZONTAL);
		sc->Add(new wxStaticText(nodes_panel, wxID_ANY, _LABEL_("card notes")), 1, wxEXPAND);
		sc->Add(collapse_notes, 0, wxALIGN_CENTER | wxRIGHT, 2);
	sn->Add(sc, 0, wxEXPAND, 2);
	sn->Add(notes, 1, wxEXPAND | wxTOP, 2);
	nodes_panel->SetSizer(sn);
	// init splitter
	splitter->SetMinimumPaneSize(15);
	splitter->SetSashGravity(1.0);
	splitter->SplitHorizontally(card_list, nodes_panel, -40);
	notes_below_editor = false;
	// init sizer
	wxSizer* s = new wxBoxSizer(wxHORIZONTAL);
		s_left = new wxBoxSizer(wxVERTICAL);
		s_left->Add(editor);
	s->Add(s_left,   0, wxEXPAND | wxRIGHT, 2);
	s->Add(splitter, 1, wxEXPAND);
	s->SetSizeHints(this);
	SetSizer(s);
	
	// init menus
	menuCard = new IconMenu();
		menuCard->Append(ID_CARD_PREV,								_MENU_("previous card"),	_HELP_("previous card"));
		menuCard->Append(ID_CARD_NEXT,								_MENU_("next card"),		_HELP_("next card"));
		menuCard->AppendSeparator();
		menuCard->Append(ID_CARD_ADD,		_("card_add"),			_MENU_("add card"),			_HELP_("add card"));
		insertManyCardsMenu = new wxMenuItem(menuCard, ID_CARD_ADD_MULT, _MENU_("add cards"), _HELP_("add cards"));
		set_menu_item_image(insertManyCardsMenu, _("card_add_multiple"));
		((wxMenu*)menuCard)->Append(insertManyCardsMenu);
																	// NOTE: space after "Del" prevents wx from making del an accellerator
																	// otherwise we delete a card when delete is pressed inside the editor
																	// Adding a space never hurts, please keep it just to be safe.
		menuCard->Append(ID_CARD_REMOVE,	_("card_del"),			_MENU_("remove card")+_(" "),_HELP_("remove card"));
		menuCard->AppendSeparator();
		IconMenu* menuRotate = new IconMenu();
			menuRotate->Append(ID_CARD_ROTATE_0,		_("card_rotate_0"),		_MENU_("rotate 0"),		_HELP_("rotate 0"),		wxITEM_CHECK);
			menuRotate->Append(ID_CARD_ROTATE_270,		_("card_rotate_270"),	_MENU_("rotate 270"),	_HELP_("rotate 270"),	wxITEM_CHECK);
			menuRotate->Append(ID_CARD_ROTATE_90,		_("card_rotate_90"),	_MENU_("rotate 90"),	_HELP_("rotate 90"),	wxITEM_CHECK);
			menuRotate->Append(ID_CARD_ROTATE_180,		_("card_rotate_180"),	_MENU_("rotate 180"),	_HELP_("rotate 180"),	wxITEM_CHECK);
		menuCard->Append(wxID_ANY,			_("card_rotate"),		_MENU_("orientation"),		_HELP_("orientation"),		wxITEM_NORMAL, menuRotate);
		menuCard->AppendSeparator();
		// This probably belongs in the window menu, but there we can't remove the separator once it is added
		menuCard->Append(ID_SELECT_COLUMNS,							_MENU_("card list columns"),_HELP_("card list columns"));
	
	menuFormat = new IconMenu();
		menuFormat->Append(ID_FORMAT_BOLD,		_("bold"),			_MENU_("bold"),				_HELP_("bold"),				wxITEM_CHECK);
		menuFormat->Append(ID_FORMAT_ITALIC,	_("italic"),		_MENU_("italic"),			_HELP_("italic"),			wxITEM_CHECK);
		menuFormat->Append(ID_FORMAT_SYMBOL,	_("symbol"),		_MENU_("symbols"),			_HELP_("symbols"),			wxITEM_CHECK);
		menuFormat->Append(ID_FORMAT_REMINDER,	_("reminder"),		_MENU_("reminder text"),	_HELP_("reminder text"),	wxITEM_CHECK);
		menuFormat->AppendSeparator();
		insertSymbolMenu = new wxMenuItem(menuFormat, ID_INSERT_SYMBOL, _MENU_("insert symbol"));
		menuFormat->Append(insertSymbolMenu);
	
	toolAddCard = nullptr;
}

void CardsPanel::updateNotesPosition() {
	wxSize editor_size = editor->GetBestSize();
	int room_below_editor = GetSize().y - editor_size.y;
	bool should_be_below = room_below_editor > 100;
	// move?
	if (should_be_below && !notes_below_editor) {
		notes_below_editor = true;
		// move the notes_panel to below the editor, it gets this as its parent
		splitter->Unsplit(nodes_panel);
		nodes_panel->Reparent(this);
		s_left->Add(nodes_panel, 1, wxEXPAND | wxTOP, 2);
		collapse_notes->Hide();
		nodes_panel->Show();
	} else if (!should_be_below && notes_below_editor) {
		notes_below_editor = false;
		// move the notes_panel back to below the card list
		s_left->Detach(nodes_panel);
		nodes_panel->Reparent(splitter);
		collapse_notes->Show();
		splitter->SplitHorizontally(card_list, nodes_panel, -80);
	}
}
bool CardsPanel::Layout() {
	updateNotesPosition();
	return SetWindowPanel::Layout();
}

CardsPanel::~CardsPanel() {
//	settings.card_notes_height = splitter->GetSashPosition();
	// we don't own the submenu
	wxMenu* menu = insertSymbolMenu->GetSubMenu();
	if (menu && menu->GetParent() == menuFormat) {
		menu->SetParent(nullptr);
	}
	insertSymbolMenu->SetSubMenu(nullptr); 
	// delete menus
	delete menuCard;
	delete menuFormat;
}

void CardsPanel::onChangeSet() {
	editor->setSet(set);
	notes->setSet(set);
	card_list->setSet(set);
	
	// change insertManyCardsMenu
	delete insertManyCardsMenu->GetSubMenu();
	insertManyCardsMenu->SetSubMenu(makeAddCardsSubmenu(false));
	// re-add the menu
	menuCard->Remove(ID_CARD_ADD_MULT);
	((wxMenu*)menuCard)->Insert(4,insertManyCardsMenu); // HACK: the position is hardcoded
	// also for the toolbar dropdown menu
	#if HAVE_TOOLBAR_DROPDOWN_MENU
		if (toolAddCard) {
			toolAddCard->SetDropdownMenu(makeAddCardsSubmenu(true));
		}
	#endif
}

wxMenu* CardsPanel::makeAddCardsSubmenu(bool add_single_card_option) {
	IconMenu* cards_scripts_menu = nullptr;
	// default item?
	if (add_single_card_option) {
		cards_scripts_menu = new IconMenu;
		cards_scripts_menu->Append(ID_CARD_ADD, _("card_add"), _MENU_("add card"), _HELP_("add card"));
		cards_scripts_menu->AppendSeparator();
	}
	// create menu for add_cards_scripts
	if (set && set->game && !set->game->add_cards_scripts.empty()) {
		int id = ID_ADD_CARDS_MENU_MIN;
		if (!cards_scripts_menu) cards_scripts_menu = new IconMenu;
		FOR_EACH(cs, set->game->add_cards_scripts) {
			cards_scripts_menu->Append(id++, cs->name, cs->description);
		}
	}
	return cards_scripts_menu;
}

// ----------------------------------------------------------------------------- : UI

void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
	// Toolbar
	tb->AddTool(ID_FORMAT_BOLD,		_(""), load_resource_tool_image(_("bold")),			wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("bold"),			_HELP_("bold"));
	tb->AddTool(ID_FORMAT_ITALIC,	_(""), load_resource_tool_image(_("italic")),		wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("italic"),		_HELP_("italic"));
	tb->AddTool(ID_FORMAT_SYMBOL,	_(""), load_resource_tool_image(_("symbol")),		wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("symbols"),		_HELP_("symbols"));
	tb->AddTool(ID_FORMAT_REMINDER,	_(""), load_resource_tool_image(_("reminder")),		wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("reminder text"),	_HELP_("reminder text"));
	tb->AddSeparator();
	#if HAVE_TOOLBAR_DROPDOWN_MENU
		toolAddCard = tb->AddTool(ID_CARD_ADD,		_(""), load_resource_tool_image(_("card_add")),		wxNullBitmap, wxITEM_DROPDOWN,_TOOLTIP_("add card"),		_HELP_("add card"));
		toolAddCard->SetDropdownMenu(makeAddCardsSubmenu(true));
	#else
		tb->AddTool(ID_CARD_ADD,		_(""), load_resource_tool_image(_("card_add")),		wxNullBitmap, wxITEM_NORMAL,_TOOLTIP_("add card"),		_HELP_("add card"));
	#endif
	tb->AddTool(ID_CARD_REMOVE,		_(""), load_resource_tool_image(_("card_del")),		wxNullBitmap, wxITEM_NORMAL,_TOOLTIP_("remove card"),	_HELP_("remove card"));
	tb->AddSeparator();
	#if HAVE_TOOLBAR_DROPDOWN_MENU
		wxToolBarToolBase* rot = tb->AddTool(ID_CARD_ROTATE,		_(""), load_resource_tool_image(_("card_rotate")),	wxNullBitmap, wxITEM_DROPDOWN, _TOOLTIP_("rotate card"),	_HELP_("rotate card"));
		IconMenu* menuRotate = new IconMenu();
			menuRotate->Append(ID_CARD_ROTATE_0,		_("card_rotate_0"),		_MENU_("rotate 0"),		_HELP_("rotate 0"),		wxITEM_CHECK);
			menuRotate->Append(ID_CARD_ROTATE_270,		_("card_rotate_270"),	_MENU_("rotate 270"),	_HELP_("rotate 270"),	wxITEM_CHECK);
			menuRotate->Append(ID_CARD_ROTATE_90,		_("card_rotate_90"),	_MENU_("rotate 90"),	_HELP_("rotate 90"),	wxITEM_CHECK);
			menuRotate->Append(ID_CARD_ROTATE_180,		_("card_rotate_180"),	_MENU_("rotate 180"),	_HELP_("rotate 180"),	wxITEM_CHECK);
		rot->SetDropdownMenu(menuRotate);
	#else
		tb->AddTool(ID_CARD_ROTATE,		_(""), load_resource_tool_image(_("card_rotate")),	wxNullBitmap,wxITEM_NORMAL, _TOOLTIP_("rotate card"),	_HELP_("rotate card"));
	#endif
	// Filter/search textbox
	tb->AddSeparator();
	if (!filter) filter = new FilterCtrl(tb, ID_CARD_FILTER);
	tb->AddControl(filter);
	tb->Realize();
	// Menus
	mb->Insert(2, menuCard,   _MENU_("cards"));
	mb->Insert(3, menuFormat, _MENU_("format"));
}

void CardsPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
	// Toolbar
	tb->DeleteTool(ID_FORMAT_BOLD);
	tb->DeleteTool(ID_FORMAT_ITALIC);
	tb->DeleteTool(ID_FORMAT_SYMBOL);
	tb->DeleteTool(ID_FORMAT_REMINDER);
	tb->DeleteTool(ID_CARD_ADD);
	tb->DeleteTool(ID_CARD_REMOVE);
	tb->DeleteTool(ID_CARD_ROTATE);
	tb->DeleteTool(filter->GetId()); filter = nullptr;
	// HACK: hardcoded size of rest of toolbar
	tb->DeleteToolByPos(12); // delete separator
	tb->DeleteToolByPos(12); // delete separator
	tb->DeleteToolByPos(12); // delete separator
	// Menus
	mb->Remove(3);
	mb->Remove(2);
	toolAddCard = nullptr;
}

void CardsPanel::onUpdateUI(wxUpdateUIEvent& ev) {
	switch (ev.GetId()) {
		case ID_CARD_PREV:       ev.Enable(card_list->canSelectPrevious());	break;
		case ID_CARD_NEXT:       ev.Enable(card_list->canSelectNext());		break;
		case ID_CARD_ROTATE_0: case ID_CARD_ROTATE_90: case ID_CARD_ROTATE_180: case ID_CARD_ROTATE_270: {
			StyleSheetSettings& ss = settings.stylesheetSettingsFor(set->stylesheetFor(card_list->getCard()));
			int a = ev.GetId() == ID_CARD_ROTATE_0   ? 0
			      : ev.GetId() == ID_CARD_ROTATE_90  ? 90
			      : ev.GetId() == ID_CARD_ROTATE_180 ? 180
			      :                                    270;
			ev.Check(ss.card_angle() == a);
			break;
		}
		case ID_CARD_ADD_MULT: {
			ev.Enable(insertManyCardsMenu->GetSubMenu() != nullptr);
			break;
		}
		case ID_CARD_REMOVE:     ev.Enable(card_list->canDelete());			break;
		case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_SYMBOL: case ID_FORMAT_REMINDER: {
			if (focused_control(this) == ID_EDITOR) {
				ev.Enable(editor->canFormat(ev.GetId()));
				ev.Check (editor->hasFormat(ev.GetId()));
			} else {
				ev.Enable(false);
				ev.Check(false);
			}
			break;
		}
		case ID_COLLAPSE_NOTES: {
			bool collapse = notes->GetSize().y > 0;
			collapse_notes->loadBitmaps(collapse ? _("btn_collapse") : _("btn_expand"));
			collapse_notes->SetHelpText(collapse ? _HELP_("collapse notes") : _HELP_("expand notes"));
			break;
		}
#if 0 //ifdef __WXGTK__ //crashes on GTK
		case ID_INSERT_SYMBOL: ev.Enable(false); break;
#else
		case ID_INSERT_SYMBOL: {
			wxMenu* menu = editor->getMenu(ID_INSERT_SYMBOL);
			ev.Enable(menu);
			if (insertSymbolMenu->GetSubMenu() != menu  ||  (menu && menu->GetParent() != menuFormat)) {
				// re-add the menu
				menuFormat->Remove(ID_INSERT_SYMBOL);
				insertSymbolMenu->SetSubMenu(menu);
				menuFormat->Append(insertSymbolMenu);
			}
			break;
		}
#endif
	}
}

void CardsPanel::onCommand(int id) {
	switch (id) {
		case ID_CARD_PREV:
			// Note: Forwarded events may cause this to occur even at the top.
			if (card_list->canSelectPrevious()) card_list->selectPrevious();
			break;
		case ID_CARD_NEXT:
			// Note: Forwarded events may cause this to occur even at the bottom.
			if (card_list->canSelectNext()) card_list->selectNext();
			break;
		case ID_CARD_ADD:
			set->actions.addAction(new AddCardAction(*set));
			break;
		case ID_CARD_REMOVE:
			card_list->doDelete();
			break;
		case ID_CARD_ROTATE:
		case ID_CARD_ROTATE_0: case ID_CARD_ROTATE_90: case ID_CARD_ROTATE_180: case ID_CARD_ROTATE_270: {
			StyleSheetSettings& ss = settings.stylesheetSettingsFor(set->stylesheetFor(card_list->getCard()));
			ss.card_angle.assign(
				  id == ID_CARD_ROTATE     ? (ss.card_angle() + 90) % 360
				: id == ID_CARD_ROTATE_0   ? 0
				: id == ID_CARD_ROTATE_90  ? 90
				: id == ID_CARD_ROTATE_180 ? 180
				:                            270
			);
			set->actions.tellListeners(DisplayChangeAction(),true);
			break;
		}
		case ID_SELECT_COLUMNS: {
			card_list->selectColumns();
		}
		case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_SYMBOL: case ID_FORMAT_REMINDER: {
			if (focused_control(this) == ID_EDITOR) {
				editor->doFormat(id);
			}
			break;
		}
		case ID_COLLAPSE_NOTES: {
			bool collapse = notes->GetSize().y > 0;
			if (collapse) {
				splitter->SetSashPosition(-1);
				card_list->SetFocus();
			} else {
				splitter->SetSashPosition(-150);
				notes->SetFocus();
			}
			break;
		}
		case ID_CARD_FILTER: {
			// card filter has changed, update the card list
			if (filter->hasFilter()) {
				card_list->setFilter(intrusive(new QueryCardListFilter(filter->getFilter())));
			} else {
				card_list->setFilter(CardListFilterP());
			}
			break;
		}
		default: {
			if (id >= ID_INSERT_SYMBOL_MENU_MIN && id <= ID_INSERT_SYMBOL_MENU_MAX) {
				// pass on to editor
				editor->onCommand(id);
			} else if (id >= ID_ADD_CARDS_MENU_MIN && id <= ID_ADD_CARDS_MENU_MAX) {
				// add multiple cards
				AddCardsScriptP script = set->game->add_cards_scripts.at(id - ID_ADD_CARDS_MENU_MIN);
				script->perform(*set);
			}
		}
	}
}

// ----------------------------------------------------------------------------- : Actions

bool CardsPanel::wantsToHandle(const Action&, bool undone) const {
	return false;
}

// ----------------------------------------------------------------------------- : Clipboard

// determine what control to use for clipboard actions
#define CUT_COPY_PASTE(op,return)									\
	int id = focused_control(this);									\
	if      (id == ID_EDITOR)    { return editor->op();    }		\
	else if (id == ID_CARD_LIST) { return card_list->op(); }		\
	else if (id == ID_NOTES)     { return notes->op();     }		\
	else                         { return false;           }

bool CardsPanel::canCut()   const { CUT_COPY_PASTE(canCut,   return) }
bool CardsPanel::canCopy()  const { CUT_COPY_PASTE(canCopy,  return) }
void CardsPanel::doCut()          { CUT_COPY_PASTE(doCut,    return (void)) }
void CardsPanel::doCopy()         { CUT_COPY_PASTE(doCopy,   return (void)) }

// always alow pasting cards, even if something else is selected
bool CardsPanel::canPaste() const {
	if (card_list->canPaste()) return true;
	int id = focused_control(this);
	if      (id == ID_EDITOR) return editor->canPaste();
	else if (id == ID_NOTES)  return notes->canPaste();
	else                      return false;
}
void CardsPanel::doPaste() {
	if (card_list->canPaste()) {
		card_list->doPaste();
	} else {
		int id = focused_control(this);
		if      (id == ID_EDITOR) editor->doPaste();
		else if (id == ID_NOTES)  notes->doPaste();
	}
}

// ----------------------------------------------------------------------------- : Searching

class CardsPanel::SearchFindInfo : public FindInfo {
  public:
	SearchFindInfo(CardsPanel& panel, wxFindReplaceData& what) : FindInfo(what), panel(panel) {}
	virtual bool handle(const CardP& card, const TextValueP& value, size_t pos, bool was_selection) {
		// Select the card
		panel.card_list->setCard(card);
		return true;
	}
  private:
	CardsPanel& panel;
};

class CardsPanel::ReplaceFindInfo : public FindInfo {
  public:
	ReplaceFindInfo(CardsPanel& panel, wxFindReplaceData& what) : FindInfo(what), panel(panel) {}
	virtual bool handle(const CardP& card, const TextValueP& value, size_t pos, bool was_selection) {
		// Select the card
		panel.card_list->setCard(card);
		// Replace
		if (was_selection) {
			panel.editor->insert(escape(what.GetReplaceString()), _("Replace"));
			return false;
		} else {
			return true;
		}
	}
	virtual bool searchSelection() const { return true; }
  private:
	CardsPanel& panel;
};

bool CardsPanel::doFind(wxFindReplaceData& what) {
	SearchFindInfo find(*this, what);
	return search(find, false);
}
bool CardsPanel::doReplace(wxFindReplaceData& what) {
	ReplaceFindInfo find(*this, what);
	return search(find, false);
}
bool CardsPanel::doReplaceAll(wxFindReplaceData& what) {
	return false; // TODO
}

bool CardsPanel::search(FindInfo& find, bool from_start) {
	bool include = from_start;
	CardP current = card_list->getCard();
	for (size_t i = 0 ; i < set->cards.size() ; ++i) {
		CardP card = card_list->getCard( (long) (find.forward() ? i : set->cards.size() - i - 1) );
		if (card == current) include = true;
		if (include) {
			editor->setCard(card);
			if (editor->search(find, from_start || card != current)) {
				return true; // done
			}
		}
	}
	editor->setCard(current);
	return false;
}

// ----------------------------------------------------------------------------- : Selection

CardP CardsPanel::selectedCard() const {
	return card_list->getCard();
}
void CardsPanel::selectCard(const CardP& card) {
	if (!set) return; // we want onChangeSet first
	card_list->setCard(card);
	editor->setCard(card);
	notes->setValue(card ? &card->notes : nullptr);
	Layout();
	updateNotesPosition();
}

void CardsPanel::selectFirstCard() {
	if (!set) return; // we want onChangeSet first
	card_list->selectFirst();
}
