// -*- c-basic-offset: 4 -*-

/** @file MaskEditorPanel.cpp
 *
 *  @brief implementation of MaskEditorPanel Class
 *
 *  @author Thomas Modes
 *
 */

/*  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This software is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public
 *  License along with this software. If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */

#include "panoinc_WX.h"
#include "panoinc.h"
#include <hugin_utils/stl_utils.h>

// hugin's
#include "base_wx/platform.h"
#include "hugin/MainFrame.h"
#include "hugin/config_defaults.h"
#include "base_wx/CommandHistory.h"
#include "base_wx/PanoCommand.h"
#include "hugin/MaskEditorPanel.h"
#include "hugin/MaskLoadDialog.h"
#include <wx/clipbrd.h>
#include "hugin/TextKillFocusHandler.h"
#include "base_wx/wxutils.h"

MaskEditorPanel::MaskEditorPanel()
{
    DEBUG_TRACE("**********************");
    m_pano = 0;
    m_maskCropCtrl=NULL;
    m_defaultMaskType=HuginBase::MaskPolygon::Mask_negative;
}

bool MaskEditorPanel::Create(wxWindow* parent, wxWindowID id,
                    const wxPoint& pos,
                    const wxSize& size,
                    long style,
                    const wxString& name)
{
    DEBUG_TRACE(" Create called *************");
    if (! wxPanel::Create(parent, id, pos, size, style, name))
    {
        return false;
    }

    m_MaskNr=UINT_MAX;
    m_File="";

    wxXmlResource::Get()->LoadPanel(this, "mask_panel");
    wxPanel * panel = XRCCTRL(*this, "mask_panel", wxPanel);

    wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
    topsizer->Add(panel, 1, wxEXPAND, 0);
    SetSizer(topsizer);

    m_editImg = XRCCTRL(*this, "mask_editor_polygon_editor", MaskImageCtrl);
    assert(m_editImg);
    m_editImg->Init(this);

    // images list
    m_imagesListMask = XRCCTRL(*this, "mask_editor_images_list", wxListCtrl);
    m_imagesListMask->InsertColumn(0, "#", wxLIST_FORMAT_RIGHT, 35);
    m_imagesListMask->InsertColumn(1, _("Filename"), wxLIST_FORMAT_LEFT, 200);
    m_imagesListMask->InsertColumn(2, _("Number of masks"), wxLIST_FORMAT_RIGHT, 120);
    m_imagesListMask->InsertColumn(3, _("Crop"), wxLIST_FORMAT_RIGHT, 120);
    m_imagesListMask->Bind(wxEVT_LIST_ITEM_SELECTED, &MaskEditorPanel::OnImageSelect, this);
    m_imagesListMask->Bind(wxEVT_LIST_ITEM_DESELECTED, &MaskEditorPanel::OnImageSelect, this);
    m_imagesListMask->Bind(wxEVT_LIST_COL_END_DRAG, &MaskEditorPanel::OnImagesColumnWidthChange, this);
    m_imagesListMask->Bind(wxEVT_CHAR, &MaskEditorPanel::OnImageListChar, this);

    //get saved width
    wxConfigBase* config = wxConfigBase::Get();
    for (int j = 0; j < m_imagesListMask->GetColumnCount(); j++)
    {
        // -1 is auto
        int width = config->Read(wxString::Format("/ImagesListMask/ColumnWidth%d", j), -1);
        if (width != -1)
        {
            m_imagesListMask->SetColumnWidth(j, width);
        }
    }
    // mask list
    m_maskList = XRCCTRL(*this, "mask_editor_mask_list", wxListCtrl);
    m_maskList->InsertColumn( 0, "#", wxLIST_FORMAT_RIGHT, 35);
    m_maskList->InsertColumn( 1, _("Mask type"), wxLIST_FORMAT_LEFT, 120);
    m_maskList->Bind(wxEVT_LIST_ITEM_SELECTED, &MaskEditorPanel::OnMaskSelect, this);
    m_maskList->Bind(wxEVT_LIST_ITEM_DESELECTED, &MaskEditorPanel::OnMaskSelect, this);
    m_maskList->Bind(wxEVT_LIST_COL_END_DRAG, &MaskEditorPanel::OnMaskColumnWidthChange, this);

    m_maskCropCtrl = XRCCTRL(*this, "mask_editor_mask_crop_notebook", wxNotebook);
    DEBUG_ASSERT(m_maskCropCtrl);
    m_maskCropCtrl->SetSelection(0);
    m_maskCropCtrl->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &MaskEditorPanel::OnModeChanged, this);
    m_maskMode=true;

    //get saved width
    for ( int j=0; j < m_maskList->GetColumnCount() ; j++ )
    {
        // -1 is auto
        int width = config->Read(wxString::Format( "/MaskEditorPanel/ColumnWidth%d", j ), -1);
        if(width != -1)
            m_maskList->SetColumnWidth(j, width);
    }
    bool activeMasks;
    config->Read("/MaskEditorPanel/ShowActiveMasks",&activeMasks,false);
    XRCCTRL(*this,"mask_editor_show_active_masks",wxCheckBox)->SetValue(activeMasks);
    m_editImg->setDrawingActiveMasks(activeMasks);

    //load and set colours
    wxColour defaultColour;
    defaultColour.Set(HUGIN_MASK_COLOUR_POLYGON_NEGATIVE);
    wxColour colour = config->Read("/MaskEditorPanel/ColourPolygonNegative", defaultColour.GetAsString(wxC2S_HTML_SYNTAX));
    XRCCTRL(*this,"mask_editor_colour_polygon_negative",wxColourPickerCtrl)->SetColour(colour);
    m_editImg->SetUserColourPolygonNegative(colour);
    defaultColour.Set(HUGIN_MASK_COLOUR_POLYGON_POSITIVE);
    colour = config->Read("/MaskEditorPanel/ColourPolygonPositive", defaultColour.GetAsString(wxC2S_HTML_SYNTAX));
    XRCCTRL(*this,"mask_editor_colour_polygon_positive",wxColourPickerCtrl)->SetColour(colour);
    m_editImg->SetUserColourPolygonPositive(colour);
    defaultColour.Set(HUGIN_MASK_COLOUR_POINT_SELECTED);
    colour = config->Read("/MaskEditorPanel/ColourPointSelected", defaultColour.GetAsString(wxC2S_HTML_SYNTAX));
    XRCCTRL(*this,"mask_editor_colour_point_selected",wxColourPickerCtrl)->SetColour(colour);
    m_editImg->SetUserColourPointSelected(colour);
    defaultColour.Set(HUGIN_MASK_COLOUR_POINT_UNSELECTED);
    colour = config->Read("/MaskEditorPanel/ColourPointUnselected", defaultColour.GetAsString(wxC2S_HTML_SYNTAX));
    XRCCTRL(*this,"mask_editor_colour_point_unselected",wxColourPickerCtrl)->SetColour(colour);
    m_editImg->SetUserColourPointUnselected(colour);
    Bind(wxEVT_COLOURPICKER_CHANGED, &MaskEditorPanel::OnColourChanged, this);

    // other controls
    m_maskType = XRCCTRL(*this, "mask_editor_choice_masktype", wxChoice);
    m_defaultMaskType=(HuginBase::MaskPolygon::MaskType)config->Read("/MaskEditorPanel/DefaultMaskType", 0l);
    m_maskType->SetSelection((int)m_defaultMaskType);
    m_maskType->Bind(wxEVT_CHOICE, &MaskEditorPanel::OnMaskTypeChange, this);
    // disable some controls
    m_maskType->Disable();
    XRCCTRL(*this, "mask_editor_choice_zoom", wxChoice)->Disable();
    XRCCTRL(*this, "mask_editor_add", wxButton)->Disable();
    XRCCTRL(*this, "mask_editor_load", wxButton)->Disable();
    XRCCTRL(*this, "mask_editor_save", wxButton)->Disable();
    XRCCTRL(*this, "mask_editor_copy", wxButton)->Disable();
    XRCCTRL(*this, "mask_editor_paste", wxButton)->Disable();
    XRCCTRL(*this, "mask_editor_delete", wxButton)->Disable();

    m_left_textctrl = XRCCTRL(*this,"crop_left_text", wxTextCtrl);
    DEBUG_ASSERT(m_left_textctrl);
    m_left_textctrl->PushEventHandler(new TextKillFocusHandler(this));
    m_left_textctrl->Bind(wxEVT_TEXT_ENTER, &MaskEditorPanel::OnSetLeft, this);

    m_cropLens = XRCCTRL(*this, "crop_all_images_lens", wxCheckBox);
    DEBUG_ASSERT(m_cropLens);
    bool doCropImagesLens;
    config->Read("/MaskEditorPanel/CropImagesLens", &doCropImagesLens, true);
    m_cropLens->SetValue(doCropImagesLens);
    m_top_textctrl = XRCCTRL(*this,"crop_top_text", wxTextCtrl);
    DEBUG_ASSERT(m_top_textctrl);
    m_top_textctrl->PushEventHandler(new TextKillFocusHandler(this));
    m_top_textctrl->Bind(wxEVT_TEXT_ENTER, &MaskEditorPanel::OnSetTop, this);

    m_right_textctrl = XRCCTRL(*this,"crop_right_text", wxTextCtrl);
    DEBUG_ASSERT(m_right_textctrl);
    m_right_textctrl->PushEventHandler(new TextKillFocusHandler(this));
    m_right_textctrl->Bind(wxEVT_TEXT_ENTER, &MaskEditorPanel::OnSetRight, this);

    m_bottom_textctrl = XRCCTRL(*this,"crop_bottom_text", wxTextCtrl);
    DEBUG_ASSERT(m_bottom_textctrl);
    m_bottom_textctrl->PushEventHandler(new TextKillFocusHandler(this));
    m_bottom_textctrl->Bind(wxEVT_TEXT_ENTER, &MaskEditorPanel::OnSetBottom, this);


    m_autocenter_cb = XRCCTRL(*this,"crop_autocenter_cb", wxCheckBox);
    DEBUG_ASSERT(m_autocenter_cb);
    m_autocenter_cb->Bind(wxEVT_CHECKBOX, &MaskEditorPanel::OnAutoCenter, this);

    //set shortcuts
    wxAcceleratorEntry entries[2];
    entries[0].Set(wxACCEL_CMD,(int)'C',XRCID("mask_editor_copy"));
    entries[1].Set(wxACCEL_CMD,(int)'V',XRCID("mask_editor_paste"));
    wxAcceleratorTable accel(2, entries);
    SetAcceleratorTable(accel);

    // apply zoom specified in xrc file
    wxCommandEvent dummy;
    dummy.SetInt(XRCCTRL(*this,"mask_editor_choice_zoom",wxChoice)->GetSelection());
    OnZoom(dummy);
    // bind remaining event handler
    Bind(wxEVT_CHOICE, &MaskEditorPanel::OnZoom, this);
    Bind(wxEVT_BUTTON, &MaskEditorPanel::OnMaskAdd, this, XRCID("mask_editor_add"));
    Bind(wxEVT_BUTTON, &MaskEditorPanel::OnMaskLoad, this, XRCID("mask_editor_load"));
    Bind(wxEVT_BUTTON, &MaskEditorPanel::OnMaskSave, this, XRCID("mask_editor_save"));
    Bind(wxEVT_BUTTON, &MaskEditorPanel::OnMaskCopy, this, XRCID("mask_editor_copy"));
    Bind(wxEVT_BUTTON, &MaskEditorPanel::OnMaskPaste, this, XRCID("mask_editor_paste"));
    Bind(wxEVT_BUTTON, &MaskEditorPanel::OnMaskDelete, this, XRCID("mask_editor_delete"));
    Bind(wxEVT_CHECKBOX, &MaskEditorPanel::OnShowActiveMasks, this, XRCID("mask_editor_show_active_masks"));
    Bind(wxEVT_BUTTON, &MaskEditorPanel::OnResetButton, this, XRCID("crop_reset_button"));
    return true;
}

