OpenCPN Partial API docs
OCPN_AUIManager.cpp
1 /***************************************************************************
2  *
3  * Project: OpenCPN
4  * Purpose: OCPN_AUIManager
5  * Author: David Register
6  *
7  ***************************************************************************
8  * Copyright (C) 2018 by David S. Register *
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  * This program is distributed in the hope that it will be useful, *
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18  * GNU General Public License for more details. *
19  * *
20  * You should have received a copy of the GNU General Public License *
21  * along with this program; if not, write to the *
22  * Free Software Foundation, Inc., *
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24  **************************************************************************/
25 
26 #ifndef WX_PRECOMP
27 #include <wx/wx.h>
28 #endif
29 
30 #include <OCPN_AUIManager.h>
31 #include "ocpn_plugin.h"
32 #include "ocpn_frame.h"
33 
34 #ifdef __WXMSW__
35 #include <wx/msw/wrapwin.h>
36 #include <wx/msw/private.h>
37 #include <wx/msw/dc.h>
38 #endif
39 
40 // -- static utility functions --
41 
42 static wxBitmap wxOPaneCreateStippleBitmap() {
43  unsigned char data[] = {0, 0, 0, 192, 192, 192, 192, 192, 192, 0, 0, 0};
44  wxImage img(2, 2, data, true);
45  return wxBitmap(img);
46 }
47 
48 static void ODrawResizeHint(wxDC& dc, const wxRect& rect) {
49 #if 1
50  wxBitmap stipple = wxOPaneCreateStippleBitmap();
51  wxBrush brush(stipple);
52  dc.SetBrush(brush);
53 #ifdef __WXMSW__
54  wxMSWDCImpl* impl = (wxMSWDCImpl*)dc.GetImpl();
55  PatBlt(GetHdcOf(*impl), rect.GetX(), rect.GetY(), rect.GetWidth(),
56  rect.GetHeight(), PATINVERT);
57 #else
58  dc.SetPen(*wxTRANSPARENT_PEN);
59 
60  dc.SetLogicalFunction(wxXOR);
61  dc.DrawRectangle(rect);
62 #endif
63 #endif
64 }
65 
66 // Convenience function
67 bool OAuiManager_HasLiveResize(wxAuiManager& manager) {
68  // With Core Graphics on Mac, it's not possible to show sash feedback,
69  // so we'll always use live update instead.
70 #if defined(__WXMAC__)
71  wxUnusedVar(manager);
72  return true;
73 #else
74  return (manager.GetFlags() & wxAUI_MGR_LIVE_RESIZE) == wxAUI_MGR_LIVE_RESIZE;
75 #endif
76 }
77 
78 // OCPN_AUIManager Implementation
79 
80 BEGIN_EVENT_TABLE(OCPN_AUIManager, wxEvtHandler)
81 EVT_AUI_PANE_BUTTON(wxAuiManager::OnPaneButton)
82 EVT_AUI_RENDER(wxAuiManager::OnRender)
83 EVT_PAINT(OCPN_AUIManager::OnPaint)
84 EVT_ERASE_BACKGROUND(OCPN_AUIManager::OnEraseBackground)
85 EVT_SIZE(OCPN_AUIManager::OnSize)
86 EVT_SET_CURSOR(OCPN_AUIManager::OnSetCursor)
87 EVT_LEFT_DOWN(OCPN_AUIManager::OnLeftDown)
88 EVT_LEFT_UP(OCPN_AUIManager::OnLeftUp)
89 EVT_MOTION(OCPN_AUIManager::OnMotionx)
90 EVT_LEAVE_WINDOW(OCPN_AUIManager::OnLeaveWindow)
91 EVT_MOUSE_CAPTURE_LOST(OCPN_AUIManager::OnCaptureLost)
92 EVT_CHILD_FOCUS(OCPN_AUIManager::OnChildFocus)
93 EVT_AUI_FIND_MANAGER(OCPN_AUIManager::OnFindManager)
94 END_EVENT_TABLE()
95 
96 OCPN_AUIManager::OCPN_AUIManager(wxWindow* managed_wnd, unsigned int flags)
97  : wxAuiManager(managed_wnd, flags)
98 
99 {}
100 
101 OCPN_AUIManager::~OCPN_AUIManager() {}
102 
103 void OCPN_AUIManager::OnMotionx(wxMouseEvent& event) {
104  // sometimes when Update() is called from inside this method,
105  // a spurious mouse move event is generated; this check will make
106  // sure that only real mouse moves will get anywhere in this method;
107  // this appears to be a bug somewhere, and I don't know where the
108  // mouse move event is being generated. only verified on MSW
109 
110  wxPoint mouse_pos = event.GetPosition();
111  if (m_lastMouseMove == mouse_pos) return;
112  m_lastMouseMove = mouse_pos;
113 
114  if (m_action == actionResize) {
115  // It's necessary to reset m_actionPart since it destroyed
116  // by the Update within DoEndResizeAction.
117  if (m_currentDragItem != -1)
118  m_actionPart = &(m_uiParts.Item(m_currentDragItem));
119  else
120  m_currentDragItem = m_uiParts.Index(*m_actionPart);
121 
122  if (m_actionPart) {
123  wxPoint pos = m_actionPart->rect.GetPosition();
124  if (m_actionPart->orientation == wxHORIZONTAL)
125  pos.y = wxMax(0, event.m_y - m_actionOffset.y);
126  else
127  pos.x = wxMax(0, event.m_x - m_actionOffset.x);
128 
129  wxSize client_size = m_frame->GetClientSize();
130  int used_width = 0, used_height = 0;
131 
132  size_t dock_i, dock_count = m_docks.GetCount();
133  for (dock_i = 0; dock_i < dock_count; ++dock_i) {
134  wxAuiDockInfo& dock = m_docks.Item(dock_i);
135  if (dock.dock_direction == wxAUI_DOCK_TOP ||
136  dock.dock_direction == wxAUI_DOCK_BOTTOM) {
137  used_height += dock.size;
138  }
139  if (dock.dock_direction == wxAUI_DOCK_LEFT ||
140  dock.dock_direction == wxAUI_DOCK_RIGHT) {
141  used_width += dock.size;
142  }
143  // if (dock.resizable)
144  // used_width += sashSize;
145  }
146 
147  if (OAuiManager_HasLiveResize(*this)) {
148  m_frame->ReleaseMouse();
149  if ((used_width < client_size.x * 9 / 10) &&
150  (used_width > client_size.x * 1 / 10))
151  DoEndResizeAction(event);
152 
153  m_frame->CaptureMouse();
154  } else {
155  bool bhasMouse = m_frame->HasCapture();
156 
157  if (bhasMouse) m_frame->ReleaseMouse();
158 
159  // Tell MyFrame that the sash is moving, so that he
160  // may disable any top-level windows and so avoid mouse focus problems.
161  MyFrame* pmf = wxDynamicCast(m_frame, MyFrame);
162  if (pmf) pmf->NotifyChildrenResize();
163 
164  wxRect rect(m_frame->ClientToScreen(pos), m_actionPart->rect.GetSize());
165  wxScreenDC dc;
166 
167  if (!m_0actionHintRect.IsEmpty()) {
168  // remove old resize hint
169  ODrawResizeHint(dc, m_0actionHintRect);
170  m_0actionHintRect = wxRect();
171  }
172 
173  wxRect frameScreenRect = m_frame->GetScreenRect();
174 
175  rect.x =
176  wxMax(rect.x, frameScreenRect.x + frameScreenRect.width * 1 / 10);
177  rect.x =
178  wxMin(rect.x, frameScreenRect.x + frameScreenRect.width * 9 / 10);
179 
180  // draw new resize hint, if it's inside the managed frame
181  if (1 /*frameScreenRect.Contains(rect)*/) {
182  ODrawResizeHint(dc, rect);
183  m_0actionHintRect = rect;
184  }
185 
186  if (bhasMouse) m_frame->CaptureMouse();
187  }
188  }
189  } else {
190  OnMotion(event);
191  }
192 }
193 
194 bool OCPN_AUIManager::DoEndResizeAction(wxMouseEvent& event) {
195  // resize the dock or the pane
196  if (m_actionPart && m_actionPart->type == wxAuiDockUIPart::typeDockSizer) {
197  // first, we must calculate the maximum size the dock may be
198  int sashSize = m_art->GetMetric(wxAUI_DOCKART_SASH_SIZE);
199 
200  int used_width = 0, used_height = 0;
201 
202  wxSize client_size = m_frame->GetClientSize();
203 
204  size_t dock_i, dock_count = m_docks.GetCount();
205  for (dock_i = 0; dock_i < dock_count; ++dock_i) {
206  wxAuiDockInfo& dock = m_docks.Item(dock_i);
207  if (dock.dock_direction == wxAUI_DOCK_TOP ||
208  dock.dock_direction == wxAUI_DOCK_BOTTOM) {
209  used_height += dock.size;
210  }
211  if (dock.dock_direction == wxAUI_DOCK_LEFT ||
212  dock.dock_direction == wxAUI_DOCK_RIGHT) {
213  used_width += dock.size;
214  }
215  if (dock.resizable) used_width += sashSize;
216  }
217 
218  int available_width = client_size.GetWidth() - used_width;
219  int available_height = client_size.GetHeight() - used_height;
220 
221 #if wxUSE_STATUSBAR
222  // if there's a status control, the available
223  // height decreases accordingly
224  if (wxDynamicCast(m_frame, wxFrame)) {
225  wxFrame* frame = static_cast<wxFrame*>(m_frame);
226  wxStatusBar* status = frame->GetStatusBar();
227  if (status) {
228  wxSize status_client_size = status->GetClientSize();
229  available_height -= status_client_size.GetHeight();
230  }
231  }
232 #endif
233 
234  wxRect& rect = m_actionPart->dock->rect;
235 
236  wxPoint new_pos(event.m_x - m_actionOffset.x, event.m_y - m_actionOffset.y);
237  int new_size, old_size = m_actionPart->dock->size;
238 
239  switch (m_actionPart->dock->dock_direction) {
240  case wxAUI_DOCK_LEFT:
241  new_size = new_pos.x - rect.x;
242  if (new_size - old_size > available_width)
243  new_size = old_size + available_width;
244  m_actionPart->dock->size = new_size;
245  break;
246  case wxAUI_DOCK_TOP:
247  new_size = new_pos.y - rect.y;
248  if (new_size - old_size > available_height)
249  new_size = old_size + available_height;
250  m_actionPart->dock->size = new_size;
251  break;
252  case wxAUI_DOCK_RIGHT:
253  new_size =
254  rect.x + rect.width - new_pos.x - m_actionPart->rect.GetWidth();
255  if (new_size - old_size > available_width)
256  new_size = old_size + available_width;
257  m_actionPart->dock->size = new_size;
258 
259  m_actionPart->dock->size =
260  wxMax(m_actionPart->dock->size, client_size.x * 1 / 10);
261  m_actionPart->dock->size =
262  wxMin(m_actionPart->dock->size, client_size.x * 9 / 10);
263 
264  break;
265  case wxAUI_DOCK_BOTTOM:
266  new_size =
267  rect.y + rect.height - new_pos.y - m_actionPart->rect.GetHeight();
268  if (new_size - old_size > available_height)
269  new_size = old_size + available_height;
270  m_actionPart->dock->size = new_size;
271  break;
272  }
273 
274  Update();
275  Repaint(NULL);
276  } else if (m_actionPart &&
277  m_actionPart->type == wxAuiDockUIPart::typePaneSizer) {
278  wxAuiDockInfo& dock = *m_actionPart->dock;
279  wxAuiPaneInfo& pane = *m_actionPart->pane;
280 
281  int total_proportion = 0;
282  int dock_pixels = 0;
283  int new_pixsize = 0;
284 
285  int caption_size = m_art->GetMetric(wxAUI_DOCKART_CAPTION_SIZE);
286  int pane_borderSize = m_art->GetMetric(wxAUI_DOCKART_PANE_BORDER_SIZE);
287  int sashSize = m_art->GetMetric(wxAUI_DOCKART_SASH_SIZE);
288 
289  wxPoint new_pos(event.m_x - m_actionOffset.x, event.m_y - m_actionOffset.y);
290 
291  // determine the pane rectangle by getting the pane part
292  wxAuiDockUIPart* pane_part = GetPanePart(pane.window);
293  wxASSERT_MSG(pane_part,
294  wxT("Pane border part not found -- shouldn't happen"));
295 
296  // determine the new pixel size that the user wants;
297  // this will help us recalculate the pane's proportion
298  if (dock.IsHorizontal())
299  new_pixsize = new_pos.x - pane_part->rect.x;
300  else
301  new_pixsize = new_pos.y - pane_part->rect.y;
302 
303  // determine the size of the dock, based on orientation
304  if (dock.IsHorizontal())
305  dock_pixels = dock.rect.GetWidth();
306  else
307  dock_pixels = dock.rect.GetHeight();
308 
309  // determine the total proportion of all resizable panes,
310  // and the total size of the dock minus the size of all
311  // the fixed panes
312  int i, dock_pane_count = dock.panes.GetCount();
313  int pane_position = -1;
314  for (i = 0; i < dock_pane_count; ++i) {
315  wxAuiPaneInfo& p = *dock.panes.Item(i);
316  if (p.window == pane.window) pane_position = i;
317 
318  // while we're at it, subtract the pane sash
319  // width from the dock width, because this would
320  // skew our proportion calculations
321  if (i > 0) dock_pixels -= sashSize;
322 
323  // also, the whole size (including decorations) of
324  // all fixed panes must also be subtracted, because they
325  // are not part of the proportion calculation
326  if (p.IsFixed()) {
327  if (dock.IsHorizontal())
328  dock_pixels -= p.best_size.x;
329  else
330  dock_pixels -= p.best_size.y;
331  } else {
332  total_proportion += p.dock_proportion;
333  }
334  }
335 
336  // new size can never be more than the number of dock pixels
337  if (new_pixsize > dock_pixels) new_pixsize = dock_pixels;
338 
339  // find a pane in our dock to 'steal' space from or to 'give'
340  // space to -- this is essentially what is done when a pane is
341  // resized; the pane should usually be the first non-fixed pane
342  // to the right of the action pane
343  int borrow_pane = -1;
344  for (i = pane_position + 1; i < dock_pane_count; ++i) {
345  wxAuiPaneInfo& p = *dock.panes.Item(i);
346  if (!p.IsFixed()) {
347  borrow_pane = i;
348  break;
349  }
350  }
351 
352  // demand that the pane being resized is found in this dock
353  // (this assert really never should be raised)
354  wxASSERT_MSG(pane_position != -1, wxT("Pane not found in dock"));
355 
356  // prevent division by zero
357  if (dock_pixels == 0 || total_proportion == 0 || borrow_pane == -1) {
358  m_action = actionNone;
359  return false;
360  }
361 
362  // calculate the new proportion of the pane
363  int new_proportion = (new_pixsize * total_proportion) / dock_pixels;
364 
365  // default minimum size
366  int min_size = 0;
367 
368  // check against the pane's minimum size, if specified. please note
369  // that this is not enough to ensure that the minimum size will
370  // not be violated, because the whole frame might later be shrunk,
371  // causing the size of the pane to violate it's minimum size
372  if (pane.min_size.IsFullySpecified()) {
373  min_size = 0;
374 
375  if (pane.HasBorder()) min_size += (pane_borderSize * 2);
376 
377  // calculate minimum size with decorations (border,caption)
378  if (pane_part->orientation == wxVERTICAL) {
379  min_size += pane.min_size.y;
380  if (pane.HasCaption()) min_size += caption_size;
381  } else {
382  min_size += pane.min_size.x;
383  }
384  }
385 
386  // for some reason, an arithmatic error somewhere is causing
387  // the proportion calculations to always be off by 1 pixel;
388  // for now we will add the 1 pixel on, but we really should
389  // determine what's causing this.
390  min_size++;
391 
392  int min_proportion = (min_size * total_proportion) / dock_pixels;
393 
394  if (new_proportion < min_proportion) new_proportion = min_proportion;
395 
396  int prop_diff = new_proportion - pane.dock_proportion;
397 
398  // borrow the space from our neighbor pane to the
399  // right or bottom (depending on orientation);
400  // also make sure we don't make the neighbor too small
401  int prop_borrow = dock.panes.Item(borrow_pane)->dock_proportion;
402 
403  if (prop_borrow - prop_diff < 0) {
404  // borrowing from other pane would make it too small,
405  // so cancel the resize operation
406  prop_borrow = min_proportion;
407  } else {
408  prop_borrow -= prop_diff;
409  }
410 
411  dock.panes.Item(borrow_pane)->dock_proportion = prop_borrow;
412  pane.dock_proportion = new_proportion;
413 
414  // repaint
415  Update();
416  Repaint(NULL);
417  }
418 
419  return true;
420 }
421 
422 void OCPN_AUIManager::OnLeftUp(wxMouseEvent& event) {
423  if (m_action == actionResize) {
424  m_frame->ReleaseMouse();
425 
426  if (!OAuiManager_HasLiveResize(*this)) {
427  // get rid of the hint rectangle
428  wxScreenDC dc;
429  ODrawResizeHint(dc, m_0actionHintRect);
430  }
431  if (m_currentDragItem != -1 && OAuiManager_HasLiveResize(*this))
432  m_actionPart = &(m_uiParts.Item(m_currentDragItem));
433 
434  DoEndResizeAction(event);
435 
436  m_currentDragItem = -1;
437 
438  } else if (m_action == actionClickButton) {
439  m_hoverButton = NULL;
440  m_frame->ReleaseMouse();
441 
442  if (m_actionPart) {
443  UpdateButtonOnScreen(m_actionPart, event);
444 
445  // make sure we're still over the item that was originally clicked
446  if (m_actionPart == HitTest(event.GetX(), event.GetY())) {
447  // fire button-click event
448  wxAuiManagerEvent e(wxEVT_AUI_PANE_BUTTON);
449  e.SetManager(this);
450  e.SetPane(m_actionPart->pane);
451 
452 #if wxCHECK_VERSION(3, 1, 4)
453  e.SetButton(m_actionPart->button);
454 #else
455  e.SetButton(m_actionPart->button->button_id);
456 #endif
457  ProcessMgrEvent(e);
458  }
459  }
460  } else if (m_action == actionClickCaption) {
461  m_frame->ReleaseMouse();
462  } else if (m_action == actionDragFloatingPane) {
463  m_frame->ReleaseMouse();
464  }
465 #if 0
466  else if (m_action == actionDragToolbarPane)
467  {
468  m_frame->ReleaseMouse();
469 
470  wxAuiPaneInfo& pane = GetPane(m_actionWindow);
471  wxASSERT_MSG(pane.IsOk(), wxT("Pane window not found"));
472 
473  // save the new positions
474  wxAuiDockInfoPtrArray docks;
475  FindDocks(m_docks, pane.dock_direction,
476  pane.dock_layer, pane.dock_row, docks);
477  if (docks.GetCount() == 1)
478  {
479  wxAuiDockInfo& dock = *docks.Item(0);
480 
481  wxArrayInt pane_positions, pane_sizes;
482  GetPanePositionsAndSizes(dock, pane_positions, pane_sizes);
483 
484  int i, dock_pane_count = dock.panes.GetCount();
485  for (i = 0; i < dock_pane_count; ++i)
486  dock.panes.Item(i)->dock_pos = pane_positions[i];
487  }
488 
489  pane.state &= ~wxAuiPaneInfo::actionPane;
490  Update();
491  }
492 #endif
493  else {
494  event.Skip();
495  }
496 
497  m_action = actionNone;
498  m_lastMouseMove = wxPoint(); // see comment in OnMotion()
499 }
500 
501 // FindDocks() is an internal function that returns a list of docks which meet
502 // the specified conditions in the parameters and returns a sorted array
503 // (sorted by layer and then row)
504 static void OCPNFindDocks(wxAuiDockInfoArray& docks, int dock_direction,
505  int dock_layer, int dock_row,
506  wxAuiDockInfoPtrArray& arr) {
507  int begin_layer = dock_layer;
508  int end_layer = dock_layer;
509  int begin_row = dock_row;
510  int end_row = dock_row;
511  int dock_count = docks.GetCount();
512  int layer, row, i, max_row = 0, max_layer = 0;
513 
514  // discover the maximum dock layer and the max row
515  for (i = 0; i < dock_count; ++i) {
516  max_row = wxMax(max_row, docks.Item(i).dock_row);
517  max_layer = wxMax(max_layer, docks.Item(i).dock_layer);
518  }
519 
520  // if no dock layer was specified, search all dock layers
521  if (dock_layer == -1) {
522  begin_layer = 0;
523  end_layer = max_layer;
524  }
525 
526  // if no dock row was specified, search all dock row
527  if (dock_row == -1) {
528  begin_row = 0;
529  end_row = max_row;
530  }
531 
532  arr.Clear();
533 
534  for (layer = begin_layer; layer <= end_layer; ++layer)
535  for (row = begin_row; row <= end_row; ++row)
536  for (i = 0; i < dock_count; ++i) {
537  wxAuiDockInfo& d = docks.Item(i);
538  if (dock_direction == -1 || dock_direction == d.dock_direction) {
539  if (d.dock_layer == layer && d.dock_row == row) arr.Add(&d);
540  }
541  }
542 }
543 
544 wxAuiDockInfo* OCPN_AUIManager::FindDock(wxAuiPaneInfo& pane) {
545  wxAuiDockInfoPtrArray arr;
546  OCPNFindDocks(m_docks, pane.dock_direction, pane.dock_layer, pane.dock_row,
547  arr);
548  if (arr.GetCount())
549  return arr.Item(0);
550  else
551  return NULL;
552 }
553 
554 void OCPN_AUIManager::SetDockSize(wxAuiDockInfo* dock, int size) {
555  dock->size = size;
556 
557  Update();
558  Repaint(NULL);
559 }
560 
561 bool OCPN_AUIManager::ProcessDockResult(wxAuiPaneInfo& target,
562  const wxAuiPaneInfo& new_pos) {
563  // printf("DockResult direction: %d layer: %d position: %d %d\n" ,
564  // new_pos.dock_direction, new_pos.dock_layer, new_pos.dock_pos,
565  // GetCanvasIndexUnderMouse());
566 
567  // If we are docking a Dashboard window, we restrict the spots that can accept
568  // the docking action
569  if (new_pos.window->GetName().IsSameAs(_T("panel"))) {
570  // Dashboards can not go on the left( interferes with global toolbar )
571  if (/*(new_pos.dock_layer != 1) ||*/ (new_pos.dock_direction ==
572  wxAUI_DOCK_LEFT))
573  return false;
574 
575  // Also, in multi-canvas mode, the dashboard is restricted to layer 1 in
576  // right hand canvas. This forces it to dock at the far right only.
577  if (GetCanvasCount() > 1) {
578  if (GetCanvasIndexUnderMouse() > 0) {
579  if (new_pos.dock_layer == 0) return false;
580  }
581  }
582  }
583 
584  return wxAuiManager::ProcessDockResult(target, new_pos);
585 }