1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Designer of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include "qdesigner_formwindow.h" |
30 | #include "qdesigner_workbench.h" |
31 | #include "formwindowbase_p.h" |
32 | |
33 | // sdk |
34 | #include <QtDesigner/abstractformwindow.h> |
35 | #include <QtDesigner/abstractformeditor.h> |
36 | #include <QtDesigner/propertysheet.h> |
37 | #include <QtDesigner/abstractpropertyeditor.h> |
38 | #include <QtDesigner/abstractformwindowmanager.h> |
39 | #include <QtDesigner/taskmenu.h> |
40 | #include <QtDesigner/qextensionmanager.h> |
41 | |
42 | #include <QtCore/qfile.h> |
43 | #include <QtCore/qregularexpression.h> |
44 | |
45 | #include <QtWidgets/qaction.h> |
46 | #include <QtWidgets/qfiledialog.h> |
47 | #include <QtWidgets/qmessagebox.h> |
48 | #include <QtWidgets/qpushbutton.h> |
49 | #include <QtWidgets/qboxlayout.h> |
50 | #include <QtWidgets/qundostack.h> |
51 | |
52 | #include <QtGui/qevent.h> |
53 | QT_BEGIN_NAMESPACE |
54 | |
55 | QDesignerFormWindow::QDesignerFormWindow(QDesignerFormWindowInterface *editor, QDesignerWorkbench *workbench, QWidget *parent, Qt::WindowFlags flags) |
56 | : QWidget(parent, flags), |
57 | m_editor(editor), |
58 | m_workbench(workbench), |
59 | m_action(new QAction(this)), |
60 | m_initialized(false), |
61 | m_windowTitleInitialized(false) |
62 | { |
63 | Q_ASSERT(workbench); |
64 | |
65 | setMaximumSize(maxw: 0xFFF, maxh: 0xFFF); |
66 | QDesignerFormEditorInterface *core = workbench->core(); |
67 | |
68 | if (m_editor) { |
69 | m_editor->setParent(this); |
70 | } else { |
71 | m_editor = core->formWindowManager()->createFormWindow(parentWidget: this); |
72 | } |
73 | |
74 | QVBoxLayout *l = new QVBoxLayout(this); |
75 | l->setContentsMargins(QMargins()); |
76 | l->addWidget(m_editor); |
77 | |
78 | m_action->setCheckable(true); |
79 | |
80 | connect(sender: m_editor->commandHistory(), signal: &QUndoStack::indexChanged, receiver: this, slot: &QDesignerFormWindow::updateChanged); |
81 | connect(sender: m_editor.data(), signal: &QDesignerFormWindowInterface::geometryChanged, |
82 | receiver: this, slot: &QDesignerFormWindow::slotGeometryChanged); |
83 | } |
84 | |
85 | QDesignerFormWindow::~QDesignerFormWindow() |
86 | { |
87 | if (workbench()) |
88 | workbench()->removeFormWindow(formWindow: this); |
89 | } |
90 | |
91 | QAction *QDesignerFormWindow::action() const |
92 | { |
93 | return m_action; |
94 | } |
95 | |
96 | void QDesignerFormWindow::changeEvent(QEvent *e) |
97 | { |
98 | switch (e->type()) { |
99 | case QEvent::WindowTitleChange: |
100 | m_action->setText(windowTitle().remove(QStringLiteral("[*]" ))); |
101 | break; |
102 | case QEvent::WindowIconChange: |
103 | m_action->setIcon(windowIcon()); |
104 | break; |
105 | case QEvent::WindowStateChange: { |
106 | const QWindowStateChangeEvent *wsce = static_cast<const QWindowStateChangeEvent *>(e); |
107 | const bool wasMinimized = Qt::WindowMinimized & wsce->oldState(); |
108 | const bool isMinimizedNow = isMinimized(); |
109 | if (wasMinimized != isMinimizedNow ) |
110 | emit minimizationStateChanged(formWindow: m_editor, minimized: isMinimizedNow); |
111 | } |
112 | break; |
113 | default: |
114 | break; |
115 | } |
116 | QWidget::changeEvent(e); |
117 | } |
118 | |
119 | QRect QDesignerFormWindow::geometryHint() const |
120 | { |
121 | const QPoint point(0, 0); |
122 | // If we have a container, we want to be just as big. |
123 | // QMdiSubWindow attempts to resize its children to sizeHint() when switching user interface modes. |
124 | if (QWidget *mainContainer = m_editor->mainContainer()) |
125 | return QRect(point, mainContainer->size()); |
126 | |
127 | return QRect(point, sizeHint()); |
128 | } |
129 | |
130 | QDesignerFormWindowInterface *QDesignerFormWindow::editor() const |
131 | { |
132 | return m_editor; |
133 | } |
134 | |
135 | QDesignerWorkbench *QDesignerFormWindow::workbench() const |
136 | { |
137 | return m_workbench; |
138 | } |
139 | |
140 | void QDesignerFormWindow::firstShow() |
141 | { |
142 | // Set up handling of file name changes and set initial title. |
143 | if (!m_windowTitleInitialized) { |
144 | m_windowTitleInitialized = true; |
145 | if (m_editor) { |
146 | connect(sender: m_editor.data(), signal: &QDesignerFormWindowInterface::fileNameChanged, |
147 | receiver: this, slot: &QDesignerFormWindow::updateWindowTitle); |
148 | updateWindowTitle(fileName: m_editor->fileName()); |
149 | updateChanged(); |
150 | } |
151 | } |
152 | show(); |
153 | } |
154 | |
155 | int QDesignerFormWindow::getNumberOfUntitledWindows() const |
156 | { |
157 | const int totalWindows = m_workbench->formWindowCount(); |
158 | if (!totalWindows) |
159 | return 0; |
160 | |
161 | int maxUntitled = 0; |
162 | // Find the number of untitled windows excluding ourselves. |
163 | // Do not fall for 'untitled.ui', match with modified place holder. |
164 | // This will cause some problems with i18n, but for now I need the string to be "static" |
165 | static const QRegularExpression rx(QStringLiteral("untitled( (\\d+))?\\[\\*\\]$" )); |
166 | Q_ASSERT(rx.isValid()); |
167 | for (int i = 0; i < totalWindows; ++i) { |
168 | QDesignerFormWindow *fw = m_workbench->formWindow(index: i); |
169 | if (fw != this) { |
170 | const QString title = m_workbench->formWindow(index: i)->windowTitle(); |
171 | const QRegularExpressionMatch match = rx.match(subject: title); |
172 | if (match.hasMatch()) { |
173 | if (maxUntitled == 0) |
174 | ++maxUntitled; |
175 | if (match.lastCapturedIndex() >= 2) { |
176 | const auto numberCapture = match.capturedRef(nth: 2); |
177 | if (!numberCapture.isEmpty()) |
178 | maxUntitled = qMax(a: numberCapture.toInt(), b: maxUntitled); |
179 | } |
180 | } |
181 | } |
182 | } |
183 | return maxUntitled; |
184 | } |
185 | |
186 | void QDesignerFormWindow::updateWindowTitle(const QString &fileName) |
187 | { |
188 | if (!m_windowTitleInitialized) { |
189 | m_windowTitleInitialized = true; |
190 | if (m_editor) |
191 | connect(sender: m_editor.data(), signal: &QDesignerFormWindowInterface::fileNameChanged, |
192 | receiver: this, slot: &QDesignerFormWindow::updateWindowTitle); |
193 | } |
194 | |
195 | QString fileNameTitle; |
196 | if (fileName.isEmpty()) { |
197 | fileNameTitle = QStringLiteral("untitled" ); |
198 | if (const int maxUntitled = getNumberOfUntitledWindows()) { |
199 | fileNameTitle += QLatin1Char(' '); |
200 | fileNameTitle += QString::number(maxUntitled + 1); |
201 | } |
202 | } else { |
203 | fileNameTitle = QFileInfo(fileName).fileName(); |
204 | } |
205 | |
206 | if (const QWidget *mc = m_editor->mainContainer()) { |
207 | setWindowIcon(mc->windowIcon()); |
208 | setWindowTitle(tr(s: "%1 - %2[*]" ).arg(args: mc->windowTitle(), args&: fileNameTitle)); |
209 | } else { |
210 | setWindowTitle(fileNameTitle); |
211 | } |
212 | } |
213 | |
214 | void QDesignerFormWindow::closeEvent(QCloseEvent *ev) |
215 | { |
216 | if (m_editor->isDirty()) { |
217 | raise(); |
218 | QMessageBox box(QMessageBox::Information, tr(s: "Save Form?" ), |
219 | tr(s: "Do you want to save the changes to this document before closing?" ), |
220 | QMessageBox::Discard | QMessageBox::Cancel | QMessageBox::Save, m_editor); |
221 | box.setInformativeText(tr(s: "If you don't save, your changes will be lost." )); |
222 | box.setWindowModality(Qt::WindowModal); |
223 | static_cast<QPushButton *>(box.button(which: QMessageBox::Save))->setDefault(true); |
224 | |
225 | switch (box.exec()) { |
226 | case QMessageBox::Save: { |
227 | bool ok = workbench()->saveForm(fw: m_editor); |
228 | ev->setAccepted(ok); |
229 | m_editor->setDirty(!ok); |
230 | break; |
231 | } |
232 | case QMessageBox::Discard: |
233 | m_editor->setDirty(false); // Not really necessary, but stops problems if we get close again. |
234 | ev->accept(); |
235 | break; |
236 | case QMessageBox::Cancel: |
237 | ev->ignore(); |
238 | break; |
239 | } |
240 | } |
241 | } |
242 | |
243 | void QDesignerFormWindow::updateChanged() |
244 | { |
245 | // Sometimes called after form window destruction. |
246 | if (m_editor) { |
247 | setWindowModified(m_editor->isDirty()); |
248 | updateWindowTitle(fileName: m_editor->fileName()); |
249 | } |
250 | } |
251 | |
252 | void QDesignerFormWindow::resizeEvent(QResizeEvent *rev) |
253 | { |
254 | if(m_initialized) { |
255 | m_editor->setDirty(true); |
256 | setWindowModified(true); |
257 | } |
258 | |
259 | m_initialized = true; |
260 | QWidget::resizeEvent(event: rev); |
261 | } |
262 | |
263 | void QDesignerFormWindow::slotGeometryChanged() |
264 | { |
265 | // If the form window changes, re-update the geometry of the current widget in the property editor. |
266 | // Note that in the case of layouts, non-maincontainer widgets must also be updated, |
267 | // so, do not do it for the main container only |
268 | const QDesignerFormEditorInterface *core = m_editor->core(); |
269 | QObject *object = core->propertyEditor()->object(); |
270 | if (object == nullptr || !object->isWidgetType()) |
271 | return; |
272 | static const QString geometryProperty = QStringLiteral("geometry" ); |
273 | const QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: core->extensionManager(), object); |
274 | const int geometryIndex = sheet->indexOf(name: geometryProperty); |
275 | if (geometryIndex == -1) |
276 | return; |
277 | core->propertyEditor()->setPropertyValue(name: geometryProperty, value: sheet->property(index: geometryIndex)); |
278 | } |
279 | |
280 | QT_END_NAMESPACE |
281 | |