void MaskEditorPanel::Init(HuginBase::Panorama * pano)
{
    m_pano=pano;
    m_imageGroups = new HuginBase::ConstStandardImageVariableGroups(*m_pano);
    // observe the panorama
    m_pano->addObserver(this);
}

MaskEditorPanel::~MaskEditorPanel()
{
    m_left_textctrl->PopEventHandler(true);
    m_right_textctrl->PopEventHandler(true);
    m_top_textctrl->PopEventHandler(true);
    m_bottom_textctrl->PopEventHandler(true);
    wxConfigBase* config = wxConfigBase::Get();
    config->Write("/MaskEditorPanel/ShowActiveMasks",XRCCTRL(*this,"mask_editor_show_active_masks",wxCheckBox)->GetValue());
    config->Write("/MaskEditorPanel/DefaultMaskType",(long)m_defaultMaskType);
    config->Write("/MaskEditorPanel/CropImagesLens", m_cropLens->GetValue());
    config->Flush();

    DEBUG_TRACE("dtor");
    if (m_imageGroups)
    {
        delete m_imageGroups;
    }
    m_pano->removeObserver(this);
}

size_t MaskEditorPanel::GetImgNr()
{
    if(m_imagesListMask->GetSelectedItemCount()==0)
    {
        return UINT_MAX;
    }
    else
    {
        return *(GetSelectedImages().begin());
    };
};

