1 // -*- c-basic-offset: 4 -*-
2 
3 /** @file MaskEditorPanel.cpp
4  *
5  *  @brief implementation of MaskEditorPanel Class
6  *
7  *  @author Thomas Modes
8  *
9  */
10 
11 /*  This program is free software; you can redistribute it and/or
12  *  modify it under the terms of the GNU General Public
13  *  License as published by the Free Software Foundation; either
14  *  version 2 of the License, or (at your option) any later version.
15  *
16  *  This software is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  *  General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public
22  *  License along with this software. If not, see
23  *  <http://www.gnu.org/licenses/>.
24  *
25  */
26 
27 #include "panoinc_WX.h"
28 #include "panoinc.h"
29 #include <hugin_utils/stl_utils.h>
30 
31 // hugin's
32 #include "base_wx/platform.h"
33 #include "hugin/MainFrame.h"
34 #include "hugin/config_defaults.h"
35 #include "base_wx/CommandHistory.h"
36 #include "base_wx/PanoCommand.h"
37 #include "hugin/MaskEditorPanel.h"
38 #include "hugin/MaskLoadDialog.h"
39 #include <wx/clipbrd.h>
40 #include "hugin/TextKillFocusHandler.h"
41 
BEGIN_EVENT_TABLE(MaskEditorPanel,wxPanel)42 BEGIN_EVENT_TABLE(MaskEditorPanel, wxPanel)
43     EVT_LIST_ITEM_SELECTED(XRCID("mask_editor_images_list"), MaskEditorPanel::OnImageSelect)
44     EVT_LIST_ITEM_DESELECTED(XRCID("mask_editor_images_list"), MaskEditorPanel::OnImageSelect)
45     EVT_LIST_ITEM_SELECTED(XRCID("mask_editor_mask_list"), MaskEditorPanel::OnMaskSelect)
46     EVT_LIST_ITEM_DESELECTED(XRCID("mask_editor_mask_list"), MaskEditorPanel::OnMaskSelect)
47     EVT_LIST_COL_END_DRAG(XRCID("mask_editor_mask_list"), MaskEditorPanel::OnColumnWidthChange)
48     EVT_CHOICE(XRCID("mask_editor_choice_zoom"), MaskEditorPanel::OnZoom)
49     EVT_CHOICE(XRCID("mask_editor_choice_masktype"), MaskEditorPanel::OnMaskTypeChange)
50     EVT_BUTTON(XRCID("mask_editor_add"), MaskEditorPanel::OnMaskAdd)
51     EVT_BUTTON(XRCID("mask_editor_load"), MaskEditorPanel::OnMaskLoad)
52     EVT_BUTTON(XRCID("mask_editor_save"), MaskEditorPanel::OnMaskSave)
53     EVT_BUTTON(XRCID("mask_editor_copy"), MaskEditorPanel::OnMaskCopy)
54     EVT_BUTTON(XRCID("mask_editor_paste"), MaskEditorPanel::OnMaskPaste)
55     EVT_BUTTON(XRCID("mask_editor_delete"), MaskEditorPanel::OnMaskDelete)
56     EVT_CHECKBOX(XRCID("mask_editor_show_active_masks"), MaskEditorPanel::OnShowActiveMasks)
57     EVT_COLOURPICKER_CHANGED(XRCID("mask_editor_colour_polygon_negative"),MaskEditorPanel::OnColourChanged)
58     EVT_COLOURPICKER_CHANGED(XRCID("mask_editor_colour_polygon_positive"),MaskEditorPanel::OnColourChanged)
59     EVT_COLOURPICKER_CHANGED(XRCID("mask_editor_colour_point_selected"),MaskEditorPanel::OnColourChanged)
60     EVT_COLOURPICKER_CHANGED(XRCID("mask_editor_colour_point_unselected"),MaskEditorPanel::OnColourChanged)
61     EVT_TEXT_ENTER (XRCID("crop_left_text") ,MaskEditorPanel::OnSetLeft )
62     EVT_TEXT_ENTER (XRCID("crop_right_text") ,MaskEditorPanel::OnSetRight )
63     EVT_TEXT_ENTER (XRCID("crop_top_text") ,MaskEditorPanel::OnSetTop )
64     EVT_TEXT_ENTER (XRCID("crop_bottom_text") ,MaskEditorPanel::OnSetBottom )
65     EVT_BUTTON ( XRCID("crop_reset_button") , MaskEditorPanel::OnResetButton )
66     EVT_CHECKBOX( XRCID("crop_autocenter_cb") , MaskEditorPanel::OnAutoCenter)
67     EVT_NOTEBOOK_PAGE_CHANGED(XRCID("mask_editor_mask_crop_notebook"), MaskEditorPanel::OnModeChanged)
68 END_EVENT_TABLE()
69 
70 MaskEditorPanel::MaskEditorPanel()
71 {
72     DEBUG_TRACE("**********************");
73     m_pano = 0;
74     m_maskCropCtrl=NULL;
75     m_defaultMaskType=HuginBase::MaskPolygon::Mask_negative;
76 }
77 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style,const wxString & name)78 bool MaskEditorPanel::Create(wxWindow* parent, wxWindowID id,
79                     const wxPoint& pos,
80                     const wxSize& size,
81                     long style,
82                     const wxString& name)
83 {
84     DEBUG_TRACE(" Create called *************");
85     if (! wxPanel::Create(parent, id, pos, size, style, name))
86     {
87         return false;
88     }
89 
90     m_selectedImages.clear();
91     m_MaskNr=UINT_MAX;
92     m_File="";
93 
94     wxXmlResource::Get()->LoadPanel(this, wxT("mask_panel"));
95     wxPanel * panel = XRCCTRL(*this, "mask_panel", wxPanel);
96 
97     wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
98     topsizer->Add(panel, 1, wxEXPAND, 0);
99     SetSizer(topsizer);
100 
101     m_editImg = XRCCTRL(*this, "mask_editor_polygon_editor", MaskImageCtrl);
102     assert(m_editImg);
103     m_editImg->Init(this);
104 
105     // images list
106     m_imagesListMask = XRCCTRL(*this, "mask_editor_images_list", ImagesListMask);
107     // mask list
108     m_maskList = XRCCTRL(*this, "mask_editor_mask_list", wxListCtrl);
109     m_maskList->InsertColumn( 0, wxT("#"), wxLIST_FORMAT_RIGHT, 35);
110     m_maskList->InsertColumn( 1, _("Mask type"), wxLIST_FORMAT_LEFT, 120);
111 
112     m_maskCropCtrl = XRCCTRL(*this, "mask_editor_mask_crop_notebook", wxNotebook);
113     DEBUG_ASSERT(m_maskCropCtrl);
114     m_maskCropCtrl->SetSelection(0);
115     m_maskMode=true;
116 
117     //get saved width
118     wxConfigBase *config=wxConfigBase::Get();
119     for ( int j=0; j < m_maskList->GetColumnCount() ; j++ )
120     {
121         // -1 is auto
122         int width = config->Read(wxString::Format( wxT("/MaskEditorPanel/ColumnWidth%d"), j ), -1);
123         if(width != -1)
124             m_maskList->SetColumnWidth(j, width);
125     }
126     bool activeMasks;
127     config->Read(wxT("/MaskEditorPanel/ShowActiveMasks"),&activeMasks,false);
128     XRCCTRL(*this,"mask_editor_show_active_masks",wxCheckBox)->SetValue(activeMasks);
129     m_editImg->setDrawingActiveMasks(activeMasks);
130 
131     //load and set colours
132     wxColour defaultColour;
133     defaultColour.Set(wxT(HUGIN_MASK_COLOUR_POLYGON_NEGATIVE));
134     wxColour colour = config->Read(wxT("/MaskEditorPanel/ColourPolygonNegative"), defaultColour.GetAsString(wxC2S_HTML_SYNTAX));
135     XRCCTRL(*this,"mask_editor_colour_polygon_negative",wxColourPickerCtrl)->SetColour(colour);
136     m_editImg->SetUserColourPolygonNegative(colour);
137     defaultColour.Set(wxT(HUGIN_MASK_COLOUR_POLYGON_POSITIVE));
138     colour = config->Read(wxT("/MaskEditorPanel/ColourPolygonPositive"), defaultColour.GetAsString(wxC2S_HTML_SYNTAX));
139     XRCCTRL(*this,"mask_editor_colour_polygon_positive",wxColourPickerCtrl)->SetColour(colour);
140     m_editImg->SetUserColourPolygonPositive(colour);
141     defaultColour.Set(wxT(HUGIN_MASK_COLOUR_POINT_SELECTED));
142     colour = config->Read(wxT("/MaskEditorPanel/ColourPointSelected"), defaultColour.GetAsString(wxC2S_HTML_SYNTAX));
143     XRCCTRL(*this,"mask_editor_colour_point_selected",wxColourPickerCtrl)->SetColour(colour);
144     m_editImg->SetUserColourPointSelected(colour);
145     defaultColour.Set(wxT(HUGIN_MASK_COLOUR_POINT_UNSELECTED));
146     colour = config->Read(wxT("/MaskEditorPanel/ColourPointUnselected"), defaultColour.GetAsString(wxC2S_HTML_SYNTAX));
147     XRCCTRL(*this,"mask_editor_colour_point_unselected",wxColourPickerCtrl)->SetColour(colour);
148     m_editImg->SetUserColourPointUnselected(colour);
149 
150     // other controls
151     m_maskType = XRCCTRL(*this, "mask_editor_choice_masktype", wxChoice);
152     m_defaultMaskType=(HuginBase::MaskPolygon::MaskType)config->Read(wxT("/MaskEditorPanel/DefaultMaskType"), 0l);
153     m_maskType->SetSelection((int)m_defaultMaskType);
154     // disable some controls
155     m_maskType->Disable();
156     XRCCTRL(*this, "mask_editor_choice_zoom", wxChoice)->Disable();
157     XRCCTRL(*this, "mask_editor_add", wxButton)->Disable();
158     XRCCTRL(*this, "mask_editor_load", wxButton)->Disable();
159     XRCCTRL(*this, "mask_editor_save", wxButton)->Disable();
160     XRCCTRL(*this, "mask_editor_copy", wxButton)->Disable();
161     XRCCTRL(*this, "mask_editor_paste", wxButton)->Disable();
162     XRCCTRL(*this, "mask_editor_delete", wxButton)->Disable();
163 
164     m_left_textctrl = XRCCTRL(*this,"crop_left_text", wxTextCtrl);
165     DEBUG_ASSERT(m_left_textctrl);
166     m_left_textctrl->PushEventHandler(new TextKillFocusHandler(this));
167 
168     m_cropLens = XRCCTRL(*this, "crop_all_images_lens", wxCheckBox);
169     DEBUG_ASSERT(m_cropLens);
170     bool doCropImagesLens;
171     config->Read(wxT("/MaskEditorPanel/CropImagesLens"), &doCropImagesLens, true);
172     m_cropLens->SetValue(doCropImagesLens);
173     m_top_textctrl = XRCCTRL(*this,"crop_top_text", wxTextCtrl);
174     DEBUG_ASSERT(m_top_textctrl);
175     m_top_textctrl->PushEventHandler(new TextKillFocusHandler(this));
176 
177     m_right_textctrl = XRCCTRL(*this,"crop_right_text", wxTextCtrl);
178     DEBUG_ASSERT(m_right_textctrl);
179     m_right_textctrl->PushEventHandler(new TextKillFocusHandler(this));
180 
181     m_bottom_textctrl = XRCCTRL(*this,"crop_bottom_text", wxTextCtrl);
182     DEBUG_ASSERT(m_bottom_textctrl);
183     m_bottom_textctrl->PushEventHandler(new TextKillFocusHandler(this));
184 
185     m_autocenter_cb = XRCCTRL(*this,"crop_autocenter_cb", wxCheckBox);
186     DEBUG_ASSERT(m_autocenter_cb);
187 
188     //set shortcuts
189     wxAcceleratorEntry entries[2];
190     entries[0].Set(wxACCEL_CMD,(int)'C',XRCID("mask_editor_copy"));
191     entries[1].Set(wxACCEL_CMD,(int)'V',XRCID("mask_editor_paste"));
192     wxAcceleratorTable accel(2, entries);
193     SetAcceleratorTable(accel);
194 
195     // apply zoom specified in xrc file
196     wxCommandEvent dummy;
197     dummy.SetInt(XRCCTRL(*this,"mask_editor_choice_zoom",wxChoice)->GetSelection());
198     OnZoom(dummy);
199     return true;
200 }
201 
Init(HuginBase::Panorama * pano)202 void MaskEditorPanel::Init(HuginBase::Panorama * pano)
203 {
204     m_pano=pano;
205     m_imagesListMask->Init(m_pano);
206     m_imageGroups = new HuginBase::ConstStandardImageVariableGroups(*m_pano);
207     // observe the panorama
208     m_pano->addObserver(this);
209 }
210 
~MaskEditorPanel()211 MaskEditorPanel::~MaskEditorPanel()
212 {
213     m_left_textctrl->PopEventHandler(true);
214     m_right_textctrl->PopEventHandler(true);
215     m_top_textctrl->PopEventHandler(true);
216     m_bottom_textctrl->PopEventHandler(true);
217     wxConfigBase* config = wxConfigBase::Get();
218     config->Write(wxT("/MaskEditorPanel/ShowActiveMasks"),XRCCTRL(*this,"mask_editor_show_active_masks",wxCheckBox)->GetValue());
219     config->Write(wxT("/MaskEditorPanel/DefaultMaskType"),(long)m_defaultMaskType);
220     config->Write(wxT("/MaskEditorPanel/CropImagesLens"), m_cropLens->GetValue());
221     config->Flush();
222 
223     DEBUG_TRACE("dtor");
224     if (m_imageGroups)
225     {
226         delete m_imageGroups;
227     }
228     m_pano->removeObserver(this);
229 }
230 
GetImgNr()231 size_t MaskEditorPanel::GetImgNr()
232 {
233     if(m_selectedImages.empty())
234     {
235         return UINT_MAX;
236     }
237     else
238     {
239         return *(m_selectedImages.begin());
240     };
241 };
242 
setImage(unsigned int imgNr,bool updateListSelection)243 void MaskEditorPanel::setImage(unsigned int imgNr, bool updateListSelection)
244 {
245     DEBUG_TRACE("image " << imgNr);
246     bool restoreMaskSelection=(imgNr==GetImgNr());
247     bool updateImage=true;
248     if(imgNr==UINT_MAX)
249     {
250         m_selectedImages.clear();
251     }
252     else
253     {
254         m_selectedImages.insert(imgNr);
255     };
256     HuginBase::MaskPolygonVector masksToDraw;
257     if (imgNr == UINT_MAX)
258     {
259         m_File = "";
260         HuginBase::MaskPolygonVector mask;
261         m_currentMasks=mask;
262         m_editImg->setCrop(HuginBase::SrcPanoImage::NO_CROP,vigra::Rect2D(), false, hugin_utils::FDiff2D(), false);
263     }
264     else
265     {
266         const HuginBase::SrcPanoImage& image=m_pano->getImage(imgNr);
267         updateImage=(m_File!=image.getFilename());
268         if(updateImage)
269             m_File=image.getFilename();
270         else
271             if(GetRot(imgNr)!=m_editImg->getCurrentRotation())
272             {
273                 updateImage=true;
274                 m_File=image.getFilename();
275             };
276         m_currentMasks=image.getMasks();
277         masksToDraw=image.getActiveMasks();
278         m_editImg->setCrop(image.getCropMode(),image.getCropRect(), image.getAutoCenterCrop(), image.getRadialDistortionCenter(), image.isCircularCrop());
279     };
280     // update mask editor
281     if(updateImage)
282         m_editImg->setImage(m_File,m_currentMasks,masksToDraw,GetRot(imgNr));
283     else
284         m_editImg->setNewMasks(m_currentMasks,masksToDraw);
285     if (m_currentMasks.empty() || m_MaskNr >= m_currentMasks.size())
286         setMask(UINT_MAX);
287     // enables or disables controls
288     bool enableCtrl=(imgNr<UINT_MAX);
289     XRCCTRL(*this, "mask_editor_choice_zoom", wxChoice)->Enable(enableCtrl);
290     XRCCTRL(*this, "mask_editor_add", wxButton)->Enable(enableCtrl);
291     XRCCTRL(*this, "mask_editor_delete", wxButton)->Enable(enableCtrl && m_MaskNr<UINT_MAX);
292     XRCCTRL(*this, "mask_editor_load", wxButton)->Enable(enableCtrl);
293     XRCCTRL(*this, "mask_editor_save", wxButton)->Enable(enableCtrl && m_MaskNr<UINT_MAX);
294     XRCCTRL(*this, "mask_editor_paste", wxButton)->Enable(enableCtrl);
295     XRCCTRL(*this, "mask_editor_copy", wxButton)->Enable(enableCtrl && m_MaskNr<UINT_MAX);
296     UpdateMaskList(restoreMaskSelection);
297     // FIXME: lets hope that nobody holds references to these images..
298     ImageCache::getInstance().softFlush();
299     if(updateListSelection)
300     {
301         m_imagesListMask->SelectSingleImage(imgNr);
302     };
303 }
304 
setMask(unsigned int maskNr)305 void MaskEditorPanel::setMask(unsigned int maskNr)
306 {
307     m_MaskNr=maskNr;
308     m_maskType->Enable(m_MaskNr<UINT_MAX);
309     m_editImg->setActiveMask(m_MaskNr);
310     XRCCTRL(*this,"mask_editor_delete", wxButton)->Enable(m_MaskNr<UINT_MAX);
311     XRCCTRL(*this, "mask_editor_save", wxButton)->Enable(m_MaskNr<UINT_MAX);
312     XRCCTRL(*this, "mask_editor_copy", wxButton)->Enable(m_MaskNr<UINT_MAX);
313     if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX)
314         m_maskType->SetSelection(m_currentMasks[m_MaskNr].getMaskType());
315     else
316         m_maskType->SetSelection((int)m_defaultMaskType);
317 };
318 
UpdateMask()319 void MaskEditorPanel::UpdateMask()
320 {
321     if(GetImgNr()<UINT_MAX)
322     {
323         m_currentMasks=m_editImg->getNewMask();
324         PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
325     };
326 };
327 
AddMask()328 void MaskEditorPanel::AddMask()
329 {
330     if(GetImgNr()<UINT_MAX)
331     {
332         m_currentMasks=m_editImg->getNewMask();
333         m_currentMasks[m_currentMasks.size()-1].setMaskType(m_defaultMaskType);
334         PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
335         //select added mask
336         SelectMask(m_currentMasks.size()-1);
337         m_editImg->selectAllMarkers();
338     };
339 };
340 
SelectMask(unsigned int newMaskNr)341 void MaskEditorPanel::SelectMask(unsigned int newMaskNr)
342 {
343     if (GetImgNr() < UINT_MAX)
344     {
345         if (newMaskNr < m_currentMasks.size())
346         {
347             m_maskList->SetItemState(newMaskNr, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
348         }
349         else
350         {
351             if (m_MaskNr < UINT_MAX)
352             {
353                 m_maskList->SetItemState(m_MaskNr, 0, wxLIST_STATE_SELECTED);
354             };
355         };
356     };
357 };
358 
panoramaChanged(HuginBase::Panorama & pano)359 void MaskEditorPanel::panoramaChanged(HuginBase::Panorama &pano)
360 {
361 };
362 
panoramaImagesChanged(HuginBase::Panorama & pano,const HuginBase::UIntSet & changed)363 void MaskEditorPanel::panoramaImagesChanged(HuginBase::Panorama &pano, const HuginBase::UIntSet &changed)
364 {
365     unsigned int nrImages = pano.getNrOfImages();
366     m_imageGroups->update();
367     ImageCache::getInstance().softFlush();
368     if (nrImages==0)
369         setImage(UINT_MAX);
370     else
371     {
372         // select some other image if we deleted the current image
373         if ((GetImgNr() < UINT_MAX) && (GetImgNr() >= nrImages))
374         {
375             for (auto i : m_selectedImages)
376             {
377                 if (i >= nrImages)
378                 {
379                     m_selectedImages.erase(i);
380                 };
381             };
382             setImage(nrImages - 1);
383         }
384         else
385             // update changed images
386             if(set_contains(changed,GetImgNr()))
387             {
388                 unsigned int countOldMasks=m_currentMasks.size();
389                 setImage(GetImgNr());
390                 if(countOldMasks!=pano.getImage(GetImgNr()).getMasks().size())
391                     SelectMask(UINT_MAX);
392             };
393     };
394 
395     if (!m_selectedImages.empty())
396     {
397         if (set_contains(changed, GetImgNr()))
398         {
399             DisplayCrop(GetImgNr());
400         }
401     }
402     else
403     {
404         UpdateCropDisplay();
405     }
406 
407 }
408 
OnImageSelect(wxListEvent & e)409 void MaskEditorPanel::OnImageSelect(wxListEvent &e)
410 {
411     m_selectedImages=m_imagesListMask->GetSelected();
412     //select no mask
413     setMask(UINT_MAX);
414     setImage(GetImgNr());
415 
416     const bool hasImage = !m_selectedImages.empty();
417     m_left_textctrl->Enable(hasImage);
418     m_top_textctrl->Enable(hasImage);
419     m_bottom_textctrl->Enable(hasImage);
420     m_right_textctrl->Enable(hasImage);
421     if (hasImage)
422     {
423         // show first image.
424         DisplayCrop(GetImgNr());
425     };
426 };
427 
OnMaskSelect(wxListEvent & e)428 void MaskEditorPanel::OnMaskSelect(wxListEvent &e)
429 {
430     setMask(GetSelectedMask());
431 };
432 
OnMaskTypeChange(wxCommandEvent & e)433 void MaskEditorPanel::OnMaskTypeChange(wxCommandEvent &e)
434 {
435     if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX)
436     {
437         m_currentMasks[m_MaskNr].setMaskType((HuginBase::MaskPolygon::MaskType)e.GetSelection());
438         m_defaultMaskType=(HuginBase::MaskPolygon::MaskType)e.GetSelection();
439         PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
440     };
441 };
442 
OnMaskAdd(wxCommandEvent & e)443 void MaskEditorPanel::OnMaskAdd(wxCommandEvent &e)
444 {
445     if(GetImgNr()<UINT_MAX)
446     {
447         //deselect current selected mask
448         if(m_MaskNr<UINT_MAX)
449             m_maskList->SetItemState(m_MaskNr,0,wxLIST_STATE_SELECTED);
450         setMask(UINT_MAX);
451         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);
452         m_editImg->startNewPolygon();
453     };
454 };
455 
OnMaskSave(wxCommandEvent & e)456 void MaskEditorPanel::OnMaskSave(wxCommandEvent &e)
457 {
458     if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX)
459     {
460         wxFileDialog dlg(this, _("Save mask"),
461                 wxConfigBase::Get()->Read(wxT("/actualPath"), wxT("")),
462                 wxT(""), _("Mask files (*.msk)|*.msk|All files (*)|*"),
463                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT, wxDefaultPosition);
464         if (dlg.ShowModal() == wxID_OK)
465         {
466             wxString fn = dlg.GetPath();
467             if (fn.Right(4) != wxT(".msk"))
468             {
469                 fn.Append(wxT(".msk"));
470                 if (wxFile::Exists(fn))
471                 {
472                     int d = wxMessageBox(wxString::Format(_("File %s exists. Overwrite?"),
473                         fn.c_str()), _("Save mask"),
474                         wxYES_NO | wxICON_QUESTION);
475                     if (d != wxYES) {
476                         return;
477                     }
478                 }
479             };
480             wxFileName filename = fn;
481             std::ofstream maskFile(filename.GetFullPath().mb_str(HUGIN_CONV_FILENAME));
482             SaveMaskToStream(maskFile, m_pano->getImage(GetImgNr()).getSize(), m_currentMasks[m_MaskNr], GetImgNr());
483             maskFile.close();
484         };
485     }
486 };
487 
OnMaskLoad(wxCommandEvent & e)488 void MaskEditorPanel::OnMaskLoad(wxCommandEvent &e)
489 {
490     if (GetImgNr()<UINT_MAX)
491     {
492         wxFileDialog dlg(this,_("Load mask"),
493                 wxConfigBase::Get()->Read(wxT("/actualPath"),wxT("")),
494                 wxT(""),_("Mask files (*.msk)|*.msk|All files (*)|*"),
495                 wxFD_OPEN, wxDefaultPosition);
496         if (dlg.ShowModal() != wxID_OK)
497         {
498             MainFrame::Get()->SetStatusText(_("Load mask: cancel"));
499             return;
500         }
501         wxFileName filename(dlg.GetPath());
502         std::ifstream in(filename.GetFullPath().mb_str(HUGIN_CONV_FILENAME));
503         vigra::Size2D maskImageSize;
504         HuginBase::MaskPolygonVector loadedMasks;
505         LoadMaskFromStream(in, maskImageSize, loadedMasks, GetImgNr());
506         in.close();
507         if(maskImageSize.area()==0 || loadedMasks.empty())
508         {
509             wxMessageBox(wxString::Format(_("Could not parse mask from file %s."),dlg.GetPath().c_str()),_("Warning"),wxOK | wxICON_EXCLAMATION,this);
510             return;
511         };
512         // compare image size from file with that of current image alert user
513         // if different.
514         if (maskImageSize != m_pano->getImage(GetImgNr()).getSize())
515         {
516             MaskLoadDialog dlg(this);
517             dlg.initValues(m_pano->getImage(GetImgNr()),loadedMasks,maskImageSize);
518             if(dlg.ShowModal()!=wxID_OK)
519             {
520                 // abort
521                 return;
522             }
523             loadedMasks=dlg.getProcessedMask();
524         }
525         for(unsigned int i=0;i<loadedMasks.size();i++)
526             m_currentMasks.push_back(loadedMasks[i]);
527         // Update the pano with the imported masks
528         PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
529     }
530 };
531 
OnMaskCopy(wxCommandEvent & e)532 void MaskEditorPanel::OnMaskCopy(wxCommandEvent &e)
533 {
534     if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX && m_maskMode)
535     {
536         std::ostringstream stream;
537         SaveMaskToStream(stream, m_pano->getImage(GetImgNr()).getSize(), m_currentMasks[m_MaskNr], GetImgNr());
538         if (wxTheClipboard->Open())
539         {
540             // This data objects are held by the clipboard,
541             // so do not delete them in the app.
542             wxTheClipboard->SetData(new wxTextDataObject(wxString(stream.str().c_str(),wxConvLocal)));
543             wxTheClipboard->Close();
544         };
545     };
546 };
547 
OnMaskPaste(wxCommandEvent & e)548 void MaskEditorPanel::OnMaskPaste(wxCommandEvent &e)
549 {
550     if(GetImgNr()<UINT_MAX && m_maskMode)
551     {
552         if (wxTheClipboard->Open())
553         {
554             vigra::Size2D maskImageSize;
555             HuginBase::MaskPolygonVector loadedMasks;
556             if (wxTheClipboard->IsSupported( wxDF_TEXT ))
557             {
558                 wxTextDataObject data;
559                 wxTheClipboard->GetData(data);
560                 std::istringstream stream(std::string(data.GetText().mb_str()));
561                 LoadMaskFromStream(stream, maskImageSize, loadedMasks, GetImgNr());
562             }
563             wxTheClipboard->Close();
564             if(maskImageSize.area()==0 || loadedMasks.empty())
565             {
566                 wxBell();
567                 return;
568             };
569             // compare image size from file with that of current image alert user
570             // if different.
571             if (maskImageSize != m_pano->getImage(GetImgNr()).getSize())
572             {
573                 MaskLoadDialog dlg(this);
574                 dlg.initValues(m_pano->getImage(GetImgNr()),loadedMasks,maskImageSize);
575                 if(dlg.ShowModal()!=wxID_OK)
576                 {
577                     // abort
578                     return;
579                 }
580                 loadedMasks=dlg.getProcessedMask();
581             }
582             for(unsigned int i=0;i<loadedMasks.size();i++)
583                 m_currentMasks.push_back(loadedMasks[i]);
584             // Update the pano with the imported masks
585             PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
586         };
587     };
588 };
589 
OnMaskDelete(wxCommandEvent & e)590 void MaskEditorPanel::OnMaskDelete(wxCommandEvent &e)
591 {
592     if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX)
593     {
594         HuginBase::MaskPolygonVector editedMasks=m_currentMasks;
595         editedMasks.erase(editedMasks.begin()+m_MaskNr);
596         //setMask(UINT_MAX);
597         PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), editedMasks));
598     };
599 };
600 
OnZoom(wxCommandEvent & e)601 void MaskEditorPanel::OnZoom(wxCommandEvent & e)
602 {
603     int posX = 0;
604     int posY = 0;
605     const wxSize ctrlSize = m_editImg->GetClientSize();
606     if (m_editImg->getScale() > 0)
607     {
608         // remember old scroll position
609         posX = (m_editImg->GetScrollPos(wxHORIZONTAL) + ctrlSize.GetWidth() / 2) / m_editImg->getScale();
610         posY = (m_editImg->GetScrollPos(wxVERTICAL) + ctrlSize.GetHeight() / 2) / m_editImg->getScale();
611     };
612     double factor;
613     switch (e.GetSelection())
614     {
615         case 0:
616             factor = 1;
617             break;
618         case 1:
619             // fit to window
620             factor = 0;
621             break;
622         case 2:
623             factor = 2;
624             break;
625         case 3:
626             factor = 1.5;
627             break;
628         case 4:
629             factor = 0.75;
630             break;
631         case 5:
632             factor = 0.5;
633             break;
634         case 6:
635             factor = 0.25;
636             break;
637         default:
638             DEBUG_ERROR("unknown scale factor");
639             factor = 1;
640     }
641     m_editImg->setScale(factor);
642     if (factor > 0)
643     {
644         m_editImg->Scroll(posX*factor - ctrlSize.GetWidth() / 2, posY*factor - ctrlSize.GetHeight() / 2);
645     };
646     if (e.GetString() == "update_selection")
647     {
648         XRCCTRL(*this, "mask_editor_choice_zoom", wxChoice)->SetSelection(e.GetSelection());
649     };
650 }
651 
OnColourChanged(wxColourPickerEvent & e)652 void MaskEditorPanel::OnColourChanged(wxColourPickerEvent &e)
653 {
654     if(e.GetId()==XRCID("mask_editor_colour_polygon_negative"))
655     {
656         m_editImg->SetUserColourPolygonNegative(e.GetColour());
657         wxConfigBase::Get()->Write(wxT("/MaskEditorPanel/ColourPolygonNegative"),e.GetColour().GetAsString(wxC2S_HTML_SYNTAX));
658     }
659     else
660         if(e.GetId()==XRCID("mask_editor_colour_polygon_positive"))
661         {
662             m_editImg->SetUserColourPolygonPositive(e.GetColour());
663             wxConfigBase::Get()->Write(wxT("/MaskEditorPanel/ColourPolygonPositive"),e.GetColour().GetAsString(wxC2S_HTML_SYNTAX));
664         }
665         else
666             if(e.GetId()==XRCID("mask_editor_colour_point_selected"))
667             {
668                 m_editImg->SetUserColourPointSelected(e.GetColour());
669                 wxConfigBase::Get()->Write(wxT("/MaskEditorPanel/ColourPointSelected"),e.GetColour().GetAsString(wxC2S_HTML_SYNTAX));
670             }
671             else
672             {
673                 m_editImg->SetUserColourPointUnselected(e.GetColour());
674                 wxConfigBase::Get()->Write(wxT("/MaskEditorPanel/ColourPointUnselected"),e.GetColour().GetAsString(wxC2S_HTML_SYNTAX));
675             }
676     m_editImg->Refresh(true);
677 };
678 
UpdateMaskList(bool restoreSelection)679 void MaskEditorPanel::UpdateMaskList(bool restoreSelection)
680 {
681     unsigned int oldSelection=GetSelectedMask();
682     m_maskList->Freeze();
683     if(GetImgNr()<UINT_MAX)
684     {
685         if(!m_currentMasks.empty())
686         {
687             if(m_maskList->GetItemCount()!=m_currentMasks.size())
688             {
689                 if(m_maskList->GetItemCount()<(int)m_currentMasks.size())
690                 {
691                     //added masks
692                     for(int i=m_maskList->GetItemCount();i<(int)m_currentMasks.size();i++)
693                         m_maskList->InsertItem(i,wxString::Format(wxT("%d"),i));
694                 }
695                 else
696                 {
697                     //deleted masks
698                     for(int i=m_maskList->GetItemCount()-1;i>=(int)m_currentMasks.size();i--)
699                         m_maskList->DeleteItem(i);
700                 };
701             };
702             for(unsigned int i=0;i<m_currentMasks.size();i++)
703             {
704                 m_maskList->SetItem(i,1,m_maskType->GetString(m_currentMasks[i].getMaskType()));
705                 if(!restoreSelection && i==oldSelection)
706                     m_maskList->SetItemState(i,0, wxLIST_STATE_SELECTED);
707             };
708         }
709         else
710             m_maskList->DeleteAllItems();
711     }
712     else
713         m_maskList->DeleteAllItems();
714     m_maskList->Thaw();
715     m_maskType->Enable(m_maskList->GetSelectedItemCount()>0);
716 }
717 
GetSelectedMask()718 unsigned int MaskEditorPanel::GetSelectedMask()
719 {
720     for(unsigned int i=0;i<(unsigned int)m_maskList->GetItemCount();i++)
721     {
722         if(m_maskList->GetItemState(i,wxLIST_STATE_SELECTED) & wxLIST_STATE_SELECTED)
723             return i;
724     };
725     return UINT_MAX;
726 };
727 
OnColumnWidthChange(wxListEvent & e)728 void MaskEditorPanel::OnColumnWidthChange( wxListEvent & e )
729 {
730     int colNum = e.GetColumn();
731     wxConfigBase::Get()->Write( wxString::Format(wxT("/MaskEditorPanel/ColumnWidth%d"),colNum), m_maskList->GetColumnWidth(colNum) );
732 }
733 
GetRot(const unsigned int imgNr)734 MaskImageCtrl::ImageRotation MaskEditorPanel::GetRot(const unsigned int imgNr)
735 {
736     if(imgNr==UINT_MAX)
737         return MaskImageCtrl::ROT0;
738 
739     double pitch=m_pano->getImage(imgNr).getPitch();
740     double roll=m_pano->getImage(imgNr).getRoll();
741 
742     MaskImageCtrl::ImageRotation rot = MaskImageCtrl::ROT0;
743     // normalize roll angle
744     while (roll > 360) roll-= 360;
745     while (roll < 0) roll += 360;
746 
747     while (pitch > 180) pitch -= 360;
748     while (pitch < -180) pitch += 360;
749     const bool headOver = (pitch > 90 || pitch < -90);
750 
751     if (wxConfig::Get()->Read("/CPEditorPanel/AutoRot", 1L))
752     {
753         if (roll >= 315 || roll < 45)
754         {
755             rot = headOver ? MaskImageCtrl::ROT180 : MaskImageCtrl::ROT0;
756         }
757         else
758         {
759             if (roll >= 45 && roll < 135)
760             {
761                 rot = headOver ? MaskImageCtrl::ROT270 : MaskImageCtrl::ROT90;
762             }
763             else
764             {
765                 if (roll >= 135 && roll < 225)
766                 {
767                     rot = headOver ? MaskImageCtrl::ROT0 : MaskImageCtrl::ROT180;
768                 }
769                 else
770                 {
771                     rot = headOver ? MaskImageCtrl::ROT90 : MaskImageCtrl::ROT270;
772                 };
773             };
774         };
775     };
776     return rot;
777 }
778 
OnShowActiveMasks(wxCommandEvent & e)779 void MaskEditorPanel::OnShowActiveMasks(wxCommandEvent &e)
780 {
781     m_editImg->setDrawingActiveMasks(e.IsChecked());
782 };
783 
DisplayCrop(int imgNr)784 void MaskEditorPanel::DisplayCrop(int imgNr)
785 {
786     const HuginBase::SrcPanoImage & img = m_pano->getImage(imgNr);
787     m_cropMode=img.getCropMode();
788     m_cropRect=img.getCropRect();
789     m_autoCenterCrop=img.getAutoCenterCrop();
790 
791     int dx = hugin_utils::roundi(img.getRadialDistortionCenterShift().x);
792     int dy = hugin_utils::roundi(img.getRadialDistortionCenterShift().y);
793     /// @todo can this be done with img.getSize() / 2 + img.getRadialDistortionCenterShift()?
794     m_cropCenter = vigra::Point2D(img.getSize().width()/2 + dx, img.getSize().height()/2 + dy);
795 
796     UpdateCropDisplay();
797 }
798 
799 // transfer our state to panorama
UpdateCrop(bool updateFromImgCtrl)800 void MaskEditorPanel::UpdateCrop(bool updateFromImgCtrl)
801 {
802     // set crop image options.
803     if(updateFromImgCtrl)
804     {
805         m_cropRect=m_editImg->getCrop();
806     };
807     std::vector<HuginBase::SrcPanoImage> srcImgs;
808     HuginBase::UIntSet imgs;
809     if (m_cropLens->IsChecked())
810     {
811         const HuginBase::UIntSetVector lensImageVector = m_imageGroups->getLenses().getPartsSet();
812         for (auto i : m_selectedImages)
813         {
814             for (auto j : lensImageVector)
815             {
816                 if (set_contains(j, i))
817                 {
818                     std::copy(j.begin(), j.end(), std::inserter(imgs, imgs.begin()));
819                     break;
820                 };
821             };
822         };
823     }
824     else
825     {
826         imgs = m_selectedImages;
827     }
828     for (auto i:imgs)
829     {
830         HuginBase::SrcPanoImage img=m_pano->getSrcImage(i);
831         img.setCropRect(m_cropRect);
832         img.setAutoCenterCrop(m_autoCenterCrop);
833         srcImgs.push_back(img);
834     };
835 
836     PanoCommand::GlobalCmdHist::getInstance().addCommand(
837             new PanoCommand::UpdateSrcImagesCmd(*m_pano, imgs, srcImgs)
838     );
839 }
840 
UpdateCropFromImage()841 void MaskEditorPanel::UpdateCropFromImage()
842 {
843     m_cropRect=m_editImg->getCrop();
844     UpdateCropDisplay();
845 };
846 
847 // redraw display with new information
UpdateCropDisplay()848 void MaskEditorPanel::UpdateCropDisplay()
849 {
850     DEBUG_TRACE("")
851     m_autocenter_cb->SetValue(m_autoCenterCrop);
852     m_left_textctrl->SetValue(wxString::Format(wxT("%d"),m_cropRect.left()));
853     m_right_textctrl->SetValue(wxString::Format(wxT("%d"),m_cropRect.right()));
854     m_top_textctrl->SetValue(wxString::Format(wxT("%d"),m_cropRect.top()));
855     m_bottom_textctrl->SetValue(wxString::Format(wxT("%d"),m_cropRect.bottom()));
856 }
857 
858 
OnSetTop(wxCommandEvent & e)859 void MaskEditorPanel::OnSetTop(wxCommandEvent & e)
860 {
861     long val;
862     if (m_top_textctrl->GetValue().ToLong(&val))
863     {
864         m_cropRect.setUpperLeft(vigra::Point2D(m_cropRect.left(), val));
865         if (m_autoCenterCrop)
866         {
867             CenterCrop();
868             UpdateCropDisplay();
869         };
870         UpdateCrop();
871     }
872     else
873     {
874         wxLogError(_("Please enter a valid number"));
875     };
876 };
877 
OnSetBottom(wxCommandEvent & e)878 void MaskEditorPanel::OnSetBottom(wxCommandEvent & e)
879 {
880     long val;
881     if (m_bottom_textctrl->GetValue().ToLong(&val))
882     {
883         m_cropRect.setLowerRight(vigra::Point2D(m_cropRect.right(), val));
884         if (m_autoCenterCrop)
885         {
886             CenterCrop();
887             UpdateCropDisplay();
888         }
889         UpdateCrop();
890     }
891     else
892     {
893         wxLogError(_("Please enter a valid number"));
894     };
895 };
896 
OnSetLeft(wxCommandEvent & e)897 void MaskEditorPanel::OnSetLeft(wxCommandEvent & e)
898 {
899     long val = 0;
900     if (m_left_textctrl->GetValue().ToLong(&val))
901     {
902         m_cropRect.setUpperLeft(vigra::Point2D(val, m_cropRect.top()));
903         if (m_autoCenterCrop)
904         {
905             CenterCrop();
906             UpdateCropDisplay();
907         }
908         UpdateCrop();
909     }
910     else
911     {
912         wxLogError(_("Please enter a valid number"));
913     };
914 };
915 
OnSetRight(wxCommandEvent & e)916 void MaskEditorPanel::OnSetRight(wxCommandEvent & e)
917 {
918     long val = 0;
919     if (m_right_textctrl->GetValue().ToLong(&val))
920     {
921         m_cropRect.setLowerRight(vigra::Point2D(val, m_cropRect.bottom()));
922         if (m_autoCenterCrop)
923         {
924             CenterCrop();
925             UpdateCropDisplay();
926         };
927         UpdateCrop();
928     }
929     else
930     {
931         wxLogError(_("Please enter a valid number"));
932     };
933 };
934 
OnResetButton(wxCommandEvent & e)935 void MaskEditorPanel::OnResetButton(wxCommandEvent & e)
936 {
937     // suitable defaults.
938     m_cropRect.setUpperLeft(vigra::Point2D(0,0));
939     m_cropRect.setLowerRight(vigra::Point2D(0,0));
940     m_autoCenterCrop = true;
941     m_cropMode=HuginBase::SrcPanoImage::NO_CROP;
942     UpdateCropDisplay();
943     UpdateCrop();
944 }
945 
OnAutoCenter(wxCommandEvent & e)946 void MaskEditorPanel::OnAutoCenter(wxCommandEvent & e)
947 {
948     m_autoCenterCrop = e.IsChecked();
949     if (m_autoCenterCrop)
950     {
951         CenterCrop();
952         UpdateCropDisplay();
953     };
954     UpdateCrop();
955 }
956 
CenterCrop()957 void MaskEditorPanel::CenterCrop()
958 {
959     vigra::Diff2D d(m_cropRect.width()/2, m_cropRect.height() / 2);
960     m_cropRect.setUpperLeft( m_cropCenter - d);
961     m_cropRect.setLowerRight( m_cropCenter + d);
962 }
963 
OnModeChanged(wxNotebookEvent & e)964 void MaskEditorPanel::OnModeChanged(wxNotebookEvent& e)
965 {
966     if(m_maskCropCtrl==NULL)
967     {
968         return;
969     };
970     if(m_maskCropCtrl->GetSelection()==0)
971     {
972         m_maskMode=true;
973         size_t imgNr=GetImgNr();
974         m_selectedImages.clear();
975         m_selectedImages.insert(imgNr);
976         m_imagesListMask->SetSingleSelect(true);
977         m_imagesListMask->SelectSingleImage(imgNr);
978         m_editImg->SetMaskMode(true);
979     }
980     else
981     {
982         m_maskMode=false;
983         m_imagesListMask->SetSingleSelect(false);
984         m_editImg->SetMaskMode(false);
985         SelectMask(UINT_MAX);
986     };
987     m_editImg->Refresh();
988 };
989 
SwitchToCropMode()990 void MaskEditorPanel::SwitchToCropMode()
991 {
992     m_maskCropCtrl->SetSelection(1);
993     wxNotebookEvent dummy;
994     OnModeChanged(dummy);
995 }
996 
IMPLEMENT_DYNAMIC_CLASS(MaskEditorPanel,wxPanel)997 IMPLEMENT_DYNAMIC_CLASS(MaskEditorPanel, wxPanel)
998 
999 MaskEditorPanelXmlHandler::MaskEditorPanelXmlHandler()
1000                 : wxXmlResourceHandler()
1001 {
1002     AddWindowStyles();
1003 }
1004 
DoCreateResource()1005 wxObject *MaskEditorPanelXmlHandler::DoCreateResource()
1006 {
1007     XRC_MAKE_INSTANCE(cp, MaskEditorPanel)
1008 
1009     cp->Create(m_parentAsWindow,
1010                    GetID(),
1011                    GetPosition(), GetSize(),
1012                    GetStyle(wxT("style")),
1013                    GetName());
1014 
1015     SetupWindow(cp);
1016 
1017     return cp;
1018 }
1019 
CanHandle(wxXmlNode * node)1020 bool MaskEditorPanelXmlHandler::CanHandle(wxXmlNode *node)
1021 {
1022     return IsOfClass(node, wxT("MaskEditorPanel"));
1023 }
1024 
1025 IMPLEMENT_DYNAMIC_CLASS(MaskEditorPanelXmlHandler, wxXmlResourceHandler)
1026