#include "Workspace.h"

#include "Resizer.h"
#include "Sketch.h"

#include <GG/DrawUtil.h>
#include <GG/GUI.h>
#include <GG/Layout.h>
#include <GG/dialogs/ColorDlg.h>
#include <GG/dialogs/ThreeButtonDlg.h>

#include <boost/algorithm/string.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>

#include <fstream>


namespace {
    const int DIALOG_BEVEL = 4;

    void SelectWorkspaceColor()
    {
        boost::shared_ptr<GG::Font> font = GG::GUI::GetGUI()->GetFont("Vera.ttf", 12);
        GG::ColorDlg dlg(0, 0, font, GG::CLR_GRAY, GG::CLR_GRAY);
        dlg.MoveTo(GG::Pt((GG::GUI::GetGUI()->AppWidth() - dlg.Width()) / 2,
                          (GG::GUI::GetGUI()->AppHeight() - dlg.Height()) / 2));
        dlg.Run();
        if (dlg.ColorWasSelected())
            Dialog::SetColor(dlg.Result());
    }

    bool VerticalStateButtonLess(GG::StateButton* lhs, GG::StateButton* rhs) {return lhs->UpperLeft().y < rhs->UpperLeft().y;}
    bool HorizontalStateButtonLess(GG::StateButton* lhs, GG::StateButton* rhs) {return lhs->UpperLeft().x < rhs->UpperLeft().x;}
    struct CreateRadioButtonGroup
    {
        CreateRadioButtonGroup(Dialog* dialog, const std::vector<GG::StateButton*>& state_buttons, bool vertical) :
            m_dialog(dialog),
            m_state_buttons(state_buttons),
            m_vertical(vertical)
        {}
        void operator()()
        {
            assert(!m_state_buttons.empty());
            std::sort(m_state_buttons.begin(), m_state_buttons.end(), m_vertical ? VerticalStateButtonLess : HorizontalStateButtonLess);
            GG::Pt ul(100000000, 100000000), lr(-100000000, -100000000);
            for (unsigned int i = 0; i < m_state_buttons.size(); ++i) {
                if (lr.x < m_state_buttons[i]->RelativeLowerRight().x)
                    lr.x = m_state_buttons[i]->RelativeLowerRight().x;
                if (lr.y < m_state_buttons[i]->RelativeLowerRight().y)
                    lr.y = m_state_buttons[i]->RelativeLowerRight().y;
                if (m_state_buttons[i]->RelativeUpperLeft().x < ul.x)
                    ul.x = m_state_buttons[i]->RelativeUpperLeft().x;
                if (m_state_buttons[i]->RelativeUpperLeft().y < ul.y)
                    ul.y = m_state_buttons[i]->RelativeUpperLeft().y;
            }
            GG::RadioButtonGroup* button_group = new GG::RadioButtonGroup(ul.x, ul.y, lr.x - ul.x, lr.y - ul.y, m_vertical ? GG::VERTICAL : GG::HORIZONTAL);
            for (unsigned int i = 0; i < m_state_buttons.size(); ++i) {
                m_state_buttons[i]->OffsetMove(-ul);
                button_group->AddButton(m_state_buttons[i]);
            }
            m_dialog->AddWnd(button_group, "RadioButtonGroup" + boost::lexical_cast<std::string>(SketchApp::GetSketchApp()->NewWndIndex()), "GG::RadioButtonGroup");
        }
    private:
        Dialog* const                 m_dialog;
        std::vector<GG::StateButton*> m_state_buttons;
        const bool                    m_vertical;
    };

    struct RenderOutline
    {
        RenderOutline(bool enable) : m_enable(enable) {}
        void operator()(GG::Layout* layout) {layout->RenderOutline(m_enable);}
        const bool m_enable;
    };

    struct ModifyFilter
    {
        ModifyFilter(GG::Wnd* filter_wnd, bool install) : m_filter_wnd(filter_wnd), m_install(install) {}
        void operator()(GG::Layout* layout)
        {
            if (m_install)
                layout->InstallEventFilter(m_filter_wnd);
            else
                layout->RemoveEventFilter(m_filter_wnd);
        }
        GG::Wnd* const m_filter_wnd;
        const bool     m_install;
    };

    std::string BaseHeaderFileName(const std::string& type_str)
    {
        if (type_str == "RadioButtonGroup" || type_str == "StateButton")
            return "Button";
        else if (type_str == "MenuBar" || type_str == "PopupMenu")
            return "Menu";
        else
            return type_str;
    }

    class TempWnd : public GG::Wnd
    {
    public:
        TempWnd() :
            GG::Wnd(0, 0, 1, 1)
        {}
    };
}


////////////////////////////////////////////////
// Workspace
////////////////////////////////////////////////
Workspace::Workspace() :
    Wnd(0, 0, 1, 1, GG::CLICKABLE)
{
    EnableChildClipping(true);
}

void Workspace::LClick(const GG::Pt& pt, Uint32 keys)
{
    ClickedSignal();
}


////////////////////////////////////////////////
// Dialog::WndData
////////////////////////////////////////////////
Dialog::WndData::WndData() :
    m_resizer(0)
{}

Dialog::WndData::WndData(const std::string& name, const std::string& type_str) :
    m_name(name),
    m_type_str(type_str),
    m_resizer(0)
{}


////////////////////////////////////////////////
// Dialog::Snapshot
////////////////////////////////////////////////
Dialog::Snapshot::Snapshot() :
    m_layout(0)
{}


////////////////////////////////////////////////
// Dialog
////////////////////////////////////////////////
// static(s)
GG::Clr Dialog::s_color(GG::CLR_GRAY);