void SelectSingleImage(wxListCtrl* list, unsigned int imgNr)
{
    unsigned int nrItems = list->GetItemCount();
    // remove potentially existing selection
    for (unsigned int i = 0; i < nrItems; i++)
    {
        if (i == imgNr)
        {
            continue;
        };
        list->SetItemState(i, 0, wxLIST_STATE_SELECTED);
    }
    if (imgNr != UINT_MAX)
    {
        list->SetItemState(imgNr, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
    };
}

void MaskEditorPanel::setImage(unsigned int imgNr, bool updateListSelection)
{
    DEBUG_TRACE("image " << imgNr);
    bool restoreMaskSelection=(imgNr==GetImgNr());
    bool updateImage=true;
    HuginBase::MaskPolygonVector masksToDraw;
    if (imgNr == UINT_MAX) 
    {
        m_File = "";
        HuginBase::MaskPolygonVector mask;
        m_currentMasks=mask;
        m_editImg->setCrop(HuginBase::SrcPanoImage::NO_CROP,vigra::Rect2D(), false, hugin_utils::FDiff2D(), false);
    } 
    else 
    {
        const HuginBase::SrcPanoImage& image=m_pano->getImage(imgNr);
        updateImage=(m_File!=image.getFilename());
        if(updateImage)
            m_File=image.getFilename();
        else
            if(GetRot(imgNr)!=m_editImg->getCurrentRotation())
            {
                updateImage=true;
                m_File=image.getFilename();
            };
        m_currentMasks=image.getMasks();
        masksToDraw=image.getActiveMasks();
        m_editImg->setCrop(image.getCropMode(),image.getCropRect(), image.getAutoCenterCrop(), image.getRadialDistortionCenter(), image.isCircularCrop());
    };
    // update mask editor
    if(updateImage)
        m_editImg->setImage(m_File,m_currentMasks,masksToDraw,GetRot(imgNr));
    else
        m_editImg->setNewMasks(m_currentMasks,masksToDraw);
    if (m_currentMasks.empty() || m_MaskNr >= m_currentMasks.size())
        setMask(UINT_MAX);
    // enables or disables controls
    bool enableCtrl=(imgNr<UINT_MAX);
    XRCCTRL(*this, "mask_editor_choice_zoom", wxChoice)->Enable(enableCtrl);
    XRCCTRL(*this, "mask_editor_add", wxButton)->Enable(enableCtrl);
    XRCCTRL(*this, "mask_editor_delete", wxButton)->Enable(enableCtrl && m_MaskNr<UINT_MAX);
    XRCCTRL(*this, "mask_editor_load", wxButton)->Enable(enableCtrl);
    XRCCTRL(*this, "mask_editor_save", wxButton)->Enable(enableCtrl && m_MaskNr<UINT_MAX);
    XRCCTRL(*this, "mask_editor_paste", wxButton)->Enable(enableCtrl);
    XRCCTRL(*this, "mask_editor_copy", wxButton)->Enable(enableCtrl && m_MaskNr<UINT_MAX);
    UpdateMaskList(restoreMaskSelection);
    // FIXME: lets hope that nobody holds references to these images..
    ImageCache::getInstance().softFlush();
    if(updateListSelection)
    {
        SelectSingleImage(m_imagesListMask, imgNr);
        m_imagesListMask->EnsureVisible(imgNr);
    };
}

void MaskEditorPanel::setMask(unsigned int maskNr)
{
    m_MaskNr=maskNr;
    m_maskType->Enable(m_MaskNr<UINT_MAX);
    m_editImg->setActiveMask(m_MaskNr);
    XRCCTRL(*this,"mask_editor_delete", wxButton)->Enable(m_MaskNr<UINT_MAX);
    XRCCTRL(*this, "mask_editor_save", wxButton)->Enable(m_MaskNr<UINT_MAX);
    XRCCTRL(*this, "mask_editor_copy", wxButton)->Enable(m_MaskNr<UINT_MAX);
    if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX)
        m_maskType->SetSelection(m_currentMasks[m_MaskNr].getMaskType());
    else
        m_maskType->SetSelection((int)m_defaultMaskType);
};

