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