Dialog::Dialog(int w, int h) :
    Wnd(5, 5, w, h, GG::CLICKABLE | GG::RESIZABLE),
    m_button_down_wnd(0),
    m_dragged_resizer(0),
    m_undo_position(-1)
{
    GG::GUI::GetGUI()->SetAccelerator(GG::GGK_DELETE, 0);
    GG::GUI::GetGUI()->SetAccelerator(GG::GGK_x, GG::GGKMOD_CTRL);
    GG::GUI::GetGUI()->SetAccelerator(GG::GGK_c, GG::GGKMOD_CTRL);
    GG::GUI::GetGUI()->SetAccelerator(GG::GGK_v, GG::GGKMOD_CTRL);
    GG::GUI::GetGUI()->SetAccelerator(GG::GGK_z, GG::GGKMOD_CTRL);
    GG::GUI::GetGUI()->SetAccelerator(GG::GGK_y, GG::GGKMOD_CTRL);
    GG::Connect(GG::GUI::GetGUI()->AcceleratorSignal(GG::GGK_DELETE, 0),
                &Dialog::Clear, this);
    GG::Connect(GG::GUI::GetGUI()->AcceleratorSignal(GG::GGK_x, GG::GGKMOD_CTRL),
                &Dialog::Cut, this);
    GG::Connect(GG::GUI::GetGUI()->AcceleratorSignal(GG::GGK_c, GG::GGKMOD_CTRL),
                &Dialog::Copy, this);
    GG::Connect(GG::GUI::GetGUI()->AcceleratorSignal(GG::GGK_v, GG::GGKMOD_CTRL),
                &Dialog::Paste, this);
    GG::Connect(GG::GUI::GetGUI()->AcceleratorSignal(GG::GGK_z, GG::GGKMOD_CTRL),
                &Dialog::Undo, this);
    GG::Connect(GG::GUI::GetGUI()->AcceleratorSignal(GG::GGK_y, GG::GGKMOD_CTRL),
                &Dialog::Redo, this);

    TakeSnapshot("");
}

Dialog::~Dialog()
{
    ClearResizers();
    ClearWnds();
    ClearSnapshots();
}

void Dialog::AddWnd(Wnd* wnd, const std::string& name, const std::string& type_str)
{
    WndPtr wnd_ptr(wnd);
    m_wnds[wnd_ptr] = WndData(name, type_str);
    GG::RadioButtonGroup* button_group = dynamic_cast<GG::RadioButtonGroup*>(wnd);
    if (button_group) {
        if (GG::Layout* layout = const_cast<GG::Layout*>(const_cast<const GG::RadioButtonGroup*>(button_group)->GetLayout()))
            m_wnds[layout] = WndData("RadioButtonGroupLayout" + boost::lexical_cast<std::string>(SketchApp::GetSketchApp()->NewWndIndex()),
                                     type_str);
    }
    try {
        GG::Pt wnd_center = wnd_ptr->RelativeUpperLeft() + GG::Pt(wnd_ptr->Width() / 2, wnd_ptr->Height() / 2);
        boost::tuple<GG::Layout*, int, int> cell = m_layout_tree.Cell(wnd_center + ClientUpperLeft());
        if (cell.get<0>())
            cell.get<0>()->Add(wnd_ptr, cell.get<1>(), cell.get<2>());
    } catch (const GG::Layout::AttemptedOverwrite&) {
        // can't insert here... no problem
    }
    ClearResizers();
    NewResizer(wnd_ptr);
    EnableOutlines(true);
    TakeSnapshot("");
}

void Dialog::Load(const std::string& filename)
{
    ClearResizers();
    RemoveLayout();
    ClearWnds();
    DeleteChildren();
    ClearSnapshots();
    SketchApp* app = SketchApp::GetSketchApp();
    std::ifstream ifs(filename.c_str());
    GG::Layout* dialog_layout = 0;
    {
        boost::archive::xml_iarchive ia(ifs);
        app->LoadWnd(dialog_layout, "", ia);
        GG::Pt dialog_ul, dialog_lr;
        unsigned int num_wnds;
        ia  & BOOST_SERIALIZATION_NVP(dialog_ul)
            & BOOST_SERIALIZATION_NVP(dialog_lr)
            & BOOST_SERIALIZATION_NVP(num_wnds);
        SizeMove(dialog_ul - GG::Pt(DIALOG_BEVEL, DIALOG_BEVEL),
                 dialog_lr + GG::Pt(DIALOG_BEVEL, DIALOG_BEVEL));
        for (unsigned int i = 0; i < num_wnds; ++i) {
            std::string name;
            std::string type;
            Wnd* window;
            ia & BOOST_SERIALIZATION_NVP(name);
            ia & BOOST_SERIALIZATION_NVP(type);
            app->LoadWnd(window, "", ia);
            WndPtr wnd_ptr(window);
            m_wnds[wnd_ptr] = WndData(name, type);
        }
    }
    if (dialog_layout)
        SetLayout(dialog_layout);
    FilterChildren();
    m_undo_position = -1;
    EnableOutlines(true);
    TakeSnapshot("");
}

void Dialog::Save(const std::string& filename)
{
    std::ofstream ofs(filename.c_str());
    ofs << SaveToString();
}

