1 // -*- c-basic-offset: 4 -*-
2 
3 /** @file CPListFrame.cpp
4  *
5  *  @brief implementation of CPListFrame Class
6  *
7  *  @author Pablo d'Angelo <pablo.dangelo@web.de>
8  *
9  *  $Id$
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 "hugin_config.h"
28 #include "panoinc_WX.h"
29 #include "panoinc.h"
30 
31 #include <algorithm>
32 #include <utility>
33 #include <functional>
34 
35 #include "base_wx/wxPlatform.h"
36 #include "hugin/CPListFrame.h"
37 #include "hugin/MainFrame.h"
38 #include "base_wx/CommandHistory.h"
39 #include "base_wx/PanoCommand.h"
40 #include "hugin/huginApp.h"
41 #include "hugin_base/panotools/PanoToolsUtils.h"
42 #include "algorithms/basic/CalculateCPStatistics.h"
43 
BEGIN_EVENT_TABLE(CPListCtrl,wxListCtrl)44 BEGIN_EVENT_TABLE(CPListCtrl, wxListCtrl)
45     EVT_CHAR(CPListCtrl::OnChar)
46     EVT_LIST_ITEM_SELECTED(wxID_ANY, CPListCtrl::OnCPListSelectionChanged)
47     EVT_LIST_ITEM_DESELECTED(wxID_ANY, CPListCtrl::OnCPListSelectionChanged)
48     EVT_LIST_COL_CLICK(wxID_ANY, CPListCtrl::OnCPListHeaderClick)
49     EVT_LIST_COL_END_DRAG(wxID_ANY, CPListCtrl::OnColumnWidthChange)
50 END_EVENT_TABLE()
51 
52 std::string makePairId(unsigned int id1, unsigned int id2)
53 {
54     // Control points from same image pair, regardless of which is left or right
55     // are counted the same so return the identical hash id.
56     std::ostringstream oss;
57 
58     if (id1 < id2) {
59         oss << id1 << "_" << id2;
60     }
61     else if (id2 < id1)  {
62         oss << id2 << "_" << id1;
63     }
64     else {
65         // Control points are from same image.
66         oss << id1;
67     }
68     return oss.str();
69 }
70 
CPListCtrl()71 CPListCtrl::CPListCtrl() : m_pano(NULL)
72 {
73     m_sortCol = 0;
74     m_sortAscend = true;
75 };
76 
~CPListCtrl()77 CPListCtrl::~CPListCtrl()
78 {
79     wxConfigBase* config = wxConfig::Get();
80     config->Write(wxT("/CPListFrame/SortColumn"), m_sortCol);
81     config->Write(wxT("/CPListFrame/SortAscending"), m_sortAscend ? 1 : 0);
82     config->Flush();
83     if (m_pano)
84     {
85         m_pano->removeObserver(this);
86     };
87 };
88 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)89 bool CPListCtrl::Create(wxWindow *parent, wxWindowID id, const wxPoint& pos,
90     const wxSize& size, long style, const wxValidator& validator, const wxString& name)
91 {
92     if (!wxListCtrl::Create(parent, id, pos, size, style))
93     {
94         return false;
95     };
96     InsertColumn(0, _("G CP#"), wxLIST_FORMAT_RIGHT, 25);
97     InsertColumn(1, _("Left Img."), wxLIST_FORMAT_RIGHT, 65);
98     InsertColumn(2, _("Right Img."), wxLIST_FORMAT_RIGHT, 65);
99     InsertColumn(3, _("P CP#"), wxLIST_FORMAT_RIGHT, 25);
100     InsertColumn(4, _("Alignment"), wxLIST_FORMAT_LEFT, 80);
101     InsertColumn(5, MainFrame::Get()->IsShowingCorrelation() ? _("Correlation") : _("Distance"), wxLIST_FORMAT_RIGHT, 80);
102 
103     //get saved width
104     for (int j = 0; j < GetColumnCount(); j++)
105     {
106         // -1 is auto
107         int width = wxConfigBase::Get()->Read(wxString::Format(wxT("/CPListFrame/ColumnWidth%d"), j), -1);
108         if (width != -1)
109         {
110             SetColumnWidth(j, width);
111         };
112     };
113     EnableAlternateRowColours(true);
114 
115     wxMemoryDC memDC;
116     memDC.SetFont(GetFont());
117     wxSize fontSize = memDC.GetTextExtent(wxT("\u25b3"));
118     wxCoord charSize = std::max(fontSize.GetWidth(), fontSize.GetHeight());
119     wxImageList* sortIcons = new wxImageList(charSize, charSize, true, 0);
120     {
121         wxBitmap bmp(charSize, charSize);
122         wxMemoryDC dc(bmp);
123         dc.SetBackgroundMode(wxPENSTYLE_TRANSPARENT);
124         dc.SetBackground(GetBackgroundColour());
125         dc.Clear();
126         dc.SetFont(GetFont());
127         dc.DrawText(wxT("\u25b3"), (charSize - fontSize.GetWidth()) / 2, (charSize - fontSize.GetHeight()) / 2);
128         dc.SelectObject(wxNullBitmap);
129         sortIcons->Add(bmp, GetBackgroundColour());
130     };
131     {
132         wxBitmap bmp(charSize, charSize);
133         wxMemoryDC dc(bmp);
134         dc.SetBackgroundMode(wxPENSTYLE_TRANSPARENT);
135         dc.SetBackground(GetBackgroundColour());
136         dc.Clear();
137         dc.SetFont(GetFont());
138         dc.DrawText(wxT("\u25bd"), (charSize - fontSize.GetWidth()) / 2, (charSize - fontSize.GetHeight()) / 2);
139         dc.SelectObject(wxNullBitmap);
140         sortIcons->Add(bmp, GetBackgroundColour());
141     };
142     AssignImageList(sortIcons, wxIMAGE_LIST_SMALL);
143     wxConfigBase* config = wxConfig::Get();
144     m_sortCol=config->Read(wxT("/CPListFrame/SortColumn"), 0l);
145     m_sortAscend = config->Read(wxT("/CPListFrame/SortAscending"), 1l) == 1;
146     config->Flush();
147     SetColumnImage(m_sortCol, m_sortAscend ? 0 : 1);
148     return true;
149 };
150 
Init(HuginBase::Panorama * pano)151 void CPListCtrl::Init(HuginBase::Panorama* pano)
152 {
153     m_pano = pano;
154     m_pano->addObserver(this);
155     panoramaChanged(*pano);
156 };
157 
OnGetItemText(long item,long column) const158 wxString CPListCtrl::OnGetItemText(long item, long column) const
159 {
160     if (item > m_internalCPList.size())
161     {
162         return wxEmptyString;
163     };
164     const HuginBase::ControlPoint& cp = m_pano->getCtrlPoint(m_internalCPList[item].globalIndex);
165     switch (column)
166     {
167         case 0:
168             return wxString::Format(wxT("%lu"), static_cast<unsigned long>(m_internalCPList[item].globalIndex));
169             break;
170         case 1:
171             return wxString::Format(wxT("%u"), cp.image1Nr);
172             break;
173         case 2:
174             return wxString::Format(wxT("%u"), cp.image2Nr);
175             break;
176         case 3:
177             return wxString::Format(wxT("%lu"), static_cast<unsigned long>(m_internalCPList[item].localNumber));
178             break;
179         case 4:
180             switch (cp.mode)
181             {
182                 case HuginBase::ControlPoint::X_Y:
183                     return wxString(_("normal"));
184                     break;
185                 case HuginBase::ControlPoint::X:
186                     return wxString(_("vert. Line"));
187                     break;
188                 case HuginBase::ControlPoint::Y:
189                     return wxString(_("horiz. Line"));
190                     break;
191                 default:
192                     return wxString::Format(_("Line %d"), cp.mode);
193                     break;
194             };
195             break;
196         case 5:
197             return wxString::Format(wxT("%.2f"), cp.error);
198             break;
199         default:
200             return wxEmptyString;
201     };
202     return wxEmptyString;
203 };
204 
OnGetItemImage(long item) const205 int CPListCtrl::OnGetItemImage(long item) const
206 {
207     return -1;
208 };
209 
panoramaChanged(HuginBase::Panorama & pano)210 void CPListCtrl::panoramaChanged(HuginBase::Panorama &pano)
211 {
212     m_onlyActiveImages = MainFrame::Get()->GetOptimizeOnlyActiveImages();
213     wxListItem item;
214     if (GetColumn(5, item))
215     {
216         if (MainFrame::Get()->IsShowingCorrelation())
217         {
218             item.SetText(_("Correlation"));
219         }
220         else
221         {
222             item.SetText(_("Distance"));
223         }
224         SetColumn(5, item);
225     };
226     UpdateInternalCPList();
227     SetItemCount(m_internalCPList.size());
228     Refresh();
229 };
230 
UpdateInternalCPList()231 void CPListCtrl::UpdateInternalCPList()
232 {
233     const HuginBase::CPVector& cps = m_pano->getCtrlPoints();
234     const HuginBase::UIntSet activeImgs = m_pano->getActiveImages();
235     // Rebuild the global->local CP map on each update as CPs might have been
236     // removed.
237     m_localIds.clear();
238     m_internalCPList.clear();
239     m_internalCPList.reserve(cps.size());
240     for (size_t i = 0; i < cps.size(); i++)
241     {
242         const HuginBase::ControlPoint& cp = cps[i];
243         if (m_onlyActiveImages && (!set_contains(activeImgs, cp.image1Nr) || !set_contains(activeImgs, cp.image2Nr)))
244         {
245             continue;
246         };
247         CPListItem cpListItem;
248         cpListItem.globalIndex = i;
249         std::string pairId = makePairId(cp.image1Nr, cp.image2Nr);
250         std::map<std::string, int>::iterator it = m_localIds.find(pairId);
251         if (it != m_localIds.end())
252         {
253             ++(it->second);
254         }
255         else
256         {
257             m_localIds[pairId] = 0;
258         }
259         cpListItem.localNumber=m_localIds[pairId];
260         m_internalCPList.push_back(cpListItem);
261     };
262     SortInternalList(true);
263 };
264 
265 // sort helper function
266 #define CompareStruct(VAR) \
267 struct Compare##VAR\
268 {\
269     bool operator()(const CPListItem& item1, const CPListItem& item2)\
270     {\
271         return item1.VAR < item2.VAR;\
272     };\
273 };
274 CompareStruct(globalIndex)
CompareStruct(localNumber)275 CompareStruct(localNumber)
276 #undef CompareStruct
277 
278 #define CompareStruct(VAR) \
279 struct Compare##VAR##Greater\
280 {\
281     bool operator()(const CPListItem& item1, const CPListItem& item2)\
282     {\
283         return item1.VAR > item2.VAR;\
284     };\
285 };
286 CompareStruct(globalIndex)
287 CompareStruct(localNumber)
288 #undef CompareStruct
289 
290 #define CompareStruct(VAR)\
291 struct Compare##VAR\
292 {\
293     explicit Compare##VAR(const HuginBase::CPVector& cps) : m_cps(cps) {};\
294     bool operator()(const CPListItem& item1, const CPListItem& item2)\
295     {\
296          return m_cps[item1.globalIndex].VAR < m_cps[item2.globalIndex].VAR; \
297     }\
298 private:\
299     const HuginBase::CPVector& m_cps;\
300 };
301 CompareStruct(image1Nr)
302 CompareStruct(image2Nr)
303 CompareStruct(mode)
304 CompareStruct(error)
305 #undef CompareStruct
306 
307 #define CompareStruct(VAR)\
308 struct Compare##VAR##Greater\
309 {\
310     explicit Compare##VAR##Greater(const HuginBase::CPVector& cps) : m_cps(cps) {};\
311     bool operator()(const CPListItem& item1, const CPListItem& item2)\
312     {\
313          return m_cps[item1.globalIndex].VAR > m_cps[item2.globalIndex].VAR; \
314     }\
315 private:\
316     const HuginBase::CPVector& m_cps;\
317 };
318 CompareStruct(image1Nr)
319 CompareStruct(image2Nr)
320 CompareStruct(mode)
321 CompareStruct(error)
322 #undef CompareStruct
323 
324 void CPListCtrl::SortInternalList(bool isAscending)
325 {
326     // nothing to sort
327     if (m_internalCPList.empty())
328     {
329         return;
330     };
331 
332     switch (m_sortCol)
333     {
334         case 0:
335             if (m_sortAscend)
336             {
337                 if (!isAscending)
338                 {
339                     std::sort(m_internalCPList.begin(), m_internalCPList.end(), CompareglobalIndex());
340                 };
341             }
342             else
343             {
344                 std::sort(m_internalCPList.begin(), m_internalCPList.end(), CompareglobalIndexGreater());
345             };
346             break;
347         case 1:
348             if (m_sortAscend)
349             {
350                 std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareimage1Nr(m_pano->getCtrlPoints()));
351             }
352             else
353             {
354                 std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareimage1NrGreater(m_pano->getCtrlPoints()));
355             };
356             break;
357         case 2:
358             if (m_sortAscend)
359             {
360                 std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareimage2Nr(m_pano->getCtrlPoints()));
361             }
362             else
363             {
364                 std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareimage2NrGreater(m_pano->getCtrlPoints()));
365             };
366             break;
367         case 3:
368             if (m_sortAscend)
369             {
370                 std::sort(m_internalCPList.begin(), m_internalCPList.end(), ComparelocalNumber());
371             }
372             else
373             {
374                 std::sort(m_internalCPList.begin(), m_internalCPList.end(), ComparelocalNumberGreater());
375             };
376             break;
377         case 4:
378             if (m_sortAscend)
379             {
380                 std::sort(m_internalCPList.begin(), m_internalCPList.end(), Comparemode(m_pano->getCtrlPoints()));
381             }
382             else
383             {
384                 std::sort(m_internalCPList.begin(), m_internalCPList.end(), ComparemodeGreater(m_pano->getCtrlPoints()));
385             };
386             break;
387         case 5:
388             if (m_sortAscend)
389             {
390                 std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareerror(m_pano->getCtrlPoints()));
391             }
392             else
393             {
394                 std::sort(m_internalCPList.begin(), m_internalCPList.end(), CompareerrorGreater(m_pano->getCtrlPoints()));
395             };
396             break;
397     };
398 };
399 
OnCPListSelectionChanged(wxListEvent & e)400 void CPListCtrl::OnCPListSelectionChanged(wxListEvent & e)
401 {
402     if (GetSelectedItemCount() == 1)
403     {
404         if (e.GetIndex() < m_internalCPList.size())
405         {
406             MainFrame::Get()->ShowCtrlPoint(m_internalCPList[e.GetIndex()].globalIndex);
407         };
408     };
409 };
410 
OnCPListHeaderClick(wxListEvent & e)411 void CPListCtrl::OnCPListHeaderClick(wxListEvent& e)
412 {
413     const int newCol = e.GetColumn();
414     if (m_sortCol == newCol)
415     {
416         m_sortAscend = !m_sortAscend;
417         SetColumnImage(m_sortCol, m_sortAscend ? 0 : 1);
418     }
419     else
420     {
421         ClearColumnImage(m_sortCol);
422         m_sortCol = newCol;
423         SetColumnImage(m_sortCol, 0);
424         m_sortAscend = true;
425     }
426     SortInternalList(false);
427     Refresh();
428 };
429 
OnColumnWidthChange(wxListEvent & e)430 void CPListCtrl::OnColumnWidthChange(wxListEvent& e)
431 {
432     const int colNum = e.GetColumn();
433     wxConfigBase::Get()->Write(wxString::Format(wxT("/CPListFrame/ColumnWidth%d"), colNum), GetColumnWidth(colNum));
434 };
435 
DeleteSelected()436 void CPListCtrl::DeleteSelected()
437 {
438     // no selected item.
439     const int nSelected = GetSelectedItemCount();
440     if (nSelected == 0)
441     {
442         wxBell();
443         return;
444     };
445 
446     HuginBase::UIntSet selected;
447     long item = GetFirstSelected();
448     long newSelection = -1;
449     if (m_internalCPList.size() - nSelected > 0)
450     {
451         newSelection = item;
452         if (item >= m_internalCPList.size() - nSelected)
453         {
454             newSelection = m_internalCPList.size() - nSelected - 1;
455         };
456     };
457     while (item>=0)
458     {
459         // deselect item
460         Select(item, false);
461         selected.insert(m_internalCPList[item].globalIndex);
462         item = GetNextSelected(item);
463     }
464     DEBUG_DEBUG("about to delete " << selected.size() << " points");
465     PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::RemoveCtrlPointsCmd(*m_pano, selected));
466 
467     if (newSelection >= 0)
468     {
469         MainFrame::Get()->ShowCtrlPoint(m_internalCPList[newSelection].globalIndex);
470         Select(newSelection, true);
471     };
472 };
473 
SelectDistanceThreshold(double threshold)474 void CPListCtrl::SelectDistanceThreshold(double threshold)
475 {
476     const bool invert = threshold < 0;
477     if (invert)
478     {
479         threshold = -threshold;
480     };
481     const HuginBase::CPVector& cps = m_pano->getCtrlPoints();
482     Freeze();
483     for (size_t i = 0; i < m_internalCPList.size(); i++)
484     {
485         const double error = cps[m_internalCPList[i].globalIndex].error;
486         Select(i, ((error > threshold) && (!invert)) || ((error < threshold) && (invert)));
487     };
488     Thaw();
489 };
490 
SelectAll()491 void CPListCtrl::SelectAll()
492 {
493     for (long i = 0; i < m_internalCPList.size(); i++)
494     {
495         Select(i, true);
496     };
497 };
498 
OnChar(wxKeyEvent & e)499 void CPListCtrl::OnChar(wxKeyEvent& e)
500 {
501     switch (e.GetKeyCode())
502     {
503         case WXK_DELETE:
504         case WXK_NUMPAD_DELETE:
505             DeleteSelected();
506             break;
507         case WXK_CONTROL_A:
508             SelectAll();
509             break;
510         default:
511             e.Skip();
512     };
513 };
514 
515 
IMPLEMENT_DYNAMIC_CLASS(CPListCtrl,wxListCtrl)516 IMPLEMENT_DYNAMIC_CLASS(CPListCtrl, wxListCtrl)
517 
518 IMPLEMENT_DYNAMIC_CLASS(CPListCtrlXmlHandler, wxListCtrlXmlHandler)
519 
520 CPListCtrlXmlHandler::CPListCtrlXmlHandler()
521 : wxListCtrlXmlHandler()
522 {
523     AddWindowStyles();
524 }
525 
DoCreateResource()526 wxObject *CPListCtrlXmlHandler::DoCreateResource()
527 {
528     XRC_MAKE_INSTANCE(cp, CPListCtrl)
529     cp->Create(m_parentAsWindow, GetID(), GetPosition(), GetSize(), GetStyle(wxT("style")), wxDefaultValidator, GetName());
530     SetupWindow(cp);
531     return cp;
532 }
533 
CanHandle(wxXmlNode * node)534 bool CPListCtrlXmlHandler::CanHandle(wxXmlNode *node)
535 {
536     return IsOfClass(node, wxT("CPListCtrl"));
537 }
538 
539 
BEGIN_EVENT_TABLE(CPListFrame,wxFrame)540 BEGIN_EVENT_TABLE(CPListFrame, wxFrame)
541     EVT_CLOSE(CPListFrame::OnClose)
542     EVT_BUTTON(XRCID("cp_list_delete"), CPListFrame::OnDeleteButton)
543     EVT_BUTTON(XRCID("cp_list_select"), CPListFrame::OnSelectButton)
544 END_EVENT_TABLE()
545 
546 CPListFrame::CPListFrame(wxFrame* parent, HuginBase::Panorama& pano) : m_pano(pano)
547 {
548     DEBUG_TRACE("");
549     bool ok = wxXmlResource::Get()->LoadFrame(this, parent, wxT("cp_list_frame"));
550     DEBUG_ASSERT(ok);
551     m_list = XRCCTRL(*this, "cp_list_frame_list", CPListCtrl);
552     DEBUG_ASSERT(m_list);
553     m_list->Init(&m_pano);
554 
555 #ifdef __WXMSW__
556     // wxFrame does have a strange background color on Windows, copy color from a child widget
557     this->SetBackgroundColour(XRCCTRL(*this, "cp_list_select", wxButton)->GetBackgroundColour());
558 #endif
559 #ifdef __WXMSW__
560     wxIcon myIcon(huginApp::Get()->GetXRCPath() + wxT("data/hugin.ico"),wxBITMAP_TYPE_ICO);
561 #else
562     wxIcon myIcon(huginApp::Get()->GetXRCPath() + wxT("data/hugin.png"),wxBITMAP_TYPE_PNG);
563 #endif
564     SetIcon(myIcon);
565 
566     //set minumum size
567     SetSizeHints(200, 300);
568     //size
569     RestoreFramePosition(this, wxT("CPListFrame"));
570 }
571 
~CPListFrame()572 CPListFrame::~CPListFrame()
573 {
574     DEBUG_TRACE("dtor");
575     StoreFramePosition(this, wxT("CPListFrame"));
576     DEBUG_TRACE("dtor end");
577 }
578 
OnClose(wxCloseEvent & event)579 void CPListFrame::OnClose(wxCloseEvent& event)
580 {
581     DEBUG_DEBUG("OnClose");
582     MainFrame::Get()->OnCPListFrameClosed();
583     DEBUG_DEBUG("closing");
584     Destroy();
585 }
586 
OnDeleteButton(wxCommandEvent & e)587 void CPListFrame::OnDeleteButton(wxCommandEvent & e)
588 {
589     m_list->DeleteSelected();
590 }
591 
OnSelectButton(wxCommandEvent & e)592 void CPListFrame::OnSelectButton(wxCommandEvent & e)
593 {
594     // calculate the mean error and the standard deviation
595     HuginBase::PTools::calcCtrlPointErrors(m_pano);
596     double min, max, mean, var;
597     HuginBase::CalculateCPStatisticsError::calcCtrlPntsErrorStats(m_pano, min, max, mean, var);
598 
599     // select points whos distance is greater than the mean
600     // hmm, maybe some theory would be nice.. this is just a
601     // guess.
602     double threshold = mean + sqrt(var);
603     wxString t;
604     do
605     {
606         t = wxGetTextFromUser(_("Enter minimum control point error.\nAll points with a higher error will be selected"), _("Select Control Points"),
607             hugin_utils::doubleTowxString(threshold, 2));
608         if (t == wxEmptyString) {
609             // do not select anything
610             return;
611         }
612     }
613     while (!hugin_utils::str2double(t, threshold));
614 
615     m_list->SelectDistanceThreshold(threshold);
616 };
617