void MaskEditorPanel::UpdateMask()
{
    if(GetImgNr()<UINT_MAX)
    {
        m_currentMasks=m_editImg->getNewMask();
        PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
    };
};

void MaskEditorPanel::AddMask()
{
    if(GetImgNr()<UINT_MAX)
    {
        m_currentMasks=m_editImg->getNewMask();
        m_currentMasks[m_currentMasks.size()-1].setMaskType(m_defaultMaskType);
        PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
        //select added mask
        SelectMask(m_currentMasks.size()-1);
        m_editImg->selectAllMarkers();
    };
};

void MaskEditorPanel::SelectMask(unsigned int newMaskNr)
{
    if (GetImgNr() < UINT_MAX)
    {
        if (newMaskNr < m_currentMasks.size())
        {
            m_maskList->SetItemState(newMaskNr, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
        }
        else
        {
            if (m_MaskNr < UINT_MAX)
            {
                m_maskList->SetItemState(m_MaskNr, 0, wxLIST_STATE_SELECTED);
            };
        };
    };
};

void MaskEditorPanel::panoramaChanged(HuginBase::Panorama &pano)
{
};

void MaskEditorPanel::UpdateImage(size_t imgNr)
{
    const HuginBase::SrcPanoImage& img = m_pano->getImage(imgNr);
    wxFileName fn(wxString(img.getFilename().c_str(), HUGIN_CONV_FILENAME));
    m_imagesListMask->SetItem(imgNr, 1, fn.GetFullName());

    wxString maskstr;
    if (img.hasMasks())
    {
        maskstr = wxString::Format("%lu", (unsigned long int) m_pano->getImage(imgNr).getMasks().size());
    }
    else
    {
        maskstr = wxString("-");
    };
    m_imagesListMask->SetItem(imgNr, 2, maskstr);

    wxString cropstr("-");
    if (img.getCropMode() != HuginBase::SrcPanoImage::NO_CROP)
    {
        vigra::Rect2D c = img.getCropRect();
        cropstr.Printf("%d,%d,%d,%d", c.left(), c.right(), c.top(), c.bottom());
    }
    m_imagesListMask->SetItem(imgNr, 3, cropstr);
}

void MaskEditorPanel::panoramaImagesChanged(HuginBase::Panorama &pano, const HuginBase::UIntSet &changed)
{
    const long nrImages = pano.getNrOfImages();
    m_imageGroups->update();
    ImageCache::getInstance().softFlush();
    /** update images list */
    {
        m_imagesListMask->Freeze();
        const size_t nrItems = m_imagesListMask->GetItemCount();
        // remove items for nonexisting images
        if (nrItems > nrImages)
        {
            for (long i = nrItems - 1; i >= nrImages; i--)
            {
                m_imagesListMask->DeleteItem(i);
            }
        }
        // add newly added images
        if (nrImages >= nrItems)
        {
            for (HuginBase::UIntSet::const_iterator it = changed.begin(); it != changed.end(); ++it)
            {
                if (*it >= nrItems)
                {
                    m_imagesListMask->InsertItem(*it, wxString::Format("%d", *it));
                }
            }
        }
        // update existing items
        for (HuginBase::UIntSet::const_iterator it = changed.begin(); it != changed.end(); ++it)
        {
            UpdateImage(*it);
        }
        m_imagesListMask->Thaw();
    }

    if (nrImages == 0)
    {
        setImage(UINT_MAX);
    }
    else
    {
        // select some other image if we deleted the current image
        if ((GetImgNr() < UINT_MAX) && (GetImgNr() >= nrImages))
        {
            setImage(nrImages - 1);
        }
        else
            // update changed images
            if(set_contains(changed,GetImgNr()))
            {
                unsigned int countOldMasks=m_currentMasks.size();
                setImage(GetImgNr());
                if(countOldMasks!=pano.getImage(GetImgNr()).getMasks().size())
                    SelectMask(UINT_MAX);
            };
    };

    if (m_imagesListMask->GetSelectedItemCount() > 0)
    {
        if (set_contains(changed, GetImgNr()))
        {
            DisplayCrop(GetImgNr());
        }
    }
    else
    {
        UpdateCropDisplay();
    }

}

void MaskEditorPanel::OnImageListChar(wxKeyEvent& e)
{
    // only for multi selection mode
    if ((m_imagesListMask->GetWindowStyle() & wxLC_SINGLE_SEL) == 0)
    {
        // ctrl + a
        if (e.GetKeyCode() == 1 && e.CmdDown())
        {
            // select all
            for (int i = 0; i < m_imagesListMask->GetItemCount(); i++)
            {
                m_imagesListMask->SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
            }
        }
    }
    e.Skip();
}

void MaskEditorPanel::OnImageSelect(wxListEvent &e)
{
    const HuginBase::UIntSet selectedImages=GetSelectedImages();
    //select no mask
    setMask(UINT_MAX);
    setImage(GetImgNr());

    const bool hasImage = !selectedImages.empty();
    m_left_textctrl->Enable(hasImage);
    m_top_textctrl->Enable(hasImage);
    m_bottom_textctrl->Enable(hasImage);
    m_right_textctrl->Enable(hasImage);
    if (hasImage)
    {
        // show first image.
        DisplayCrop(GetImgNr());
    };
};

void MaskEditorPanel::OnMaskSelect(wxListEvent &e)
{
    setMask(GetSelectedMask());
};

void MaskEditorPanel::OnMaskTypeChange(wxCommandEvent &e)
{
    if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX)
    {
        m_currentMasks[m_MaskNr].setMaskType((HuginBase::MaskPolygon::MaskType)e.GetSelection());
        m_defaultMaskType=(HuginBase::MaskPolygon::MaskType)e.GetSelection();
        PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
    };
};