void Dialog::Export(const std::string& base_filename,
                    GenerateCodeDlg::ExportFiles exported_files,
                    const std::string& header_extension,
                    const std::string& impl_extension,
                    const std::string& sketch_extension)
{
    std::string header = base_filename + header_extension;
    std::string implementation = base_filename + impl_extension;
    std::string sketch = base_filename + sketch_extension;

    std::map<std::string, bool> classes_used;
    std::set<std::string> headers_required;
    for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
        std::string type_str = it->second.m_type_str;
        type_str = type_str.substr(4);
        unsigned int bracket_pos = type_str.find_first_of('<');
        std::string class_name = type_str.substr(0, bracket_pos);
        classes_used[class_name] = bracket_pos != std::string::npos;
        headers_required.insert(BaseHeaderFileName(class_name));
    }

    std::string wnd_definitions;
    if (exported_files != GenerateCodeDlg::HEADER_IMPL_AND_SKETCH) {
        wnd_definitions = SaveToString();
        boost::replace_all(wnd_definitions, "\"", "\\\"");
        wnd_definitions = "        \"" + wnd_definitions;
        boost::replace_all(wnd_definitions, "\n", "\"\n        \"");
        // the -9 removes the final spaces and quote mark
        wnd_definitions = wnd_definitions.substr(0, wnd_definitions.size() - 9);
    }

    std::stringstream ctor_stream;
    ctor_stream << base_filename << "::" << base_filename << "(GG::Wnd* parent)\n"
                << "{\n";
    ctor_stream << "    GG::GUI* gui = GG::GUI::GetGUI();\n"
                << "\n";
    if (exported_files != GenerateCodeDlg::HEADER_IMPL_AND_SKETCH) {
        ctor_stream << "    std::stringstream stream(" << (exported_files == GenerateCodeDlg::HEADER_ONLY ? (base_filename + "_detail::") : "") << "WND_DEFINITIONS);\n"
                    << "    boost::archive::xml_iarchive ia(stream);\n";
    } else {
        ctor_stream << "    std::ifstream ifs(\"" << sketch << "\");\n"
                    << "    boost::archive::xml_iarchive ia(ifs);\n";
    }
    ctor_stream << "    GG::Layout* dialog_layout;\n"
                << "    int num_wnds;\n"
                << "    std::string name, type;\n"
                << "    std::set<GG::Wnd*> wnds;\n"
                << "    GG::Pt dialog_client_ul, dialog_client_lr;\n"
                << "\n"
                << "    gui->LoadWnd(dialog_layout, \"dialog_layout\", ia);\n"
                << "    ia & BOOST_SERIALIZATION_NVP(dialog_client_ul)\n"
                << "       & BOOST_SERIALIZATION_NVP(dialog_client_lr)\n"
                << "       & BOOST_SERIALIZATION_NVP(num_wnds);\n";
    for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
        ctor_stream << "    ia & BOOST_SERIALIZATION_NVP(name) & BOOST_SERIALIZATION_NVP(type);\n"
                    << "    gui->LoadWnd(" << it->second.m_name << ", \"" << it->second.m_name << "\", ia);\n"
                    << "    wnds.insert(" << it->second.m_name << ");\n";
    }
    ctor_stream << "\n"
                << "    parent->SizeMove(dialog_client_ul - (parent->ClientUpperLeft() - parent->UpperLeft()),\n"
                << "                     dialog_client_lr + (parent->LowerRight() - parent->ClientLowerRight()));\n"
                << "    if (dialog_layout) {\n"
                << "        parent->SetLayout(dialog_layout);\n"
                << "    } else {\n"
                << "        for (std::set<GG::Wnd*>::const_iterator it = wnds.begin(); it != wnds.end(); ++it) {\n"
                << "            if (!(*it)->Parent() || wnds.find((*it)->Parent()) == wnds.end())\n"
                << "                parent->AttachChild(*it);\n"
                << "        }\n"
                << "    }\n"
                << "}\n";

    // write header file
    std::stringstream header_stream;
    std::string header_define = boost::to_upper_copy(boost::replace_all_copy(header, ".", "_"));
    header_stream << "#ifndef " << header_define << "\n"
                  << "#define " << header_define << "\n"
                  << "\n";
    if (exported_files == GenerateCodeDlg::HEADER_ONLY) {
        header_stream << "#include <string>\n"
                      << "\n";
    }
    if (exported_files == GenerateCodeDlg::HEADER_ONLY) {
        header_stream << "#include <GG/GUI.h>\n"
                      << "\n"
                      << "#include <boost/archive/xml_oarchive.hpp>\n"
                      << "#include <boost/archive/xml_iarchive.hpp>\n"
                      << "\n";
        for (std::set<std::string>::const_iterator it = headers_required.begin(); it != headers_required.end(); ++it) {
            header_stream << "#include <GG/" << *it << ".h>\n";
        }
    } else {
        header_stream << "namespace GG {\n"
                      << "    class Wnd;\n";
        for (std::map<std::string, bool>::const_iterator it = classes_used.begin(); it != classes_used.end(); ++it) {
            if (it->second)
                header_stream << "    template <class T>\n";
            header_stream << "    class " << it->first << ";\n";
        }
        header_stream << "}\n";
    }
    header_stream << "\n"
                  << "struct " << base_filename << "\n"
                  << "{\n"
                  << "    " << base_filename << "(GG::Wnd* parent);\n"
                  << "\n";
    for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
        header_stream << "    " << it->second.m_type_str << "* " << it->second.m_name << ";\n";
    }
    header_stream << "};\n";
    if (exported_files == GenerateCodeDlg::HEADER_ONLY) {
        header_stream << "\n"
                      << "namespace " << base_filename << "_detail {\n"
                      << "    const std::string WND_DEFINITIONS(\n"
                      << wnd_definitions
                      << "    );\n"
                      << "}\n"
                      << "\n"
                      << "inline " << ctor_stream.str();
    }
    header_stream << "\n"
                  << "#endif // " << header_define << "\n";

    std::ofstream header_ofs(header.c_str());
    header_ofs << header_stream.str();
    header_ofs.close();

    // write implementation file
    if (exported_files != GenerateCodeDlg::HEADER_ONLY) {
        std::stringstream implementation_stream;
        implementation_stream << "#include \"" << header << "\"\n"
                              << "\n"
                              << "#include <GG/GUI.h>\n"
                              << "\n"
                              << "#include <boost/archive/xml_oarchive.hpp>\n"
                              << "#include <boost/archive/xml_iarchive.hpp>\n"
                              << "\n";
        for (std::set<std::string>::const_iterator it = headers_required.begin(); it != headers_required.end(); ++it) {
            implementation_stream << "#include <GG/" << *it << ".h>\n";
        }
        implementation_stream << "\n"
                              << "#include <string>\n"
                              << (exported_files != GenerateCodeDlg::HEADER_IMPL_AND_SKETCH ? "#include <sstream>\n" : "#include <fstream>\n")
                              << "\n"
                              << "\n";
        if (exported_files != GenerateCodeDlg::HEADER_IMPL_AND_SKETCH) {
            implementation_stream << "namespace {\n"
                                  << "    const std::string WND_DEFINITIONS(\n"
                                  << wnd_definitions
                                  << "    );\n"
                                  << "}\n"
                                  << "\n"
                                  << "\n";
        }
        implementation_stream << ctor_stream.str();

        std::ofstream implementation_ofs(implementation.c_str());
        implementation_ofs << implementation_stream.str();
        implementation_ofs.close();
    }

    // write sketch file
    if (exported_files == GenerateCodeDlg::HEADER_IMPL_AND_SKETCH)
        Save(sketch.c_str());
}

void Dialog::WndRenamedSlot(Wnd* wnd, const std::string& name)
{
    for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
        if (it->first == wnd) {
            it->second.m_name = name;
            break;
        }
    }
    TakeSnapshot(name);
}

void Dialog::WndEditedSlot(Wnd* wnd)
{
    std::string name;
    for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
        if (it->first == wnd) {
            name = it->second.m_name;
            break;
        }
    }
    TakeSnapshot(name);
}

void Dialog::WorkspaceClickedSlot()
{
    ClearResizers();
}

void Dialog::Render()
{
    glDisable(GL_TEXTURE_2D);
    GG::Pt ul = UpperLeft(), lr = LowerRight();
    GG::BeveledRectangle(ul.x, ul.y, lr.x, lr.y, s_color, s_color, true, DIALOG_BEVEL);
    for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
        if (it->first->Parent())
            continue;
        it->first->OffsetMove(ClientUpperLeft());
        GG::GUI::RenderWindow(it->first);
        it->first->OffsetMove(-ClientUpperLeft());
    }
    if (m_drag_origin != m_drag_position) {
        GG::Rect rect(m_drag_origin, m_drag_position);
        GG::FlatRectangle(rect.ul.x, rect.ul.y,
                          rect.lr.x, rect.lr.y,
                          GG::CLR_ZERO, GG::CLR_WHITE, 1);
    }
    glEnable(GL_TEXTURE_2D);
}

