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 QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
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 Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qcssutil_p.h"
41#include "private/qcssparser_p.h"
42#include "qpainter.h"
43#include <qmath.h>
44
45#ifndef QT_NO_CSSPARSER
46
47QT_BEGIN_NAMESPACE
48
49using namespace QCss;
50
51static QPen qPenFromStyle(const QBrush& b, qreal width, BorderStyle s)
52{
53 Qt::PenStyle ps = Qt::NoPen;
54
55 switch (s) {
56 case BorderStyle_Dotted:
57 ps = Qt::DotLine;
58 break;
59 case BorderStyle_Dashed:
60 ps = width == 1 ? Qt::DotLine : Qt::DashLine;
61 break;
62 case BorderStyle_DotDash:
63 ps = Qt::DashDotLine;
64 break;
65 case BorderStyle_DotDotDash:
66 ps = Qt::DashDotDotLine;
67 break;
68 case BorderStyle_Inset:
69 case BorderStyle_Outset:
70 case BorderStyle_Solid:
71 ps = Qt::SolidLine;
72 break;
73 default:
74 break;
75 }
76
77 return QPen(b, width, ps, Qt::FlatCap);
78}
79
80void qDrawRoundedCorners(QPainter *p, qreal x1, qreal y1, qreal x2, qreal y2,
81 const QSizeF& r1, const QSizeF& r2,
82 Edge edge, BorderStyle s, QBrush c)
83{
84 const qreal pw = (edge == TopEdge || edge == BottomEdge) ? y2-y1 : x2-x1;
85 if (s == BorderStyle_Double) {
86 qreal wby3 = pw/3;
87 switch (edge) {
88 case TopEdge:
89 case BottomEdge:
90 qDrawRoundedCorners(p, x1, y1, x2, y1+wby3, r1, r2, edge, BorderStyle_Solid, c);
91 qDrawRoundedCorners(p, x1, y2-wby3, x2, y2, r1, r2, edge, BorderStyle_Solid, c);
92 break;
93 case LeftEdge:
94 qDrawRoundedCorners(p, x1, y1+1, x1+wby3, y2, r1, r2, LeftEdge, BorderStyle_Solid, c);
95 qDrawRoundedCorners(p, x2-wby3, y1+1, x2, y2, r1, r2, LeftEdge, BorderStyle_Solid, c);
96 break;
97 case RightEdge:
98 qDrawRoundedCorners(p, x1, y1+1, x1+wby3, y2, r1, r2, RightEdge, BorderStyle_Solid, c);
99 qDrawRoundedCorners(p, x2-wby3, y1+1, x2, y2, r1, r2, RightEdge, BorderStyle_Solid, c);
100 break;
101 default:
102 break;
103 }
104 return;
105 } else if (s == BorderStyle_Ridge || s == BorderStyle_Groove) {
106 BorderStyle s1, s2;
107 if (s == BorderStyle_Groove) {
108 s1 = BorderStyle_Inset;
109 s2 = BorderStyle_Outset;
110 } else {
111 s1 = BorderStyle_Outset;
112 s2 = BorderStyle_Inset;
113 }
114 int pwby2 = qRound(pw/2);
115 switch (edge) {
116 case TopEdge:
117 qDrawRoundedCorners(p, x1, y1, x2, y1 + pwby2, r1, r2, TopEdge, s1, c);
118 qDrawRoundedCorners(p, x1, y1 + pwby2, x2, y2, r1, r2, TopEdge, s2, c);
119 break;
120 case BottomEdge:
121 qDrawRoundedCorners(p, x1, y1 + pwby2, x2, y2, r1, r2, BottomEdge, s1, c);
122 qDrawRoundedCorners(p, x1, y1, x2, y2-pwby2, r1, r2, BottomEdge, s2, c);
123 break;
124 case LeftEdge:
125 qDrawRoundedCorners(p, x1, y1, x1 + pwby2, y2, r1, r2, LeftEdge, s1, c);
126 qDrawRoundedCorners(p, x1 + pwby2, y1, x2, y2, r1, r2, LeftEdge, s2, c);
127 break;
128 case RightEdge:
129 qDrawRoundedCorners(p, x1 + pwby2, y1, x2, y2, r1, r2, RightEdge, s1, c);
130 qDrawRoundedCorners(p, x1, y1, x2 - pwby2, y2, r1, r2, RightEdge, s2, c);
131 break;
132 default:
133 break;
134 }
135 } else if ((s == BorderStyle_Outset && (edge == TopEdge || edge == LeftEdge))
136 || (s == BorderStyle_Inset && (edge == BottomEdge || edge == RightEdge)))
137 c = c.color().lighter();
138
139 p->save();
140 qreal pwby2 = pw/2;
141 p->setBrush(Qt::NoBrush);
142 QPen pen = qPenFromStyle(c, pw, s);
143 pen.setCapStyle(Qt::SquareCap); // this eliminates the offby1 errors that we might hit below
144 p->setPen(pen);
145 switch (edge) {
146 case TopEdge:
147 if (!r1.isEmpty())
148 p->drawArc(QRectF(x1 - r1.width() + pwby2, y1 + pwby2,
149 2*r1.width() - pw, 2*r1.height() - pw), 135*16, -45*16);
150 if (!r2.isEmpty())
151 p->drawArc(QRectF(x2 - r2.width() + pwby2, y1 + pwby2,
152 2*r2.width() - pw, 2*r2.height() - pw), 45*16, 45*16);
153 break;
154 case BottomEdge:
155 if (!r1.isEmpty())
156 p->drawArc(QRectF(x1 - r1.width() + pwby2, y2 - 2*r1.height() + pwby2,
157 2*r1.width() - pw, 2*r1.height() - pw), -90 * 16, -45 * 16);
158 if (!r2.isEmpty())
159 p->drawArc(QRectF(x2 - r2.width() + pwby2, y2 - 2*r2.height() + pwby2,
160 2*r2.width() - pw, 2*r2.height() - pw), -90 * 16, 45 * 16);
161 break;
162 case LeftEdge:
163 if (!r1.isEmpty())
164 p->drawArc(QRectF(x1 + pwby2, y1 - r1.height() + pwby2,
165 2*r1.width() - pw, 2*r1.height() - pw), 135*16, 45*16);
166 if (!r2.isEmpty())
167 p->drawArc(QRectF(x1 + pwby2, y2 - r2.height() + pwby2,
168 2*r2.width() - pw, 2*r2.height() - pw), 180*16, 45*16);
169 break;
170 case RightEdge:
171 if (!r1.isEmpty())
172 p->drawArc(QRectF(x2 - 2*r1.width() + pwby2, y1 - r1.height() + pwby2,
173 2*r1.width() - pw, 2*r1.height() - pw), 45*16, -45*16);
174 if (!r2.isEmpty())
175 p->drawArc(QRectF(x2 - 2*r2.width() + pwby2, y2 - r2.height() + pwby2,
176 2*r2.width() - pw, 2*r2.height() - pw), 315*16, 45*16);
177 break;
178 default:
179 break;
180 }
181 p->restore();
182}
183
184
185void qDrawEdge(QPainter *p, qreal x1, qreal y1, qreal x2, qreal y2, qreal dw1, qreal dw2,
186 QCss::Edge edge, QCss::BorderStyle style, QBrush c)
187{
188 p->save();
189 const qreal width = (edge == TopEdge || edge == BottomEdge) ? (y2-y1) : (x2-x1);
190
191 if (width <= 2 && style == BorderStyle_Double)
192 style = BorderStyle_Solid;
193
194 switch (style) {
195 case BorderStyle_Inset:
196 case BorderStyle_Outset:
197 if ((style == BorderStyle_Outset && (edge == TopEdge || edge == LeftEdge))
198 || (style == BorderStyle_Inset && (edge == BottomEdge || edge == RightEdge)))
199 c = c.color().lighter();
200 Q_FALLTHROUGH();
201 case BorderStyle_Solid: {
202 p->setPen(Qt::NoPen);
203 p->setBrush(c);
204 if (width == 1 || (dw1 == 0 && dw2 == 0)) {
205 p->drawRect(QRectF(x1, y1, x2-x1, y2-y1));
206 } else { // draw trapezoid
207 QPolygonF quad;
208 switch (edge) {
209 case TopEdge:
210 quad << QPointF(x1, y1) << QPointF(x1 + dw1, y2)
211 << QPointF(x2 - dw2, y2) << QPointF(x2, y1);
212 break;
213 case BottomEdge:
214 quad << QPointF(x1 + dw1, y1) << QPointF(x1, y2)
215 << QPointF(x2, y2) << QPointF(x2 - dw2, y1);
216 break;
217 case LeftEdge:
218 quad << QPointF(x1, y1) << QPointF(x1, y2)
219 << QPointF(x2, y2 - dw2) << QPointF(x2, y1 + dw1);
220 break;
221 case RightEdge:
222 quad << QPointF(x1, y1 + dw1) << QPointF(x1, y2 - dw2)
223 << QPointF(x2, y2) << QPointF(x2, y1);
224 break;
225 default:
226 break;
227 }
228 p->drawConvexPolygon(quad);
229 }
230 break;
231 }
232 case BorderStyle_Dotted:
233 case BorderStyle_Dashed:
234 case BorderStyle_DotDash:
235 case BorderStyle_DotDotDash:
236 p->setPen(qPenFromStyle(c, width, style));
237 if (width == 1)
238 p->drawLine(QLineF(x1, y1, x2 - 1, y2 - 1));
239 else if (edge == TopEdge || edge == BottomEdge)
240 p->drawLine(QLineF(x1 + width/2, (y1 + y2)/2, x2 - width/2, (y1 + y2)/2));
241 else
242 p->drawLine(QLineF((x1+x2)/2, y1 + width/2, (x1+x2)/2, y2 - width/2));
243 break;
244
245 case BorderStyle_Double: {
246 int wby3 = qRound(width/3);
247 int dw1by3 = qRound(dw1/3);
248 int dw2by3 = qRound(dw2/3);
249 switch (edge) {
250 case TopEdge:
251 qDrawEdge(p, x1, y1, x2, y1 + wby3, dw1by3, dw2by3, TopEdge, BorderStyle_Solid, c);
252 qDrawEdge(p, x1 + dw1 - dw1by3, y2 - wby3, x2 - dw2 + dw1by3, y2,
253 dw1by3, dw2by3, TopEdge, BorderStyle_Solid, c);
254 break;
255 case LeftEdge:
256 qDrawEdge(p, x1, y1, x1 + wby3, y2, dw1by3, dw2by3, LeftEdge, BorderStyle_Solid, c);
257 qDrawEdge(p, x2 - wby3, y1 + dw1 - dw1by3, x2, y2 - dw2 + dw2by3, dw1by3, dw2by3,
258 LeftEdge, BorderStyle_Solid, c);
259 break;
260 case BottomEdge:
261 qDrawEdge(p, x1 + dw1 - dw1by3, y1, x2 - dw2 + dw2by3, y1 + wby3, dw1by3, dw2by3,
262 BottomEdge, BorderStyle_Solid, c);
263 qDrawEdge(p, x1, y2 - wby3, x2, y2, dw1by3, dw2by3, BottomEdge, BorderStyle_Solid, c);
264 break;
265 case RightEdge:
266 qDrawEdge(p, x2 - wby3, y1, x2, y2, dw1by3, dw2by3, RightEdge, BorderStyle_Solid, c);
267 qDrawEdge(p, x1, y1 + dw1 - dw1by3, x1 + wby3, y2 - dw2 + dw2by3, dw1by3, dw2by3,
268 RightEdge, BorderStyle_Solid, c);
269 break;
270 default:
271 break;
272 }
273 break;
274 }
275 case BorderStyle_Ridge:
276 case BorderStyle_Groove: {
277 BorderStyle s1, s2;
278 if (style == BorderStyle_Groove) {
279 s1 = BorderStyle_Inset;
280 s2 = BorderStyle_Outset;
281 } else {
282 s1 = BorderStyle_Outset;
283 s2 = BorderStyle_Inset;
284 }
285 int dw1by2 = qFloor(dw1/2), dw2by2 = qFloor(dw2/2);
286 int wby2 = qRound(width/2);
287 switch (edge) {
288 case TopEdge:
289 qDrawEdge(p, x1, y1, x2, y1 + wby2, dw1by2, dw2by2, TopEdge, s1, c);
290 qDrawEdge(p, x1 + dw1by2, y1 + wby2, x2 - dw2by2, y2, dw1by2, dw2by2, TopEdge, s2, c);
291 break;
292 case BottomEdge:
293 qDrawEdge(p, x1, y1 + wby2, x2, y2, dw1by2, dw2by2, BottomEdge, s1, c);
294 qDrawEdge(p, x1 + dw1by2, y1, x2 - dw2by2, y1 + wby2, dw1by2, dw2by2, BottomEdge, s2, c);
295 break;
296 case LeftEdge:
297 qDrawEdge(p, x1, y1, x1 + wby2, y2, dw1by2, dw2by2, LeftEdge, s1, c);
298 qDrawEdge(p, x1 + wby2, y1 + dw1by2, x2, y2 - dw2by2, dw1by2, dw2by2, LeftEdge, s2, c);
299 break;
300 case RightEdge:
301 qDrawEdge(p, x1 + wby2, y1, x2, y2, dw1by2, dw2by2, RightEdge, s1, c);
302 qDrawEdge(p, x1, y1 + dw1by2, x1 + wby2, y2 - dw2by2, dw1by2, dw2by2, RightEdge, s2, c);
303 break;
304 default:
305 break;
306 }
307 }
308 default:
309 break;
310 }
311 p->restore();
312}
313
314void qNormalizeRadii(const QRect &br, const QSize *radii,
315 QSize *tlr, QSize *trr, QSize *blr, QSize *brr)
316{
317 *tlr = radii[0].expandedTo(QSize(0, 0));
318 *trr = radii[1].expandedTo(QSize(0, 0));
319 *blr = radii[2].expandedTo(QSize(0, 0));
320 *brr = radii[3].expandedTo(QSize(0, 0));
321 if (tlr->width() + trr->width() > br.width())
322 *tlr = *trr = QSize(0, 0);
323 if (blr->width() + brr->width() > br.width())
324 *blr = *brr = QSize(0, 0);
325 if (tlr->height() + blr->height() > br.height())
326 *tlr = *blr = QSize(0, 0);
327 if (trr->height() + brr->height() > br.height())
328 *trr = *brr = QSize(0, 0);
329}
330
331// Determines if Edge e1 draws over Edge e2. Depending on this trapezoids or rectangles are drawn
332static bool paintsOver(const QCss::BorderStyle *styles, const QBrush *colors, QCss::Edge e1, QCss::Edge e2)
333{
334 QCss::BorderStyle s1 = styles[e1];
335 QCss::BorderStyle s2 = styles[e2];
336
337 if (s2 == BorderStyle_None || colors[e2] == Qt::transparent)
338 return true;
339
340 if ((s1 == BorderStyle_Solid && s2 == BorderStyle_Solid) && (colors[e1] == colors[e2])
341 && colors[e1].isOpaque()) {
342 return true;
343 }
344
345 return false;
346}
347
348void qDrawBorder(QPainter *p, const QRect &rect, const QCss::BorderStyle *styles,
349 const int *borders, const QBrush *colors, const QSize *radii)
350{
351 const QRectF br(rect);
352 QSize tlr, trr, blr, brr;
353 qNormalizeRadii(rect, radii, &tlr, &trr, &blr, &brr);
354
355 // Drawn in increasing order of precendence
356 if (styles[BottomEdge] != BorderStyle_None && borders[BottomEdge] > 0) {
357 qreal dw1 = (blr.width() || paintsOver(styles, colors, BottomEdge, LeftEdge)) ? 0 : borders[LeftEdge];
358 qreal dw2 = (brr.width() || paintsOver(styles, colors, BottomEdge, RightEdge)) ? 0 : borders[RightEdge];
359 qreal x1 = br.x() + blr.width();
360 qreal y1 = br.y() + br.height() - borders[BottomEdge];
361 qreal x2 = br.x() + br.width() - brr.width();
362 qreal y2 = br.y() + br.height() ;
363
364 qDrawEdge(p, x1, y1, x2, y2, dw1, dw2, BottomEdge, styles[BottomEdge], colors[BottomEdge]);
365 if (blr.width() || brr.width())
366 qDrawRoundedCorners(p, x1, y1, x2, y2, blr, brr, BottomEdge, styles[BottomEdge], colors[BottomEdge]);
367 }
368 if (styles[RightEdge] != BorderStyle_None && borders[RightEdge] > 0) {
369 qreal dw1 = (trr.height() || paintsOver(styles, colors, RightEdge, TopEdge)) ? 0 : borders[TopEdge];
370 qreal dw2 = (brr.height() || paintsOver(styles, colors, RightEdge, BottomEdge)) ? 0 : borders[BottomEdge];
371 qreal x1 = br.x() + br.width() - borders[RightEdge];
372 qreal y1 = br.y() + trr.height();
373 qreal x2 = br.x() + br.width();
374 qreal y2 = br.y() + br.height() - brr.height();
375
376 qDrawEdge(p, x1, y1, x2, y2, dw1, dw2, RightEdge, styles[RightEdge], colors[RightEdge]);
377 if (trr.height() || brr.height())
378 qDrawRoundedCorners(p, x1, y1, x2, y2, trr, brr, RightEdge, styles[RightEdge], colors[RightEdge]);
379 }
380 if (styles[LeftEdge] != BorderStyle_None && borders[LeftEdge] > 0) {
381 qreal dw1 = (tlr.height() || paintsOver(styles, colors, LeftEdge, TopEdge)) ? 0 : borders[TopEdge];
382 qreal dw2 = (blr.height() || paintsOver(styles, colors, LeftEdge, BottomEdge)) ? 0 : borders[BottomEdge];
383 qreal x1 = br.x();
384 qreal y1 = br.y() + tlr.height();
385 qreal x2 = br.x() + borders[LeftEdge];
386 qreal y2 = br.y() + br.height() - blr.height();
387
388 qDrawEdge(p, x1, y1, x2, y2, dw1, dw2, LeftEdge, styles[LeftEdge], colors[LeftEdge]);
389 if (tlr.height() || blr.height())
390 qDrawRoundedCorners(p, x1, y1, x2, y2, tlr, blr, LeftEdge, styles[LeftEdge], colors[LeftEdge]);
391 }
392 if (styles[TopEdge] != BorderStyle_None && borders[TopEdge] > 0) {
393 qreal dw1 = (tlr.width() || paintsOver(styles, colors, TopEdge, LeftEdge)) ? 0 : borders[LeftEdge];
394 qreal dw2 = (trr.width() || paintsOver(styles, colors, TopEdge, RightEdge)) ? 0 : borders[RightEdge];
395 qreal x1 = br.x() + tlr.width();
396 qreal y1 = br.y();
397 qreal x2 = br.left() + br.width() - trr.width();
398 qreal y2 = br.y() + borders[TopEdge];
399
400 qDrawEdge(p, x1, y1, x2, y2, dw1, dw2, TopEdge, styles[TopEdge], colors[TopEdge]);
401 if (tlr.width() || trr.width())
402 qDrawRoundedCorners(p, x1, y1, x2, y2, tlr, trr, TopEdge, styles[TopEdge], colors[TopEdge]);
403 }
404}
405
406#endif //QT_NO_CSSPARSER
407
408QT_END_NAMESPACE
409