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