void Dialog::LButtonDown(const GG::Pt& pt, Uint32 keys)
{
    m_button_down_wnd = WndUnderPt(pt, keys & GG::GGKMOD_SHIFT);
    m_drag_origin = m_drag_position = pt;
    m_drag_offset = pt - UpperLeft();
}

void Dialog::LDrag(const GG::Pt& pt, const GG::Pt& move, Uint32 keys)
{
    if (m_button_down_wnd) {
        if (!m_dragged_resizer) {
            ClearResizers();
            m_dragged_resizer = NewResizer(m_button_down_wnd);
            m_resizer_drag_offset = m_drag_origin - m_dragged_resizer->UpperLeft();
        } else {
            m_dragged_resizer->MoveTo(pt - m_resizer_drag_offset - ClientUpperLeft());
        }
    } else if (keys & GG::GGKMOD_SHIFT) {
        MoveTo(pt - m_drag_offset - Parent()->ClientUpperLeft());
    } else {
        m_drag_position = pt;
    }
}

void Dialog::LButtonUp(const GG::Pt& pt, Uint32 keys)
{
    if (m_dragged_resizer)
        TakeSnapshot("");
    m_button_down_wnd = 0;
    m_dragged_resizer = 0;
    m_drag_origin = m_drag_position = GG::Pt();
}

void Dialog::LClick(const GG::Pt& pt, Uint32 keys)
{
    WndPtr wnd_under_pt = WndUnderPt(pt, keys & GG::GGKMOD_SHIFT);
    if (wnd_under_pt && wnd_under_pt == m_button_down_wnd) {
        if (!(keys & GG::GGKMOD_CTRL))
            ClearResizers();
        NewResizer(wnd_under_pt);
    } else {
        if (!(keys & GG::GGKMOD_CTRL))
            ClearResizers();
        if (m_drag_origin != m_drag_position) {
            GG::Rect rect(m_drag_origin, m_drag_position);
            rect -= ClientUpperLeft();
            for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
                GG::Pt center = it->first->Size();
                center.x /= 2;
                center.y /= 2;
                center += it->first->UpperLeft();
                if (rect.Contains(center)) {
                    if (it->second.m_resizer)
                        DeleteResizer(it->second.m_resizer);
                    else
                        NewResizer(it->first);
                }
            }
        }
    }
    if (m_dragged_resizer)
        TakeSnapshot("");
    m_button_down_wnd = 0;
    m_dragged_resizer = 0;
    m_drag_origin = m_drag_position = GG::Pt();
    EmitEditableWndSignal();
}

void Dialog::RButtonDown(const GG::Pt& pt, Uint32 keys)
{
    boost::shared_ptr<GG::Font> font = GG::GUI::GetGUI()->GetFont("Vera.ttf", 12);
    GG::PopupMenu menu(pt.x, pt.y, font, ContextMenu(this));
    menu.Run();
}

GG::Pt Dialog::ClientUpperLeft() const
{
    return UpperLeft() + GG::Pt(DIALOG_BEVEL, DIALOG_BEVEL);
}

GG::Pt Dialog::ClientLowerRight() const
{
    return LowerRight() - GG::Pt(DIALOG_BEVEL, DIALOG_BEVEL);
}

void Dialog::DoHorizontalLayout()
{
    DoLayout(HORZ);
}

void Dialog::DoVerticalLayout()
{
    DoLayout(VERT);
}

void Dialog::DoGridLayout()
{
    DoLayout(GRID);
}

void Dialog::DoRemoveLayout()
{
    RemoveLayout();
    UnfilterChildren();
    DechildifyControls();
    TakeSnapshot("");
}

void Dialog::GroupHorizontalLayout()
{
    GroupLayout(HORZ);
}

void Dialog::GroupVerticalLayout()
{
    GroupLayout(VERT);
}

void Dialog::GroupGridLayout()
{
    GroupLayout(GRID);
}

void Dialog::GroupRemoveLayout()
{
    assert(m_resizers.size() == 1);
    Resizer* resizer = *m_resizers.begin();
    assert(dynamic_cast<GG::Layout*>(resizer->GetWnd()));
    GG::Layout* layout = static_cast<GG::Layout*>(resizer->GetWnd());
    ClearResizers();
    std::list<Wnd*> layout_children = layout->Children();
    layout->DetachAndResetChildren();
    for (std::list<Wnd*>::iterator it = layout_children.begin(); it != layout_children.end(); ++it) {
        (*it)->OffsetMove(layout->ClientUpperLeft());
    }
    DeleteWnd(layout, m_wnds);
    TakeSnapshot("");
}

void Dialog::RemoveRadioButtonGroup()
{
    assert(m_resizers.size() == 1);
    Resizer* resizer = *m_resizers.begin();
    assert(dynamic_cast<GG::RadioButtonGroup*>(resizer->GetWnd()));
    GG::RadioButtonGroup* button_group = static_cast<GG::RadioButtonGroup*>(resizer->GetWnd());
    ClearResizers();
    GG::Layout* layout = button_group->DetachLayout();
    assert(layout);
    std::list<Wnd*> layout_children = layout->Children();
    layout->DetachAndResetChildren();
    for (std::list<Wnd*>::iterator it = layout_children.begin(); it != layout_children.end(); ++it) {
        (*it)->OffsetMove(button_group->ClientUpperLeft());
    }
    DeleteWnd(layout, m_wnds);
    DeleteWnd(button_group, m_wnds);
    TakeSnapshot("");
}

