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 "tabordereditor.h" |
30 | |
31 | #include <metadatabase_p.h> |
32 | #include <qdesigner_command_p.h> |
33 | #include <qdesigner_utils_p.h> |
34 | #include <qlayout_widget_p.h> |
35 | #include <orderdialog_p.h> |
36 | |
37 | #include <QtDesigner/qextensionmanager.h> |
38 | #include <QtDesigner/abstractformwindow.h> |
39 | #include <QtDesigner/abstractformwindowcursor.h> |
40 | #include <QtDesigner/abstractformeditor.h> |
41 | #include <QtDesigner/abstractwidgetfactory.h> |
42 | #include <QtDesigner/propertysheet.h> |
43 | |
44 | #include <QtGui/qpainter.h> |
45 | #include <QtGui/qevent.h> |
46 | #include <QtWidgets/qmenu.h> |
47 | #include <QtWidgets/qapplication.h> |
48 | |
49 | Q_DECLARE_METATYPE(QWidgetList) |
50 | |
51 | QT_BEGIN_NAMESPACE |
52 | |
53 | namespace { |
54 | enum { VBOX_MARGIN = 1, HBOX_MARGIN = 4, BG_ALPHA = 32 }; |
55 | } |
56 | |
57 | static QRect fixRect(const QRect &r) |
58 | { |
59 | return QRect(r.x(), r.y(), r.width() - 1, r.height() - 1); |
60 | } |
61 | |
62 | namespace qdesigner_internal { |
63 | |
64 | TabOrderEditor::TabOrderEditor(QDesignerFormWindowInterface *form, QWidget *parent) : |
65 | QWidget(parent), |
66 | m_form_window(form), |
67 | m_bg_widget(nullptr), |
68 | m_undo_stack(form->commandHistory()), |
69 | m_font_metrics(font()), |
70 | m_current_index(0), |
71 | m_beginning(true) |
72 | { |
73 | connect(sender: form, signal: &QDesignerFormWindowInterface::widgetRemoved, receiver: this, slot: &TabOrderEditor::widgetRemoved); |
74 | |
75 | QFont tabFont = font(); |
76 | tabFont.setPointSize(tabFont.pointSize()*2); |
77 | tabFont.setBold(true); |
78 | setFont(tabFont); |
79 | m_font_metrics = QFontMetrics(tabFont); |
80 | setAttribute(Qt::WA_MouseTracking, on: true); |
81 | } |
82 | |
83 | QDesignerFormWindowInterface *TabOrderEditor::formWindow() const |
84 | { |
85 | return m_form_window; |
86 | } |
87 | |
88 | void TabOrderEditor::setBackground(QWidget *background) |
89 | { |
90 | if (background == m_bg_widget) { |
91 | return; |
92 | } |
93 | |
94 | m_bg_widget = background; |
95 | updateBackground(); |
96 | } |
97 | |
98 | void TabOrderEditor::updateBackground() |
99 | { |
100 | if (m_bg_widget == nullptr) { |
101 | // nothing to do |
102 | return; |
103 | } |
104 | |
105 | initTabOrder(); |
106 | update(); |
107 | } |
108 | |
109 | void TabOrderEditor::widgetRemoved(QWidget*) |
110 | { |
111 | initTabOrder(); |
112 | } |
113 | |
114 | void TabOrderEditor::showEvent(QShowEvent *e) |
115 | { |
116 | QWidget::showEvent(event: e); |
117 | updateBackground(); |
118 | } |
119 | |
120 | QRect TabOrderEditor::indicatorRect(int index) const |
121 | { |
122 | if (index < 0 || index >= m_tab_order_list.size()) |
123 | return QRect(); |
124 | |
125 | const QWidget *w = m_tab_order_list.at(i: index); |
126 | const QString text = QString::number(index + 1); |
127 | |
128 | const QPoint tl = mapFromGlobal(w->mapToGlobal(w->rect().topLeft())); |
129 | const QSize size = m_font_metrics.size(flags: Qt::TextSingleLine, str: text); |
130 | QRect r(tl - QPoint(size.width(), size.height())/2, size); |
131 | r = QRect(r.left() - HBOX_MARGIN, r.top() - VBOX_MARGIN, |
132 | r.width() + HBOX_MARGIN*2, r.height() + VBOX_MARGIN*2); |
133 | |
134 | return r; |
135 | } |
136 | |
137 | static bool isWidgetVisible(QWidget *widget) |
138 | { |
139 | while (widget && widget->parentWidget()) { |
140 | if (!widget->isVisibleTo(widget->parentWidget())) |
141 | return false; |
142 | |
143 | widget = widget->parentWidget(); |
144 | } |
145 | |
146 | return true; |
147 | } |
148 | |
149 | void TabOrderEditor::paintEvent(QPaintEvent *e) |
150 | { |
151 | QPainter p(this); |
152 | p.setClipRegion(e->region()); |
153 | |
154 | int cur = m_current_index - 1; |
155 | if (!m_beginning && cur < 0) |
156 | cur = m_tab_order_list.size() - 1; |
157 | |
158 | for (int i = 0; i < m_tab_order_list.size(); ++i) { |
159 | QWidget *widget = m_tab_order_list.at(i); |
160 | if (!isWidgetVisible(widget)) |
161 | continue; |
162 | |
163 | const QRect r = indicatorRect(index: i); |
164 | |
165 | QColor c = Qt::darkGreen; |
166 | if (i == cur) |
167 | c = Qt::red; |
168 | else if (i > cur) |
169 | c = Qt::blue; |
170 | p.setPen(c); |
171 | c.setAlpha(BG_ALPHA); |
172 | p.setBrush(c); |
173 | p.drawRect(r: fixRect(r)); |
174 | |
175 | p.setPen(Qt::white); |
176 | p.drawText(r, text: QString::number(i + 1), o: QTextOption(Qt::AlignCenter)); |
177 | } |
178 | } |
179 | |
180 | bool TabOrderEditor::skipWidget(QWidget *w) const |
181 | { |
182 | if (qobject_cast<QLayoutWidget*>(object: w) |
183 | || w == formWindow()->mainContainer() |
184 | || w->isHidden()) |
185 | return true; |
186 | |
187 | if (!formWindow()->isManaged(widget: w)) { |
188 | return true; |
189 | } |
190 | |
191 | QExtensionManager *ext = formWindow()->core()->extensionManager(); |
192 | if (const QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: ext, object: w)) { |
193 | const int index = sheet->indexOf(QStringLiteral("focusPolicy" )); |
194 | if (index != -1) { |
195 | bool ok = false; |
196 | Qt::FocusPolicy q = (Qt::FocusPolicy) Utils::valueOf(value: sheet->property(index), ok: &ok); |
197 | return !ok || !(q & Qt::TabFocus); |
198 | } |
199 | } |
200 | |
201 | return true; |
202 | } |
203 | |
204 | void TabOrderEditor::initTabOrder() |
205 | { |
206 | m_tab_order_list.clear(); |
207 | |
208 | QDesignerFormEditorInterface *core = formWindow()->core(); |
209 | |
210 | if (const QDesignerMetaDataBaseItemInterface *item = core->metaDataBase()->item(object: formWindow())) { |
211 | m_tab_order_list = item->tabOrder(); |
212 | } |
213 | |
214 | // Remove any widgets that have been removed form the form |
215 | for (int i = 0; i < m_tab_order_list.size(); ) { |
216 | QWidget *w = m_tab_order_list.at(i); |
217 | if (!formWindow()->mainContainer()->isAncestorOf(child: w) || skipWidget(w)) |
218 | m_tab_order_list.removeAt(i); |
219 | else |
220 | ++i; |
221 | } |
222 | |
223 | // Append any widgets that are in the form but are not in the tab order |
224 | QWidgetList childQueue; |
225 | childQueue.append(t: formWindow()->mainContainer()); |
226 | while (!childQueue.isEmpty()) { |
227 | QWidget *child = childQueue.takeFirst(); |
228 | childQueue += qvariant_cast<QWidgetList>(v: child->property(name: "_q_widgetOrder" )); |
229 | |
230 | if (skipWidget(w: child)) |
231 | continue; |
232 | |
233 | if (!m_tab_order_list.contains(t: child)) |
234 | m_tab_order_list.append(t: child); |
235 | } |
236 | |
237 | // Just in case we missed some widgets |
238 | QDesignerFormWindowCursorInterface *cursor = formWindow()->cursor(); |
239 | for (int i = 0; i < cursor->widgetCount(); ++i) { |
240 | |
241 | QWidget *widget = cursor->widget(index: i); |
242 | if (skipWidget(w: widget)) |
243 | continue; |
244 | |
245 | if (!m_tab_order_list.contains(t: widget)) |
246 | m_tab_order_list.append(t: widget); |
247 | } |
248 | |
249 | m_indicator_region = QRegion(); |
250 | for (int i = 0; i < m_tab_order_list.size(); ++i) { |
251 | if (m_tab_order_list.at(i)->isVisible()) |
252 | m_indicator_region |= indicatorRect(index: i); |
253 | } |
254 | |
255 | if (m_current_index >= m_tab_order_list.size()) |
256 | m_current_index = m_tab_order_list.size() - 1; |
257 | if (m_current_index < 0) |
258 | m_current_index = 0; |
259 | } |
260 | |
261 | void TabOrderEditor::mouseMoveEvent(QMouseEvent *e) |
262 | { |
263 | e->accept(); |
264 | #if QT_CONFIG(cursor) |
265 | if (m_indicator_region.contains(p: e->pos())) |
266 | setCursor(Qt::PointingHandCursor); |
267 | else |
268 | setCursor(QCursor()); |
269 | #endif |
270 | } |
271 | |
272 | int TabOrderEditor::widgetIndexAt(const QPoint &pos) const |
273 | { |
274 | int target_index = -1; |
275 | for (int i = 0; i < m_tab_order_list.size(); ++i) { |
276 | if (!m_tab_order_list.at(i)->isVisible()) |
277 | continue; |
278 | if (indicatorRect(index: i).contains(p: pos)) { |
279 | target_index = i; |
280 | break; |
281 | } |
282 | } |
283 | |
284 | return target_index; |
285 | } |
286 | |
287 | void TabOrderEditor::mousePressEvent(QMouseEvent *e) |
288 | { |
289 | e->accept(); |
290 | |
291 | if (!m_indicator_region.contains(p: e->pos())) { |
292 | if (QWidget *child = m_bg_widget->childAt(p: e->pos())) { |
293 | QDesignerFormEditorInterface *core = m_form_window->core(); |
294 | if (core->widgetFactory()->isPassiveInteractor(widget: child)) { |
295 | |
296 | QMouseEvent event(QEvent::MouseButtonPress, |
297 | child->mapFromGlobal(e->globalPos()), |
298 | e->button(), e->buttons(), e->modifiers()); |
299 | |
300 | qApp->sendEvent(receiver: child, event: &event); |
301 | |
302 | QMouseEvent event2(QEvent::MouseButtonRelease, |
303 | child->mapFromGlobal(e->globalPos()), |
304 | e->button(), e->buttons(), e->modifiers()); |
305 | |
306 | qApp->sendEvent(receiver: child, event: &event2); |
307 | |
308 | updateBackground(); |
309 | } |
310 | } |
311 | return; |
312 | } |
313 | |
314 | if (e->button() != Qt::LeftButton) |
315 | return; |
316 | |
317 | const int target_index = widgetIndexAt(pos: e->pos()); |
318 | if (target_index == -1) |
319 | return; |
320 | |
321 | m_beginning = false; |
322 | |
323 | if (e->modifiers() & Qt::ControlModifier) { |
324 | m_current_index = target_index + 1; |
325 | if (m_current_index >= m_tab_order_list.size()) |
326 | m_current_index = 0; |
327 | update(); |
328 | return; |
329 | } |
330 | |
331 | if (m_current_index == -1) |
332 | return; |
333 | |
334 | m_tab_order_list.swapItemsAt(i: target_index, j: m_current_index); |
335 | |
336 | ++m_current_index; |
337 | if (m_current_index == m_tab_order_list.size()) |
338 | m_current_index = 0; |
339 | |
340 | TabOrderCommand *cmd = new TabOrderCommand(formWindow()); |
341 | cmd->init(newTabOrder: m_tab_order_list); |
342 | formWindow()->commandHistory()->push(cmd); |
343 | } |
344 | |
345 | void TabOrderEditor::(QContextMenuEvent *e) |
346 | { |
347 | QMenu (this); |
348 | const int target_index = widgetIndexAt(pos: e->pos()); |
349 | QAction *setIndex = menu.addAction(text: tr(s: "Start from Here" )); |
350 | setIndex->setEnabled(target_index >= 0); |
351 | |
352 | QAction *resetIndex = menu.addAction(text: tr(s: "Restart" )); |
353 | menu.addSeparator(); |
354 | QAction *showDialog = menu.addAction(text: tr(s: "Tab Order List..." )); |
355 | showDialog->setEnabled(m_tab_order_list.size() > 1); |
356 | |
357 | QAction *result = menu.exec(pos: e->globalPos()); |
358 | if (result == resetIndex) { |
359 | m_current_index = 0; |
360 | m_beginning = true; |
361 | update(); |
362 | } else if (result == setIndex) { |
363 | m_beginning = false; |
364 | m_current_index = target_index + 1; |
365 | if (m_current_index >= m_tab_order_list.size()) |
366 | m_current_index = 0; |
367 | update(); |
368 | } else if (result == showDialog) { |
369 | showTabOrderDialog(); |
370 | } |
371 | } |
372 | |
373 | void TabOrderEditor::mouseDoubleClickEvent(QMouseEvent *e) |
374 | { |
375 | if (e->button() != Qt::LeftButton) |
376 | return; |
377 | |
378 | const int target_index = widgetIndexAt(pos: e->pos()); |
379 | if (target_index >= 0) |
380 | return; |
381 | |
382 | m_beginning = true; |
383 | m_current_index = 0; |
384 | update(); |
385 | } |
386 | |
387 | void TabOrderEditor::resizeEvent(QResizeEvent *e) |
388 | { |
389 | updateBackground(); |
390 | QWidget::resizeEvent(event: e); |
391 | } |
392 | |
393 | void TabOrderEditor::showTabOrderDialog() |
394 | { |
395 | if (m_tab_order_list.size() < 2) |
396 | return; |
397 | OrderDialog dlg(this); |
398 | dlg.setWindowTitle(tr(s: "Tab Order List" )); |
399 | dlg.setDescription(tr(s: "Tab Order" )); |
400 | dlg.setFormat(OrderDialog::TabOrderFormat); |
401 | dlg.setPageList(m_tab_order_list); |
402 | |
403 | if (dlg.exec() == QDialog::Rejected) |
404 | return; |
405 | |
406 | const QWidgetList newOrder = dlg.pageList(); |
407 | if (newOrder == m_tab_order_list) |
408 | return; |
409 | |
410 | m_tab_order_list = newOrder; |
411 | TabOrderCommand *cmd = new TabOrderCommand(formWindow()); |
412 | cmd->init(newTabOrder: m_tab_order_list); |
413 | formWindow()->commandHistory()->push(cmd); |
414 | update(); |
415 | } |
416 | |
417 | } |
418 | |
419 | QT_END_NAMESPACE |
420 | |