Hugintrunk  0.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
CPListFrame.cpp
Go to the documentation of this file.
1 // -*- c-basic-offset: 4 -*-
2 
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/config_defaults.h"
44 
45 std::string makePairId(unsigned int id1, unsigned int id2)
46 {
47  // Control points from same image pair, regardless of which is left or right
48  // are counted the same so return the identical hash id.
49  std::ostringstream oss;
50 
51  if (id1 < id2) {
52  oss << id1 << "_" << id2;
53  }
54  else if (id2 < id1) {
55  oss << id2 << "_" << id1;
56  }
57  else {
58  // Control points are from same image.
59  oss << id1;
60  }
61  return oss.str();
62 }
63 
64 CPListCtrl::CPListCtrl() : m_pano(NULL)
65 {
66  m_sortCol = 0;
67  m_sortAscend = true;
68 };
69 
71 {
72  wxConfigBase* config = wxConfig::Get();
73  config->Write(wxT("/CPListFrame/SortColumn"), m_sortCol);
74  config->Write(wxT("/CPListFrame/SortAscending"), m_sortAscend ? 1 : 0);
75  config->Flush();
76  if (m_pano)
77  {
78  m_pano->removeObserver(this);
79  };
80 };
81 
82 bool CPListCtrl::Create(wxWindow *parent, wxWindowID id, const wxPoint& pos,
83  const wxSize& size, long style, const wxValidator& validator, const wxString& name)
84 {
85  if (!wxListCtrl::Create(parent, id, pos, size, style))
86  {
87  return false;
88  };
89  InsertColumn(0, _("G CP#"), wxLIST_FORMAT_RIGHT, 25);
90  InsertColumn(1, _("Left Img."), wxLIST_FORMAT_RIGHT, 65);
91  InsertColumn(2, _("Right Img."), wxLIST_FORMAT_RIGHT, 65);
92  InsertColumn(3, _("P CP#"), wxLIST_FORMAT_RIGHT, 25);
93  InsertColumn(4, _("Alignment"), wxLIST_FORMAT_LEFT, 80);
94  InsertColumn(5, MainFrame::Get()->IsShowingCorrelation() ? _("Correlation") : _("Distance"), wxLIST_FORMAT_RIGHT, 80);
95 
96  //get saved width
97  for (int j = 0; j < GetColumnCount(); j++)
98  {
99  // -1 is auto
100  int width = wxConfigBase::Get()->Read(wxString::Format(wxT("/CPListFrame/ColumnWidth%d"), j), -1);
101  if (width != -1)
102  {
103  SetColumnWidth(j, width);
104  };
105  };
106  EnableAlternateRowColours(true);
107 
108  wxConfigBase* config = wxConfig::Get();
109  m_sortCol=config->Read(wxT("/CPListFrame/SortColumn"), 0l);
110  m_sortAscend = config->Read(wxT("/CPListFrame/SortAscending"), 1l) == 1;
111  config->Flush();
112  ShowSortIndicator(m_sortCol, m_sortAscend);
113  // bind event handler
114  Bind(wxEVT_CHAR, &CPListCtrl::OnChar, this);
115  Bind(wxEVT_LIST_ITEM_SELECTED, &CPListCtrl::OnCPListSelectionChanged, this);
116  Bind(wxEVT_LIST_ITEM_DESELECTED, &CPListCtrl::OnCPListSelectionChanged, this);
117  Bind(wxEVT_LIST_COL_CLICK, &CPListCtrl::OnCPListHeaderClick, this);
118  Bind(wxEVT_LIST_COL_END_DRAG, &CPListCtrl::OnColumnWidthChange, this);
119  return true;
120 };
121 
123 {
124  m_pano = pano;
125  m_pano->addObserver(this);
126  panoramaChanged(*pano);
127 };
128 
129 wxString CPListCtrl::OnGetItemText(long item, long column) const
130 {
131  if (item > m_internalCPList.size())
132  {
133  return wxEmptyString;
134  };
135  const HuginBase::ControlPoint& cp = m_pano->getCtrlPoint(m_internalCPList[item].globalIndex);
136  switch (column)
137  {
138  case 0:
139  return wxString::Format(wxT("%lu"), static_cast<unsigned long>(m_internalCPList[item].globalIndex));
140  break;
141  case 1:
142  return wxString::Format(wxT("%u"), cp.image1Nr);
143  break;
144  case 2:
145  return wxString::Format(wxT("%u"), cp.image2Nr);
146  break;
147  case 3:
148  return wxString::Format(wxT("%lu"), static_cast<unsigned long>(m_internalCPList[item].localNumber));
149  break;
150  case 4:
151  switch (cp.mode)
152  {
154  return wxString(_("normal"));
155  break;
157  return wxString(_("vert. Line"));
158  break;
160  return wxString(_("horiz. Line"));
161  break;
162  default:
163  return wxString::Format(_("Line %d"), cp.mode);
164  break;
165  };
166  break;
167  case 5:
168  return wxString::Format(wxT("%.2f"), cp.error);
169  break;
170  default:
171  return wxEmptyString;
172  };
173  return wxEmptyString;
174 };
175 
176 int CPListCtrl::OnGetItemImage(long item) const
177 {
178  return -1;
179 };
180 
182 {
184  const bool isShowingCorrelation = MainFrame::Get()->IsShowingCorrelation();
185  wxListItem item;
186  if (GetColumn(5, item))
187  {
188  if (isShowingCorrelation)
189  {
190  item.SetText(_("Correlation"));
191  }
192  else
193  {
194  item.SetText(_("Distance"));
195  }
196  SetColumn(5, item);
197  };
198  XRCCTRL(*GetParent(), "cp_list_select", wxButton)->SetLabel(isShowingCorrelation ? _("Select by Correlation") : _("Select by Distance"));
200  SetItemCount(m_internalCPList.size());
201  Refresh();
202 };
203 
205 {
206  const HuginBase::CPVector& cps = m_pano->getCtrlPoints();
207  const HuginBase::UIntSet activeImgs = m_pano->getActiveImages();
208  // Rebuild the global->local CP map on each update as CPs might have been
209  // removed.
210  m_localIds.clear();
211  m_internalCPList.clear();
212  m_internalCPList.reserve(cps.size());
213  for (size_t i = 0; i < cps.size(); i++)
214  {
215  const HuginBase::ControlPoint& cp = cps[i];
216  if (m_onlyActiveImages && (!set_contains(activeImgs, cp.image1Nr) || !set_contains(activeImgs, cp.image2Nr)))
217  {
218  continue;
219  };
220  CPListItem cpListItem;
221  cpListItem.globalIndex = i;
222  std::string pairId = makePairId(cp.image1Nr, cp.image2Nr);
223  std::map<std::string, int>::iterator it = m_localIds.find(pairId);
224  if (it != m_localIds.end())
225  {
226  ++(it->second);
227  }
228  else
229  {
230  m_localIds[pairId] = 0;
231  }
232  cpListItem.localNumber=m_localIds[pairId];
233  m_internalCPList.push_back(cpListItem);
234  };
235  SortInternalList(true);
236 };
237 
238 // sort helper function
239 // sort by global or local number only
240 #define CompareStruct(VAR, TYPESUFFIX, OP) \
241 struct Compare##TYPESUFFIX\
242 {\
243  bool operator()(const CPListItem& item1, const CPListItem& item2)\
244  {\
245  return item1.VAR OP item2.VAR;\
246  };\
247 };
248 CompareStruct(globalIndex, globalIndex, <)
249 CompareStruct(globalIndex, globalIndexGreater, >)
250 CompareStruct(localNumber, localNumber, <)
251 CompareStruct(localNumber, localNumberGreater, >)
252 #undef CompareStruct
253 
254 // sort by image number, take second image number as second criterion and local number as third
255 #define CompareStruct(VAR1, VAR2, TYPESUFFIX, OP)\
256 struct Compare##TYPESUFFIX\
257 {\
258  explicit Compare##TYPESUFFIX(const HuginBase::CPVector& cps) : m_cps(cps) {};\
259  bool operator()(const CPListItem& item1, const CPListItem& item2)\
260  {\
261  return m_cps[item1.globalIndex].VAR1 * 1e4 + m_cps[item1.globalIndex].VAR2 + item1.localNumber * 1.0 / m_cps.size() OP\
262  m_cps[item2.globalIndex].VAR1 * 1e4 + m_cps[item2.globalIndex].VAR2 + item2.localNumber * 1.0 / m_cps.size();\
263  }\
264 private:\
265  const HuginBase::CPVector& m_cps;\
266 };
267 CompareStruct(image1Nr, image2Nr, image1Nr, <)
268 CompareStruct(image1Nr, image2Nr, image1NrGreater, >)
269 CompareStruct(image2Nr, image1Nr, image2Nr, <)
270 CompareStruct(image2Nr, image1Nr, image2NrGreater, >)
271 #undef CompareStruct
272 
273 // sort by mode or error
274 #define CompareStruct(VAR, TYPESUFFIX, OP)\
275 struct Compare##TYPESUFFIX\
276 {\
277  explicit Compare##TYPESUFFIX(const HuginBase::CPVector& cps) : m_cps(cps) {};\
278  bool operator()(const CPListItem& item1, const CPListItem& item2)\
279  {\
280  return m_cps[item1.globalIndex].VAR OP m_cps[item2.globalIndex].VAR;\
281  }\
282 private:\
283  const HuginBase::CPVector& m_cps;\
284 };
285 CompareStruct(mode, mode, <)
286 CompareStruct(mode, modeGreater, >)
287 CompareStruct(error, error, <)
288 CompareStruct(error, errorGreater, >)
289 #undef CompareStruct
290 
291 void CPListCtrl::SortInternalList(bool isAscending)
292 {
293  // nothing to sort
294  if (m_internalCPList.empty())
295  {
296  return;
297  };
298 
299  switch (m_sortCol)
300  {
301  case 0:
302  if (m_sortAscend)
303  {
304  if (!isAscending)
305  {
306  std::sort(m_internalCPList.begin(), m_internalCPList.end(), CompareglobalIndex());
307  };
308  }
309  else
310  {
311  std::sort(m_internalCPList.begin(), m_internalCPList.end(), CompareglobalIndexGreater());
312  };
313  break;
314  case 1:
315  if (m_sortAscend)
316  {
317  std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareimage1Nr(m_pano->getCtrlPoints()));
318  }
319  else
320  {
321  std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareimage1NrGreater(m_pano->getCtrlPoints()));
322  };
323  break;
324  case 2:
325  if (m_sortAscend)
326  {
327  std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareimage2Nr(m_pano->getCtrlPoints()));
328  }
329  else
330  {
331  std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareimage2NrGreater(m_pano->getCtrlPoints()));
332  };
333  break;
334  case 3:
335  if (m_sortAscend)
336  {
337  std::sort(m_internalCPList.begin(), m_internalCPList.end(), ComparelocalNumber());
338  }
339  else
340  {
341  std::sort(m_internalCPList.begin(), m_internalCPList.end(), ComparelocalNumberGreater());
342  };
343  break;
344  case 4:
345  if (m_sortAscend)
346  {
347  std::sort(m_internalCPList.begin(), m_internalCPList.end(), Comparemode(m_pano->getCtrlPoints()));
348  }
349  else
350  {
351  std::sort(m_internalCPList.begin(), m_internalCPList.end(), ComparemodeGreater(m_pano->getCtrlPoints()));
352  };
353  break;
354  case 5:
355  if (m_sortAscend)
356  {
357  std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareerror(m_pano->getCtrlPoints()));
358  }
359  else
360  {
361  std::sort(m_internalCPList.begin(), m_internalCPList.end(), CompareerrorGreater(m_pano->getCtrlPoints()));
362  };
363  break;
364  };
365 };
366 
368 {
369  if (GetSelectedItemCount() == 1)
370  {
371  if (e.GetIndex() < m_internalCPList.size())
372  {
373  MainFrame::Get()->ShowCtrlPoint(m_internalCPList[e.GetIndex()].globalIndex);
374  };
375  };
376 };
377 
379 {
380  const int newCol = e.GetColumn();
381  if (m_sortCol == newCol)
382  {
384  }
385  else
386  {
387  m_sortCol = newCol;
388  m_sortAscend = true;
389  };
390  ShowSortIndicator(m_sortCol, m_sortAscend);
391  SortInternalList(false);
392  Refresh();
393 };
394 
396 {
397  const int colNum = e.GetColumn();
398  wxConfigBase::Get()->Write(wxString::Format(wxT("/CPListFrame/ColumnWidth%d"), colNum), GetColumnWidth(colNum));
399 };
400 
402 {
403  // no selected item.
404  const int nSelected = GetSelectedItemCount();
405  if (nSelected == 0)
406  {
407  wxBell();
408  return;
409  };
410 
411  HuginBase::UIntSet selected;
412  long item = GetFirstSelected();
413  long newSelection = -1;
414  if (m_internalCPList.size() - nSelected > 0)
415  {
416  newSelection = item;
417  if (item >= m_internalCPList.size() - nSelected)
418  {
419  newSelection = m_internalCPList.size() - nSelected - 1;
420  };
421  };
422  while (item>=0)
423  {
424  // deselect item
425  Select(item, false);
426  selected.insert(m_internalCPList[item].globalIndex);
427  item = GetNextSelected(item);
428  }
429  DEBUG_DEBUG("about to delete " << selected.size() << " points");
431 
432  if (newSelection >= 0)
433  {
434  MainFrame::Get()->ShowCtrlPoint(m_internalCPList[newSelection].globalIndex);
435  Select(newSelection, true);
436  };
437 };
438 
440 {
441  const bool invert = threshold < 0;
442  if (invert)
443  {
444  threshold = -threshold;
445  };
446  const HuginBase::CPVector& cps = m_pano->getCtrlPoints();
447  Freeze();
448  for (size_t i = 0; i < m_internalCPList.size(); i++)
449  {
450  const double error = cps[m_internalCPList[i].globalIndex].error;
451  Select(i, ((error > threshold) && (!invert)) || ((error < threshold) && (invert)));
452  };
453  Thaw();
454 };
455 
457 {
458  for (long i = 0; i < m_internalCPList.size(); i++)
459  {
460  Select(i, true);
461  };
462 };
463 
464 void CPListCtrl::OnChar(wxKeyEvent& e)
465 {
466  switch (e.GetKeyCode())
467  {
468  case WXK_DELETE:
469  case WXK_NUMPAD_DELETE:
470  DeleteSelected();
471  break;
472  case WXK_CONTROL_A:
473  SelectAll();
474  break;
475  default:
476  e.Skip();
477  };
478 };
479 
480 
482 
483 IMPLEMENT_DYNAMIC_CLASS(CPListCtrlXmlHandler, wxListCtrlXmlHandler)
484 
485 CPListCtrlXmlHandler::CPListCtrlXmlHandler()
486 : wxListCtrlXmlHandler()
487 {
488  AddWindowStyles();
489 }
490 
492 {
493  XRC_MAKE_INSTANCE(cp, CPListCtrl)
494  cp->Create(m_parentAsWindow, GetID(), GetPosition(), GetSize(), GetStyle(wxT("style")), wxDefaultValidator, GetName());
495  SetupWindow(cp);
496  return cp;
497 }
498 
499 bool CPListCtrlXmlHandler::CanHandle(wxXmlNode *node)
500 {
501  return IsOfClass(node, wxT("CPListCtrl"));
502 }
503 
504 CPListFrame::CPListFrame(wxWindow* parent, HuginBase::Panorama& pano) : m_pano(pano)
505 {
506  DEBUG_TRACE("");
507  bool ok = wxXmlResource::Get()->LoadDialog(this, parent, wxT("cp_list_frame"));
508  DEBUG_ASSERT(ok);
509  m_list = XRCCTRL(*this, "cp_list_frame_list", CPListCtrl);
511  m_list->Init(&m_pano);
512 
513  //set minumum size
514  SetSizeHints(200, 300);
515  //size
516  RestoreFramePosition(this, wxT("CPListFrame"));
517  Bind(wxEVT_CLOSE_WINDOW, &CPListFrame::OnClose, this);
518  Bind(wxEVT_BUTTON, &CPListFrame::OnDeleteButton, this, XRCID("cp_list_delete"));
519  Bind(wxEVT_BUTTON, &CPListFrame::OnSelectButton, this, XRCID("cp_list_select"));
520 }
521 
523 {
524  DEBUG_TRACE("dtor");
525  StoreFramePosition(this, wxT("CPListFrame"));
526  DEBUG_TRACE("dtor end");
527 }
528 
529 void CPListFrame::OnClose(wxCloseEvent& event)
530 {
531  DEBUG_DEBUG("OnClose");
533  DEBUG_DEBUG("closing");
534  Destroy();
535 }
536 
537 void CPListFrame::OnDeleteButton(wxCommandEvent & e)
538 {
540 }
541 
542 void CPListFrame::OnSelectButton(wxCommandEvent & e)
543 {
544  double threshold;
545  const bool isShowingCorrelation = MainFrame::Get()->IsShowingCorrelation();
546  if(isShowingCorrelation)
547  {
548  threshold = HUGIN_FT_CORR_THRESHOLD;
549  wxConfig::Get()->Read(wxT("/Finetune/CorrThreshold"), &threshold, HUGIN_FT_CORR_THRESHOLD);;
550  }
551  else
552  {
553  // calculate the mean error and the standard deviation
555  double min, max, mean, var;
557 
558  // select points whos distance is greater than the mean
559  // hmm, maybe some theory would be nice.. this is just a
560  // guess.
561  threshold = mean + sqrt(var);
562  };
563  wxString t;
564  do
565  {
566  t = wxGetTextFromUser(isShowingCorrelation ?
567  _("Enter minimum control point correlation.\nAll points with lower correlation will be selected.") :
568  _("Enter minimum control point error.\nAll points with a higher error will be selected"),
569  _("Select Control Points"),
570  hugin_utils::doubleTowxString(threshold, 2));
571  if (t == wxEmptyString) {
572  // do not select anything
573  return;
574  }
575  }
576  while (!hugin_utils::str2double(t, threshold));
577 
578  m_list->SelectDistanceThreshold(isShowingCorrelation ? -threshold : threshold);
579 };
const bool GetOptimizeOnlyActiveImages() const
Definition: MainFrame.cpp:1639
void OnColumnWidthChange(wxListEvent &e)
column width changed
void DeleteSelected()
Delete the selected points.
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxLC_REPORT|wxLC_VIRTUAL, const wxValidator &validator=wxDefaultValidator, const wxString &name=wxListCtrlNameStr)
Definition: CPListFrame.cpp:82
void UpdateInternalCPList()
helper class for virtual listview control
Definition: CPListFrame.h:33
bool str2double(const wxString &s, double &d)
Definition: wxPlatform.cpp:37
bool removeObserver(PanoramaObserver *observer)
remove a panorama observer.
Definition: Panorama.cpp:1551
virtual int OnGetItemImage(long item) const
show no images
std::vector< CPListItem > m_internalCPList
Definition: CPListFrame.h:87
#define DEBUG_TRACE(msg)
Definition: utils.h:67
CPListFrame(wxWindow *parent, HuginBase::Panorama &pano)
ctor.
int m_sortCol
Definition: CPListFrame.h:84
void OnDeleteButton(wxCommandEvent &e)
static void calcCtrlPntsErrorStats(const PanoramaData &pano, double &min, double &max, double &mean, double &var, const int &imgNr=-1, const bool onlyActive=false, const bool ignoreLineCp=false)
void Init(HuginBase::Panorama *pano)
void OnClose(wxCloseEvent &event)
void OnSelectButton(wxCommandEvent &e)
void SortInternalList(bool isAscending)
wxString doubleTowxString(double d, int digits)
Definition: wxPlatform.cpp:31
virtual wxString OnGetItemText(long item, long column) const
create labels for virtual list control
bool set_contains(const _Container &c, const typename _Container::key_type &key)
Definition: stl_utils.h:74
#define CompareStruct(VAR, TYPESUFFIX, OP)
#define DEBUG_ASSERT(cond)
Definition: utils.h:80
include file for the hugin project
const CPVector & getCtrlPoints() const
get all control point of this Panorama
Definition: Panorama.h:319
remove several control points
Definition: PanoCommand.h:307
represents a control point
Definition: ControlPoint.h:38
std::set< unsigned int > UIntSet
Definition: PanoramaData.h:51
void OnChar(wxKeyEvent &e)
handle keystrokes
void ShowCtrlPoint(unsigned int cpNr)
Definition: MainFrame.cpp:2005
void calcCtrlPointErrors(PanoramaData &pano)
Update the Ctrl Point errors without optimizing.
Model for a panorama.
Definition: Panorama.h:152
void SelectAll()
select all items
List all control points of this project.
Definition: CPListFrame.h:43
static MainFrame * Get()
hack.. kind of a pseudo singleton...
Definition: MainFrame.cpp:2153
void StoreFramePosition(wxTopLevelWindow *frame, const wxString &basename)
Store window size and position in configfile/registry.
Definition: LensCalApp.cpp:210
HuginBase::Panorama * m_pano
Definition: CPListFrame.h:82
void OnCPListFrameClosed()
Definition: MainFrame.cpp:1608
const ControlPoint & getCtrlPoint(std::size_t nr) const
get a control point, counting starts with 0
Definition: Panorama.h:312
IMPLEMENT_DYNAMIC_CLASS(wxTreeListHeaderWindow, wxWindow)
evaluate x, points are on a vertical line
Definition: ControlPoint.h:47
void RestoreFramePosition(wxTopLevelWindow *frame, const wxString &basename)
Restore window size and position from configfile/registry.
Definition: LensCalApp.cpp:156
xrc handler for CPImagesComboBox
Definition: CPListFrame.h:94
static GlobalCmdHist & getInstance()
void OnCPListSelectionChanged(wxListEvent &e)
selection event handler
size_t globalIndex
Definition: CPListFrame.h:35
virtual wxObject * DoCreateResource()
Create CPImagesComboBox from resource.
virtual ~CPListFrame()
dtor.
void addCommand(PanoCommand *command, bool execute=true)
Adds a command to the history.
bool m_sortAscend
Definition: CPListFrame.h:85
void SelectDistanceThreshold(double threshold)
select all cp with the given error bigger than the threshold
UIntSet getActiveImages() const
get active images
Definition: Panorama.cpp:1585
void OnCPListHeaderClick(wxListEvent &e)
sort criterium changed
Utility calls into PanoTools using CPP interface.
std::vector< deghosting::BImagePtr > threshold(const std::vector< deghosting::FImagePtr > &inputImages, const double threshold, const uint16_t flags)
Threshold function used for creating alpha masks for images.
Definition: threshold.h:41
void addObserver(PanoramaObserver *o)
add a panorama observer.
Definition: Panorama.cpp:1546
CPListCtrl * m_list
Definition: CPListFrame.h:123
include file for the hugin project
#define HUGIN_FT_CORR_THRESHOLD
size_t localNumber
Definition: CPListFrame.h:36
static T max(T x, T y)
Definition: svm.cpp:65
#define DEBUG_DEBUG(msg)
Definition: utils.h:68
std::string makePairId(unsigned int id1, unsigned int id2)
Definition: CPListFrame.cpp:45
std::vector< ControlPoint > CPVector
Definition: ControlPoint.h:99
platform/compiler specific stuff.
std::map< std::string, int > m_localIds
Definition: CPListFrame.h:88
bool m_onlyActiveImages
Definition: CPListFrame.h:86
virtual bool CanHandle(wxXmlNode *node)
Internal use to identify right xml handler.
bool IsShowingCorrelation() const
Definition: MainFrame.cpp:2169
virtual void panoramaChanged(HuginBase::Panorama &pano)
Notification about a Panorama change.
evaluate y, points are on a horizontal line
Definition: ControlPoint.h:48
static T min(T x, T y)
Definition: svm.cpp:62
HuginBase::Panorama & m_pano
Definition: CPListFrame.h:124