bool Dialog::EventFilter(Wnd* w, const Event& event)
{
    Resizer* resizer = dynamic_cast<Resizer*>(w);
    static bool resizer_alt_click_begun = false;
    if (resizer) {
        switch (event.Type()) {
        case Event::LButtonDown:
            if (event.KeyMods() & GG::GGKMOD_SHIFT)
                resizer_alt_click_begun = true;
            else
                return false;
            LButtonDown(event.Point(), event.KeyMods());
            break;
        case Event::LDrag:
            if (!resizer_alt_click_begun)
                return false;
            break;
        case Event::LButtonUp:
            if (!resizer_alt_click_begun)
                return false;
            resizer_alt_click_begun = false;
            LButtonUp(event.Point(), event.KeyMods());
            break;
        case Event::LClick:
            if (!resizer_alt_click_begun)
                return false;
            resizer_alt_click_begun = false;
            LClick(event.Point(), event.KeyMods());
            break;
        case Event::RButtonDown:
            GG::RadioButtonGroup* button_group = dynamic_cast<GG::RadioButtonGroup*>(resizer->GetWnd());
            if (1 < m_resizers.size() || dynamic_cast<GG::Layout*>(resizer->GetWnd()) || (button_group && button_group->NumButtons())) {
                boost::shared_ptr<GG::Font> font = GG::GUI::GetGUI()->GetFont("Vera.ttf", 12);
                GG::PopupMenu menu(event.Point().x, event.Point().y, font, ContextMenu(this, resizer));
                menu.Run();
            }
            break;
        default:
            break;
        }
    } else {
        switch (event.Type()) {
        case Event::LButtonDown: LButtonDown(event.Point(), event.KeyMods()); break;
        case Event::LDrag:       LDrag(event.Point(), event.DragMove(), event.KeyMods()); break;
        case Event::LButtonUp:   LButtonUp(event.Point(), event.KeyMods()); break;
        case Event::LClick:      LClick(event.Point(), event.KeyMods()); break;
        default:
            break;
        }
    }
    return true;
}

void Dialog::AcceptDrops(std::list<GG::Wnd*>& wnds, const GG::Pt& pt)
{
    assert(wnds.size() == 1);
    Wnd* wnd = *wnds.begin();
    boost::tuple<GG::Layout*, int, int> cell = m_layout_tree.Cell(pt);
    if (!GetLayout() || (cell.get<0>() && !cell.get<0>()->Cells()[cell.get<1>()][cell.get<2>()])) {
        DroppedWndSignal(wnd->DragDropDataType(), pt);
        GG::GUI::GetGUI()->SetFocusWnd(this);
    }
    wnds.clear();
}

void Dialog::SetColor(const GG::Clr& color)
{
    s_color = color;
}

std::string Dialog::SaveToString()
{
    EnableOutlines(false);
    SketchApp* app = SketchApp::GetSketchApp();
    GG::Layout* dialog_layout = GetLayout();
    UnfilterChildren();
    if (dialog_layout)
        DetachChild(dialog_layout);
    std::stringstream stream;
    {
        boost::archive::xml_oarchive oa(stream);
        app->SaveWnd(dialog_layout, "dialog_layout", oa);
        GG::Pt dialog_client_ul = RelativeUpperLeft() + GG::Pt(DIALOG_BEVEL, DIALOG_BEVEL),
            dialog_client_lr = RelativeLowerRight() - GG::Pt(DIALOG_BEVEL, DIALOG_BEVEL);
        unsigned int num_wnds = m_wnds.size();
        oa  & BOOST_SERIALIZATION_NVP(dialog_client_ul)
            & BOOST_SERIALIZATION_NVP(dialog_client_lr)
            & BOOST_SERIALIZATION_NVP(num_wnds);
        for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
            oa & boost::serialization::make_nvp("name", it->second.m_name);
            oa & boost::serialization::make_nvp("type", it->second.m_type_str);
            app->SaveWnd(it->first, it->second.m_name, oa);
        }
    }
    if (dialog_layout)
        AttachChild(dialog_layout);
    FilterChildren();
    EnableOutlines(true);
    return stream.str();
}

Dialog::WndPtr Dialog::WndUnderPt(const GG::Pt& pt, bool drill_down)
{
    Resizer* first_resizer = m_resizers.empty() ? 0 : *m_resizers.begin();
    if (drill_down && first_resizer && first_resizer->GetWnd()->Parent()) {
        return first_resizer->GetWnd()->Parent();
    } else {
        WndPtr retval = 0;
        for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
            GG::Rect rect(it->first->UpperLeft(), it->first->LowerRight());
            if (!GG::MatchesOrContains(this, it->first))
                rect += ClientUpperLeft();
            if (rect.Contains(pt))
                return WndUnderPtWithinWnd(it->first, pt);
        }
        return retval;
    }
}

Dialog::WndPtr Dialog::WndUnderPtWithinWnd(Wnd* wnd, const GG::Pt& pt)
{
    if (dynamic_cast<GG::Layout*>(wnd) || dynamic_cast<GG::RadioButtonGroup*>(wnd)) {
        for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
            if (it->first->Parent() == wnd) {
                GG::Rect rect(it->first->UpperLeft(), it->first->LowerRight());
                if (!GG::MatchesOrContains(this, it->first))
                    rect += ClientUpperLeft();
                if (rect.Contains(pt))
                    return WndUnderPtWithinWnd(it->first, pt);
            }
        }
    }
    return wnd;
}

Resizer* Dialog::NewResizer(WndPtr wnd)
{
    Resizer* resizer = new Resizer(wnd, this);
    m_wnds[wnd].m_resizer = resizer;
    AttachChild(resizer);
    m_resizers.insert(resizer);
    GG::Connect(resizer->ReselectedSignal, &Dialog::ResizerReselectedSlot, this);
    GG::Connect(resizer->DeselectedSignal, &Dialog::ResizerDeselectedSlot, this);
    GG::Connect(resizer->DraggedOrResizedSignal, &Dialog::TakeSnapshot, this);
    resizer->InstallEventFilter(this);
    // ensure that we never have a top-level resizer and one of its descendents selected at the same time
    std::set<WndPtr> top_level_selected_wnds = FindTopLevelSelectedWnds();
    for (std::set<Resizer*>::iterator it = m_resizers.begin(); it != m_resizers.end(); ) {
        std::set<Resizer*>::iterator temp = it++;
        if (top_level_selected_wnds.find((*temp)->GetWnd()) == top_level_selected_wnds.end())
            DeleteResizer(*temp);
    }
    // ensure that all selected Wnds have the same parent (or that all have no parent)
    if (m_wnds[wnd].m_resizer) {
        Wnd* wnd_parent = wnd->Parent();
        for (std::set<Resizer*>::iterator it = m_resizers.begin(); it != m_resizers.end(); ) {
            std::set<Resizer*>::iterator temp = it++;
            if ((*temp)->GetWnd()->Parent() != wnd_parent)
                DeleteResizer(*temp);
        }
    }
    EmitEditableWndSignal();
    return resizer;
}

void Dialog::DeleteResizer(Resizer* resizer)
{
    std::map<WndPtr, WndData>::iterator it = m_wnds.find(resizer->GetWnd());
    if (it != m_wnds.end())
        it->second.m_resizer = 0;
    DeleteChild(resizer);
    m_resizers.erase(resizer);
    EmitEditableWndSignal();
}