void MaskEditorPanel::OnMaskAdd(wxCommandEvent &e)
{
    if(GetImgNr()<UINT_MAX)
    {
        //deselect current selected mask
        if(m_MaskNr<UINT_MAX)
            m_maskList->SetItemState(m_MaskNr,0,wxLIST_STATE_SELECTED);
        setMask(UINT_MAX);
        MainFrame::Get()->SetStatusText(_("Create a polygon mask by clicking with the left mouse button on image, set the last point with the right mouse button."),0);
        m_editImg->startNewPolygon();
    };
};

void MaskEditorPanel::OnMaskSave(wxCommandEvent &e)
{
    if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX)
    {
        wxFileDialog dlg(this, _("Save mask"),
                wxConfigBase::Get()->Read("/actualPath", wxEmptyString),
                wxEmptyString, _("Mask files (*.msk)|*.msk|All files (*)|*"), 
                wxFD_SAVE | wxFD_OVERWRITE_PROMPT, wxDefaultPosition);
        if (dlg.ShowModal() == wxID_OK) 
        {
            wxString fn = dlg.GetPath();
            if (fn.Right(4) != ".msk")
            {
                fn.Append(".msk");
                if (wxFile::Exists(fn))
                {
                    if (!hugin_utils::AskUserOverwrite(fn, _("Hugin"), this))
                    {
                        return;
                    }
                }
            };
            wxFileName filename = fn;
            std::ofstream maskFile(filename.GetFullPath().mb_str(HUGIN_CONV_FILENAME));
            SaveMaskToStream(maskFile, m_pano->getImage(GetImgNr()).getSize(), m_currentMasks[m_MaskNr], GetImgNr());
            maskFile.close();
        };
    }
};

void MaskEditorPanel::OnMaskLoad(wxCommandEvent &e)
{
    if (GetImgNr()<UINT_MAX)
    {
        wxFileDialog dlg(this,_("Load mask"),
                wxConfigBase::Get()->Read("/actualPath",wxEmptyString),
                wxEmptyString,_("Mask files (*.msk)|*.msk|All files (*)|*"),
                wxFD_OPEN, wxDefaultPosition);
        if (dlg.ShowModal() != wxID_OK)
        {
            MainFrame::Get()->SetStatusText(_("Load mask: cancel"));
            return;
        }
        wxFileName filename(dlg.GetPath());
        std::ifstream in(filename.GetFullPath().mb_str(HUGIN_CONV_FILENAME));
        vigra::Size2D maskImageSize;
        HuginBase::MaskPolygonVector loadedMasks;
        LoadMaskFromStream(in, maskImageSize, loadedMasks, GetImgNr());
        in.close();
        if(maskImageSize.area()==0 || loadedMasks.empty())
        {
            hugin_utils::HuginMessageBox(wxString::Format(_("Could not parse mask from file %s."), dlg.GetPath()), _("Hugin"), wxOK | wxICON_EXCLAMATION, this);
            return;
        };
        // compare image size from file with that of current image alert user
        // if different.
        if (maskImageSize != m_pano->getImage(GetImgNr()).getSize()) 
        {
            MaskLoadDialog dlg(this);
            dlg.initValues(m_pano->getImage(GetImgNr()),loadedMasks,maskImageSize);
            if(dlg.ShowModal()!=wxID_OK)
            {
                // abort
                return;
            }
            loadedMasks=dlg.getProcessedMask();
        }
        for(unsigned int i=0;i<loadedMasks.size();i++)
            m_currentMasks.push_back(loadedMasks[i]);
        // Update the pano with the imported masks
        PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
    }
};

void MaskEditorPanel::OnMaskCopy(wxCommandEvent &e)
{
    if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX && m_maskMode)
    {
        std::ostringstream stream;
        SaveMaskToStream(stream, m_pano->getImage(GetImgNr()).getSize(), m_currentMasks[m_MaskNr], GetImgNr());
        if (wxTheClipboard->Open())
        {
            // This data objects are held by the clipboard,
            // so do not delete them in the app.
            wxTheClipboard->SetData(new wxTextDataObject(wxString(stream.str().c_str(),wxConvLocal)));
            wxTheClipboard->Close();
        };
    };
};

