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 void CMDIArea::fixSystemMenu(QMdiSubWindow* subWindow) {
86  // Change Qt QMdiSubWindow Close action to have no shortcuts
87  // This makes our closeWindow actions with Ctrl-W work correctly
88  for (auto * const action : subWindow->systemMenu()->actions()) {
89  if (action->text().contains(QStringLiteral("Close"))) {
90  action->setShortcuts(QList<QKeySequence>());
91  break;
92  }
93  }
94 }
95 
96 QMdiSubWindow * CMDIArea::addSubWindow(QWidget * widget,
97  Qt::WindowFlags windowFlags)
98 {
99  QMdiSubWindow * const subWindow = QMdiArea::addSubWindow(widget, windowFlags);
100  subWindow->installEventFilter(this);
101  fixSystemMenu(subWindow);
102 
103  // Manual arrangement mode
106  // Note that the window size/maximization may be changed later by a session restore.
107  // If we already have an active window, make the new one simular to it
108  if (activeSubWindow()) {
109  if (activeSubWindow()->isMaximized()) {
110  subWindow->showMaximized(); // Maximize the new window
111  } else {
112  // Make new window the same size as the active window and move it slightly.
113  subWindow->resize(activeSubWindow()->size());
114  QRect subWinGeom = activeSubWindow()->geometry();
115  static const int MOVESIZE = 30;
116  subWinGeom.translate(MOVESIZE, MOVESIZE);
117  // If it goes off screen, move it almost to the top left
118  if (!frameRect().contains(subWinGeom))
119  subWinGeom.moveTo(MOVESIZE, MOVESIZE);
120  subWindow->setGeometry(subWinGeom);
121  }
122  } else {
123  //set the window to be big enough
124  subWindow->resize(400, 400);
125  }
126  subWindow->raise();
127  } else {
128  // Automatic arrangement modes
131  }
132  return subWindow;
133 }
134 
135 void CMDIArea::setMDIArrangementMode( const MDIArrangementMode newArrangementMode ) {
136  m_mdiArrangementMode = newArrangementMode;
137  switch (m_mdiArrangementMode) {
139  setViewMode(QMdiArea::SubWindowView);
140  break;
141  case ArrangementModeTile:
142  setViewMode(QMdiArea::SubWindowView);
143  myTile();
144  break;
146  setViewMode(QMdiArea::TabbedView);
147  break;
148  default:
149  setViewMode(QMdiArea::SubWindowView);
151  break;
152  }
153 }
154 
155 void CMDIArea::closeTab(int i) {
156  QMdiSubWindow *sub = subWindowList()[i];
157  QWidget *win = sub->widget();
158  win->close();
159  setActiveSubWindow(sub);
160  closeActiveSubWindow();
161 }
162 
164  if (!updatesEnabled()) {
165  return;
166  }
168  if (windows.isEmpty()) {
169  return;
170  }
171  setViewMode(QMdiArea::SubWindowView);
172 
173  setUpdatesEnabled(false);
174 
175  QMdiSubWindow * const active = activeSubWindow();
176 
177  const int widthForEach = width() / windows.count();
178  int x = 0;
179  for (auto * const window : windows) {
180  window->showNormal();
181 
182  const int preferredWidth = window->minimumWidth() + window->baseSize().width();
183  const int actWidth = qMax(widthForEach, preferredWidth);
184 
185  window->setGeometry(x, 0, actWidth, height());
186  x += actWidth;
187  }
188 
189  if (active != nullptr) {
190  active->setFocus();
191  }
192 
193  setUpdatesEnabled(true);
195 }
196 
198  if (!updatesEnabled()) {
199  return;
200  }
202  if (windows.isEmpty()) {
203  return;
204  }
205  setViewMode(QMdiArea::SubWindowView);
206 
207  setUpdatesEnabled(false);
208  QMdiSubWindow * const active = activeSubWindow();
209 
210  const int heightForEach = height() / windows.count();
211  int y = 0;
212  for (auto * const window : windows) {
213  window->showNormal();
214 
215  const int preferredHeight = window->minimumHeight() + window->baseSize().height();
216  const int actHeight = qMax(heightForEach, preferredHeight);
217 
218  window->setGeometry( 0, y, width(), actHeight );
219  y += actHeight;
220  }
221 
222  if (active != nullptr) {
223  active->setFocus();
224  }
225 
226  setUpdatesEnabled(true);
228 }
229 
230 // Tile the windows
232  if (!updatesEnabled()) {
233  return;
234  }
236  if (windows.isEmpty()) {
237  return;
238  }
239  setViewMode(QMdiArea::SubWindowView);
240 
241  setUpdatesEnabled(false);
242  QMdiSubWindow * const active = activeSubWindow();
243 
244  const QRect domain = contentsRect();
245  const int n = windows.size();
246  const int ncols = qMax(qCeil(qSqrt(qreal(n))), 1);
247  const int nrows = qMax((n % ncols) ? (n / ncols + 1) : (n / ncols), 1);
248  const int nspecial = (n % ncols) ? (ncols - n % ncols) : 0;
249  const int dx = domain.width() / ncols;
250  const int dy = domain.height() / nrows;
251 
252  int i = 0;
253  for (int row = 0; row < nrows; ++row) {
254  const int y1 = int(row * (dy + 1));
255  for (int col = 0; col < ncols; ++col) {
256  if (row == 1 && col < nspecial)
257  continue;
258  const int x1 = int(col * (dx + 1));
259  int x2 = int(x1 + dx);
260  int y2 = int(y1 + dy);
261  if (row == 0 && col < nspecial) {
262  y2 *= 2;
263  if (nrows != 2)
264  y2 += 1;
265  else
266  y2 = domain.bottom();
267  }
268  if (col == ncols - 1 && x2 != domain.right())
269  x2 = domain.right();
270  if (row == nrows - 1 && y2 != domain.bottom())
271  y2 = domain.bottom();
272  QWidget *widget = windows.at(i++);
273  QRect newGeometry = QRect(QPoint(x1, y1), QPoint(x2, y2));
274  widget->setGeometry(QStyle::visualRect(widget->layoutDirection(), domain, newGeometry));
275  }
276  }
277 
278  if (active != nullptr) {
279  active->setFocus();
280  }
281 
282  setUpdatesEnabled(true);
284 }
285 
287  if (!updatesEnabled()) {
288  return;
289  }
291  if (windows.isEmpty()) {
292  return;
293  }
294  setViewMode(QMdiArea::SubWindowView);
295 
296  if (windows.count() == 1) {
297  windows.first()->showMaximized();
298  }
299  else {
300  setUpdatesEnabled(false);
301 
302  QMdiSubWindow * const active = activeSubWindow();
303 
304  static const unsigned offsetX = 40;
305  static const unsigned offsetY = 40;
306  const unsigned int windowWidth = width() - (windows.count() - 1) * offsetX;
307  const unsigned int windowHeight = height() - (windows.count() - 1) * offsetY;
308  unsigned int x = 0;
309  unsigned int y = 0;
310 
311  for (auto * const window : windows) {
312  if (window == active) { //leave out the active window which should be the top window
313  continue;
314  }
315  window->showNormal();
316  window->raise(); //make it the on-top-of-window-stack window to make sure they're in the right order
317  window->setGeometry(x, y, windowWidth, windowHeight);
318  x += offsetX;
319  y += offsetY;
320  }
321  active->showNormal();
322  active->setGeometry(x, y, windowWidth, windowHeight);
323  active->raise();
324  active->activateWindow();
325 
326  setUpdatesEnabled(true);
327  }
328 
330 }
331 
333  if (activeSubWindow()) {
334  Q_EMIT sigSetToplevelCaption(activeSubWindow()->windowTitle());
335  }
336  else {
337  Q_EMIT sigSetToplevelCaption(QString());
338  }
339 }
340 
342  //Take care: when new windows are added, they will not appear
343  //in subWindowList() when their ChildAdded-Event is triggered
345  for (auto * const w : subWindowList())
346  if (!w->isHidden())
347  ret.append(w);
348  return ret;
349 }
350 
352 
354 
355 void CMDIArea::highlightTextInActiveWindow(const QString& text, bool caseSensitive) {
356  CDisplayWindow* const displayWindow = getDisplayWindow(activeSubWindow());
357  displayWindow->displayWidget()->highlightText(text, caseSensitive);
358 }
359 
360 void CMDIArea::findTextInActiveWindow(bool const backward)
361 { getDisplayWindow(activeSubWindow())->displayWidget()->findText(backward); }
362 
363 void CMDIArea::resizeEvent(QResizeEvent* e) {
364  /*
365  Do not call QMdiArea::resizeEvent(e) if we are in manual arrangement
366  mode, since this would mess up our layout. Also, don't call it for the
367  automatic arrangement modes that we implement. Call it only for those
368  modes implemented by Qt
369  */
371  QMdiArea::resizeEvent(e);
372  }
373  else if (updatesEnabled()) {
374  // Handle resize for automatic modes that we implement
376  }
377 }
378 
379 //handle events of the client windows to update layout if necessary
380 bool CMDIArea::eventFilter(QObject *o, QEvent *e) {
381  const QMdiSubWindow * const w = qobject_cast<QMdiSubWindow*>(o);
382 
383  // Let the event be handled by other filters:
384  if (w == nullptr)
385  return QMdiArea::eventFilter(o, e);
386 
387  switch (e->type()) {
388  case QEvent::WindowStateChange: {
389  Qt::WindowStates const newState(w->windowState());
390  Qt::WindowStates const oldState(
391  static_cast<QWindowStateChangeEvent *>(e)->oldState());
392 
393  /*
394  Do not handle window activation or deactivation here, it will
395  produce wrong results because this event is handled too early. Let
396  lambda connected to subWindowActivated() handle this.
397  */
398 
399  // Check if subwindow was maximized or un-maximized:
400  if ((newState ^ oldState) & Qt::WindowMaximized) {
402  break;
403  }
404 
405  // Check if subwindow was minimized or de-minimized:
406  if ((newState ^ oldState) & Qt::WindowMinimized) {
408  }
409  break;
410  }
411  case QEvent::Close:
413  break;
414  case QEvent::WindowTitleChange:
415  if (o == activeSubWindow()) {
416  Q_EMIT sigSetToplevelCaption(w->windowTitle());
417  }
418  return QMdiArea::eventFilter(o, e);
419  break;
420  default:
421  break;
422  }
423 
424  return false; // Don't filter the event out
425 }
426 
428  if (updatesEnabled()) {
429  switch (m_mdiArrangementMode) {
431  QTimer::singleShot(0, this, &CMDIArea::myTileVertical);
432  break;
434  QTimer::singleShot(0, this, &CMDIArea::myTileHorizontal);
435  break;
436  case ArrangementModeTile:
437  QTimer::singleShot(0, this, &CMDIArea::myTile);
438  break;
440  QTimer::singleShot(0, this, &CMDIArea::myCascade);
441  break;
442  default:
443  break;
444  }
445  }
446 }
447 
449 {
450  for (auto * const subWindow : subWindowList()) {
451  Qt::WindowFlags flags = subWindow->windowFlags();
452  if (enable) {
453  flags |= (Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint);
454  }
455  else {
456  flags &= ~(Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint);
457  }
458  subWindow->setWindowFlags(flags);
459  }
460 }
461 
#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:286
void findTextInActiveWindow(bool const backward)
Definition: cmdiarea.cpp:360
void fixSystemMenu(QMdiSubWindow *subWindow)
Definition: cmdiarea.cpp:85
void myTileVertical()
Definition: cmdiarea.cpp:163
bool eventFilter(QObject *o, QEvent *e) override
Definition: cmdiarea.cpp:380
void emitWindowCaptionChanged()
Definition: cmdiarea.cpp:332
void setMDIArrangementMode(const MDIArrangementMode mode)
Definition: cmdiarea.cpp:135
void closeTab(int i)
Definition: cmdiarea.cpp:155
void enableWindowMinMaxFlags(bool enable)
Definition: cmdiarea.cpp:448
void triggerWindowUpdate()
Definition: cmdiarea.cpp:427
void myTile()
Definition: cmdiarea.cpp:231
void highlightTextInActiveWindow(const QString &text, bool caseSensitive)
Definition: cmdiarea.cpp:355
CDisplayWindow * m_activeWindow
Definition: cmdiarea.h:180
void resizeEvent(QResizeEvent *e) override
Definition: cmdiarea.cpp:363
QList< QMdiSubWindow * > usableWindowList() const
Definition: cmdiarea.cpp:341
void sigSetToplevelCaption(const QString &)
BibleTime * m_bibleTime
Definition: cmdiarea.h:181
void findNextTextInActiveWindow()
Definition: cmdiarea.cpp:351
void findPreviousTextInActiveWindow()
Definition: cmdiarea.cpp:353
MDIArrangementMode m_mdiArrangementMode
Definition: cmdiarea.h:34
void myTileHorizontal()
Definition: cmdiarea.cpp:197
QMdiSubWindow * addSubWindow(QWidget *widget, Qt::WindowFlags windowFlags=Qt::WindowFlags())
Definition: cmdiarea.cpp:96
if(plainSearchedText)
CDisplayWindow * getDisplayWindow(const QMdiSubWindow *const mdiWindow)
Definition: cmdiarea.cpp:37