1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "navigatortreeview.h"
27 
28 #include <qmath.h>
29 
30 #include "navigatorview.h"
31 #include "navigatortreemodel.h"
32 #include "qproxystyle.h"
33 #include "previewtooltip.h"
34 
35 #include <metainfo.h>
36 #include <theme.h>
37 
38 #include <utils/icon.h>
39 #include <utils/utilsicons.h>
40 
41 #include <QLineEdit>
42 #include <QPen>
43 #include <QPixmapCache>
44 #include <QMouseEvent>
45 #include <QPainter>
46 #include <QStyleFactory>
47 #include <QEvent>
48 #include <QImage>
49 
50 namespace QmlDesigner {
51 
52 namespace {
53 
54 // This style basically allows us to span the entire row
55 // including the arrow indicators which would otherwise not be
56 // drawn by the delegate
57 class TableViewStyle : public QProxyStyle
58 {
59 public:
TableViewStyle(QObject * parent)60     TableViewStyle(QObject *parent) : QProxyStyle(QStyleFactory::create("fusion"))
61     {
62         setParent(parent);
63         baseStyle()->setParent(parent);
64     }
65 
drawPrimitive(PrimitiveElement element,const QStyleOption * option,QPainter * painter,const QWidget * widget=nullptr) const66     void drawPrimitive(PrimitiveElement element,
67                        const QStyleOption *option,
68                        QPainter *painter,
69                        const QWidget *widget = nullptr) const override
70     {
71         static QRect mouseOverStateSavedFrameRectangle;
72         if (element == QStyle::PE_PanelItemViewRow) {
73             if (option->state & QStyle::State_MouseOver)
74                 mouseOverStateSavedFrameRectangle = option->rect;
75 
76             painter->fillRect(option->rect.adjusted(0, delegateMargin, 0, -delegateMargin),
77                               Theme::getColor(Theme::Color::DSnavigatorItemBackground));
78         } else if (element == PE_IndicatorItemViewItemDrop) {
79             // between elements and on elements we have a width
80             if (option->rect.width() > 0) {
81                 m_currentTextColor = option->palette.text().color();
82                 QRect frameRectangle = adjustedRectangleToWidgetWidth(option->rect, widget);
83                 painter->save();
84                 if (option->rect.height() == 0) {
85                     bool isNotRootItem = option->rect.top() > 10 && mouseOverStateSavedFrameRectangle.top() > 10;
86                     if (isNotRootItem)
87                         drawIndicatorLine(frameRectangle.topLeft(), frameRectangle.topRight(), painter);
88                 } else {
89                     drawHighlightFrame(frameRectangle, painter);
90                 }
91                 painter->restore();
92             }
93         } else if (element == PE_FrameFocusRect) {
94             // don't draw
95         } else if (element == PE_IndicatorBranch) {
96             painter->save();
97             static const int decoration_size = 10;
98             int mid_h = option->rect.x() + option->rect.width() / 2;
99             int mid_v = option->rect.y() + option->rect.height() / 2;
100             int bef_h = mid_h;
101             int bef_v = mid_v;
102             int aft_h = mid_h;
103             int aft_v = mid_v;
104 
105             QBrush brush(Theme::getColor(Theme::Color::DSnavigatorBranch), Qt::SolidPattern);
106             if (option->state & State_Item) {
107                 if (option->direction == Qt::RightToLeft)
108                     painter->fillRect(option->rect.left(), mid_v, bef_h - option->rect.left(), 1, brush);
109                 else
110                     painter->fillRect(aft_h, mid_v, option->rect.right() - aft_h + 1, 1, brush);
111             }
112             if (option->state & State_Sibling)
113                 painter->fillRect(mid_h, aft_v, 1, option->rect.bottom() - aft_v + 1, brush);
114             if (option->state & (State_Open | State_Children | State_Item | State_Sibling))
115                 painter->fillRect(mid_h, option->rect.y(), 1, bef_v - option->rect.y(), brush);
116             if (option->state & State_Children) {
117                 int delta = decoration_size / 2;
118                 bef_h -= delta;
119                 bef_v -= delta;
120                 //aft_h += delta;
121                 //aft_v += delta;
122 
123                 const QRectF rect(bef_h, bef_v, decoration_size + 1, decoration_size + 1);
124                 painter->fillRect(rect, QBrush(Theme::getColor(Theme::Color::DSpanelBackground)));
125 
126                 static const QPointF collapsePoints[3] = {
127                     QPointF(0.0, 0.0),
128                     QPointF(4.0, 4.0),
129                     QPointF(0.0, 8.0)
130                 };
131 
132                 static const QPointF expandPoints[3] = {
133                     QPointF(0.0, 0.0),
134                     QPointF(8.0, 0.0),
135                     QPointF(4.0, 4.0)
136                 };
137 
138                 auto color = Theme::getColor(Theme::Color::DSnavigatorBranchIndicator);
139                 painter->setPen(color);
140                 painter->setBrush(color);
141 
142                 if (option->state & QStyle::State_Open) {
143                     painter->translate(QPointF(mid_h - 4, mid_v - 2));
144                     painter->drawConvexPolygon(expandPoints, 3);
145                 } else {
146                     painter->translate(QPointF(mid_h - 2, mid_v - 4));
147                     painter->drawConvexPolygon(collapsePoints, 3);
148                 }
149             }
150             painter->restore();
151 
152         } else {
153             QProxyStyle::drawPrimitive(element, option, painter, widget);
154         }
155     }
156 
styleHint(StyleHint hint,const QStyleOption * option=nullptr,const QWidget * widget=nullptr,QStyleHintReturn * returnData=nullptr) const157     int styleHint(StyleHint hint,
158                   const QStyleOption *option = nullptr,
159                   const QWidget *widget = nullptr,
160                   QStyleHintReturn *returnData = nullptr) const override
161     {
162         if (hint == SH_ItemView_ShowDecorationSelected)
163             return 0;
164         else
165             return QProxyStyle::styleHint(hint, option, widget, returnData);
166     }
167 
168 private: // functions
highlightBrushColor() const169     QColor highlightBrushColor() const
170     {
171         QColor color = Theme::getColor(Theme::Color::DSnavigatorDropIndicatorBackground);
172         color.setAlphaF(0.7f);
173         return color;
174     }
highlightLineColor() const175     QColor highlightLineColor() const
176     {
177         QColor color = Theme::getColor(Theme::Color::DSnavigatorDropIndicatorOutline);
178         color.setAlphaF(0.7f);
179         return color;
180     }
181 
drawHighlightFrame(const QRect & frameRectangle,QPainter * painter) const182     void drawHighlightFrame(const QRect &frameRectangle, QPainter *painter) const
183     {
184         painter->setPen(QPen(highlightLineColor(), 2));
185         painter->setBrush(highlightBrushColor());
186         painter->drawRect(frameRectangle);
187     }
188 
drawIndicatorLine(const QPoint & leftPoint,const QPoint & rightPoint,QPainter * painter) const189     void drawIndicatorLine(const QPoint &leftPoint, const QPoint &rightPoint, QPainter *painter) const
190     {
191         painter->setPen(QPen(highlightLineColor(), 3));
192         painter->drawLine(leftPoint, rightPoint);
193     }
194 
adjustedRectangleToWidgetWidth(const QRect & originalRectangle,const QWidget * widget) const195     QRect adjustedRectangleToWidgetWidth(const QRect &originalRectangle, const QWidget *widget) const
196     {
197         QRect adjustesRectangle = originalRectangle;
198         adjustesRectangle.setLeft(0);
199         adjustesRectangle.setWidth(widget->rect().width());
200         return adjustesRectangle.adjusted(0, 0, -1, -1);
201     }
202 private: // variables
203     mutable QColor m_currentTextColor;
204 };
205 
206 }
207 
NavigatorTreeView(QWidget * parent)208 NavigatorTreeView::NavigatorTreeView(QWidget *parent)
209     : QTreeView(parent)
210 {
211     setStyle(new TableViewStyle(this));
212     setMinimumWidth(240);
213     setRootIsDecorated(false);
214     setIndentation(indentation() * 0.5);
215     viewport()->setAttribute(Qt::WA_Hover);
216 }
217 
drawSelectionBackground(QPainter * painter,const QStyleOption & option)218 void NavigatorTreeView::drawSelectionBackground(QPainter *painter, const QStyleOption &option)
219 {
220     painter->save();
221     painter->fillRect(option.rect.adjusted(0, delegateMargin, 0, -delegateMargin),
222                       Theme::getColor(Theme::Color::DSnavigatorItemBackgroundSelected));
223     painter->restore();
224 }
225 
viewportEvent(QEvent * event)226 bool NavigatorTreeView::viewportEvent(QEvent *event)
227 {
228     const QPoint offset(10, 5);
229 
230     if (event->type() == QEvent::ToolTip) {
231         auto navModel = qobject_cast<NavigatorTreeModel *>(model());
232         if (navModel) {
233             QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
234             QModelIndex index = indexAt(helpEvent->pos());
235             QVariantMap imgMap = navModel->data(index, ToolTipImageRole).toMap();
236 
237             if (!imgMap.isEmpty()) {
238                 m_previewToolTipNodeId = index.internalId();
239                 if (!m_previewToolTip || devicePixelRatioF() != m_previewToolTip->devicePixelRatioF()) {
240                     if (!m_previewToolTip) {
241                         connect(navModel, &NavigatorTreeModel::toolTipPixmapUpdated,
242                                 [this](const QString &id, const QPixmap &pixmap) {
243                             if (m_previewToolTip && m_previewToolTip->id() == id)
244                                 m_previewToolTip->setPixmap(pixmap);
245                         });
246                     } else {
247                         delete m_previewToolTip;
248                     }
249                     m_previewToolTip = new PreviewToolTip();
250                 }
251                 m_previewToolTip->setId(imgMap["id"].toString());
252                 m_previewToolTip->setType(imgMap["type"].toString());
253                 m_previewToolTip->setInfo(imgMap["info"].toString());
254                 m_previewToolTip->setPixmap(imgMap["pixmap"].value<QPixmap>());
255                 m_previewToolTip->move(helpEvent->globalPos() + offset);
256                 if (!m_previewToolTip->isVisible())
257                     m_previewToolTip->show();
258             } else if (m_previewToolTip) {
259                 m_previewToolTip->hide();
260                 m_previewToolTipNodeId = -1;
261             }
262         }
263     } else if (event->type() == QEvent::Leave) {
264         if (m_previewToolTip) {
265             m_previewToolTip->hide();
266             m_previewToolTipNodeId = -1;
267         }
268     } else if (event->type() == QEvent::HoverMove) {
269         if (m_previewToolTip && m_previewToolTip->isVisible()) {
270             auto *he = static_cast<QHoverEvent *>(event);
271             QModelIndex index = indexAt(he->pos());
272             if (!index.isValid() || index.internalId() != quintptr(m_previewToolTipNodeId)) {
273                 m_previewToolTip->hide();
274                 m_previewToolTipNodeId = -1;
275             } else {
276                 m_previewToolTip->move(mapToGlobal(he->pos()) + offset);
277             }
278         }
279     }
280 
281     return QTreeView::viewportEvent(event);
282 }
283 
284 }
285