void MaskEditorPanel::OnMaskPaste(wxCommandEvent &e)
{
    if(GetImgNr()<UINT_MAX && m_maskMode)
    {
        if (wxTheClipboard->Open())
        {
            vigra::Size2D maskImageSize;
            HuginBase::MaskPolygonVector loadedMasks;
            if (wxTheClipboard->IsSupported( wxDF_TEXT ))
            {
                wxTextDataObject data;
                wxTheClipboard->GetData(data);
                std::istringstream stream(std::string(data.GetText().mb_str()));
                LoadMaskFromStream(stream, maskImageSize, loadedMasks, GetImgNr());
            }
            wxTheClipboard->Close();
            if(maskImageSize.area()==0 || loadedMasks.empty())
            {
                wxBell();
                return;
            };
            // compare image size from file with that of current image alert user
            // if different.
            if (maskImageSize != m_pano->getImage(GetImgNr()).getSize()) 
            {
                MaskLoadDialog dlg(this);
                dlg.initValues(m_pano->getImage(GetImgNr()),loadedMasks,maskImageSize);
                if(dlg.ShowModal()!=wxID_OK)
                {
                    // abort
                    return;
                }
                loadedMasks=dlg.getProcessedMask();
            }
            for(unsigned int i=0;i<loadedMasks.size();i++)
                m_currentMasks.push_back(loadedMasks[i]);
            // Update the pano with the imported masks
            PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
        };
    };
};

void MaskEditorPanel::OnMaskDelete(wxCommandEvent &e)
{
    if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX)
    {
        HuginBase::MaskPolygonVector editedMasks=m_currentMasks;
        editedMasks.erase(editedMasks.begin()+m_MaskNr);
        //setMask(UINT_MAX);
        PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), editedMasks));
    };
};

void MaskEditorPanel::OnZoom(wxCommandEvent & e)
{
    int posX = 0;
    int posY = 0;
    const wxSize ctrlSize = m_editImg->GetClientSize();
    if (m_editImg->getScale() > 0)
    {
        // remember old scroll position
        posX = (m_editImg->GetScrollPos(wxHORIZONTAL) + ctrlSize.GetWidth() / 2) / m_editImg->getScale();
        posY = (m_editImg->GetScrollPos(wxVERTICAL) + ctrlSize.GetHeight() / 2) / m_editImg->getScale();
    };
    double factor;
    switch (e.GetSelection()) 
    {
        case 0:
            factor = 1;
            break;
        case 1:
            // fit to window
            factor = 0;
            break;
        case 2:
            factor = 2;
            break;
        case 3:
            factor = 1.5;
            break;
        case 4:
            factor = 0.75;
            break;
        case 5:
            factor = 0.5;
            break;
        case 6:
            factor = 0.25;
            break;
        default:
            DEBUG_ERROR("unknown scale factor");
            factor = 1;
    }
    m_editImg->setScale(factor);
    if (factor > 0)
    {
        m_editImg->Scroll(posX*factor - ctrlSize.GetWidth() / 2, posY*factor - ctrlSize.GetHeight() / 2);
    };
    if (e.GetString() == "update_selection")
    {
        XRCCTRL(*this, "mask_editor_choice_zoom", wxChoice)->SetSelection(e.GetSelection());
    };
}

void MaskEditorPanel::OnColourChanged(wxColourPickerEvent &e)
{
    if(e.GetId()==XRCID("mask_editor_colour_polygon_negative"))
    {
        m_editImg->SetUserColourPolygonNegative(e.GetColour());
        wxConfigBase::Get()->Write("/MaskEditorPanel/ColourPolygonNegative",e.GetColour().GetAsString(wxC2S_HTML_SYNTAX));
    }
    else 
        if(e.GetId()==XRCID("mask_editor_colour_polygon_positive"))
        {
            m_editImg->SetUserColourPolygonPositive(e.GetColour());
            wxConfigBase::Get()->Write("/MaskEditorPanel/ColourPolygonPositive",e.GetColour().GetAsString(wxC2S_HTML_SYNTAX));
        }
        else
            if(e.GetId()==XRCID("mask_editor_colour_point_selected"))
            {
                m_editImg->SetUserColourPointSelected(e.GetColour());
                wxConfigBase::Get()->Write("/MaskEditorPanel/ColourPointSelected",e.GetColour().GetAsString(wxC2S_HTML_SYNTAX));
            }
            else
            {
                m_editImg->SetUserColourPointUnselected(e.GetColour());
                wxConfigBase::Get()->Write("/MaskEditorPanel/ColourPointUnselected",e.GetColour().GetAsString(wxC2S_HTML_SYNTAX));
            }
    m_editImg->Refresh(true);
};

void MaskEditorPanel::UpdateMaskList(bool restoreSelection)
{
    unsigned int oldSelection=GetSelectedMask();
    m_maskList->Freeze();
    if(GetImgNr()<UINT_MAX)
    {
        if(!m_currentMasks.empty())
        {
            if(m_maskList->GetItemCount()!=m_currentMasks.size())
            {
                if(m_maskList->GetItemCount()<(int)m_currentMasks.size())
                {
                    //added masks
                    for(int i=m_maskList->GetItemCount();i<(int)m_currentMasks.size();i++)
                        m_maskList->InsertItem(i,wxString::Format("%d",i));
                }
                else
                {
                    //deleted masks
                    for(int i=m_maskList->GetItemCount()-1;i>=(int)m_currentMasks.size();i--)
                        m_maskList->DeleteItem(i);
                };
            };
            for(unsigned int i=0;i<m_currentMasks.size();i++)
            {
                m_maskList->SetItem(i,1,m_maskType->GetString(m_currentMasks[i].getMaskType()));
                if(!restoreSelection && i==oldSelection)
                    m_maskList->SetItemState(i,0, wxLIST_STATE_SELECTED);
            };
        }
        else
            m_maskList->DeleteAllItems();
    }
    else
        m_maskList->DeleteAllItems();
    m_maskList->Thaw();
    m_maskType->Enable(m_maskList->GetSelectedItemCount()>0);
}

