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-2025 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
35namespace {
36
37inline 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
85QMdiSubWindow * 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
134void CMDIArea::setMDIArrangementMode( const MDIArrangementMode newArrangementMode ) {
135 m_mdiArrangementMode = newArrangementMode;
136 switch (m_mdiArrangementMode) {
138 setViewMode(QMdiArea::SubWindowView);
139 break;
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 }
158 QList<QMdiSubWindow*> windows = usableWindowList();
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 }
192 QList<QMdiSubWindow*> windows = usableWindowList();
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 }
226 QList<QMdiSubWindow*> windows = usableWindowList();
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 }
281 QList<QMdiSubWindow*> windows = usableWindowList();
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
332QList<QMdiSubWindow*> CMDIArea::usableWindowList() const {
333 //Take care: when new windows are added, they will not appear
334 //in subWindowList() when their ChildAdded-Event is triggered
335 QList<QMdiSubWindow*> ret;
336 for (auto * const w : subWindowList())
337 if (!w->isHidden())
338 ret.append(w);
339 return ret;
340}
341
343
345
346void CMDIArea::highlightTextInActiveWindow(const QString& text, bool caseSensitive) {
347 CDisplayWindow* const displayWindow = getDisplayWindow(activeSubWindow());
348 displayWindow->displayWidget()->highlightText(text, caseSensitive);
349}
350
351void CMDIArea::findTextInActiveWindow(bool const backward)
352{ getDisplayWindow(activeSubWindow())->displayWidget()->findText(backward); }
353
354void 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
371bool 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;
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 noexcept
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
CDisplayWindow * getDisplayWindow(const QMdiSubWindow *const mdiWindow)
Definition cmdiarea.cpp:37