OpenCPN Partial API docs
Loading...
Searching...
No Matches
ocpn_aui_manager.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2018 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#ifndef WX_PRECOMP
25#include <wx/wx.h>
26#endif
27
28#include <ocpn_aui_manager.h>
29#include "ocpn_plugin.h"
30#include "ocpn_frame.h"
31
32#ifdef __WXMSW__
33#include <wx/msw/wrapwin.h>
34#include <wx/msw/private.h>
35#include <wx/msw/dc.h>
36#endif
37
38// -- static utility functions --
39
41
42static 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
48static 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
67static 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
80BEGIN_EVENT_TABLE(OCPN_AUIManager, wxEvtHandler)
81EVT_AUI_PANE_BUTTON(wxAuiManager::OnPaneButton)
82EVT_AUI_RENDER(wxAuiManager::OnRender)
83EVT_PAINT(OCPN_AUIManager::OnPaint)
84EVT_ERASE_BACKGROUND(OCPN_AUIManager::OnEraseBackground)
85EVT_SIZE(OCPN_AUIManager::OnSize)
86EVT_SET_CURSOR(OCPN_AUIManager::OnSetCursor)
87EVT_LEFT_DOWN(OCPN_AUIManager::OnLeftDown)
88EVT_LEFT_UP(OCPN_AUIManager::OnLeftUp)
89EVT_MOTION(OCPN_AUIManager::OnMotionx)
90EVT_LEAVE_WINDOW(OCPN_AUIManager::OnLeaveWindow)
91EVT_MOUSE_CAPTURE_LOST(OCPN_AUIManager::OnCaptureLost)
92EVT_CHILD_FOCUS(OCPN_AUIManager::OnChildFocus)
93EVT_AUI_FIND_MANAGER(OCPN_AUIManager::OnFindManager)
94END_EVENT_TABLE()
95
96OCPN_AUIManager::OCPN_AUIManager(wxWindow* managed_wnd, unsigned int flags)
97 : wxAuiManager(managed_wnd, flags)
98
99{}
100
101OCPN_AUIManager::~OCPN_AUIManager() {}
102
103void 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 auto pmf = dynamic_cast<MyFrame*>(m_frame);
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
194bool 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 (dynamic_cast<wxFrame*>(m_frame)) {
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, "Pane border part not found -- shouldn't happen");
294
295 // determine the new pixel size that the user wants;
296 // this will help us recalculate the pane's proportion
297 if (dock.IsHorizontal())
298 new_pixsize = new_pos.x - pane_part->rect.x;
299 else
300 new_pixsize = new_pos.y - pane_part->rect.y;
301
302 // determine the size of the dock, based on orientation
303 if (dock.IsHorizontal())
304 dock_pixels = dock.rect.GetWidth();
305 else
306 dock_pixels = dock.rect.GetHeight();
307
308 // determine the total proportion of all resizable panes,
309 // and the total size of the dock minus the size of all
310 // the fixed panes
311 int i, dock_pane_count = dock.panes.GetCount();
312 int pane_position = -1;
313 for (i = 0; i < dock_pane_count; ++i) {
314 wxAuiPaneInfo& p = *dock.panes.Item(i);
315 if (p.window == pane.window) pane_position = i;
316
317 // while we're at it, subtract the pane sash
318 // width from the dock width, because this would
319 // skew our proportion calculations
320 if (i > 0) dock_pixels -= sashSize;
321
322 // also, the whole size (including decorations) of
323 // all fixed panes must also be subtracted, because they
324 // are not part of the proportion calculation
325 if (p.IsFixed()) {
326 if (dock.IsHorizontal())
327 dock_pixels -= p.best_size.x;
328 else
329 dock_pixels -= p.best_size.y;
330 } else {
331 total_proportion += p.dock_proportion;
332 }
333 }
334
335 // new size can never be more than the number of dock pixels
336 if (new_pixsize > dock_pixels) new_pixsize = dock_pixels;
337
338 // find a pane in our dock to 'steal' space from or to 'give'
339 // space to -- this is essentially what is done when a pane is
340 // resized; the pane should usually be the first non-fixed pane
341 // to the right of the action pane
342 int borrow_pane = -1;
343 for (i = pane_position + 1; i < dock_pane_count; ++i) {
344 wxAuiPaneInfo& p = *dock.panes.Item(i);
345 if (!p.IsFixed()) {
346 borrow_pane = i;
347 break;
348 }
349 }
350
351 // demand that the pane being resized is found in this dock
352 // (this assert really never should be raised)
353 wxASSERT_MSG(pane_position != -1, "Pane not found in dock");
354
355 // prevent division by zero
356 if (dock_pixels == 0 || total_proportion == 0 || borrow_pane == -1) {
357 m_action = actionNone;
358 return false;
359 }
360
361 // calculate the new proportion of the pane
362 int new_proportion = (new_pixsize * total_proportion) / dock_pixels;
363
364 // default minimum size
365 int min_size = 0;
366
367 // check against the pane's minimum size, if specified. please note
368 // that this is not enough to ensure that the minimum size will
369 // not be violated, because the whole frame might later be shrunk,
370 // causing the size of the pane to violate it's minimum size
371 if (pane.min_size.IsFullySpecified()) {
372 min_size = 0;
373
374 if (pane.HasBorder()) min_size += (pane_borderSize * 2);
375
376 // calculate minimum size with decorations (border,caption)
377 if (pane_part->orientation == wxVERTICAL) {
378 min_size += pane.min_size.y;
379 if (pane.HasCaption()) min_size += caption_size;
380 } else {
381 min_size += pane.min_size.x;
382 }
383 }
384
385 // for some reason, an arithmatic error somewhere is causing
386 // the proportion calculations to always be off by 1 pixel;
387 // for now we will add the 1 pixel on, but we really should
388 // determine what's causing this.
389 min_size++;
390
391 int min_proportion = (min_size * total_proportion) / dock_pixels;
392
393 if (new_proportion < min_proportion) new_proportion = min_proportion;
394
395 int prop_diff = new_proportion - pane.dock_proportion;
396
397 // borrow the space from our neighbor pane to the
398 // right or bottom (depending on orientation);
399 // also make sure we don't make the neighbor too small
400 int prop_borrow = dock.panes.Item(borrow_pane)->dock_proportion;
401
402 if (prop_borrow - prop_diff < 0) {
403 // borrowing from other pane would make it too small,
404 // so cancel the resize operation
405 prop_borrow = min_proportion;
406 } else {
407 prop_borrow -= prop_diff;
408 }
409
410 dock.panes.Item(borrow_pane)->dock_proportion = prop_borrow;
411 pane.dock_proportion = new_proportion;
412
413 // repaint
414 Update();
415 Repaint(NULL);
416 }
417
418 return true;
419}
420
421void OCPN_AUIManager::OnLeftUp(wxMouseEvent& event) {
422 if (m_action == actionResize) {
423 m_frame->ReleaseMouse();
424
425 if (!OAuiManager_HasLiveResize(*this)) {
426 // get rid of the hint rectangle
427 wxScreenDC dc;
428 ODrawResizeHint(dc, m_0actionHintRect);
429 }
430 if (m_currentDragItem != -1 && OAuiManager_HasLiveResize(*this))
431 m_actionPart = &(m_uiParts.Item(m_currentDragItem));
432
433 DoEndResizeAction(event);
434
435 m_currentDragItem = -1;
436
437 } else if (m_action == actionClickButton) {
438 m_hoverButton = NULL;
439 m_frame->ReleaseMouse();
440
441 if (m_actionPart) {
442 UpdateButtonOnScreen(m_actionPart, event);
443
444 // make sure we're still over the item that was originally clicked
445 if (m_actionPart == HitTest(event.GetX(), event.GetY())) {
446 // fire button-click event
447 wxAuiManagerEvent e(wxEVT_AUI_PANE_BUTTON);
448 e.SetManager(this);
449 e.SetPane(m_actionPart->pane);
450
451#if wxCHECK_VERSION(3, 1, 4)
452 e.SetButton(m_actionPart->button);
453#else
454 e.SetButton(m_actionPart->button->button_id);
455#endif
456 ProcessMgrEvent(e);
457 }
458 }
459 } else if (m_action == actionClickCaption) {
460 m_frame->ReleaseMouse();
461 } else if (m_action == actionDragFloatingPane) {
462 m_frame->ReleaseMouse();
463 }
464#if 0
465 else if (m_action == actionDragToolbarPane)
466 {
467 m_frame->ReleaseMouse();
468
469 wxAuiPaneInfo& pane = GetPane(m_actionWindow);
470 wxASSERT_MSG(pane.IsOk(), "Pane window not found");
471
472 // save the new positions
473 wxAuiDockInfoPtrArray docks;
474 FindDocks(m_docks, pane.dock_direction,
475 pane.dock_layer, pane.dock_row, docks);
476 if (docks.GetCount() == 1)
477 {
478 wxAuiDockInfo& dock = *docks.Item(0);
479
480 wxArrayInt pane_positions, pane_sizes;
481 GetPanePositionsAndSizes(dock, pane_positions, pane_sizes);
482
483 int i, dock_pane_count = dock.panes.GetCount();
484 for (i = 0; i < dock_pane_count; ++i)
485 dock.panes.Item(i)->dock_pos = pane_positions[i];
486 }
487
488 pane.state &= ~wxAuiPaneInfo::actionPane;
489 Update();
490 }
491#endif
492 else {
493 event.Skip();
494 }
495
496 m_action = actionNone;
497 m_lastMouseMove = wxPoint(); // see comment in OnMotion()
498}
499
500// FindDocks() is an internal function that returns a list of docks which meet
501// the specified conditions in the parameters and returns a sorted array
502// (sorted by layer and then row)
503static void OCPNFindDocks(wxAuiDockInfoArray& docks, int dock_direction,
504 int dock_layer, int dock_row,
505 wxAuiDockInfoPtrArray& arr) {
506 int begin_layer = dock_layer;
507 int end_layer = dock_layer;
508 int begin_row = dock_row;
509 int end_row = dock_row;
510 int dock_count = docks.GetCount();
511 int layer, row, i, max_row = 0, max_layer = 0;
512
513 // discover the maximum dock layer and the max row
514 for (i = 0; i < dock_count; ++i) {
515 max_row = wxMax(max_row, docks.Item(i).dock_row);
516 max_layer = wxMax(max_layer, docks.Item(i).dock_layer);
517 }
518
519 // if no dock layer was specified, search all dock layers
520 if (dock_layer == -1) {
521 begin_layer = 0;
522 end_layer = max_layer;
523 }
524
525 // if no dock row was specified, search all dock row
526 if (dock_row == -1) {
527 begin_row = 0;
528 end_row = max_row;
529 }
530
531 arr.Clear();
532
533 for (layer = begin_layer; layer <= end_layer; ++layer)
534 for (row = begin_row; row <= end_row; ++row)
535 for (i = 0; i < dock_count; ++i) {
536 wxAuiDockInfo& d = docks.Item(i);
537 if (dock_direction == -1 || dock_direction == d.dock_direction) {
538 if (d.dock_layer == layer && d.dock_row == row) arr.Add(&d);
539 }
540 }
541}
542
543wxAuiDockInfo* OCPN_AUIManager::FindDock(wxAuiPaneInfo& pane) {
544 wxAuiDockInfoPtrArray arr;
545 OCPNFindDocks(m_docks, pane.dock_direction, pane.dock_layer, pane.dock_row,
546 arr);
547 if (arr.GetCount())
548 return arr.Item(0);
549 else
550 return NULL;
551}
552
553void OCPN_AUIManager::SetDockSize(wxAuiDockInfo* dock, int size) {
554 dock->size = size;
555
556 Update();
557 Repaint(NULL);
558}
559
560bool OCPN_AUIManager::ProcessDockResult(wxAuiPaneInfo& target,
561 const wxAuiPaneInfo& new_pos) {
562 // printf("DockResult direction: %d layer: %d position: %d %d\n" ,
563 // new_pos.dock_direction, new_pos.dock_layer, new_pos.dock_pos,
564 // GetCanvasIndexUnderMouse());
565
566 // If we are docking a Dashboard window, we restrict the spots that can accept
567 // the docking action
568 if (new_pos.window->GetName().IsSameAs("panel")) {
569 // Dashboards can not go on the left( interferes with global toolbar )
570 if (/*(new_pos.dock_layer != 1) ||*/ (new_pos.dock_direction ==
571 wxAUI_DOCK_LEFT))
572 return false;
573
574 // Also, in multi-canvas mode, the dashboard is restricted to layer 1 in
575 // right hand canvas. This forces it to dock at the far right only.
576 if (GetCanvasCount() > 1) {
577 if (GetCanvasIndexUnderMouse() > 0) {
578 if (new_pos.dock_layer == 0) return false;
579 }
580 }
581 }
582
583 return wxAuiManager::ProcessDockResult(target, new_pos);
584}
Main application frame.
Definition ocpn_frame.h:139
OCPN_AUIManager * g_pauimgr
Global instance.
OCPN_AUIManager.
OpenCPN top window.
PlugIn Object Definition/API.
int GetCanvasCount()
Gets total number of chart canvases.
int GetCanvasIndexUnderMouse()
Gets index of chart canvas under mouse cursor.