void Dialog::DeleteWnd(WndPtr wnd, std::map<WndPtr, WndData>& wnds)
{
    std::map<WndPtr, WndData>::iterator it = wnds.find(wnd);
    if (it != wnds.end()) {
        wnds.erase(wnd);
        for (std::list<Wnd*>::const_iterator it = wnd->Children().begin(); it != wnd->Children().end(); ) {
            std::list<Wnd*>::const_iterator temp = it++;
            DeleteWnd(*temp, wnds);
        }
        delete wnd;
    }
}

void Dialog::ClearResizers()
{
    for (std::set<Resizer*>::iterator it = m_resizers.begin(); it != m_resizers.end(); ++it) {
        std::map<WndPtr, WndData>::iterator it2 = m_wnds.find((*it)->GetWnd());
        if (it2 != m_wnds.end())
            it2->second.m_resizer = 0;
        DeleteChild(*it);
    }
    m_resizers.clear();
    EmitEditableWndSignal();
}

void Dialog::ClearWnds()
{
    while (!m_wnds.empty()) {
        DeleteWnd(m_wnds.begin()->first, m_wnds);
    }
}

void Dialog::ClearSnapshots(int from_idx/* = 0*/)
{
    assert(0 <= from_idx);
    if (static_cast<int>(m_undo_history.size()) <= from_idx)
        return;
    for (unsigned int i = from_idx; i < m_undo_history.size(); ++i) {
        while (!m_undo_history[i].m_snapshot_wnds.empty()) {
            DeleteWnd(m_undo_history[i].m_snapshot_wnds.begin()->first, m_undo_history[i].m_snapshot_wnds);
        }
        delete m_undo_history[i].m_layout;
    }
    if (from_idx)
        m_undo_history.resize(from_idx);
    else
        m_undo_history.clear();
}

void Dialog::ClearClipboard()
{
    while (!m_clipboard.empty()) {
        DeleteWnd(m_clipboard.begin()->first, m_clipboard);
    }
}

void Dialog::ResizerReselectedSlot(Resizer* resizer)
{
    WndPtr wnd = resizer->GetWnd();
    ClearResizers();
    NewResizer(wnd);
    EditableWndSignal(wnd, m_wnds[wnd].m_name);
}

void Dialog::ResizerDeselectedSlot(Resizer* resizer)
{
    DeleteResizer(resizer);
}

bool Dialog::Clear()
{
    if (!GG::MatchesOrContains(this, GG::GUI::GetGUI()->FocusWnd()))
        return false;
    for (std::set<Resizer*>::iterator it = m_resizers.begin(); it != m_resizers.end(); ++it) {
        WndPtr wnd = (*it)->GetWnd();
        if (wnd->Parent() && dynamic_cast<GG::RadioButtonGroup*>(wnd->Parent()->Parent()))
            continue;
        DeleteWnd(wnd, m_wnds);
    }
    ClearResizers();
    TakeSnapshot("");
    return true;
}

bool Dialog::Cut()
{
    Copy(true);
    return true;
}

bool Dialog::Copy()
{
    Copy(false);
    return true;
}

bool Dialog::Paste()
{
    if (GetLayout())
        return true;
    // TODO: adjust the names of the items pasted to prevent multiple items with the same name; also, try to keep the
    // pasted name of CUT items the first time they are pasted.
    ClearResizers();
    std::set<WndPtr> top_level_wnds = FindTopLevelWnds(m_clipboard);
    for (std::set<WndPtr>::const_iterator it = top_level_wnds.begin(); it != top_level_wnds.end(); ++it) {
        AddCloneTree(*it, SketchApp::GetSketchApp()->Clone(*it), m_clipboard, m_wnds, true);
    }
    FilterChildren();
    TakeSnapshot("");
    return true;
}

void Dialog::Copy(bool delete_copies)
{
    ClearClipboard();
    std::set<WndPtr> top_level_selected_wnds = FindTopLevelSelectedWnds();
    UnfilterChildren();
    GG::Layout* dialog_layout = DetachLayout();
    std::map<WndPtr, WndPtr> wnds_to_clones;
    for (std::set<WndPtr>::iterator it = top_level_selected_wnds.begin(); it != top_level_selected_wnds.end(); ++it) {
        if (delete_copies && (*it)->Parent() && dynamic_cast<GG::RadioButtonGroup*>((*it)->Parent()->Parent()))
            continue;
        WndPtr clone(SketchApp::GetSketchApp()->Clone(*it));
        wnds_to_clones[*it] = clone;
        if (WndPtr parent = clone->Parent()) {
            if (GG::Layout* layout = dynamic_cast<GG::Layout*>(parent))
                layout->Remove(clone);
            else
                parent->DetachChild(clone);
            delete (parent->RootParent() ? parent->RootParent() : parent);
        }
    }
    for (std::map<WndPtr, WndPtr>::iterator it = wnds_to_clones.begin(); it != wnds_to_clones.end(); ++it) {
        AddCloneTree(it->first, it->second, m_wnds, m_clipboard, false);
        if (delete_copies)
            DeleteWnd(it->first, m_wnds);
    }
    if (delete_copies)
        ClearResizers();
    if (dialog_layout)
        SetLayout(dialog_layout);
    FilterChildren();
    TakeSnapshot("");
}

void Dialog::AddCloneTree(WndPtr wnd, WndPtr clone,
                          std::map<WndPtr, WndData>& src,
                          std::map<WndPtr, WndData>& dst,
                          bool create_resizers)
{
    dst[clone] = src[wnd];
    dst[clone].m_resizer = 0;
    if (create_resizers)
        NewResizer(clone);
    if (dynamic_cast<GG::Layout*>(clone) || dynamic_cast<GG::RadioButtonGroup*>(clone)) {
        std::list<Wnd*>::const_iterator clone_it = clone->Children().begin();
        std::list<Wnd*>::const_iterator wnd_it = wnd->Children().begin();
        for (; clone_it != clone->Children().end(); ++clone_it, ++wnd_it) {
            AddCloneTree(const_cast<Wnd*>(*wnd_it), const_cast<Wnd*>(*clone_it), src, dst, false);
        }
    }
}

