BibleTime
cmdiarea.cpp
Go to the documentation of this file.
1 /*********
2 *
3 * In the name of the Father, and of the Son, and of the Holy Spirit.
4 *
5 * This file is part of BibleTime's source code, https://bibletime.info/
6 *
7 * Copyright 1999-2021 by the BibleTime developers.
8 * The BibleTime source code is licensed under the GNU General Public License
9 * version 2.0.
10 *
11 **********/
12 
13 #include "cmdiarea.h"
14 
15 #include <QAction>
16 #include <QEvent>
17 #include <QFlags>
18 #include <QKeySequence>
19 #include <QMdiSubWindow>
20 #include <QMenu>
21 #include <QPoint>
22 #include <QRect>
23 #include <QSize>
24 #include <QStyle>
25 #include <QTimer>
26 #include <QWidget>
27 #include <QWindowStateChangeEvent>
28 #include "../util/btassert.h"
29 #include "../util/btconnect.h"
30 #include "bibletime.h"
33 
34 
35 namespace {
36 
37 inline CDisplayWindow * getDisplayWindow(const QMdiSubWindow * const mdiWindow) {
38  return qobject_cast<CDisplayWindow *>(mdiWindow->widget());
39 }
40 
41 } // anonymous namespace
42 
43 
45  : QMdiArea((BT_ASSERT(parent), parent))
46  , m_mdiArrangementMode(ArrangementModeManual)
47  , m_activeWindow(nullptr)
48  , m_bibleTime(parent)
49 {
50  setFocusPolicy(Qt::ClickFocus);
51 
52  setDocumentMode(true); // Document-style tabs for Mac
53  setTabsClosable(true);
54  setTabsMovable(true);
55 
56  /*
57  Activate windows based on the history of activation, e.g. when one has window A
58  activated, and activates window B and then closes window B, then window A is activated.
59  */
60  setActivationOrder(QMdiArea::ActivationHistoryOrder);
61 
62  // Show scrollbars only when needed:
63  setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
64  setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
65 
66  BT_CONNECT(this, &QMdiArea::subWindowActivated,
67  [this](QMdiSubWindow* client) {
68  if (subWindowList().isEmpty())
70 
71  if (client == nullptr) {
72  return;
73  }
74  Q_EMIT sigSetToplevelCaption( client->windowTitle().trimmed() );
75 
76  // Notify child window it is active
77  CDisplayWindow* const activeWindow = getDisplayWindow(client);
78  if (activeWindow != nullptr && activeWindow != m_activeWindow) {
79  m_activeWindow = activeWindow;
80  activeWindow->windowActivated();
81  }
82  });
83 }
84 
85 QMdiSubWindow * CMDIArea::addDisplayWindow(CDisplayWindow * const displayWindow)
86 {
87  QMdiSubWindow * const subWindow = addSubWindow(displayWindow);
88  subWindow->setWindowIcon(displayWindow->windowIcon());
89  BT_CONNECT(displayWindow, &CDisplayWindow::windowIconChanged,
90  subWindow, &QMdiSubWindow::setWindowIcon);
91 
92  // Change Qt QMdiSubWindow Close action to have no shortcuts
93  // This makes our closeWindow actions with Ctrl-W work correctly
94  for (auto * const action : subWindow->systemMenu()->actions()) {
95  if (action->text().contains(QStringLiteral("Close"))) {
96  action->setShortcuts(QList<QKeySequence>());
97  break;
98  }
99  }
100 
101  // Manual arrangement mode
104 
105  // Note that the window size/maximization may be changed later by a session restore.
106  // If we already have an active window, make the new one simular to it
107  if (activeSubWindow()) {
108  if (activeSubWindow()->isMaximized()) {
109  subWindow->showMaximized(); // Maximize the new window
110  } else {
111  // Make new window the same size as the active window and move it slightly.
112  subWindow->resize(activeSubWindow()->size());
113  QRect subWinGeom = activeSubWindow()->geometry();
114  static const int MOVESIZE = 30;
115  subWinGeom.translate(MOVESIZE, MOVESIZE);
116  // If it goes off screen, move it almost to the top left
117  if (!frameRect().contains(subWinGeom))
118  subWinGeom.moveTo(MOVESIZE, MOVESIZE);
119  subWindow->setGeometry(subWinGeom);
120  }
121  } else {
122  //set the window to be big enough
123  subWindow->resize(400, 400);
124  }
125  subWindow->raise();
126  } else {
127  // Automatic arrangement modes
130  }
131  return subWindow;
132 }
133 
134 void CMDIArea::setMDIArrangementMode( const MDIArrangementMode newArrangementMode ) {
135  m_mdiArrangementMode = newArrangementMode;
136  switch (m_mdiArrangementMode) {
138  setViewMode(QMdiArea::SubWindowView);
139  break;
140  case ArrangementModeTile:
141  setViewMode(QMdiArea::SubWindowView);
142  myTile();
143  break;
145  setViewMode(QMdiArea::TabbedView);
146  break;
147  default:
148  setViewMode(QMdiArea::SubWindowView);
150  break;
151  }
152 }
153 
155  if (!updatesEnabled()) {
156  return;
157  }
159  if (windows.isEmpty()) {
160  return;
161  }
162  setViewMode(QMdiArea::SubWindowView);
163 
164  setUpdatesEnabled(false);
165 
166  QMdiSubWindow * const active = activeSubWindow();
167 
168  const int widthForEach = width() / windows.count();
169  int x = 0;
170  for (auto * const window : windows) {
171  window->showNormal();
172 
173  const int preferredWidth = window->minimumWidth() + window->baseSize().width();
174  const int actWidth = qMax(widthForEach, preferredWidth);
175 
176  window->setGeometry(x, 0, actWidth, height());
177  x += actWidth;
178  }
179 
180  if (active != nullptr) {
181  active->setFocus();
182  }
183 
184  setUpdatesEnabled(true);
186 }
187 
189  if (!updatesEnabled()) {
190  return;
191  }
193  if (windows.isEmpty()) {
194  return;
195  }
196  setViewMode(QMdiArea::SubWindowView);
197 
198  setUpdatesEnabled(false);
199  QMdiSubWindow * const active = activeSubWindow();
200 
201  const int heightForEach = height() / windows.count();
202  int y = 0;
203  for (auto * const window : windows) {
204  window->showNormal();
205 
206  const int preferredHeight = window->minimumHeight() + window->baseSize().height();
207  const int actHeight = qMax(heightForEach, preferredHeight);
208 
209  window->setGeometry( 0, y, width(), actHeight );
210  y += actHeight;
211  }
212 
213  if (active != nullptr) {
214  active->setFocus();
215  }
216 
217  setUpdatesEnabled(true);
219 }
220 
221 // Tile the windows
223  if (!updatesEnabled()) {
224  return;
225  }
227  if (windows.isEmpty()) {
228  return;
229  }
230  setViewMode(QMdiArea::SubWindowView);
231 
232  setUpdatesEnabled(false);
233  QMdiSubWindow * const active = activeSubWindow();
234 
235  const QRect domain = contentsRect();
236  const int n = windows.size();
237  const int ncols = qMax(qCeil(qSqrt(qreal(n))), 1);
238  const int nrows = qMax((n % ncols) ? (n / ncols + 1) : (n / ncols), 1);
239  const int nspecial = (n % ncols) ? (ncols - n % ncols) : 0;
240  const int dx = domain.width() / ncols;
241  const int dy = domain.height() / nrows;
242 
243  int i = 0;
244  for (int row = 0; row < nrows; ++row) {
245  const int y1 = int(row * (dy + 1));
246  for (int col = 0; col < ncols; ++col) {
247  if (row == 1 && col < nspecial)
248  continue;
249  const int x1 = int(col * (dx + 1));
250  int x2 = int(x1 + dx);
251  int y2 = int(y1 + dy);
252  if (row == 0 && col < nspecial) {
253  y2 *= 2;
254  if (nrows != 2)
255  y2 += 1;
256  else
257  y2 = domain.bottom();
258  }
259  if (col == ncols - 1 && x2 != domain.right())
260  x2 = domain.right();
261  if (row == nrows - 1 && y2 != domain.bottom())
262  y2 = domain.bottom();
263  QWidget *widget = windows.at(i++);
264  QRect newGeometry = QRect(QPoint(x1, y1), QPoint(x2, y2));
265  widget->setGeometry(QStyle::visualRect(widget->layoutDirection(), domain, newGeometry));
266  }
267  }
268 
269  if (active != nullptr) {
270  active->setFocus();
271  }
272 
273  setUpdatesEnabled(true);
275 }
276 
278  if (!updatesEnabled()) {
279  return;
280  }
282  if (windows.isEmpty()) {
283  return;
284  }
285  setViewMode(QMdiArea::SubWindowView);
286 
287  if (windows.count() == 1) {
288  windows.first()->showMaximized();
289  }
290  else {
291  setUpdatesEnabled(false);
292 
293  QMdiSubWindow * const active = activeSubWindow();
294 
295  static const unsigned offsetX = 40;
296  static const unsigned offsetY = 40;
297  const unsigned int windowWidth = width() - (windows.count() - 1) * offsetX;
298  const unsigned int windowHeight = height() - (windows.count() - 1) * offsetY;
299  unsigned int x = 0;
300  unsigned int y = 0;
301 
302  for (auto * const window : windows) {
303  if (window == active) { //leave out the active window which should be the top window
304  continue;
305  }
306  window->showNormal();
307  window->raise(); //make it the on-top-of-window-stack window to make sure they're in the right order
308  window->setGeometry(x, y, windowWidth, windowHeight);
309  x += offsetX;
310  y += offsetY;
311  }
312  active->showNormal();
313  active->setGeometry(x, y, windowWidth, windowHeight);
314  active->raise();
315  active->activateWindow();
316 
317  setUpdatesEnabled(true);
318  }
319 
321 }
322 
324  if (activeSubWindow()) {
325  Q_EMIT sigSetToplevelCaption(activeSubWindow()->windowTitle());
326  }
327  else {
328  Q_EMIT sigSetToplevelCaption(QString());
329  }
330 }
331 
333  //Take care: when new windows are added, they will not appear
334  //in subWindowList() when their ChildAdded-Event is triggered
336  for (auto * const w : subWindowList())
337  if (!w->isHidden())
338  ret.append(w);
339  return ret;
340 }
341 
343 
345 
346 void CMDIArea::highlightTextInActiveWindow(const QString& text, bool caseSensitive) {
347  CDisplayWindow* const displayWindow = getDisplayWindow(activeSubWindow());
348  displayWindow->displayWidget()->highlightText(text, caseSensitive);
349 }
350 
351 void CMDIArea::findTextInActiveWindow(bool const backward)
352 { getDisplayWindow(activeSubWindow())->displayWidget()->findText(backward); }
353 
354 void CMDIArea::resizeEvent(QResizeEvent* e) {
355  /*
356  Do not call QMdiArea::resizeEvent(e) if we are in manual arrangement
357  mode, since this would mess up our layout. Also, don't call it for the
358  automatic arrangement modes that we implement. Call it only for those
359  modes implemented by Qt
360  */
362  QMdiArea::resizeEvent(e);
363  }
364  else if (updatesEnabled()) {
365  // Handle resize for automatic modes that we implement
367  }
368 }
369 
370 //handle events of the client windows to update layout if necessary
371 bool CMDIArea::eventFilter(QObject *o, QEvent *e) {
372  const QMdiSubWindow * const w = qobject_cast<QMdiSubWindow*>(o);
373 
374  // Let the event be handled by other filters:
375  if (w == nullptr)
376  return QMdiArea::eventFilter(o, e);
377 
378  switch (e->type()) {
379  case QEvent::WindowStateChange: {
380  Qt::WindowStates const newState(w->windowState());
381  Qt::WindowStates const oldState(
382  static_cast<QWindowStateChangeEvent *>(e)->oldState());
383 
384  /*
385  Do not handle window activation or deactivation here, it will
386  produce wrong results because this event is handled too early. Let
387  lambda connected to subWindowActivated() handle this.
388  */
389 
390  // Check if subwindow was maximized or un-maximized:
391  if ((newState ^ oldState) & Qt::WindowMaximized) {
393  break;
394  }
395 
396  // Check if subwindow was minimized or de-minimized:
397  if ((newState ^ oldState) & Qt::WindowMinimized) {
399  }
400  break;
401  }
402  case QEvent::Close:
404  break;
405  case QEvent::WindowTitleChange:
406  if (o == activeSubWindow()) {
407  Q_EMIT sigSetToplevelCaption(w->windowTitle());
408  }
409  break;
410  default:
411  break;
412  }
413 
414  return QMdiArea::eventFilter(o, e);
415 }
416 
418  if (updatesEnabled()) {
419  switch (m_mdiArrangementMode) {
421  QTimer::singleShot(0, this, &CMDIArea::myTileVertical);
422  break;
424  QTimer::singleShot(0, this, &CMDIArea::myTileHorizontal);
425  break;
426  case ArrangementModeTile:
427  QTimer::singleShot(0, this, &CMDIArea::myTile);
428  break;
430  QTimer::singleShot(0, this, &CMDIArea::myCascade);
431  break;
432  default:
433  break;
434  }
435  }
436 }
437 
439 {
440  for (auto * const subWindow : subWindowList()) {
441  Qt::WindowFlags flags = subWindow->windowFlags();
442  if (enable) {
443  flags |= (Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint);
444  }
445  else {
446  flags &= ~(Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint);
447  }
448  subWindow->setWindowFlags(flags);
449  }
450 }
451 
#define BT_ASSERT(...)
Definition: btassert.h:17
#define BT_CONNECT(...)
Definition: btconnect.h:20
void clearMdiToolBars()
void findText(bool const backward)
void highlightText(const QString &text, bool caseSensitive)
The base class for all display windows of BibleTime.
BtModelViewReadDisplay * displayWidget() const
MDIArrangementMode
Definition: cmdiarea.h:41
@ ArrangementModeTileVertical
Definition: cmdiarea.h:42
@ ArrangementModeCascade
Definition: cmdiarea.h:44
@ ArrangementModeManual
Definition: cmdiarea.h:45
@ ArrangementModeTabbed
Definition: cmdiarea.h:47
@ ArrangementModeTileHorizontal
Definition: cmdiarea.h:43
@ ArrangementModeTile
Definition: cmdiarea.h:46
CMDIArea(BibleTime *parent)
Definition: cmdiarea.cpp:44
void myCascade()
Definition: cmdiarea.cpp:277
void findTextInActiveWindow(bool const backward)
Definition: cmdiarea.cpp:351
void myTileVertical()
Definition: cmdiarea.cpp:154
bool eventFilter(QObject *o, QEvent *e) override
Definition: cmdiarea.cpp:371
void emitWindowCaptionChanged()
Definition: cmdiarea.cpp:323
void setMDIArrangementMode(const MDIArrangementMode mode)
Definition: cmdiarea.cpp:134
void enableWindowMinMaxFlags(bool enable)
Definition: cmdiarea.cpp:438
void triggerWindowUpdate()
Definition: cmdiarea.cpp:417
void myTile()
Definition: cmdiarea.cpp:222
void highlightTextInActiveWindow(const QString &text, bool caseSensitive)
Definition: cmdiarea.cpp:346
CDisplayWindow * m_activeWindow
Definition: cmdiarea.h:167
void resizeEvent(QResizeEvent *e) override
Definition: cmdiarea.cpp:354
QList< QMdiSubWindow * > usableWindowList() const
Definition: cmdiarea.cpp:332
QMdiSubWindow * addDisplayWindow(CDisplayWindow *displayWindow)
Definition: cmdiarea.cpp:85
void sigSetToplevelCaption(const QString &)
BibleTime * m_bibleTime
Definition: cmdiarea.h:168
void findNextTextInActiveWindow()
Definition: cmdiarea.cpp:342
void findPreviousTextInActiveWindow()
Definition: cmdiarea.cpp:344
MDIArrangementMode m_mdiArrangementMode
Definition: cmdiarea.h:34
void myTileHorizontal()
Definition: cmdiarea.cpp:188
if(plainSearchedText)
CDisplayWindow * getDisplayWindow(const QMdiSubWindow *const mdiWindow)
Definition: cmdiarea.cpp:37