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 
QDesignerFormWindow(QDesignerFormWindowInterface * editor,QDesignerWorkbench * workbench,QWidget * parent,Qt::WindowFlags flags)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(0xFFF, 0xFFF);
66     QDesignerFormEditorInterface *core = workbench->core();
67 
68     if (m_editor) {
69         m_editor->setParent(this);
70     } else {
71         m_editor = core->formWindowManager()->createFormWindow(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(m_editor->commandHistory(), &QUndoStack::indexChanged, this, &QDesignerFormWindow::updateChanged);
81     connect(m_editor.data(), &QDesignerFormWindowInterface::geometryChanged,
82             this, &QDesignerFormWindow::slotGeometryChanged);
83 }
84 
~QDesignerFormWindow()85 QDesignerFormWindow::~QDesignerFormWindow()
86 {
87     if (workbench())
88         workbench()->removeFormWindow(this);
89 }
90 
action() const91 QAction *QDesignerFormWindow::action() const
92 {
93     return m_action;
94 }
95 
changeEvent(QEvent * e)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(m_editor, isMinimizedNow);
111     }
112         break;
113         default:
114             break;
115     }
116     QWidget::changeEvent(e);
117 }
118 
geometryHint() const119 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 
editor() const130 QDesignerFormWindowInterface *QDesignerFormWindow::editor() const
131 {
132     return m_editor;
133 }
134 
workbench() const135 QDesignerWorkbench *QDesignerFormWindow::workbench() const
136 {
137     return m_workbench;
138 }
139 
firstShow()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(m_editor.data(), &QDesignerFormWindowInterface::fileNameChanged,
147                     this, &QDesignerFormWindow::updateWindowTitle);
148             updateWindowTitle(m_editor->fileName());
149             updateChanged();
150         }
151     }
152     show();
153 }
154 
getNumberOfUntitledWindows() const155 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(i);
169         if (fw != this) {
170             const QString title = m_workbench->formWindow(i)->windowTitle();
171             const QRegularExpressionMatch match = rx.match(title);
172             if (match.hasMatch()) {
173                 if (maxUntitled == 0)
174                     ++maxUntitled;
175                 if (match.lastCapturedIndex() >= 2) {
176                     const auto numberCapture = match.capturedRef(2);
177                     if (!numberCapture.isEmpty())
178                         maxUntitled = qMax(numberCapture.toInt(), maxUntitled);
179                 }
180             }
181         }
182     }
183     return maxUntitled;
184 }
185 
updateWindowTitle(const QString & fileName)186 void QDesignerFormWindow::updateWindowTitle(const QString &fileName)
187 {
188     if (!m_windowTitleInitialized) {
189         m_windowTitleInitialized = true;
190         if (m_editor)
191             connect(m_editor.data(), &QDesignerFormWindowInterface::fileNameChanged,
192                     this, &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("%1 - %2[*]").arg(mc->windowTitle(), fileNameTitle));
209     } else {
210         setWindowTitle(fileNameTitle);
211     }
212 }
213 
closeEvent(QCloseEvent * ev)214 void QDesignerFormWindow::closeEvent(QCloseEvent *ev)
215 {
216     if (m_editor->isDirty()) {
217         raise();
218         QMessageBox box(QMessageBox::Information, tr("Save Form?"),
219                 tr("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("If you don't save, your changes will be lost."));
222         box.setWindowModality(Qt::WindowModal);
223         static_cast<QPushButton *>(box.button(QMessageBox::Save))->setDefault(true);
224 
225         switch (box.exec()) {
226             case QMessageBox::Save: {
227                 bool ok = workbench()->saveForm(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 
updateChanged()243 void QDesignerFormWindow::updateChanged()
244 {
245     // Sometimes called after form window destruction.
246     if (m_editor) {
247         setWindowModified(m_editor->isDirty());
248         updateWindowTitle(m_editor->fileName());
249     }
250 }
251 
resizeEvent(QResizeEvent * rev)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(rev);
261 }
262 
slotGeometryChanged()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*>(core->extensionManager(), object);
274     const int geometryIndex = sheet->indexOf(geometryProperty);
275     if (geometryIndex == -1)
276         return;
277     core->propertyEditor()->setPropertyValue(geometryProperty, sheet->property(geometryIndex));
278 }
279 
280 QT_END_NAMESPACE
281