void Dialog::TakeSnapshot(const std::string& wnd_editor_wnd_name)
{
    ++m_undo_position;
    ClearSnapshots(m_undo_position);
    m_undo_history.resize(m_undo_position + 1);
    Snapshot& snapshot = m_undo_history.back();
    snapshot.m_snapshot_wnds.clear();
    snapshot.m_name = wnd_editor_wnd_name;
    UnfilterChildren();
    if (GG::Layout* layout = DetachLayout()) {
        snapshot.m_layout = static_cast<GG::Layout*>(SketchApp::GetSketchApp()->Clone(layout));
        std::list<Wnd*>::const_iterator layout_it = layout->Children().begin();
        std::list<Wnd*>::const_iterator clone_it = snapshot.m_layout->Children().begin();
        for (; clone_it != snapshot.m_layout->Children().end(); ++layout_it, ++clone_it) {
            AddCloneTree(const_cast<Wnd*>(*layout_it), const_cast<Wnd*>(*clone_it), m_wnds, snapshot.m_snapshot_wnds, false);
        }
        SetLayout(layout);
    } else {
        std::set<WndPtr> top_level_wnds = FindTopLevelWnds(m_wnds);
        for (std::set<WndPtr>::iterator it = top_level_wnds.begin(); it != top_level_wnds.end(); ++it) {
            AddCloneTree(*it, SketchApp::GetSketchApp()->Clone(*it), m_wnds, snapshot.m_snapshot_wnds, false);
        }
    }
    FilterChildren();
}

void Dialog::RestoreToSnapshot(int idx, bool undo)
{
    assert(0 <= idx && idx < static_cast<int>(m_undo_history.size()));
    ClearResizers();
    RemoveLayout();
    ClearWnds();
    DeleteChildren();
    Snapshot& snapshot = m_undo_history[idx];
    if (snapshot.m_layout) {
        GG::Layout* layout = static_cast<GG::Layout*>(SketchApp::GetSketchApp()->Clone(snapshot.m_layout));
        SetLayout(layout);
        std::list<Wnd*>::const_iterator layout_it = snapshot.m_layout->Children().begin();
        std::list<Wnd*>::const_iterator clone_it = layout->Children().begin();
        for (; clone_it != layout->Children().end(); ++layout_it, ++clone_it) {
            AddCloneTree(const_cast<Wnd*>(*layout_it), const_cast<Wnd*>(*clone_it), snapshot.m_snapshot_wnds, m_wnds, false);
        }
    } else {
        std::set<WndPtr> top_level_wnds = FindTopLevelWnds(snapshot.m_snapshot_wnds);
        for (std::set<WndPtr>::iterator it = top_level_wnds.begin(); it != top_level_wnds.end(); ++it) {
            AddCloneTree(*it, SketchApp::GetSketchApp()->Clone(*it), snapshot.m_snapshot_wnds, m_wnds, false);
        }
    }
    FilterChildren();
    EmitEditableWndSignal();
}

bool Dialog::Undo()
{
    if (0 < m_undo_position)
        RestoreToSnapshot(--m_undo_position, true);
    return true;
}

bool Dialog::Redo()
{
    if (m_undo_position < static_cast<int>(m_undo_history.size()) - 1)
        RestoreToSnapshot(++m_undo_position, false);
    return true;
}

void Dialog::DoLayout(LayoutType type)
{
    RemoveLayout();
    DechildifyControls();
    ChildifyControls();
    bool undo = false;
    try {
        if (type == VERT)
            VerticalLayout();
        else if (type == HORZ)
            HorizontalLayout();
        else if (type == GRID)
            GridLayout();
        FilterChildren();
        EnableOutlines(true);
        TakeSnapshot("");
    } catch (const GG::Layout::AttemptedOverwrite&) {
        undo = true;
    } catch (const BadLayout&) {
        undo = true;
    }
    if (undo) {
        RemoveLayout();
        DechildifyControls();
        boost::shared_ptr<GG::Font> font = GG::GUI::GetGUI()->GetFont("Vera.ttf", 12);
        GG::ThreeButtonDlg dlg(350, 100, "Cannot produce a grid layout, because there are overlapping controls.",
                               font, GG::CLR_GRAY, GG::CLR_GRAY, GG::CLR_GRAY, GG::CLR_BLACK, 1);
        dlg.Run();
    }
}

void Dialog::GroupLayout(LayoutType type)
{
    TempWnd wnd;
    GG::Pt ul(100000000, 100000000), lr(-100000000, -100000000);
    if (m_resizers.size() == 1) {
        assert(dynamic_cast<GG::Layout*>((*m_resizers.begin())->GetWnd()));
        GG::Layout* prev_layout = static_cast<GG::Layout*>((*m_resizers.begin())->GetWnd());
        m_wnds.erase(prev_layout);
        ul = prev_layout->RelativeUpperLeft();
        lr = prev_layout->RelativeLowerRight();
        prev_layout->MoveTo(GG::Pt(0, 0));
        wnd.SizeMove(ul, lr);
        wnd.SetLayout(prev_layout);
        wnd.RemoveLayout();
    } else {
        for (std::set<Resizer*>::iterator it = m_resizers.begin(); it != m_resizers.end(); ++it) {
            if ((*it)->GetWnd()->RelativeUpperLeft().x < ul.x)
                ul.x = (*it)->GetWnd()->RelativeUpperLeft().x;
            if ((*it)->GetWnd()->RelativeUpperLeft().y < ul.y)
                ul.y = (*it)->GetWnd()->RelativeUpperLeft().y;
            if (lr.x < (*it)->GetWnd()->RelativeLowerRight().x)
                lr.x = (*it)->GetWnd()->RelativeLowerRight().x;
            if (lr.y < (*it)->GetWnd()->RelativeLowerRight().y)
                lr.y = (*it)->GetWnd()->RelativeLowerRight().y;
        }
        wnd.SizeMove(ul, lr);
        for (std::set<Resizer*>::iterator it = m_resizers.begin(); it != m_resizers.end(); ++it) {
            Wnd* wnd_ptr = (*it)->GetWnd();
            wnd.AttachChild(wnd_ptr);
            wnd_ptr->OffsetMove(-wnd.ClientUpperLeft());
        }
    }
    bool undo = false;
    try {
        if (type == VERT)
            wnd.VerticalLayout();
        else if (type == HORZ)
            wnd.HorizontalLayout();
        else if (type == GRID)
            wnd.GridLayout();
        GG::Layout* layout = wnd.DetachLayout();
        layout->MoveTo(ul);
        m_wnds[layout] = WndData("Layout" + boost::lexical_cast<std::string>(SketchApp::GetSketchApp()->NewWndIndex()),
                                 "GG::Layout");
        ClearResizers();
        EnableOutlines(true);
        TakeSnapshot("");
    } catch (const GG::Layout::AttemptedOverwrite&) {
        undo = true;
    } catch (const BadLayout&) {
        undo = true;
    }
    if (undo) {
        ClearResizers();
        wnd.RemoveLayout();
        std::list<Wnd*> child_list = wnd.Children();
        for (std::list<Wnd*>::const_iterator it = child_list.begin(); it != child_list.end(); ++it) {
            wnd.DetachChild(*it);
            (*it)->OffsetMove(wnd.ClientUpperLeft());
        }
        boost::shared_ptr<GG::Font> font = GG::GUI::GetGUI()->GetFont("Vera.ttf", 12);
        GG::ThreeButtonDlg dlg(350, 100, "Cannot produce a grid layout, because there are overlapping controls.",
                               font, GG::CLR_GRAY, GG::CLR_GRAY, GG::CLR_GRAY, GG::CLR_BLACK, 1);
        dlg.Run();
    }
}