unsigned int MaskEditorPanel::GetSelectedMask()
{
    for(unsigned int i=0;i<(unsigned int)m_maskList->GetItemCount();i++)
    {
        if(m_maskList->GetItemState(i,wxLIST_STATE_SELECTED) & wxLIST_STATE_SELECTED)
            return i;
    };
    return UINT_MAX;
};

const HuginBase::UIntSet MaskEditorPanel::GetSelectedImages()
{
    HuginBase::UIntSet selectedImages;
    for (unsigned int i = 0; i < (unsigned int)m_imagesListMask->GetItemCount(); i++)
    {
        if (m_imagesListMask->GetItemState(i, wxLIST_STATE_SELECTED) & wxLIST_STATE_SELECTED)
        {
            selectedImages.insert(i);
        }
    }
    return selectedImages;
}

void MaskEditorPanel::OnImagesColumnWidthChange(wxListEvent& e)
{
    int colNum = e.GetColumn();
    wxConfigBase::Get()->Write(wxString::Format("/ImagesListMask/ColumnWidth%d", colNum), m_imagesListMask->GetColumnWidth(colNum));
}

void MaskEditorPanel::OnMaskColumnWidthChange( wxListEvent & e )
{
    int colNum = e.GetColumn();
    wxConfigBase::Get()->Write( wxString::Format("/MaskEditorPanel/ColumnWidth%d",colNum), m_maskList->GetColumnWidth(colNum) );
}

MaskImageCtrl::ImageRotation MaskEditorPanel::GetRot(const unsigned int imgNr)
{
    if(imgNr==UINT_MAX)
        return MaskImageCtrl::ROT0;

    double pitch=m_pano->getImage(imgNr).getPitch();
    double roll=m_pano->getImage(imgNr).getRoll();
    
    MaskImageCtrl::ImageRotation rot = MaskImageCtrl::ROT0;
    // normalize roll angle
    while (roll > 360) roll-= 360;
    while (roll < 0) roll += 360;

    while (pitch > 180) pitch -= 360;
    while (pitch < -180) pitch += 360;
    const bool headOver = (pitch > 90 || pitch < -90);

    if (wxConfig::Get()->Read("/CPEditorPanel/AutoRot", 1L))
    {
        if (roll >= 315 || roll < 45)
        {
            rot = headOver ? MaskImageCtrl::ROT180 : MaskImageCtrl::ROT0;
        }
        else
        {
            if (roll >= 45 && roll < 135)
            {
                rot = headOver ? MaskImageCtrl::ROT270 : MaskImageCtrl::ROT90;
            }
            else
            {
                if (roll >= 135 && roll < 225)
                {
                    rot = headOver ? MaskImageCtrl::ROT0 : MaskImageCtrl::ROT180;
                }
                else
                {
                    rot = headOver ? MaskImageCtrl::ROT90 : MaskImageCtrl::ROT270;
                };
            };
        };
    };
    return rot;
}

void MaskEditorPanel::OnShowActiveMasks(wxCommandEvent &e)
{
    m_editImg->setDrawingActiveMasks(e.IsChecked());
};

void MaskEditorPanel::DisplayCrop(int imgNr)
{
    const HuginBase::SrcPanoImage & img = m_pano->getImage(imgNr);
    m_cropMode=img.getCropMode();
    m_cropRect=img.getCropRect();
    m_autoCenterCrop=img.getAutoCenterCrop();

    int dx = hugin_utils::roundi(img.getRadialDistortionCenterShift().x);
    int dy = hugin_utils::roundi(img.getRadialDistortionCenterShift().y);
    /// @todo can this be done with img.getSize() / 2 + img.getRadialDistortionCenterShift()?
    m_cropCenter = vigra::Point2D(img.getSize().width()/2 + dx, img.getSize().height()/2 + dy);

    UpdateCropDisplay();
}

// transfer our state to panorama
void MaskEditorPanel::UpdateCrop(bool updateFromImgCtrl)
{
    // set crop image options.
    if(updateFromImgCtrl)
    {
        m_cropRect=m_editImg->getCrop();
    };
    std::vector<HuginBase::SrcPanoImage> srcImgs;
    HuginBase::UIntSet imgs;
    if (m_cropLens->IsChecked())
    {
        const HuginBase::UIntSetVector lensImageVector = m_imageGroups->getLenses().getPartsSet();
        for (auto i : GetSelectedImages())
        {
            for (auto j : lensImageVector)
            {
                if (set_contains(j, i))
                {
                    std::copy(j.begin(), j.end(), std::inserter(imgs, imgs.begin()));
                    break;
                };
            };
        };
    }
    else
    {
        imgs = GetSelectedImages();
    }
    for (auto i:imgs)
    {
        HuginBase::SrcPanoImage img=m_pano->getSrcImage(i);
        img.setCropRect(m_cropRect);
        img.setAutoCenterCrop(m_autoCenterCrop);
        srcImgs.push_back(img);
    };

    PanoCommand::GlobalCmdHist::getInstance().addCommand(
            new PanoCommand::UpdateSrcImagesCmd(*m_pano, imgs, srcImgs)
    );
}

void MaskEditorPanel::UpdateCropFromImage()
{
    m_cropRect=m_editImg->getCrop();
    UpdateCropDisplay();
};

// redraw display with new information
void MaskEditorPanel::UpdateCropDisplay()
{
    DEBUG_TRACE("")
    m_autocenter_cb->SetValue(m_autoCenterCrop);
    m_left_textctrl->SetValue(wxString::Format("%d",m_cropRect.left()));
    m_right_textctrl->SetValue(wxString::Format("%d",m_cropRect.right()));
    m_top_textctrl->SetValue(wxString::Format("%d",m_cropRect.top()));
    m_bottom_textctrl->SetValue(wxString::Format("%d",m_cropRect.bottom()));
}


