CPImageCtrl.cpp

Go to the documentation of this file.
00001 // -*- c-basic-offset: 4 -*-
00002 
00027 // standard wx include
00028 #include <config.h>
00029 #include "panoinc_WX.h"
00030 
00031 // standard hugin include
00032 #include "panoinc.h"
00033 #include "base_wx/platform.h"
00034 
00035 #include <vigra/inspectimage.hxx>
00036 #include <vigra/transformimage.hxx>
00037 #include <vigra/basicimageview.hxx>
00038 #ifdef HAVE_CXX11
00039 #include <functional>  // std::bind 
00040 #else
00041 #include <boost/bind.hpp>
00042 #endif
00043 
00044 #include "hugin/config_defaults.h"
00045 #include "hugin/CPImageCtrl.h"
00046 #include "base_wx/wxImageCache.h"
00047 #include "hugin/CPEditorPanel.h"
00048 #include "hugin/MainFrame.h"
00049 #include "hugin/huginApp.h"
00050 #include "base_wx/wxcms.h"
00051 
00052 #include "vigra_ext/ImageTransforms.h"
00053 
00054 #if !wxCHECK_VERSION(3,0,0)
00055 #define wxPENSTYLE_SOLID wxSOLID
00056 #define wxPENSTYLE_DOT wxDOT
00057 #define wxBRUSHSTYLE_TRANSPARENT wxTRANSPARENT
00058 #define wxBRUSHSTYLE_SOLID wxSOLID
00059 #endif
00060 
00061 // definition of the control point event
00062 
00063 IMPLEMENT_DYNAMIC_CLASS( CPEvent, wxEvent )
00064 #if defined _WIN32 && defined Hugin_shared
00065 DEFINE_LOCAL_EVENT_TYPE( EVT_CPEVENT )
00066 #else
00067 DEFINE_EVENT_TYPE( EVT_CPEVENT )
00068 #endif
00069 
00070 CPEvent::CPEvent( )
00071 {
00072     SetEventType( EVT_CPEVENT );
00073     SetEventObject( (wxWindow *) NULL );
00074     mode = NONE;
00075 }
00076 
00077 CPEvent::CPEvent(wxWindow* win, hugin_utils::FDiff2D & p)
00078 {
00079     SetEventType( EVT_CPEVENT );
00080     SetEventObject( win );
00081     mode = NEW_POINT_CHANGED;
00082     point = p;
00083 }
00084 
00085 CPEvent::CPEvent(wxWindow *win, unsigned int cpNr)
00086 {
00087     SetEventType( EVT_CPEVENT );
00088     SetEventObject( win );
00089     mode = POINT_SELECTED;
00090     pointNr = cpNr;
00091 }
00092 
00093 CPEvent::CPEvent(wxWindow* win, unsigned int cpNr, const hugin_utils::FDiff2D & p)
00094 {
00095     SetEventType( EVT_CPEVENT );
00096     SetEventObject( win );
00097     mode = POINT_CHANGED;
00098     pointNr = cpNr;
00099     point = p;
00100 }
00101 
00102 CPEvent::CPEvent(wxWindow* win, const hugin_utils::FDiff2D & p1, const hugin_utils::FDiff2D & p2)
00103 {
00104     SetEventType(EVT_CPEVENT);
00105     SetEventObject(win);
00106     mode=DELETE_REGION_SELECTED;
00107     region=wxRect(hugin_utils::roundi(std::min(p1.x,p2.x)), hugin_utils::roundi(std::min(p1.y,p2.y)),
00108         abs(hugin_utils::roundi(p2.x-p1.x)),abs(hugin_utils::roundi(p2.y-p1.y)));
00109 };
00110 
00111 CPEvent::CPEvent(wxWindow* win, CPEventMode evt_mode, const hugin_utils::FDiff2D & p)
00112 {
00113     SetEventType(EVT_CPEVENT);
00114     SetEventObject(win);
00115     mode = evt_mode;
00116     point = p;
00117 }
00118 
00119 CPEvent::CPEvent(wxWindow* win, CPEventMode evt_mode, const HuginBase::ControlPoint cp)
00120 {
00121     SetEventType(EVT_CPEVENT);
00122     SetEventObject(win);
00123     mode=evt_mode;
00124     m_cp=cp;
00125 };
00126 
00127 CPEvent::CPEvent(wxWindow* win, CPEventMode evt_mode, size_t cpNr, const HuginBase::ControlPoint cp)
00128 {
00129     SetEventType(EVT_CPEVENT);
00130     SetEventObject(win);
00131     mode=evt_mode;
00132     pointNr=cpNr;
00133     m_cp=cp;
00134 };
00135 
00136 wxEvent * CPEvent::Clone() const
00137 {
00138     return new CPEvent(*this);
00139 }
00140 
00141 DisplayedControlPoint::DisplayedControlPoint(const HuginBase::ControlPoint& cp, CPImageCtrl* control, bool mirrored)
00142 {
00143     m_cp=cp;
00144     m_control=control;
00145     m_mirrored=mirrored;
00146     m_line=(m_cp.mode!=HuginBase::ControlPoint::X_Y) && (m_cp.image1Nr==m_cp.image2Nr);
00147 };
00148 
00149 void DisplayedControlPoint::SetColour(wxColour pointColour, wxColour textColour)
00150 {
00151     m_pointColour=pointColour;
00152     m_textColour=textColour;
00153 };
00154 
00155 void DisplayedControlPoint::SetLabel(wxString newLabel)
00156 {
00157     m_label=newLabel;
00158 };
00159 
00160 void DisplayedControlPoint::SetControl(CPImageCtrl* control)
00161 {
00162     m_control=control;
00163 };
00164 
00165 void DrawCross(wxDC& dc, wxPoint p, int l)
00166 {
00167     dc.DrawLine(p + wxPoint(-l, 0),
00168                 p + wxPoint(-1, 0));
00169     dc.DrawLine(p + wxPoint(2, 0),
00170                 p + wxPoint(l+1, 0));
00171     dc.DrawLine(p + wxPoint(0, -l),
00172                 p + wxPoint(0, -1));
00173     dc.DrawLine(p + wxPoint(0, 2),
00174                 p + wxPoint(0, l+1));
00175 };
00176 
00177 void DisplayedControlPoint::Draw(wxDC& dc, bool selected, bool newPoint)
00178 {
00179     if(m_control==NULL)
00180     {
00181         return;
00182     };
00183     // select color
00184     wxColour bgColor = m_pointColour;
00185     wxColour textColor = m_textColour;
00186     bool drawMag = false;
00187     if (selected)
00188     {
00189         bgColor = wxTheColourDatabase->Find(wxT("RED"));
00190         textColor = wxTheColourDatabase->Find(wxT("WHITE"));
00191         drawMag = !m_control->GetMouseInWindow() || m_control->GetForceMagnifier();
00192     }
00193     if (newPoint)
00194     {
00195         bgColor = wxTheColourDatabase->Find(wxT("YELLOW"));
00196         textColor = wxTheColourDatabase->Find(wxT("BLACK"));
00197         drawMag = true;
00198     }
00199 
00200     dc.SetPen(wxPen(wxT("WHITE"), 1, wxPENSTYLE_SOLID));
00201     dc.SetBrush(wxBrush(wxT("BLACK"),wxBRUSHSTYLE_TRANSPARENT));
00202 
00203     hugin_utils::FDiff2D pointInput = m_mirrored ? hugin_utils::FDiff2D(m_cp.x2, m_cp.y2) : hugin_utils::FDiff2D(m_cp.x1, m_cp.y1);
00204     hugin_utils::FDiff2D point = m_control->applyRot(pointInput);
00205     wxPoint p = m_control->roundP(m_control->scale(point));
00206     hugin_utils::FDiff2D pointInput2;
00207     wxPoint p2;
00208     if(m_line)
00209     {
00210         pointInput2=m_mirrored ? hugin_utils::FDiff2D(m_cp.x1, m_cp.y1) : hugin_utils::FDiff2D(m_cp.x2, m_cp.y2);
00211         hugin_utils::FDiff2D point2 = m_control->applyRot(pointInput2);
00212         p2 = m_control->roundP(m_control->scale(point2));
00213     };
00214     int l = 6;
00215     // draw cursor line, choose white or black
00216     vigra::Rect2D box;
00217     if(m_line)
00218     {
00219         box.setUpperLeft(vigra::Point2D(hugin_utils::roundi(std::min(m_cp.x1, m_cp.x2)) - l, hugin_utils::roundi(std::min(m_cp.y1, m_cp.y2)) - l));
00220         box.setSize(hugin_utils::roundi(abs(m_cp.x1 - m_cp.x2) + 2.0*l), hugin_utils::roundi(abs(m_cp.y1 - m_cp.y2) + 2.0*l));
00221     }
00222     else
00223     {
00224         box.setUpperLeft(vigra::Point2D(hugin_utils::roundi(pointInput.x - l), hugin_utils::roundi(pointInput.y - l)));
00225         box.setSize(2*l, 2*l);
00226     };                
00227     // only use part inside.
00228     box &= vigra::Rect2D(m_control->GetImg()->size());
00229     if(box.width()<=0 || box.height()<=0)
00230     {
00231         return;
00232     };
00233     // calculate mean "luminance value"
00234     vigra::FindAverage<vigra::UInt8> average;   // init functor
00235     vigra::RGBToGrayAccessor<vigra::RGBValue<vigra::UInt8> > lumac;
00236     vigra::inspectImage(m_control->GetImg()->upperLeft()+ box.upperLeft(),
00237                         m_control->GetImg()->upperLeft()+ box.lowerRight(),
00238                         lumac, average);
00239     if (average() < 150)
00240     {
00241         dc.SetPen(wxPen(wxT("WHITE"), 1, wxPENSTYLE_SOLID));
00242     }
00243     else
00244     {
00245         dc.SetPen(wxPen(wxT("BLACK"), 1, wxPENSTYLE_SOLID));
00246     }
00247 
00248     if(m_line)
00249     {
00250         DrawLine(dc);
00251         DrawCross(dc, p, l);
00252         DrawCross(dc, p2, l);
00253     }
00254     else
00255     {
00256         if (m_cp.mode != HuginBase::ControlPoint::X_Y)
00257         {
00258             DrawLineSegment(dc);
00259         };
00260         DrawCross(dc, p, l);
00261     };
00262     // calculate distance to the image boundaries,
00263     // decide where to put the label and magnifier
00264     m_labelPos=DrawTextMag(dc, p, pointInput, drawMag, textColor, bgColor);
00265     if(m_line)
00266     {
00267         m_labelPos2=DrawTextMag(dc, p2, pointInput2, drawMag, textColor, bgColor);
00268     };
00269 }
00270 
00271 wxRect DisplayedControlPoint::DrawTextMag(wxDC& dc, wxPoint p, hugin_utils::FDiff2D pointInput, bool drawMag, wxColour textColour, wxColour bgColour)
00272 {
00273     wxRect labelPos;
00274     int l = 6;
00275     wxSize clientSize = m_control->GetClientSize();
00276     int vx0, vy0;
00277     m_control->GetViewStart(&vx0, &vy0);
00278     wxFont font(8, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_LIGHT);
00279     dc.SetFont(font);
00280     wxPoint pClient(p.x - vx0, p.y - vy0);
00281     // space in upper left, upper right, lower left, lower right
00282     int maxDistUR = std::min(clientSize.x - pClient.x, pClient.y);
00283     int maxDistLL = std::min(pClient.x, clientSize.y - pClient.y);
00284     int maxDistLR = std::min(clientSize.x - pClient.x, clientSize.y - pClient.y);
00285 
00286     // text and magnifier offset
00287     int toff = l-1;
00288     // default to lower right
00289     wxPoint tul = p + wxPoint(toff,toff);
00290 
00291     // calculate text position and extend
00292     // width of border around text label
00293     int tB = 2;
00294     wxCoord tw, th;
00295     dc.GetTextExtent(m_label, &tw, &th);
00296 
00297     if (drawMag)
00298     {
00299         wxBitmap magBitmap = m_control->generateMagBitmap(pointInput, p);
00300         // TODO: select position depending on visible part of canvas
00301         wxPoint ulMag = tul;
00302         // choose placement of the magnifier
00303         int w = toff  + magBitmap.GetWidth()+3;
00304         int db = 5;
00305         if ( maxDistLR > w + db  )
00306         {
00307             ulMag = p + wxPoint(toff,toff);
00308         }
00309         else
00310         {
00311             if (maxDistLL > w + db)
00312             {
00313                 ulMag = p + wxPoint(-w, toff);
00314             }
00315             else
00316             {
00317                 if (maxDistUR > w + db)
00318                 {
00319                     ulMag = p + wxPoint(toff, -w);
00320                 }
00321                 else
00322                 {
00323                     ulMag = p + wxPoint(-w, -w);
00324                 }
00325             }
00326         };
00327 
00328         dc.DrawBitmap(magBitmap, ulMag);
00329         dc.SetPen(wxPen(wxT("BLACK"), 1, wxPENSTYLE_SOLID));
00330         dc.SetBrush(wxBrush(wxT("WHITE"),wxBRUSHSTYLE_TRANSPARENT));
00331 
00332         // draw Bevel
00333         int bw = magBitmap.GetWidth();
00334         int bh = magBitmap.GetHeight();
00335         dc.DrawLine(ulMag.x-1, ulMag.y+bh, 
00336                     ulMag.x+bw+1, ulMag.y+bh);
00337         dc.DrawLine(ulMag.x+bw, ulMag.y+bh, 
00338                     ulMag.x+bw, ulMag.y-2);
00339         dc.SetPen(wxPen(wxT("WHITE"), 1, wxPENSTYLE_SOLID));
00340         dc.DrawLine(ulMag.x-1, ulMag.y-1, 
00341                     ulMag.x+bw+1, ulMag.y-1);
00342         dc.DrawLine(ulMag.x-1, ulMag.y+bh, 
00343                     ulMag.x-1, ulMag.y-2);
00344     }
00345     // choose placement of text.
00346     int db = 5;
00347     int w = toff+tw+2*tB;
00348     if ( maxDistLR > w + db && (!drawMag) )
00349     {
00350         tul = p + wxPoint(toff,toff);
00351     }
00352     else
00353     {
00354         if (maxDistLL > w + db)
00355         {
00356             tul = p + wxPoint(-w, toff);
00357         }
00358         else
00359         {
00360             if (maxDistUR > w + db)
00361             {
00362                 tul = p + wxPoint(toff, -(toff) - (th+2*tB));
00363             }
00364             else
00365             {
00366                 tul = p + wxPoint(-w, -(toff) - (th+2*tB));
00367             };
00368         };
00369     };
00370 
00371     // draw background
00372     dc.SetPen(wxPen(textColour, 1, wxPENSTYLE_SOLID));
00373     dc.SetBrush(wxBrush(bgColour, wxBRUSHSTYLE_SOLID));
00374     dc.DrawRectangle(tul.x, tul.y, tw+2*tB+1, th+2*tB);
00375     labelPos.SetLeft(tul.x);
00376     labelPos.SetTop(tul.y);
00377     labelPos.SetWidth(tw+2*tB+1);
00378     labelPos.SetHeight(th+2*tB);
00379     // draw number
00380     dc.SetTextForeground(textColour);
00381     dc.DrawText(m_label, tul + wxPoint(tB,tB));
00382     return labelPos;
00383 };
00384 
00385 void DisplayedControlPoint::DrawLine(wxDC& dc)
00386 {
00387     if(m_control==NULL)
00388     {
00389         return;
00390     };
00391     hugin_utils::FDiff2D p1, p2, dp;
00392     // transform end points to pano space
00393     if(!m_control->getFirstInvTrans()->transformImgCoord(p1, hugin_utils::FDiff2D(m_cp.x1, m_cp.y1)))
00394     {
00395         return;
00396     };
00397     if(!m_control->getFirstInvTrans()->transformImgCoord(p2, hugin_utils::FDiff2D(m_cp.x2, m_cp.y2)))
00398     {
00399         return;
00400     };
00401     dp=p2-p1;
00402     int len = hugin_utils::roundi(sqrt((m_cp.x1 - m_cp.x2)*(m_cp.x1 - m_cp.x2) + (m_cp.y1 - m_cp.y2)*(m_cp.y1 - m_cp.y2))) + 1;
00403     if(len<5)
00404     {
00405         //very short line, draw straight line
00406         dc.DrawLine(m_control->roundP(m_control->scale(m_control->applyRot(hugin_utils::FDiff2D(m_cp.x1, m_cp.y1)))),
00407             m_control->roundP(m_control->scale(m_control->applyRot(hugin_utils::FDiff2D(m_cp.x2, m_cp.y2)))));
00408     }
00409     else
00410     {
00411         //longer line, draw correct line, taking output projection into account
00412         wxPoint* points=new wxPoint[len+1];
00413         for(size_t i=0; i<len+1; i++)
00414         {
00415             hugin_utils::FDiff2D p = p1 + dp*((double)i / len);
00416             // transform line coordinates back to image space
00417             if(!m_control->getFirstTrans()->transformImgCoord(p2, p))
00418             {
00419                 delete []points;
00420                 //fall through, draw direct line, not exact, but better than no line
00421                 dc.DrawLine(m_control->roundP(m_control->scale(m_control->applyRot(hugin_utils::FDiff2D(m_cp.x1, m_cp.y1)))),
00422                     m_control->roundP(m_control->scale(m_control->applyRot(hugin_utils::FDiff2D(m_cp.x2, m_cp.y2)))));
00423                 return;
00424             };
00425             points[i]=m_control->roundP(m_control->scale(m_control->applyRot(p2)));
00426         };
00427         dc.DrawLines(len+1, points);
00428         delete []points;
00429     };
00430 };
00431 
00432 void DisplayedControlPoint::DrawLineSegment(wxDC& dc)
00433 {
00434     if(m_control==NULL)
00435     {
00436         return;
00437     };
00438     // calculate line equation
00439     hugin_utils::FDiff2D p1_image = m_mirrored ? hugin_utils::FDiff2D(m_cp.x2, m_cp.y2) : hugin_utils::FDiff2D(m_cp.x1, m_cp.y1);
00440     hugin_utils::FDiff2D p2_image = m_mirrored ? hugin_utils::FDiff2D(m_cp.x1, m_cp.y1) : hugin_utils::FDiff2D(m_cp.x2, m_cp.y2);
00441     hugin_utils::FDiff2D p1, p2, dp;
00442     if(!m_control->getFirstInvTrans()->transformImgCoord(p1, p1_image))
00443     {
00444         return;
00445     };
00446     if(!m_control->getSecondInvTrans()->transformImgCoord(p2, p2_image))
00447     {
00448         return;
00449     };
00450     dp=p2-p1;
00451     // now find the parameter to draw an appropriate long line segment
00452     double f=1.0;
00453     int image_width=m_control->GetRealImageSize().GetWidth();
00454     int image_height=m_control->GetRealImageSize().GetHeight();
00455     int image_dimension=std::min(image_width, image_height);
00456     int line_length=-1;
00457     while(f>1e-4)
00458     {
00459         p2=p1+dp*f;
00460         if(m_control->getFirstTrans()->transformImgCoord(p2_image, p2))
00461         {
00462             double length=sqrt(p1_image.squareDistance(p2_image));
00463             if(length > 0.05f * image_dimension && length < 0.75f * image_dimension)
00464             {
00465                 line_length=hugin_utils::roundi(length);
00466                 break;
00467             };
00468         };
00469         f*=0.9;
00470     };
00471     // found no suitable length, don't draw line
00472     if(line_length<1)
00473     {
00474         return;
00475     };
00476     // now calc line positions
00477     wxPoint* points=new wxPoint[2*line_length+1];
00478     for(size_t i=0; i<2*line_length+1; i++)
00479     {
00480         hugin_utils::FDiff2D p = p1 + dp*f*(((double)i - line_length) / (2.0f*line_length));
00481         if(!m_control->getFirstTrans()->transformImgCoord(p2, p))
00482         {
00483             delete []points;
00484             return;
00485         };
00486         points[i]=m_control->roundP(m_control->scale(m_control->applyRot(p2)));
00487     };
00488     //and finally draw line segment
00489     dc.SetClippingRegion(wxPoint(0,0), m_control->GetBitmapSize());
00490     dc.DrawLines(2*line_length+1, points);
00491     dc.DestroyClippingRegion();
00492     delete []points;
00493 };
00494 
00495 const bool DisplayedControlPoint::isOccupiedLabel(const wxPoint mousePos) const
00496 {
00497     if(m_line)
00498     {
00499         return m_labelPos.Contains(mousePos) || m_labelPos2.Contains(mousePos);
00500     }
00501     else
00502     {
00503         return m_labelPos.Contains(mousePos);
00504     };
00505 };
00506 
00507 const bool DisplayedControlPoint::isOccupiedPos(const hugin_utils::FDiff2D &p) const
00508 {
00509     double d=m_control->invScale(3.0);
00510     if(m_line)
00511     {
00512         return (p.x < m_cp.x1 + d && p.x > m_cp.x1 - d && p.y < m_cp.y1 + d && p.y > m_cp.y1 - d) ||
00513                (p.x < m_cp.x2 + d && p.x > m_cp.x2 - d && p.y < m_cp.y2 + d && p.y > m_cp.y2 - d);
00514     }
00515     else
00516     {
00517         if(m_mirrored)
00518         {
00519             return (p.x < m_cp.x2 + d && p.x > m_cp.x2 - d && p.y < m_cp.y2 + d && p.y > m_cp.y2 - d);
00520         }
00521         else
00522         {
00523             return (p.x < m_cp.x1 + d && p.x > m_cp.x1 - d && p.y < m_cp.y1 + d && p.y > m_cp.y1 - d);
00524         };
00525     };
00526 };
00527 
00528 void DisplayedControlPoint::CheckSelection(const wxPoint mousePos, const hugin_utils::FDiff2D& p)
00529 {
00530     if(!m_line)
00531     {
00532         return;
00533     };
00534     double d=m_control->invScale(3.0);
00535     m_mirrored=m_labelPos2.Contains(mousePos) || 
00536         (p.x < m_cp.x2 + d && p.x > m_cp.x2 - d && p.y < m_cp.y2 + d && p.y > m_cp.y2 - d);
00537 };
00538 
00539 void DisplayedControlPoint::UpdateControlPointX(double x)
00540 {
00541     if(m_mirrored)
00542     {
00543         m_cp.x2=x;
00544     }
00545     else
00546     {
00547         m_cp.x1=x;
00548     };
00549 };
00550 
00551 void DisplayedControlPoint::UpdateControlPointY(double y)
00552 {
00553     if(m_mirrored)
00554     {
00555         m_cp.y2=y;
00556     }
00557     else
00558     {
00559         m_cp.y1=y;
00560     };
00561 };
00562 
00563 void DisplayedControlPoint::UpdateControlPoint(hugin_utils::FDiff2D newPoint)
00564 {
00565     if(m_mirrored)
00566     {
00567         m_cp.x2=newPoint.x;
00568         m_cp.y2=newPoint.y;
00569     }
00570     else
00571     {
00572         m_cp.x1=newPoint.x;
00573         m_cp.y1=newPoint.y;
00574     };
00575 };
00576 
00577 void DisplayedControlPoint::ShiftControlPoint(hugin_utils::FDiff2D shift)
00578 {
00579     if(m_mirrored)
00580     {
00581         m_cp.x2+=shift.x;
00582         m_cp.y2+=shift.y;
00583     }
00584     else
00585     {
00586         m_cp.x1+=shift.x;
00587         m_cp.y1+=shift.y;
00588     };
00589 };
00590 
00591 void DisplayedControlPoint::StartLineControlPoint(hugin_utils::FDiff2D newPoint)
00592 {
00593     //start a new line control point
00594     m_line=true;
00595     m_mirrored=true;
00596     m_label=_("new");
00597     m_cp.image1Nr=UINT_MAX;
00598     m_cp.x1=newPoint.x;
00599     m_cp.y1=newPoint.y;
00600     m_cp.image2Nr=m_cp.image1Nr;
00601     m_cp.x2=m_cp.x1;
00602     m_cp.y2=m_cp.y1;
00603     m_cp.mode = HuginBase::ControlPoint::X;
00604 };
00605 
00606 hugin_utils::FDiff2D DisplayedControlPoint::GetPos() const
00607 {
00608     return m_mirrored ? hugin_utils::FDiff2D(m_cp.x2, m_cp.y2) : hugin_utils::FDiff2D(m_cp.x1, m_cp.y1);
00609 };
00610 
00611 bool DisplayedControlPoint::operator==(const DisplayedControlPoint other)
00612 {
00613     return m_cp==other.GetControlPoint() && m_mirrored == other.IsMirrored() && m_label == other.GetLabel();
00614 };
00615 
00616 // our image control
00617 BEGIN_EVENT_TABLE(CPImageCtrl, wxScrolledWindow)
00618     EVT_SIZE(CPImageCtrl::OnSize)
00619     EVT_CHAR(CPImageCtrl::OnKey)
00620 //    EVT_KEY_UP(CPImageCtrl::OnKeyUp)
00621     EVT_KEY_DOWN(CPImageCtrl::OnKeyDown)
00622     EVT_LEAVE_WINDOW(CPImageCtrl::OnMouseLeave)
00623     EVT_ENTER_WINDOW(CPImageCtrl::OnMouseEnter)
00624     EVT_MOTION(CPImageCtrl::mouseMoveEvent)
00625     EVT_LEFT_DOWN(CPImageCtrl::mousePressLMBEvent)
00626     EVT_LEFT_UP(CPImageCtrl::mouseReleaseLMBEvent)
00627     EVT_RIGHT_DOWN(CPImageCtrl::mousePressRMBEvent)
00628     EVT_RIGHT_UP(CPImageCtrl::mouseReleaseRMBEvent)
00629     EVT_MIDDLE_DOWN(CPImageCtrl::mousePressMMBEvent)
00630     EVT_MIDDLE_UP(CPImageCtrl::mouseReleaseMMBEvent)
00631     EVT_TIMER(-1, CPImageCtrl::OnTimer)
00632 END_EVENT_TABLE()
00633 
00634 bool CPImageCtrl::Create(wxWindow * parent, wxWindowID id,
00635                          const wxPoint& pos,
00636                          const wxSize& size,
00637                          long style,
00638                          const wxString& name)
00639 {
00640     wxScrolledWindow::Create(parent, id, pos, size, style, name);
00641     selectedPointNr = 0;
00642     editState = NO_IMAGE;
00643     scaleFactor = 1;
00644     fitToWindow = false;
00645     m_showSearchArea = false;
00646     m_searchRectWidth = 0;
00647     m_showTemplateArea = false;
00648     m_templateRectWidth = 0;
00649     m_editPanel = 0;
00650     m_imgRotation = ROT0;
00651     m_sameImage = false;
00652 
00653     wxString filename;
00654 
00655 #if defined(__WXMSW__) 
00656     wxString cursorPath = huginApp::Get()->GetXRCPath() + wxT("/data/cursor_cp_pick.cur");
00657     m_CPSelectCursor = new wxCursor(cursorPath, wxBITMAP_TYPE_CUR);
00658 #else
00659     m_CPSelectCursor = new wxCursor(wxCURSOR_CROSS);
00660 #endif
00661     SetCursor(*m_CPSelectCursor);
00662 
00663     // TODO: define custom, light background colors.
00664     pointColors.push_back(wxTheColourDatabase->Find(wxT("BLUE")));
00665     textColours.push_back(wxTheColourDatabase->Find(wxT("WHITE")));
00666 
00667     pointColors.push_back(wxTheColourDatabase->Find(wxT("GREEN")));
00668     textColours.push_back(wxTheColourDatabase->Find(wxT("WHITE")));
00669 
00670     pointColors.push_back(wxTheColourDatabase->Find(wxT("CYAN")));
00671     textColours.push_back(wxTheColourDatabase->Find(wxT("BLACK")));
00672     pointColors.push_back(wxTheColourDatabase->Find(wxT("GOLD")));
00673     textColours.push_back(wxTheColourDatabase->Find(wxT("BLACK")));
00674 
00675     pointColors.push_back(wxTheColourDatabase->Find(wxT("NAVY")));
00676     textColours.push_back(wxTheColourDatabase->Find(wxT("WHITE")));
00677 
00678     pointColors.push_back(wxTheColourDatabase->Find(wxT("DARK TURQUOISE")));
00679     textColours.push_back(wxTheColourDatabase->Find(wxT("BLACK")));
00680 
00681     pointColors.push_back(wxTheColourDatabase->Find(wxT("SALMON")));
00682     textColours.push_back(wxTheColourDatabase->Find(wxT("BLACK")));
00683 
00684     pointColors.push_back(wxTheColourDatabase->Find(wxT("MAROON")));
00685     textColours.push_back(wxTheColourDatabase->Find(wxT("BLACK")));
00686 
00687     pointColors.push_back(wxTheColourDatabase->Find(wxT("KHAKI")));
00688     textColours.push_back(wxTheColourDatabase->Find(wxT("BLACK")));
00689 
00690     m_searchRectWidth = 120;
00691     m_mouseInWindow = false;
00692     m_forceMagnifier = false;
00693     m_timer.SetOwner(this);
00694 
00695     return true;
00696 }
00697 
00698 void CPImageCtrl::Init(CPEditorPanel * parent)
00699 {
00700     m_editPanel = parent;
00701     m_sameImage = false;
00702 }
00703 
00704 CPImageCtrl::~CPImageCtrl()
00705 {
00706     DEBUG_TRACE("dtor");
00707     this->SetCursor(wxNullCursor);
00708     delete m_CPSelectCursor;
00709     DEBUG_TRACE("dtor end");
00710 }
00711 
00712 void CPImageCtrl::OnDraw(wxDC & dc)
00713 {
00714     wxSize vSize = GetClientSize();
00715     // draw image (FIXME, redraw only visible regions.)
00716     if (editState != NO_IMAGE && m_img.get()) {
00717                 //clear the blank rectangle to the left of the image
00718         if (bitmap.GetWidth() < vSize.GetWidth()) {
00719             dc.SetPen(wxPen(GetBackgroundColour(), 1, wxPENSTYLE_SOLID));
00720             dc.SetBrush(wxBrush(GetBackgroundColour(), wxBRUSHSTYLE_SOLID));
00721             dc.DrawRectangle(bitmap.GetWidth(), 0,
00722                              vSize.GetWidth() - bitmap.GetWidth(),vSize.GetHeight());
00723         }
00724                 //clear the blank rectangle below the image
00725         if (bitmap.GetHeight() < vSize.GetHeight()) {
00726             dc.SetPen(wxPen(GetBackgroundColour(), 1, wxPENSTYLE_SOLID));
00727             dc.SetBrush(wxBrush(GetBackgroundColour(), wxBRUSHSTYLE_SOLID));
00728                         dc.DrawRectangle(0, bitmap.GetHeight(),
00729                              vSize.GetWidth(), vSize.GetHeight() - bitmap.GetHeight());
00730         }
00731         dc.DrawBitmap(bitmap,0,0);
00732         } else {
00733                 // clear the rectangle and exit
00734         dc.SetPen(wxPen(GetBackgroundColour(), 1, wxPENSTYLE_SOLID));
00735         dc.SetBrush(wxBrush(GetBackgroundColour(), wxBRUSHSTYLE_SOLID));
00736         dc.Clear();
00737                 return;
00738         }
00739 
00740     // draw known points.
00741     for(size_t i=0; i<m_points.size(); i++)
00742     {
00743         if (!(editState == KNOWN_POINT_SELECTED && i==selectedPointNr))
00744         {
00745             m_points[i].Draw(dc, false);
00746         };
00747     }
00748 
00749     switch(editState) {
00750     case NEW_POINT_SELECTED:
00751         // Boundary check
00752         if ((newPoint.x < 0) || (newPoint.y < 0)) {
00753             // Tried to create a point outside of the canvas.  Ignore it.
00754             break;
00755         } 
00756         {
00757             DisplayedControlPoint dsp(HuginBase::ControlPoint(0, newPoint.x, newPoint.y, 0, 0, 0), this, false);
00758             dsp.SetLabel(_("new"));
00759             dsp.Draw(dc, false, true);
00760         }
00761         if (m_showTemplateArea) {
00762             dc.SetLogicalFunction(wxINVERT);
00763             dc.SetPen(wxPen(wxT("RED"), 1, wxPENSTYLE_SOLID));
00764             dc.SetBrush(wxBrush(wxT("WHITE"), wxBRUSHSTYLE_TRANSPARENT));
00765             wxPoint upperLeft = applyRot(roundP(newPoint));
00766             upperLeft = scale(upperLeft);
00767 
00768             int width = scale(m_templateRectWidth);
00769 
00770             dc.DrawRectangle(upperLeft.x-width, upperLeft.y-width, 2*width, 2*width);
00771             dc.SetLogicalFunction(wxCOPY);
00772         }
00773 
00774         break;
00775     case NEW_LINE_CREATING:
00776         m_selectedPoint.Draw(dc, false, true);
00777         break;
00778     case KNOWN_POINT_SELECTED:
00779         m_points[selectedPointNr].Draw(dc, true);
00780         break;
00781     case NO_SELECTION:
00782     case NO_IMAGE:
00783         break;
00784     }
00785 
00786     if (m_showSearchArea && m_mousePos.x != -1){
00787         dc.SetLogicalFunction(wxINVERT);
00788         dc.SetPen(wxPen(wxT("WHITE"), 1, wxPENSTYLE_SOLID));
00789         dc.SetBrush(wxBrush(wxT("WHITE"), wxBRUSHSTYLE_TRANSPARENT));
00790 
00791         hugin_utils::FDiff2D upperLeft = applyRot(m_mousePos);
00792         upperLeft = scale(upperLeft);
00793         int width = scale(m_searchRectWidth);
00794         DEBUG_DEBUG("drawing rect " << upperLeft << " with width " << 2*width << " orig: " << m_searchRectWidth*2  << " scale factor: " << getScaleFactor());
00795 
00796         dc.DrawRectangle(hugin_utils::roundi(upperLeft.x - width), hugin_utils::roundi(upperLeft.y - width), 2 * width, 2 * width);
00797         dc.SetLogicalFunction(wxCOPY);
00798     }
00799 }
00800 
00801 class ScalingTransform
00802 {
00803 public:
00804     explicit ScalingTransform(double scale)
00805     : m_scale(scale) {};
00806 
00807     bool transformImgCoord(double & sx, double & sy, double x, double y)
00808     {
00809         sx = m_scale*x;
00810         sy = m_scale*y;
00811         return true;
00812     }
00813 
00814     double m_scale;
00815 };
00816 
00817 wxBitmap CPImageCtrl::generateMagBitmap(hugin_utils::FDiff2D point, wxPoint canvasPos) const
00818 {
00819     typedef vigra::RGBValue<vigra::UInt8> VT;
00820     DEBUG_TRACE("")
00821 
00822     // draw magnified image (TODO: warp!)
00823     double magScale = 3.0;
00824     wxConfigBase::Get()->Read(wxT("/CPEditorPanel/MagnifierScale"), &magScale);
00825     // width (and height) of magnifier region (output), should be odd
00826     int magWidth = wxConfigBase::Get()->Read(wxT("/CPEditorPanel/MagnifierWidth"),61l);
00827     int hw = magWidth/2;
00828     magWidth = hw*2+1;
00829 
00830     // setup simple scaling transformation function.
00831     ScalingTransform transform(1.0/magScale);
00832     ScalingTransform invTransform(magScale);
00833     wxImage img(magWidth, magWidth);
00834     vigra::BasicImageView<VT> magImg((VT*)img.GetData(), magWidth,magWidth);
00835     vigra::BImage maskImg(magWidth, magWidth);
00836     vigra_ext::PassThroughFunctor<vigra::UInt8> ptf;
00837 
00838 
00839     // middle pixel
00840     double mx, my;
00841     invTransform.transformImgCoord(mx, my, point.x, point.y);
00842 
00843     // apply the transform
00844     AppBase::DummyProgressDisplay progDisp;
00845     vigra_ext::transformImageIntern(vigra::srcImageRange(*(m_img->image8)),
00846                          vigra::destImageRange(magImg),
00847                          vigra::destImage(maskImg),
00848                          transform,
00849                          ptf,
00850                          vigra::Diff2D(hugin_utils::roundi(mx - hw),
00851                                        hugin_utils::roundi(my - hw)),
00852                          vigra_ext::interp_cubic(),
00853                          false,
00854                          &progDisp,
00855                          false);
00856 
00857     // TODO: contrast enhancement
00858     vigra::FindMinMax<vigra::UInt8> minmax;
00859     vigra::inspectImage(vigra::srcImageRange(magImg), minmax);
00860 
00861     // transform to range 0...255
00862     vigra::transformImage(vigra::srcImageRange(magImg), vigra::destImage(magImg),
00863                           vigra::linearRangeMapping(
00864                             VT(minmax.min), VT(minmax.max),               // src range
00865                             VT(0, 0, 0), VT(255, 255, 255)) // dest range
00866                           );
00867 //    vigra::transformImage(srcImageRange(magImg), destImage(magImg),
00868 //       vigra::BrightnessContrastFunctor<float>(brightness, contrast, minmax.min, minmax.max));
00869 
00870     // draw cursor
00871     for(int x=0; x < magWidth; x++) {
00872         VT p =magImg(x,hw+1);
00873         vigra::UInt8 v = 0.3/255*p.red() + 0.6/255*p.green() + 0.1/255*p.blue() < 0.5 ? 255 : 0;
00874         p[0] = v;
00875         p[1] = v;
00876         p[2] = v;
00877         magImg(x,hw+1) = p;
00878         p = magImg(hw+1, x);
00879         v = 0.3/255*p.red() + 0.6/255*p.green() + 0.1/255*p.blue() < 0.5 ? 255 : 0;
00880         p[0] = v;
00881         p[1] = v;
00882         p[2] = v;
00883         magImg(hw+1, x) = p;
00884     }
00885 
00886     // rotate image according to current display
00887     switch(m_imgRotation) {
00888         case ROT90:
00889             img = img.Rotate90(true);
00890             break;
00891         case ROT180:
00892             // this is slower than it needs to be...
00893             img = img.Rotate90(true);
00894             img = img.Rotate90(true);
00895             break;
00896         case ROT270:
00897             img = img.Rotate90(false);
00898             break;
00899         default:
00900             break;
00901     }
00902     return wxBitmap (img);
00903 }
00904 
00905 wxSize CPImageCtrl::DoGetBestSize() const
00906 {
00907     return wxSize(imageSize.GetWidth(),imageSize.GetHeight());
00908 }
00909 
00910 
00911 void CPImageCtrl::setImage(const std::string & file, ImageRotation imgRot)
00912 {
00913     DEBUG_TRACE("setting Image " << file);
00914     imageFilename = file;
00915     m_sameImage=false;
00916     wxString fn(imageFilename.c_str(),HUGIN_CONV_FILENAME);
00917     if (wxFileName::FileExists(fn)) {
00918         m_imgRotation = imgRot;
00919         m_img = ImageCache::getInstance().getImageIfAvailable(imageFilename);
00920         editState = NO_SELECTION;
00921         if (m_img.get()) {
00922             rescaleImage();
00923         } else {
00924             // load the image in the background.
00925             m_imgRequest = ImageCache::getInstance().requestAsyncImage(imageFilename);
00926             m_imgRequest->ready.push_back(
00927 #ifdef HAVE_CXX11
00928                 std::bind(&CPImageCtrl::OnImageLoaded, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
00929 #else
00930                 boost::bind(&CPImageCtrl::OnImageLoaded, this, _1, _2, _3)
00931 #endif
00932                 );
00933             // With m_img.get() 0, everything will act as normal except drawing.
00934         }
00935     } else {
00936         editState = NO_IMAGE;
00937         bitmap = wxBitmap();
00938         // delete the image (release shared_ptr)
00939         // create an empty image.
00940         m_img = ImageCache::EntryPtr(new ImageCache::Entry);
00941     }
00942 }
00943 
00944 void CPImageCtrl::setTransforms(HuginBase::PTools::Transform* firstTrans, HuginBase::PTools::Transform* firstInvTrans, HuginBase::PTools::Transform* secondInvTrans)
00945 {
00946     m_firstTrans=firstTrans;
00947     m_firstInvTrans=firstInvTrans;
00948     m_secondInvTrans=secondInvTrans;
00949 };
00950 
00951 void CPImageCtrl::setSameImage(bool sameImage)
00952 {
00953     m_sameImage=sameImage;
00954 };
00955 
00956 void CPImageCtrl::OnImageLoaded(ImageCache::EntryPtr entry, std::string filename, bool load_small)
00957 {
00958     // check we are still displaying this image
00959     if (imageFilename == filename)
00960     {
00961         m_img = entry;
00962         rescaleImage();
00963     }
00964 }
00965 
00966 void CPImageCtrl::rescaleImage()
00967 {
00968     if (editState == NO_IMAGE || !m_img.get()) {
00969         return;
00970     }
00971     wxImage img = imageCacheEntry2wxImage(m_img);
00972     if (img.GetWidth() == 0) {
00973         return;
00974     }
00975     imageSize = wxSize(img.GetWidth(), img.GetHeight());
00976     m_realSize = imageSize;
00977     if (fitToWindow) {
00978         scaleFactor = calcAutoScaleFactor(imageSize);
00979     }
00980     DEBUG_DEBUG("src image size "
00981                 << imageSize.GetHeight() << "x" << imageSize.GetWidth());
00982     if (getScaleFactor() == 1.0) {
00983         //the icc correction would work on the original cached image file
00984         //therefore we need to create a copy to work on it
00985         img = img.Copy();
00986     }
00987     else
00988     {
00989         // rescale image
00990         imageSize.SetWidth(scale(imageSize.GetWidth()));
00991         imageSize.SetHeight(scale(imageSize.GetHeight()));
00992         DEBUG_DEBUG("rescaling to " << imageSize.GetWidth() << "x"
00993             << imageSize.GetHeight());
00994         img = img.Scale(imageSize.GetWidth(), imageSize.GetHeight());
00995     };
00996     // need to rotate full image. warning. this can be very memory intensive
00997     if (m_imgRotation != ROT0)
00998     {
00999         switch (m_imgRotation)
01000         {
01001             case ROT90:
01002                 img = img.Rotate90(true);
01003                 break;
01004             case ROT180:
01005                 // this is slower than it needs to be...
01006                 img = img.Rotate90(true);
01007                 img = img.Rotate90(true);
01008                 break;
01009             case ROT270:
01010                 img = img.Rotate90(false);
01011                 break;
01012             default:
01013                 break;
01014         };
01015     };
01016     // do color correction only if input image has icc profile or if we found a monitor profile
01017     if (!m_img->iccProfile->empty() || huginApp::Get()->HasMonitorProfile())
01018     {
01019         HuginBase::Color::CorrectImage(img, *(m_img->iccProfile), huginApp::Get()->GetMonitorProfile());
01020     };
01021     bitmap = wxBitmap(img);
01022 
01023     if (m_imgRotation == ROT90 || m_imgRotation == ROT270) {
01024         SetVirtualSize(imageSize.GetHeight(), imageSize.GetWidth());
01025     } else {
01026         SetVirtualSize(imageSize.GetWidth(), imageSize.GetHeight());
01027     }
01028     SetScrollRate(1,1);
01029     Refresh(FALSE);
01030 }
01031 
01032 void CPImageCtrl::setCtrlPoint(const HuginBase::ControlPoint& cp, const bool mirrored)
01033 {
01034     DisplayedControlPoint dcp(cp, this, mirrored);
01035     dcp.SetColour(pointColors[m_points.size() % pointColors.size()], textColours[m_points.size() % textColours.size()]);
01036     dcp.SetLabel(wxString::Format(wxT("%lu"), (unsigned long int)m_points.size()));
01037     m_points.push_back(dcp);
01038 }
01039 
01040 void CPImageCtrl::clearCtrlPointList()
01041 {
01042     m_points.clear();
01043     if(editState == KNOWN_POINT_SELECTED)
01044     {
01045         editState = NO_SELECTION;
01046     };
01047     selectedPointNr = UINT_MAX;
01048 };
01049 
01050 void CPImageCtrl::clearNewPoint()
01051 {
01052     DEBUG_TRACE("clearNewPoint");
01053     if (editState != NO_IMAGE) {
01054         editState = NO_SELECTION;
01055     }
01056 }
01057 
01058 
01059 void CPImageCtrl::selectPoint(unsigned int nr)
01060 {
01061     DEBUG_TRACE("nr: " << nr);
01062     if (nr < m_points.size()) {
01063         selectedPointNr = nr;
01064         editState = KNOWN_POINT_SELECTED;
01065         showPosition(m_points[nr].GetPos());
01066         update();
01067     } else {
01068         DEBUG_DEBUG("trying to select invalid point nr: " << nr << ". Nr of points: " << m_points.size());
01069     }
01070 }
01071 
01072 void CPImageCtrl::deselect()
01073 {
01074     DEBUG_TRACE("deselecting points");
01075     if (editState == KNOWN_POINT_SELECTED) {
01076         editState = NO_SELECTION;
01077     }
01078     // update view
01079     update();
01080 }
01081 
01082 void CPImageCtrl::showPosition(hugin_utils::FDiff2D point, bool warpPointer)
01083 {
01084     DEBUG_DEBUG("x: " << point.x  << " y: " << point.y);
01085     // transform and scale the co-ordinate to the screen.
01086     point = applyRot(point);
01087     point = scale(point);
01088     int x = hugin_utils::roundi(point.x);
01089     int y = hugin_utils::roundi(point.y);
01090 
01091     wxSize sz = GetClientSize();
01092     int scrollx = x - sz.GetWidth()/2;
01093     int scrolly = y - sz.GetHeight()/2;
01094     Scroll(scrollx, scrolly);
01095     if (warpPointer) {
01096         int sx,sy;
01097         GetViewStart(&sx, &sy);
01098         DEBUG_DEBUG("relative coordinages: " << x-sx << "," << y-sy);
01099         WarpPointer(x-sx,y-sy);
01100     }
01101 }
01102 
01103 CPImageCtrl::EditorState CPImageCtrl::isOccupied(wxPoint mousePos, const hugin_utils::FDiff2D &p, unsigned int & pointNr) const
01104 {
01105     // check if mouse is hovering over a label
01106     if(m_points.size()>0)
01107     {
01108         for(int i=m_points.size()-1; i>=0; i--)
01109         {
01110             if(m_points[i].isOccupiedLabel(mousePos))
01111             {
01112                 pointNr = i;
01113                 return KNOWN_POINT_SELECTED;
01114             }
01115         };
01116         // check if mouse is over a known point
01117         for(std::vector<DisplayedControlPoint>::const_iterator it=m_points.begin(); it!=m_points.end(); ++it)
01118         {
01119             if(it->isOccupiedPos(p))
01120             {
01121                 pointNr = it - m_points.begin();
01122                 return KNOWN_POINT_SELECTED;
01123             }
01124         };
01125     };
01126 
01127     return NEW_POINT_SELECTED;
01128 }
01129 
01130 void CPImageCtrl::DrawSelectionRectangle(hugin_utils::FDiff2D pos1,hugin_utils::FDiff2D pos2)
01131 {
01132     wxClientDC dc(this);
01133     PrepareDC(dc);
01134     dc.SetLogicalFunction(wxINVERT);
01135     dc.SetPen(wxPen(*wxWHITE,1, wxPENSTYLE_DOT));
01136     dc.SetBrush(*wxTRANSPARENT_BRUSH);
01137     wxPoint p1=roundP(scale(applyRot(pos1)));
01138     wxPoint p2=roundP(scale(applyRot(pos2)));
01139     dc.DrawRectangle(p1.x,p1.y,p2.x-p1.x,p2.y-p1.y);
01140 };
01141 
01142 void CPImageCtrl::mouseMoveEvent(wxMouseEvent& mouse)
01143 {
01144     if (!m_img.get()) return; // ignore events if no image loaded.
01145     wxPoint unScrolledMousePos;
01146     CalcUnscrolledPosition(mouse.GetPosition().x, mouse.GetPosition().y,
01147                            &unScrolledMousePos.x, & unScrolledMousePos.y);
01148     hugin_utils::FDiff2D mpos(unScrolledMousePos.x, unScrolledMousePos.y);
01149     bool doUpdate = false;
01150     mpos = applyRotInv(invScale(mpos));
01151     wxPoint mpos_ = applyRotInv(invScale(unScrolledMousePos));
01152     // if mouseclick is out of image, ignore
01153     if ((mpos.x >= m_realSize.GetWidth() || mpos.y >= m_realSize.GetHeight()) && editState!=SELECT_DELETE_REGION)
01154     {
01155         return;
01156     }
01157 
01158 //    DEBUG_DEBUG(" pos:" << mpos.x << ", " << mpos.y);
01159     // only if the shift key is not pressed.
01160     if (mouse.LeftIsDown() && ! mouse.ShiftDown()) {
01161         switch(editState) {
01162         case NO_SELECTION:
01163             DEBUG_DEBUG("mouse down movement without selection, in NO_SELECTION state!");
01164             break;
01165         case KNOWN_POINT_SELECTED:
01166             if (mpos.x >= 0 && mpos.x <= m_realSize.GetWidth()){
01167                 m_points[selectedPointNr].UpdateControlPointX(mpos.x);
01168             } else if (mpos.x < 0) {
01169                 m_points[selectedPointNr].UpdateControlPointX(0);
01170             } else if (mpos.x > m_realSize.GetWidth()) {
01171                 m_points[selectedPointNr].UpdateControlPointX(m_realSize.GetWidth());
01172             }
01173 
01174             if (mpos.y >= 0 && mpos.y <= m_realSize.GetHeight()){
01175                 m_points[selectedPointNr].UpdateControlPointY(mpos.y);
01176             } else if (mpos.y < 0) {
01177                 m_points[selectedPointNr].UpdateControlPointY(0);
01178             } else if (mpos.y > m_realSize.GetHeight()) {
01179                 m_points[selectedPointNr].UpdateControlPointY(m_realSize.GetHeight());
01180             }
01181             // emit a notify event here.
01182             //
01183             //emit(pointMoved(selectedPointNr, points[selectedPointNr]));
01184             // do more intelligent updating here?
01185             doUpdate = true;
01186             break;
01187             // not possible.
01188         case NEW_POINT_SELECTED:
01189             DEBUG_DEBUG("WARNING: mouse move in new point state")
01190             newPoint = mpos;
01191             doUpdate = true;
01192             break;
01193         case NEW_LINE_CREATING:
01194             m_selectedPoint.UpdateControlPoint(mpos);
01195             doUpdate = true;
01196             break;
01197         case NO_IMAGE:
01198             break;
01199         }
01200     }
01201 
01202     if ((mouse.MiddleIsDown() || mouse.ShiftDown() || mouse.m_controlDown ) && editState!=SELECT_DELETE_REGION) {
01203         // scrolling with the mouse
01204         if (m_mouseScrollPos !=mouse.GetPosition()) {
01205             wxPoint delta_ = mouse.GetPosition() - m_mouseScrollPos;
01206             double speed = (double)GetVirtualSize().GetHeight() / GetClientSize().GetHeight();
01207 //          int speed = wxConfigBase::Get()->Read(wxT("/CPEditorPanel/scrollSpeed"),5);
01208             wxPoint delta;
01209             delta.x = hugin_utils::roundi(delta_.x * speed);
01210             delta.y =  hugin_utils::roundi(delta_.y * speed);
01211             // scrolling is done later
01212             if (mouse.ShiftDown()) {
01213                 // emit scroll event, so that other window can be scrolled
01214                 // as well.
01215                 CPEvent e(this, CPEvent::SCROLLED, hugin_utils::FDiff2D(delta.x, delta.y));
01216                 emit(e);
01217             } else {
01218                 // scroll only our window
01219                 ScrollDelta(delta);
01220             }
01221             m_mouseScrollPos = mouse.GetPosition();
01222         }
01223     }
01224 
01225     if(mouse.RightIsDown() && editState==SELECT_DELETE_REGION)
01226     {
01227         //update selection rectangle
01228         DrawSelectionRectangle(rectStartPos,m_mousePos);
01229         DrawSelectionRectangle(rectStartPos,mpos);
01230     }
01231 //    DEBUG_DEBUG("ImageDisplay: mouse move, state: " << editState);
01232 
01233     // draw a rectangle
01234     if (m_showSearchArea) {
01235         doUpdate = true;
01236     }
01237 
01238     unsigned int selPointNr;
01239     if (isOccupied(unScrolledMousePos, mpos, selPointNr) == KNOWN_POINT_SELECTED &&
01240         (! (editState == KNOWN_POINT_SELECTED && selectedPointNr == selPointNr) ) ) {
01241         SetCursor(wxCursor(wxCURSOR_ARROW));
01242     } else {
01243         SetCursor(*m_CPSelectCursor);
01244     }
01245 
01246     m_mousePos = mpos;
01247     // repaint
01248     if (doUpdate) {
01249         update();
01250     }
01251 }
01252 
01253 
01254 void CPImageCtrl::mousePressLMBEvent(wxMouseEvent& mouse)
01255 {
01256     DEBUG_DEBUG("LEFT MOUSE DOWN");
01257     if (!m_img.get()) return; // ignore events if no image loaded.
01258     //ignore left mouse button if selecting region with right mouse button
01259     if(editState==SELECT_DELETE_REGION) 
01260         return;
01261     wxPoint unScrolledMousePos;
01262     CalcUnscrolledPosition(mouse.GetPosition().x, mouse.GetPosition().y,
01263                            &unScrolledMousePos.x, & unScrolledMousePos.y);
01264     hugin_utils::FDiff2D mpos(unScrolledMousePos.x, unScrolledMousePos.y);
01265     mpos = applyRotInv(invScale(mpos));
01266     wxPoint mpos_ = applyRotInv(invScale(unScrolledMousePos));
01267     DEBUG_DEBUG("mousePressEvent, pos:" << mpos.x
01268                 << ", " << mpos.y);
01269     // if mouseclick is out of image, ignore
01270     if (mpos.x >= m_realSize.GetWidth() || mpos.y >= m_realSize.GetHeight()) {
01271         return;
01272     }
01273     unsigned int selPointNr = 0;
01274     EditorState clickState = isOccupied(unScrolledMousePos, mpos, selPointNr);
01275     if (mouse.LeftDown() && editState != NO_IMAGE
01276         && mpos.x < m_realSize.x && mpos.y < m_realSize.y)
01277     {
01278         // we can always select a new point
01279         if (clickState == KNOWN_POINT_SELECTED) {
01280             DEBUG_DEBUG("click on point: " << selPointNr);
01281             selectedPointNr = selPointNr;
01282             m_points[selectedPointNr].CheckSelection(unScrolledMousePos, mpos);
01283             m_selectedPoint = m_points[selectedPointNr];
01284             editState = clickState;
01285             CPEvent e( this, selectedPointNr);
01286             m_forceMagnifier = true;
01287             emit(e);
01288         } else if (clickState == NEW_POINT_SELECTED) {
01289             DEBUG_DEBUG("click on new space, select new point");
01290             if(m_sameImage && mouse.AltDown())
01291             {
01292                 editState = NEW_LINE_CREATING;
01293                 m_selectedPoint.StartLineControlPoint(mpos);
01294                 m_selectedPoint.SetControl(this);
01295             }
01296             else
01297             {
01298                 editState = NEW_POINT_SELECTED;
01299             };
01300             newPoint = mpos;
01301         } else {
01302             DEBUG_ERROR("invalid state " << clickState << " on mouse down");
01303         }
01304 //        DEBUG_DEBUG("ImageDisplay: mouse down, state change: " << oldstate
01305 //                    << " -> " << editState);
01306     }
01307     m_mousePos = mpos;
01308 }
01309 
01310 void CPImageCtrl::OnTimer(wxTimerEvent & e)
01311 {
01312     if (!m_img.get()) return; // ignore events if no image loaded.
01313     m_forceMagnifier = false;
01314     update();
01315 }
01316 
01317 void CPImageCtrl::mouseReleaseLMBEvent(wxMouseEvent& mouse)
01318 {
01319     DEBUG_DEBUG("LEFT MOUSE UP");
01320     if (!m_img.get()) return; // ignore events if no image loaded.
01321     //ignore left mouse button if selecting region with right mouse button
01322     if(editState==SELECT_DELETE_REGION) 
01323         return;
01324 
01325     m_timer.Start(2000, true);
01326 
01327     wxPoint mpos_;
01328     CalcUnscrolledPosition(mouse.GetPosition().x, mouse.GetPosition().y,
01329                            &mpos_.x, & mpos_.y);
01330     hugin_utils::FDiff2D mpos(mpos_.x, mpos_.y);
01331     mpos = applyRotInv(invScale(mpos));
01332     DEBUG_DEBUG("mouseReleaseEvent, pos:" << mpos.x
01333                 << ", " << mpos.y);
01334     // if mouseclick is out of image, ignore
01335     if (mpos.x >= m_realSize.GetWidth() || mpos.y >= m_realSize.GetHeight()) {
01336         return;
01337     }
01338 //    EditorState oldState = editState;
01339     if (mouse.LeftUp()) {
01340         switch(editState) {
01341         case NO_SELECTION:
01342             DEBUG_DEBUG("mouse release without selection");
01343             break;
01344         case KNOWN_POINT_SELECTED:
01345         {
01346             DEBUG_DEBUG("mouse release with known point " << selectedPointNr);
01347             if (! (m_selectedPoint == m_points[selectedPointNr]) ) {
01348                 CPEvent e( this, CPEvent::POINT_CHANGED, selectedPointNr, m_points[selectedPointNr].GetControlPoint());
01349                 emit(e);
01350             }
01351             break;
01352         }
01353         case NEW_POINT_SELECTED:
01354         {
01355             DEBUG_DEBUG("new Point changed (event fire): x:" << mpos.x << " y:" << mpos.y);
01356             // fire the wxWin event
01357             CPEvent e( this, newPoint);
01358             emit(e);
01359             break;
01360         }
01361         case NEW_LINE_CREATING:
01362         {
01363             //notify parent
01364             CPEvent e(this, CPEvent::NEW_LINE_ADDED, m_selectedPoint.GetControlPoint());
01365             emit(e);
01366             break;
01367         }
01368         case NO_IMAGE:
01369             break;
01370 
01371         }
01372 //        DEBUG_DEBUG("ImageDisplay: mouse release, state change: " << oldState
01373 //                    << " -> " << editState);
01374     }
01375 
01376 }
01377 
01378 
01379 void CPImageCtrl::mouseReleaseMMBEvent(wxMouseEvent& mouse)
01380 {
01381     DEBUG_DEBUG("middle mouse button released, leaving scroll mode")
01382 //    SetCursor(wxCursor(wxCURSOR_BULLSEYE));
01383 }
01384 
01385 
01386 void CPImageCtrl::mousePressMMBEvent(wxMouseEvent& mouse)
01387 {
01388     DEBUG_DEBUG("middle mouse button pressed, entering scroll mode")
01389     if (!m_img.get()) return; // ignore events if no image loaded.
01390     m_mouseScrollPos = mouse.GetPosition();
01391 //    SetCursor(wxCursor(wxCURSOR_HAND));
01392 }
01393 
01394 void CPImageCtrl::mousePressRMBEvent(wxMouseEvent& mouse)
01395 {
01396     //ignore event if no image loaded
01397     if(!m_img.get()) 
01398         return;
01399     wxPoint mpos_;
01400     CalcUnscrolledPosition(mouse.GetPosition().x, mouse.GetPosition().y, &mpos_.x, & mpos_.y);
01401     hugin_utils::FDiff2D mpos(mpos_.x, mpos_.y);
01402     mpos = applyRotInv(invScale(mpos));
01403     // if mouseclick is out of image, ignore
01404     if (mpos.x >= m_realSize.GetWidth() || mpos.y >= m_realSize.GetHeight())
01405     {
01406         return;
01407     }
01408     if(mouse.CmdDown() && (editState==NO_SELECTION || editState==KNOWN_POINT_SELECTED || editState==NEW_POINT_SELECTED))
01409     {
01410         rectStartPos=mpos;
01411         editState=SELECT_DELETE_REGION;
01412         DrawSelectionRectangle(mpos,mpos);
01413     };
01414 };
01415 
01416 void CPImageCtrl::mouseReleaseRMBEvent(wxMouseEvent& mouse)
01417 {
01418     if (!m_img.get()) return; // ignore events if no image loaded.
01419     wxPoint mpos_;
01420     CalcUnscrolledPosition(mouse.GetPosition().x, mouse.GetPosition().y,
01421                            &mpos_.x, & mpos_.y);
01422     hugin_utils::FDiff2D mpos(mpos_.x, mpos_.y);
01423     mpos = applyRotInv(invScale(mpos));
01424     DEBUG_DEBUG("mouseReleaseEvent, pos:" << mpos.x
01425                 << ", " << mpos.y);
01426 
01427     if (mouse.RightUp())
01428     {
01429         if(editState==SELECT_DELETE_REGION)
01430         {
01431             DrawSelectionRectangle(rectStartPos,mpos);
01432             editState=NO_SELECTION;
01433             CPEvent e(this,rectStartPos,mpos);
01434             emit(e);
01435         }
01436         else
01437         {
01438             // if mouseclick is out of image, ignore
01439             if (mpos.x >= m_realSize.GetWidth() || mpos.y >= m_realSize.GetHeight()) {
01440                 return;
01441             }
01442             // set right up event
01443             DEBUG_DEBUG("Emitting right click (rmb release)");
01444             CPEvent e(this, CPEvent::RIGHT_CLICK, mpos);
01445             emit(e);
01446         }
01447     }
01448 }
01449 
01450 void CPImageCtrl::update()
01451 {
01452     DEBUG_TRACE("edit state:" << editState);
01453     wxClientDC dc(this);
01454     PrepareDC(dc);
01455     OnDraw(dc);
01456 }
01457 
01458 bool CPImageCtrl::emit(CPEvent & ev)
01459 {
01460     if ( ProcessEvent( ev ) == FALSE ) {
01461         wxLogWarning( _("Could not process event!") );
01462         return false;
01463     } else {
01464         return true;
01465     }
01466 }
01467 
01468 void CPImageCtrl::setScale(double factor)
01469 {
01470     if (factor == 0) {
01471         fitToWindow = true;
01472         factor = calcAutoScaleFactor(imageSize);
01473     } else {
01474         fitToWindow = false;
01475     }
01476     DEBUG_DEBUG("new scale factor:" << factor);
01477     // update if factor changed
01478     if (factor != scaleFactor) {
01479         scaleFactor = factor;
01480         // keep existing scale focussed.
01481         rescaleImage();
01482     }
01483 }
01484 
01485 double CPImageCtrl::calcAutoScaleFactor(wxSize size)
01486 {
01487     // TODO correctly autoscale rotated iamges
01488     int w = size.GetWidth();
01489     int h = size.GetHeight();
01490     if (m_imgRotation ==  ROT90 || m_imgRotation == ROT270) {
01491         int t = w;
01492         w = h;
01493         h = t;
01494     }
01495 
01496 //    wxSize csize = GetClientSize();
01497     wxSize csize = GetSize();
01498     DEBUG_DEBUG("csize: " << csize.GetWidth() << "x" << csize.GetHeight() << "image: " << w << "x" << h);
01499     double s1 = (double)csize.GetWidth()/w;
01500     double s2 = (double)csize.GetHeight()/h;
01501     DEBUG_DEBUG("s1: " << s1 << "  s2:" << s2);
01502     return s1 < s2 ? s1 : s2;
01503 }
01504 
01505 double CPImageCtrl::getScaleFactor() const
01506 {
01507     return scaleFactor;
01508 }
01509 
01510 void CPImageCtrl::OnSize(wxSizeEvent &e)
01511 {
01512     DEBUG_TRACE("size: " << e.GetSize().GetWidth() << "x" << e.GetSize().GetHeight());
01513     // rescale bitmap if needed.
01514     if (imageFilename != "") {
01515         if (fitToWindow) {
01516             setScale(0);
01517         }
01518     }
01519 }
01520 
01521 void CPImageCtrl::OnKey(wxKeyEvent & e)
01522 {
01523     if (!m_img.get()) return; // ignore events if no image loaded.
01524     DEBUG_TRACE(" OnKey, key:" << e.m_keyCode);
01525     wxPoint delta(0,0);
01526     // check for cursor keys, if control is not pressed
01527     if ((!e.CmdDown()) && e.GetKeyCode() == WXK_LEFT ) delta.x = -1;
01528     if ((!e.CmdDown()) && e.GetKeyCode() == WXK_RIGHT ) delta.x = 1;
01529     if ((!e.CmdDown()) && e.GetKeyCode() == WXK_UP ) delta.y = -1;
01530     if ((!e.CmdDown()) && e.GetKeyCode() == WXK_DOWN ) delta.y = 1;
01531     if ( (delta.x != 0 || delta.y != 0 ) && (e.ShiftDown() || e.CmdDown())) {
01532         // move to the left
01533         double speed = (double) GetClientSize().GetWidth()/10;
01534         delta.x = (int) (delta.x * speed);
01535         delta.y = (int) (delta.y * speed);
01536         if (e.ShiftDown()) {
01537             // emit scroll event, so that other window can be scrolled
01538             // as well.
01539             CPEvent e(this, CPEvent::SCROLLED, hugin_utils::FDiff2D(delta.x, delta.y));
01540             emit(e);
01541         } else if (e.CmdDown()) {
01542             ScrollDelta(delta);
01543         }
01544     } else if (delta.x != 0 || delta.y != 0 ) {
01545 
01546         hugin_utils::FDiff2D shift(delta.x/3.0, delta.y/3.0);
01547         // rotate shift according to current display
01548         double t;
01549         switch (m_imgRotation) {
01550             case ROT90:
01551                 t = shift.x;
01552                 shift.x = shift.y;
01553                 shift.y = -t;
01554                 break;
01555             case ROT180:
01556                 shift.x = -shift.x;
01557                 shift.y = -shift.y;
01558                 break;
01559             case ROT270:
01560                 t = shift.x;
01561                 shift.x = -shift.y;
01562                 shift.y = t;
01563             default:
01564                 break;
01565         }
01566         // move control point by half a pixel, if a point is selected
01567         if (editState == KNOWN_POINT_SELECTED ) {
01568             DisplayedControlPoint updatedCp=m_points[selectedPointNr];
01569             updatedCp.ShiftControlPoint(shift);
01570             CPEvent e( this, CPEvent::POINT_CHANGED, selectedPointNr, updatedCp.GetControlPoint());
01571             emit(e);
01572             m_forceMagnifier = true;
01573             m_timer.Stop();
01574             m_timer.Start(2000, true);
01575         } else if (editState == NEW_POINT_SELECTED) {
01576             newPoint = newPoint + shift;
01577             // update display.
01578             update();
01579         }
01580 
01581     } else if (e.m_keyCode == 'a') {
01582         DEBUG_DEBUG("adding point with a key, faking right click");
01583         // faking right mouse button with "a"
01584         // set right up event
01585         CPEvent ev(this, CPEvent::RIGHT_CLICK, hugin_utils::FDiff2D(0,0));
01586         emit(ev);
01587     } else {
01588         // forward some keys...
01589         bool forward = false;
01590         switch (e.GetKeyCode())
01591         {
01592             case 'g':
01593             case '0':
01594             case '1':
01595             case '2':
01596             case 'f':
01597             case WXK_RIGHT:
01598             case WXK_LEFT:
01599             case WXK_UP:
01600             case WXK_DOWN:
01601             case WXK_DELETE:
01602                 forward = true;
01603                 break;
01604             default:
01605                 break;
01606         }
01607 
01608         if (forward) {
01609             // dangelo: I don't understand why some keys are forwarded and others are not..
01610             // Delete is forwared under wxGTK, and g not..
01611             // wxWidgets 2.6.1 using gtk 2 doesn't set the event object
01612             // properly.. do it here by hand
01613             e.SetEventObject(this);
01614             DEBUG_DEBUG("forwarding key " << e.GetKeyCode()
01615                         << " origin: id:" << e.GetId() << " obj: "
01616                         << e.GetEventObject());
01617             // forward all keys to our parent
01618             //GetParent()->GetEventHandler()->ProcessEvent(e);
01619             m_editPanel->GetEventHandler()->ProcessEvent(e);
01620         } else {
01621             e.Skip();
01622         }
01623     }
01624 }
01625 
01626 void CPImageCtrl::OnKeyDown(wxKeyEvent & e)
01627 {
01628     DEBUG_TRACE("key:" << e.m_keyCode);
01629     if (!m_img.get()) return; // ignore events if no image loaded.
01630     if (e.m_keyCode == WXK_SHIFT || e.m_keyCode == WXK_CONTROL) {
01631         DEBUG_DEBUG("shift or control down, reseting scoll position");
01632         m_mouseScrollPos = e.GetPosition();
01633     }
01634     e.Skip();
01635 }
01636 
01637 void CPImageCtrl::OnMouseLeave(wxMouseEvent & e)
01638 {
01639     DEBUG_TRACE("MOUSE LEAVE");
01640     m_mousePos = hugin_utils::FDiff2D(-1,-1);
01641     m_mouseInWindow = false;
01642     update();
01643 }
01644 
01645 void CPImageCtrl::OnMouseEnter(wxMouseEvent & e)
01646 {
01647     DEBUG_TRACE("MOUSE Enter, setting focus");
01648     m_mouseInWindow = true;
01649     SetFocus();
01650     update();
01651 }
01652 
01653 hugin_utils::FDiff2D CPImageCtrl::getNewPoint()
01654 {
01655     // only possible if a new point is actually selected
01656     // DEBUG_ASSERT(editState == NEW_POINT_SELECTED);
01657     return newPoint;
01658 }
01659 
01660 void CPImageCtrl::setNewPoint(const hugin_utils::FDiff2D & p)
01661 {
01662     DEBUG_DEBUG("setting new point " << p.x << "," << p.y);
01663     // should we need to check for some precondition?
01664     newPoint = p;
01665     editState = NEW_POINT_SELECTED;
01666 
01667     // show new point.
01668     showPosition(p);
01669 
01670     // we do not send an event, since CPEditorPanel
01671     // caused the change.. so it doesn't need to filter
01672     // out its own change messages.
01673 }
01674 
01675 void CPImageCtrl::showSearchArea(bool show)
01676 {
01677     m_showSearchArea = show;
01678     if (show)
01679     {
01680         int templSearchAreaPercent = wxConfigBase::Get()->Read(wxT("/Finetune/SearchAreaPercent"), HUGIN_FT_SEARCH_AREA_PERCENT);
01681         m_searchRectWidth = (m_realSize.GetWidth() * templSearchAreaPercent) / 200;
01682         DEBUG_DEBUG("Setting new search area: w in %:" << templSearchAreaPercent << " bitmap width: " << bitmap.GetWidth() << "  resulting size: " << m_searchRectWidth);
01683         m_mousePos = hugin_utils::FDiff2D(-1,-1);
01684     }
01685 }
01686 
01687 void CPImageCtrl::showTemplateArea(bool show)
01688 {
01689     m_showTemplateArea = show;
01690     if (show)
01691     {
01692         m_templateRectWidth = wxConfigBase::Get()->Read(wxT("/Finetune/TemplateSize"),HUGIN_FT_TEMPLATE_SIZE) / 2;
01693     }
01694 }
01695 
01696 wxPoint CPImageCtrl::MaxScrollDelta(wxPoint delta)
01697 {
01698     int x,y;
01699     GetViewStart( &x, &y );
01700 
01701     wxSize winSize = GetClientSize();
01702     wxSize imgSize;
01703     imgSize.x = bitmap.GetWidth();
01704     imgSize.y = bitmap.GetHeight();
01705     // check for top and left border
01706     if (x + delta.x < 0) {
01707         delta.x = -x;
01708     }
01709     if (y + delta.y < 0) {
01710         delta.y = -y;
01711     }
01712     // check for right and bottom border
01713     int right = x + delta.x + winSize.x ;
01714     if (right > imgSize.x) {
01715         delta.x = imgSize.x - right;
01716         if (delta.x < 0) {
01717             delta.x = 0;
01718         }
01719     }
01720     int bottom = y + delta.y + winSize.y ;
01721     if (bottom > imgSize.y) {
01722         delta.y = imgSize.y - bottom;
01723         if (delta.y < 0) {
01724             delta.y = 0;
01725         }
01726     }
01727     return delta;
01728 }
01729 
01730 void CPImageCtrl::ScrollDelta(const wxPoint & delta)
01731 {
01732     // TODO: adjust
01733     if (delta.x == 0 && delta.y == 0) {
01734         return;
01735     }
01736     int x,y;
01737     GetViewStart( &x, &y );
01738     x = x + delta.x;
01739     y = y + delta.y;
01740     if (x<0) x = 0;
01741     if (y<0) y = 0;
01742     Scroll( x, y);
01743 }
01744 
01745 const wxSize CPImageCtrl::GetBitmapSize() const
01746 {
01747 #if wxCHECK_VERSION(2,9,0)
01748     return bitmap.GetSize();
01749 #else
01750     return wxSize(bitmap.GetWidth(), bitmap.GetHeight());
01751 #endif
01752 };
01753 
01754 IMPLEMENT_DYNAMIC_CLASS(CPImageCtrl, wxScrolledWindow)
01755 
01756 CPImageCtrlXmlHandler::CPImageCtrlXmlHandler()
01757                 : wxXmlResourceHandler()
01758 {
01759     AddWindowStyles();
01760 }
01761 
01762 wxObject *CPImageCtrlXmlHandler::DoCreateResource()
01763 {
01764     XRC_MAKE_INSTANCE(cp, CPImageCtrl)
01765 
01766     cp->Create(m_parentAsWindow,
01767                    GetID(),
01768                    GetPosition(), GetSize(),
01769                    GetStyle(wxT("style")),
01770                    GetName());
01771 
01772     SetupWindow( cp);
01773 
01774     return cp;
01775 }
01776 
01777 bool CPImageCtrlXmlHandler::CanHandle(wxXmlNode *node)
01778 {
01779     return IsOfClass(node, wxT("CPImageCtrl"));
01780 }
01781 
01782 IMPLEMENT_DYNAMIC_CLASS(CPImageCtrlXmlHandler, wxXmlResourceHandler)

Generated on 10 Feb 2016 for Hugintrunk by  doxygen 1.4.7