void Dialog::EmitEditableWndSignal()
{
    if (m_resizers.size() == 1) {
        WndPtr wnd_ptr = (*m_resizers.begin())->GetWnd();
        EditableWndSignal(wnd_ptr, m_wnds[wnd_ptr].m_name);
    } else {
        EditableWndSignal(0, "");
    }
}

void Dialog::FilterChildren()
{
    for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
        it->first->InstallEventFilter(this);
    }
    VisitDialogLayouts(ModifyFilter(this, true), true);
}

void Dialog::UnfilterChildren()
{
    for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
        it->first->RemoveEventFilter(this);
    }
    VisitDialogLayouts(ModifyFilter(this, false), true);
}

void Dialog::ChildifyControls()
{
    ClearResizers();
    for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
        if (!it->first->Parent())
            AttachChild(it->first);
    }
}

void Dialog::DechildifyControls()
{
    for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
        DetachChild(it->first);
    }
}

void Dialog::EnableOutlines(bool enable)
{
    for (std::map<WndPtr, WndData>::iterator it = m_wnds.begin(); it != m_wnds.end(); ++it) {
        if (GG::RadioButtonGroup* button_group = dynamic_cast<GG::RadioButtonGroup*>(it->first))
            button_group->RenderOutline(enable);
    }
    VisitDialogLayouts(RenderOutline(enable), false);
    VisitChildLayouts(RenderOutline(enable), false);
}

std::set<Dialog::WndPtr> Dialog::FindTopLevelWnds(const std::map<WndPtr, WndData>& all_wnds)
{
    std::set<Dialog::WndPtr> retval;
    for (std::map<WndPtr, WndData>::const_iterator it = all_wnds.begin(); it != all_wnds.end(); ++it) {
        if (!it->first->Parent() || all_wnds.find(it->first->Parent()) == all_wnds.end())
            retval.insert(it->first);
    }
    return retval;
}

std::set<Dialog::WndPtr> Dialog::FindTopLevelSelectedWnds()
{
    std::set<Dialog::WndPtr> retval;
    std::set<WndPtr> selected_wnds;
    for (std::set<Resizer*>::iterator it = m_resizers.begin(); it != m_resizers.end(); ++it) {
        selected_wnds.insert((*it)->GetWnd());
    }
    retval = selected_wnds;
    for (std::set<WndPtr>::iterator it = selected_wnds.begin(); it != selected_wnds.end(); ++it) {
        Wnd* parent = (*it)->Parent();
        while (parent) {
            if (selected_wnds.find(parent) != selected_wnds.end()) {
                retval.erase(*it);
                break;
            }
            parent = parent->Parent();
        }
    }
    return retval;
}

GG::MenuItem Dialog::ContextMenu(Dialog* dialog)
{
    assert(dialog);
    GG::MenuItem menu_item;
    menu_item.next_level.push_back(GG::MenuItem("Horizontal Window Layout", 0, false, false, &Dialog::DoHorizontalLayout, dialog));
    menu_item.next_level.push_back(GG::MenuItem("Vertical Window Layout", 0, false, false, &Dialog::DoVerticalLayout, dialog));
    menu_item.next_level.push_back(GG::MenuItem("Grid Window Layout", 0, false, false, &Dialog::DoGridLayout, dialog));
    if (const_cast<const Dialog*>(dialog)->GetLayout())
        menu_item.next_level.push_back(GG::MenuItem("Remove Window Layout", 0, false, false, &Dialog::DoRemoveLayout, dialog));
    menu_item.next_level.push_back(GG::MenuItem("Change Window Color ...", 0, false, false, GG::MenuItem::SelectedSlotType(&SelectWorkspaceColor)));
    return menu_item;
}

GG::MenuItem Dialog::ContextMenu(Dialog* dialog, Resizer* resizer)
{
    assert(resizer);
    GG::MenuItem menu_item;
    if (!dynamic_cast<GG::RadioButtonGroup*>(resizer->GetWnd())) {
        menu_item.next_level.push_back(GG::MenuItem("Horizontal Group Layout", 0, false, false, &Dialog::GroupHorizontalLayout, dialog));
        menu_item.next_level.push_back(GG::MenuItem("Vertical Group Layout", 0, false, false, &Dialog::GroupVerticalLayout, dialog));
        menu_item.next_level.push_back(GG::MenuItem("Grid Group Layout", 0, false, false, &Dialog::GroupGridLayout, dialog));
    }
    if (dynamic_cast<GG::Layout*>(resizer->GetWnd())) {
        menu_item.next_level.push_back(GG::MenuItem("Remove Group Layout", 0, false, false, &Dialog::GroupRemoveLayout, dialog));
    } else if (GG::RadioButtonGroup* button_group = dynamic_cast<GG::RadioButtonGroup*>(resizer->GetWnd())) {
        assert(button_group->NumButtons());
        menu_item.next_level.push_back(GG::MenuItem("Remove Radio Button Group", 0, false, false, &Dialog::RemoveRadioButtonGroup, dialog));
    } else {
        std::vector<GG::StateButton*> state_buttons;
        for (std::set<Resizer*>::iterator it = m_resizers.begin(); it != m_resizers.end(); ++it) {
            GG::StateButton* state_button = dynamic_cast<GG::StateButton*>((*it)->GetWnd());
            if (state_button && !const_cast<const GG::StateButton*>(state_button)->ContainingLayout())
                state_buttons.push_back(state_button);
            else
                break;
        }
        if (state_buttons.size() == m_resizers.size()) {
            menu_item.next_level.push_back(GG::MenuItem("Create Vertical Radio Button Group", 0, false, false, GG::MenuItem::SelectedSlotType(CreateRadioButtonGroup(dialog, state_buttons, true))));
            menu_item.next_level.push_back(GG::MenuItem("Create Horizontal Radio Button Group", 0, false, false, GG::MenuItem::SelectedSlotType(CreateRadioButtonGroup(dialog, state_buttons, false))));
        }
    }
    return menu_item;
}