void MaskEditorPanel::OnSetTop(wxCommandEvent & e)
{
    long val;
    if (m_top_textctrl->GetValue().ToLong(&val))
    {
        m_cropRect.setUpperLeft(vigra::Point2D(m_cropRect.left(), val));
        if (m_autoCenterCrop)
        {
            CenterCrop();
            UpdateCropDisplay();
        };
        UpdateCrop();
    }
    else
    {
        wxLogError(_("Please enter a valid number"));
    };
};

void MaskEditorPanel::OnSetBottom(wxCommandEvent & e)
{
    long val;
    if (m_bottom_textctrl->GetValue().ToLong(&val))
    {
        m_cropRect.setLowerRight(vigra::Point2D(m_cropRect.right(), val));
        if (m_autoCenterCrop)
        {
            CenterCrop();
            UpdateCropDisplay();
        }
        UpdateCrop();
    }
    else
    {
        wxLogError(_("Please enter a valid number"));
    };
};

void MaskEditorPanel::OnSetLeft(wxCommandEvent & e)
{
    long val = 0;
    if (m_left_textctrl->GetValue().ToLong(&val))
    {
        m_cropRect.setUpperLeft(vigra::Point2D(val, m_cropRect.top()));
        if (m_autoCenterCrop)
        {
            CenterCrop();
            UpdateCropDisplay();
        }
        UpdateCrop();
    }
    else
    {
        wxLogError(_("Please enter a valid number"));
    };
};

void MaskEditorPanel::OnSetRight(wxCommandEvent & e)
{
    long val = 0;
    if (m_right_textctrl->GetValue().ToLong(&val))
    {
        m_cropRect.setLowerRight(vigra::Point2D(val, m_cropRect.bottom()));
        if (m_autoCenterCrop)
        {
            CenterCrop();
            UpdateCropDisplay();
        };
        UpdateCrop();
    }
    else
    {
        wxLogError(_("Please enter a valid number"));
    };
};

void MaskEditorPanel::OnResetButton(wxCommandEvent & e)
{
    // suitable defaults.
    m_cropRect.setUpperLeft(vigra::Point2D(0,0));
    m_cropRect.setLowerRight(vigra::Point2D(0,0));
    m_autoCenterCrop = true;
    m_cropMode=HuginBase::SrcPanoImage::NO_CROP;
    UpdateCropDisplay();
    UpdateCrop();
}

void MaskEditorPanel::OnAutoCenter(wxCommandEvent & e)
{
    m_autoCenterCrop = e.IsChecked();
    if (m_autoCenterCrop)
    {
        CenterCrop();
        UpdateCropDisplay();
    };
    UpdateCrop();
}

void MaskEditorPanel::CenterCrop()
{
    vigra::Diff2D d(m_cropRect.width()/2, m_cropRect.height() / 2);
    m_cropRect.setUpperLeft( m_cropCenter - d);
    m_cropRect.setLowerRight( m_cropCenter + d);
}

void MaskEditorPanel::SetSingleSelection(bool singleSelMode)
{
    const size_t imgNr = GetImgNr();
#ifdef __WXMSW__
    m_imagesListMask->SetSingleStyle(wxLC_SINGLE_SEL, singleSelMode);
#else
    // updating the style flags invalidates the items in the list box
    // so first delete all items, then change style and add them again
    m_imagesListMask->Freeze();
    m_imagesListMask->DeleteAllItems();
    m_imagesListMask->SetSingleStyle(wxLC_SINGLE_SEL, singleSelMode);
    for (size_t i = 0; i < m_pano->getNrOfImages(); ++i)
    {
        m_imagesListMask->InsertItem(m_imagesListMask->GetItemCount(), wxEmptyString);
        UpdateImage(i);
    };
    m_imagesListMask->Thaw();
#endif
    SelectSingleImage(m_imagesListMask, imgNr);
}

void MaskEditorPanel::OnModeChanged(wxNotebookEvent& e)
{
    if(m_maskCropCtrl==NULL)
    {
        return;
    };
    if(m_maskCropCtrl->GetSelection()==0)
    {
        m_maskMode=true;
        SetSingleSelection(true);
        m_editImg->SetMaskMode(true);
    }
    else
    {
        m_maskMode=false;
        SetSingleSelection(false);
        m_editImg->SetMaskMode(false);
        SelectMask(UINT_MAX);
    };
    m_editImg->Refresh();
};

void MaskEditorPanel::SwitchToCropMode()
{
    m_maskCropCtrl->SetSelection(1);
    wxNotebookEvent dummy;
    OnModeChanged(dummy);
}

IMPLEMENT_DYNAMIC_CLASS(MaskEditorPanel, wxPanel)

MaskEditorPanelXmlHandler::MaskEditorPanelXmlHandler()
                : wxXmlResourceHandler()
{
    AddWindowStyles();
}

wxObject *MaskEditorPanelXmlHandler::DoCreateResource()
{
    XRC_MAKE_INSTANCE(cp, MaskEditorPanel)

    cp->Create(m_parentAsWindow,
                   GetID(),
                   GetPosition(), GetSize(),
                   GetStyle("style"),
                   GetName());

    SetupWindow(cp);

    return cp;
}

bool MaskEditorPanelXmlHandler::CanHandle(wxXmlNode *node)
{
    return IsOfClass(node, "MaskEditorPanel");
}

IMPLEMENT_DYNAMIC_CLASS(MaskEditorPanelXmlHandler, wxXmlResourceHandler)
