wxRectTracker
|
00001 /* **************************************************************************** 00002 * RectTracker.cpp * 00003 * * 00004 * (c) 2004-2008 - Rémi Peyronnet <remi+rphoto@via.ecp.fr> * 00005 * * 00006 * RectTracker was originally designed for RPhoto, but can be used separately * 00007 * It is a control similar to the CRectTracker of MS MFC. * 00008 * * 00009 * Licence : wxWindows (based on L-GPL) * 00010 * * 00011 * ************************************************************************** */ 00012 00013 #include "RectTracker.h" 00014 00015 IMPLEMENT_CLASS(wxRectTracker, wxEvtHandler) 00016 00017 static const wxRect NullRect(0, 0, -1, -1); 00018 00019 wxRectTracker::wxRectTracker(wxWindow* wnd, wxFrame* frame) : 00020 wxEvtHandler(), m_wnd(wnd), m_frame(frame) 00021 { 00022 m_iHandlerWidth = 5; 00023 m_cursorMove = new wxCursor(wxCURSOR_SIZING); 00024 m_state = 0; 00025 m_Rect = m_maxRect = NullRect; 00026 m_iHandlerMask = RT_MASK_ALL; 00027 if (wnd) wnd->PushEventHandler(this); 00028 } 00029 00030 wxRectTracker::~wxRectTracker() 00031 { 00032 if (m_wnd) m_wnd->RemoveEventHandler(this); 00033 delete m_cursorMove; 00034 } 00035 00036 BEGIN_EVENT_TABLE(wxRectTracker, wxEvtHandler) 00037 EVT_PAINT(wxRectTracker::OnPaint) 00038 EVT_MOTION(wxRectTracker::OnMouseMotion) 00039 EVT_LEFT_DOWN(wxRectTracker::OnMouseLeftDown) 00040 EVT_LEFT_UP(wxRectTracker::OnMouseLeftUp) 00041 EVT_CHAR(wxRectTracker::OnKey) 00042 END_EVENT_TABLE() 00043 00044 const wxEventType wxEVT_TRACKER_CHANGED = wxNewEventType(); 00045 const wxEventType wxEVT_TRACKER_CHANGING = wxNewEventType(); 00046 00047 void wxRectTracker::OnPaint(wxPaintEvent& event) 00048 { 00049 this->GetNextHandler()->ProcessEvent(event); 00050 wxClientDC dc(m_wnd); 00051 if (wxDynamicCast(m_wnd,wxScrolledWindow)) 00052 { 00053 wxDynamicCast(m_wnd,wxScrolledWindow)->DoPrepareDC(dc); 00054 } 00055 OnDraw(&dc); 00056 } 00057 00058 void wxRectTracker::OnDraw(wxDC* dc) 00059 { 00060 if ((m_state & RT_STATE_DISABLED) != 0) return; 00061 int w = m_Rect.width, h = m_Rect.height; 00062 if ((w >=0) && (h >= 0)) DrawRect(*dc, m_Rect.GetLeft(), m_Rect.GetTop(), w, h); 00063 } 00064 00065 // DrawRect operates with a wxPaintDC => Window coordinates 00066 void wxRectTracker::DrawRect(wxDC & dc, int x, int y, int w, int h) 00067 { 00068 // Rect 00069 dc.SetBrush(*wxTRANSPARENT_BRUSH); 00070 dc.SetPen(*wxBLACK_PEN); 00071 dc.DrawRectangle(x,y,w,h); 00072 dc.SetPen(wxPen(*wxWHITE,1,wxDOT)); 00073 dc.DrawRectangle(x,y,w,h); 00074 00075 // Handlers 00076 int z = m_iHandlerWidth; 00077 if (m_iHandlerMask != RT_MASK_NONE) 00078 { 00079 dc.SetBrush(*wxBLACK_BRUSH); 00080 dc.SetPen(*wxBLACK_PEN); 00081 00082 if (m_iHandlerMask & RT_MASK_TOP_LEFT) dc.DrawRectangle(x+1 , y+1 , z, z); 00083 if (m_iHandlerMask & RT_MASK_TOP_RIGHT) dc.DrawRectangle(x-1+w-z, y+1 , z, z); 00084 if (m_iHandlerMask & RT_MASK_BOTTOM_LEFT) dc.DrawRectangle(x+1 , y-1+h-z, z, z); 00085 if (m_iHandlerMask & RT_MASK_BOTTOM_RIGHT) dc.DrawRectangle(x-1+w-z, y-1+h-z, z, z); 00086 00087 if (m_iHandlerMask & RT_MASK_TOP_MID) dc.DrawRectangle(x+(w-z)/2, y+1 , z, z); 00088 if (m_iHandlerMask & RT_MASK_BOTTOM_MID) dc.DrawRectangle(x+(w-z)/2, y+h-z-1, z, z); 00089 if (m_iHandlerMask & RT_MASK_MID_LEFT) dc.DrawRectangle(x+1 , y+(h-z)/2, z, z); 00090 if (m_iHandlerMask & RT_MASK_MID_RIGHT) dc.DrawRectangle(x+w-z-1, y+(h-z)/2, z, z); 00091 } 00092 } 00093 00094 void wxRectTracker::DrawRect(wxDC & dc, wxRect rect) 00095 { 00096 DrawRect(dc, rect.x, rect.y, rect.width, rect.height); 00097 } 00098 00099 // DrawTracker operates with the parent's wxWindowDC => Parent coordinates 00100 void wxRectTracker::DrawTracker(wxDC & dc, int x, int y, int w, int h) 00101 { 00102 // Convert coordinates if scrolled 00103 if (wxDynamicCast(m_wnd,wxScrolledWindow) != NULL) 00104 { 00105 wxDynamicCast(m_wnd,wxScrolledWindow)->CalcScrolledPosition(x, y, &x, &y); 00106 } 00107 // Inverted Rect 00108 dc.SetLogicalFunction(wxINVERT); 00109 dc.SetBrush(*wxTRANSPARENT_BRUSH); 00110 dc.SetPen(*wxGREY_PEN); 00111 dc.DrawRectangle(x,y,w,h); 00112 dc.SetLogicalFunction(wxCOPY); 00113 } 00114 00115 void wxRectTracker::DrawTracker(wxDC & dc, const wxRect& rect) 00116 { 00117 DrawTracker(dc, rect.x, rect.y, rect.width, rect.height); 00118 } 00119 00120 00121 void wxRectTracker::OnKey(wxKeyEvent & event) 00122 { 00123 if ( ((m_state & RT_STATE_DISABLED) != 0) || !IsShown()) { event.Skip(); return; } 00124 00125 int incr = 0, dx = 0, dy = 0; 00126 int handler; 00127 wxRect tmpRect; 00128 tmpRect = GetUnscrolledRect(); 00129 00130 if ((tmpRect.width <= 0) || (tmpRect.height <= 0)) { event.Skip(); return; } 00131 00132 if (event.ControlDown()) incr = 10; else incr = 1; 00133 00134 switch(event.GetKeyCode()) 00135 { 00136 case WXK_ESCAPE : 00137 if ((m_state & RT_STATE_MOUSE_CAPTURED) != 0) 00138 { 00139 m_wnd->ReleaseMouse(); 00140 m_state ^= RT_STATE_MOUSE_CAPTURED; 00141 Update(); 00142 } 00143 else 00144 { 00145 Hide(); 00146 } 00147 break; 00148 case WXK_LEFT : dx -= incr; break; 00149 case WXK_UP : dy -= incr; break; 00150 case WXK_RIGHT : dx += incr; break; 00151 case WXK_DOWN : dy += incr; break; 00152 default: 00153 event.Skip(); 00154 } 00155 00156 if ((dx != 0) || (dy != 0)) 00157 { 00158 if (event.ShiftDown()) 00159 { 00160 tmpRect.width += dx; 00161 tmpRect.height += dy; 00162 handler = (dx == 0)?RT_HANDLER_BOTTOM_MID:RT_HANDLER_MID_RIGHT; 00163 } 00164 else 00165 { 00166 tmpRect.x += dx; 00167 tmpRect.y += dy; 00168 handler = 0; 00169 } 00170 AdjustTrackerRect(tmpRect, handler); 00171 SetUnscrolledRect(tmpRect); 00172 Update(); 00173 } 00174 00175 } 00176 00177 void wxRectTracker::OnMouseMotion(wxMouseEvent & event) 00178 { 00179 int hit; 00180 int dx, dy; 00181 00182 if ((m_state & RT_STATE_DISABLED) != 0) return; 00183 00184 wxMouseEvent mouse(event); 00185 00186 if (wxDynamicCast(m_wnd,wxScrolledWindow)) 00187 { 00188 wxDynamicCast(m_wnd,wxScrolledWindow)->CalcUnscrolledPosition(mouse.m_x, mouse.m_y, &mouse.m_x, &mouse.m_y); 00189 } 00190 00191 if (m_frame && IsShown()) 00192 { 00193 m_frame->SetStatusText(wxString::Format(_("Mouse position : %d, %d / RectSize : %d,%d->%d,%d(+%d,%d)"), 00194 event.m_x, event.m_y, 00195 m_Rect.GetLeft(), m_Rect.GetTop(), m_Rect.GetRight(), m_Rect.GetBottom(), m_Rect.GetWidth(), m_Rect.GetHeight())); 00196 } 00197 00198 // Just moving ? 00199 if (!event.Dragging()) 00200 { 00201 hit = HitTest(mouse.m_x, mouse.m_y); 00202 switch (hit) 00203 { 00204 case RT_HANDLER_TOP_MID: 00205 case RT_HANDLER_BOTTOM_MID: 00206 m_wnd->SetCursor(wxCursor(wxCURSOR_SIZENS)); 00207 break; 00208 case RT_HANDLER_MID_LEFT: 00209 case RT_HANDLER_MID_RIGHT: 00210 m_wnd->SetCursor(wxCursor(wxCURSOR_SIZEWE)); 00211 break; 00212 case RT_HANDLER_TOP_LEFT: 00213 case RT_HANDLER_BOTTOM_RIGHT: 00214 m_wnd->SetCursor(wxCursor(wxCURSOR_SIZENWSE)); 00215 break; 00216 case RT_HANDLER_TOP_RIGHT: 00217 case RT_HANDLER_BOTTOM_LEFT: 00218 m_wnd->SetCursor(wxCursor(wxCURSOR_SIZENESW)); 00219 break; 00220 case RT_HANDLER_NONE: 00221 m_wnd->SetCursor(wxCursor(wxCURSOR_HAND /* *m_cursorMove */)); 00222 break; 00223 case RT_HANDLER_OUTSIDE: 00224 m_wnd->SetCursor(wxCursor(wxCURSOR_ARROW)); 00225 break; 00226 default: 00227 m_wnd->SetCursor(wxCursor(wxCURSOR_ARROW)); 00228 } 00229 } 00230 else if ((m_state & RT_STATE_DRAGGING)) 00231 { 00232 // Dragging 00233 00234 wxASSERT(m_wnd!=NULL); 00235 00236 // Drawing Tracker Rect 00237 wxClientDC dc(m_wnd); 00238 if (wxDynamicCast(m_wnd,wxScrolledWindow)) 00239 { 00240 wxDynamicCast(m_wnd,wxScrolledWindow)->DoPrepareDC(dc); 00241 } 00242 00243 dx = 0; dy = 0; 00244 dc.SetDeviceOrigin(dx, dy); 00245 00246 00247 if ((m_state & RT_STATE_FIRSTDRAG) == 0) 00248 { 00249 m_state |= RT_STATE_FIRSTDRAG; 00250 } 00251 else 00252 { 00253 // Erase previous Tracker 00254 DrawTracker(dc, m_prevRect); 00255 } 00256 00257 // Update the new position 00258 // - Which Tracker ? 00259 hit = HitTest(m_leftClick.x, m_leftClick.y); 00260 // - Default Rect values 00261 if (hit != RT_HANDLER_OUTSIDE) 00262 { 00263 m_curRect = GetUnscrolledRect(); 00264 } 00265 else 00266 { 00267 m_curRect = wxRect(m_leftClick, m_leftClick); 00268 hit = RT_HANDLER_BOTTOM_RIGHT; 00269 } 00270 00271 dx = (mouse.m_x - m_leftClick.x); 00272 dy = (mouse.m_y - m_leftClick.y); 00273 00274 if (hit == RT_HANDLER_OUTSIDE) 00275 { 00276 m_curRect.width += dx; 00277 m_curRect.height += dy; 00278 } 00279 else if (hit == RT_HANDLER_NONE) 00280 { 00281 m_curRect.x += dx; 00282 m_curRect.y += dy; 00283 } 00284 else 00285 { 00286 // Use bit combination of handler values to update position 00287 if ( (hit & RT_HANDLER_MID_RIGHT) != 0) 00288 { 00289 m_curRect.width += dx; 00290 } 00291 if ( (hit & RT_HANDLER_BOTTOM_MID) != 0) 00292 { 00293 m_curRect.height += dy; 00294 } 00295 if ( (hit & RT_HANDLER_MID_LEFT) != 0) 00296 { 00297 m_curRect.x += dx; 00298 m_curRect.width -= dx; 00299 } 00300 if ( (hit & RT_HANDLER_TOP_MID) != 0) 00301 { 00302 m_curRect.y += dy; 00303 m_curRect.height -= dy; 00304 } 00305 } 00306 if (m_frame) 00307 { 00308 m_frame->SetStatusText(wxString::Format(_("Mouse click : %d, %d / Hit : %d / RectSize : %d,%d->%d,%d(+%d,%d)"), 00309 m_leftClick.x, m_leftClick.y, 00310 hit, 00311 m_Rect.GetLeft(), m_Rect.GetTop(), m_Rect.GetRight(), m_Rect.GetBottom(), m_Rect.GetWidth(), m_Rect.GetHeight())); 00312 } 00313 // Correct Orientation (size and virtual handler) 00314 if (m_curRect.width < 0) 00315 { 00316 m_curRect.width = - m_curRect.width; 00317 m_curRect.x -= m_curRect.width; 00318 if ((hit & RT_HANDLER_MID_LEFT) != 0) hit = (hit ^ RT_HANDLER_MID_LEFT) | RT_HANDLER_MID_RIGHT; 00319 else if ((hit & RT_HANDLER_MID_RIGHT) != 0) hit = (hit ^ RT_HANDLER_MID_RIGHT) | RT_HANDLER_MID_LEFT; 00320 } 00321 if (m_curRect.height < 0) 00322 { 00323 m_curRect.height = - m_curRect.height; 00324 m_curRect.y -= m_curRect.height; 00325 if ((hit & RT_HANDLER_TOP_MID) != 0) hit = (hit ^ RT_HANDLER_TOP_MID) | RT_HANDLER_BOTTOM_MID; 00326 else if ((hit & RT_HANDLER_BOTTOM_MID) != 0) hit = (hit ^ RT_HANDLER_BOTTOM_MID) | RT_HANDLER_TOP_MID; 00327 } 00328 00329 // Adjust current Tracker size 00330 AdjustTrackerRect(m_curRect, hit); 00331 00332 // Draw current Tracker 00333 DrawTracker(dc, m_curRect); 00334 00335 // Update Parent's Status Bar 00336 m_prevRect = m_curRect; 00337 wxCommandEvent evt(wxEVT_TRACKER_CHANGING, m_wnd->GetId()); 00338 m_wnd->GetEventHandler()->ProcessEvent(evt); 00339 } 00340 00341 // Check there is no abuse mouse capture 00342 if (!(event.LeftIsDown()) && ((m_state & RT_STATE_MOUSE_CAPTURED) != 0)) 00343 { 00344 m_wnd->ReleaseMouse(); 00345 m_state ^= RT_STATE_MOUSE_CAPTURED; 00346 } 00347 00348 // Update prev_move 00349 m_prevMove = mouse.GetPosition(); 00350 00351 event.Skip(); 00352 } 00353 00354 void wxRectTracker::OnMouseLeftDown(wxMouseEvent & event) 00355 { 00356 m_state |= RT_STATE_DRAGGING; 00357 m_leftClick = event.GetPosition(); 00358 if (wxDynamicCast(m_wnd,wxScrolledWindow)) 00359 { 00360 wxDynamicCast(m_wnd,wxScrolledWindow)->CalcUnscrolledPosition(m_leftClick.x, m_leftClick.y, &m_leftClick.x, &m_leftClick.y); 00361 } 00362 if (HitTest(m_leftClick.x, m_leftClick.y) == RT_HANDLER_OUTSIDE) 00363 { 00364 Hide(); 00365 Update(); 00366 } 00367 00368 if ((m_state & RT_STATE_MOUSE_CAPTURED) == 0) 00369 { 00370 m_wnd->CaptureMouse(); 00371 m_state |= RT_STATE_MOUSE_CAPTURED; 00372 } 00373 00374 event.Skip(); 00375 } 00376 00377 void wxRectTracker::OnMouseLeftUp(wxMouseEvent & event) 00378 { 00379 if ((m_state & RT_STATE_MOUSE_CAPTURED) != 0) 00380 { 00381 m_wnd->ReleaseMouse(); 00382 m_state ^= RT_STATE_MOUSE_CAPTURED; 00383 } 00384 if ((m_state & RT_STATE_DRAGGING) != 0) 00385 { 00386 SetUnscrolledRect(m_prevRect); 00387 m_state ^= RT_STATE_DRAGGING; 00388 m_state ^= RT_STATE_FIRSTDRAG; 00389 m_leftClick = wxPoint(0, 0); 00390 Update(); 00391 } 00392 event.Skip(); 00393 } 00394 00395 int wxRectTracker::HitTest(int x, int y) const 00396 { 00397 int w, h; 00398 int z = m_iHandlerWidth; 00399 00400 w = m_Rect.GetWidth(); 00401 h = m_Rect.GetHeight(); 00402 x = x - m_Rect.GetLeft(); 00403 y = y - m_Rect.GetTop(); 00404 00405 if ( (y < 0) || (y > h) || (x < 0) || (x > w) ) return RT_HANDLER_OUTSIDE; 00406 00407 // Split vertically, then horizontally 00408 if ( (y <= h) && (y >= h-z) ) 00409 { 00410 // Bottom line 00411 if ( (x >= w-z) && (x <= w) ) 00412 { 00413 // Bottom Right 00414 return RT_HANDLER_BOTTOM_RIGHT; 00415 } 00416 else if ( (x >= (w-z)/2) && (x <= (w+z)/2) ) 00417 { 00418 // Bottom Mid 00419 return RT_HANDLER_BOTTOM_MID; 00420 } 00421 else if ( (x >= 0) && (x <= z) ) 00422 { 00423 // Bottom left 00424 return RT_HANDLER_BOTTOM_LEFT; 00425 } 00426 } 00427 else if ( (y >= (h-z)/2 ) && (y <= (h+z)/2) ) 00428 { 00429 // Mid line 00430 if ( (x >= w-z) && (x <= w) ) 00431 { 00432 // Mid Right 00433 return RT_HANDLER_MID_RIGHT; 00434 } 00435 else if ( (x >= 0) && (x <= z) ) 00436 { 00437 // Mid left 00438 return RT_HANDLER_MID_LEFT; 00439 } 00440 } 00441 else if ( (y >= 0) && (y <= z) ) 00442 { 00443 // Top line 00444 if ( (x >= w-z) && (x <= w) ) 00445 { 00446 // Top Right 00447 return RT_HANDLER_TOP_RIGHT; 00448 } 00449 else if ( (x >= (w-z)/2) && (x <= (w+z)/2) ) 00450 { 00451 // Top Mid 00452 return RT_HANDLER_TOP_MID; 00453 } 00454 else if ( (x >= 0) && (x <= z) ) 00455 { 00456 // Top left 00457 return RT_HANDLER_TOP_LEFT; 00458 } 00459 } 00460 return RT_HANDLER_NONE; 00461 } 00462 00463 void wxRectTracker::AdjustTrackerRectMax(wxRect &m_curRect, int handler) 00464 { 00465 00466 // Adjust m_maxRect 00467 if ((m_maxRect.width < 0) || (m_maxRect.height < 0)) return; 00468 00469 // - Left X 00470 if (m_curRect.x < m_maxRect.x) 00471 { 00472 if (handler != RT_HANDLER_NONE) m_curRect.width -= m_maxRect.x - m_curRect.x; 00473 m_curRect.x = m_maxRect.x; 00474 } 00475 // - Right X 00476 if ((m_curRect.x + m_curRect.width) > (m_maxRect.x + m_maxRect.width)) 00477 { 00478 if (handler != RT_HANDLER_NONE) m_curRect.width = m_maxRect.x + m_maxRect.width - m_curRect.x; 00479 m_curRect.x = m_maxRect.x + m_maxRect.width - m_curRect.width; 00480 } 00481 // - Top Y 00482 if (m_curRect.y < m_maxRect.y) 00483 { 00484 if (handler != RT_HANDLER_NONE) m_curRect.height -= m_maxRect.y - m_curRect.y; 00485 m_curRect.y = m_maxRect.y; 00486 } 00487 // - Bottom Y 00488 if ((m_curRect.y + m_curRect.height) > (m_maxRect.y + m_maxRect.height)) 00489 { 00490 if (handler != RT_HANDLER_NONE) m_curRect.height = m_maxRect.y + m_maxRect.height - m_curRect.y; 00491 m_curRect.y = m_maxRect.y + m_maxRect.height - m_curRect.height; 00492 } 00493 00494 } 00495 00496 void wxRectTracker::AdjustTrackerRect(wxRect &curRect, int handler) 00497 { 00498 AdjustTrackerRectMax(curRect, handler); 00499 } 00500 00501 void wxRectTracker::SetMaxRect(const wxRect& maxRect) 00502 { 00503 this->m_maxRect = maxRect; 00504 if (this->m_maxRect.height < 0) this->m_maxRect.height = - this->m_maxRect.height; 00505 if (this->m_maxRect.width < 0) this->m_maxRect.width = - this->m_maxRect.width; 00506 Update(); 00507 } 00508 00509 void wxRectTracker::Update() 00510 { 00511 m_curRect = GetUnscrolledRect(); 00512 AdjustTrackerRect(m_curRect, -1); 00513 if (m_curRect != GetUnscrolledRect()) 00514 { 00515 SetUnscrolledRect(m_curRect); 00516 } 00517 m_wnd->Refresh(); 00518 wxCommandEvent evt(wxEVT_TRACKER_CHANGED, m_wnd->GetId()); 00519 m_wnd->GetEventHandler()->ProcessEvent(evt); 00520 } 00521 00522 00523 // TODO : rename this function, as it is now returning the scrolled rect 00524 wxRect wxRectTracker::GetUnscrolledRect() const 00525 { 00526 return m_Rect; 00527 } 00528 00529 void wxRectTracker::SetUnscrolledRect(const wxRect &rect_ref) 00530 { 00531 m_Rect = rect_ref; 00532 } 00533 00534 00535 /* 00536 wxRect wxRectTracker::GetUnscrolledRect() 00537 { 00538 wxRect rect; 00539 rect = m_wnd->GetRect(); 00540 //rect = m_Rect; 00541 00542 // Convert coordinates if scrolled 00543 if (wxDynamicCast(m_wnd,wxScrolledWindow)) 00544 { 00545 wxDynamicCast(m_wnd,wxScrolledWindow)->CalcUnscrolledPosition(rect.x, rect.y, &rect.x, &rect.y); 00546 } 00547 return rect; 00548 } 00549 00550 void wxRectTracker::SetUnscrolledRect(const wxRect& rect_ref) 00551 { 00552 wxRect rect = rect_ref; 00553 00554 // Convert coordinates if scrolled 00555 if (wxDynamicCast(m_wnd,wxScrolledWindow)) 00556 { 00557 wxDynamicCast(m_wnd,wxScrolledWindow)->CalcScrolledPosition(rect.x, rect.y, &rect.x, &rect.y); 00558 } 00559 m_wnd->SetSize(rect); 00560 00561 //m_Rect = rect; 00562 } 00563 */ 00564 00565 void wxRectTracker::Enable() 00566 { 00567 this->SetEvtHandlerEnabled(TRUE); 00568 m_state &= ~RT_STATE_DISABLED; 00569 } 00570 00571 void wxRectTracker::Disable() 00572 { 00573 this->SetEvtHandlerEnabled(FALSE); 00574 if ((m_state & RT_STATE_MOUSE_CAPTURED) != 0) 00575 { 00576 m_wnd->ReleaseMouse(); 00577 m_state ^= RT_STATE_MOUSE_CAPTURED; 00578 } 00579 //SetSize(wxRect(0, 0, 0, 0)); 00580 Update(); 00581 m_state |= RT_STATE_DISABLED; 00582 } 00583 00584 bool wxRectTracker::IsShown() const 00585 { 00586 return (m_Rect != NullRect); 00587 } 00588 00589 void wxRectTracker::Hide() 00590 { 00591 m_Rect = NullRect; 00592 m_prevRect = NullRect; 00593 m_wnd->Refresh(false); 00594 }