1// @file PlatQt.cpp
2// Copyright (c) 1990-2011, Scientific Toolworks, Inc.
3//
4// The License.txt file describes the conditions under which this software may be distributed.
5//
6// Author: Jason Haslam
7//
8// Additions Copyright (c) 2011 Archaeopteryx Software, Inc. d/b/a Wingware
9// Scintilla platform layer for Qt
10
11#include <cstdio>
12
13#include "PlatQt.h"
14#include "Scintilla.h"
15#include "UniConversion.h"
16#include "DBCS.h"
17
18#include <QApplication>
19#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
20#include <QScreen>
21#endif
22#include <QFont>
23#include <QColor>
24#include <QRect>
25#include <QPaintDevice>
26#include <QPaintEngine>
27#include <QWidget>
28#include <QPixmap>
29#include <QPainter>
30#include <QPainterPath>
31#include <QMenu>
32#include <QAction>
33#include <QTime>
34#include <QMessageBox>
35#include <QTextCodec>
36#include <QListWidget>
37#include <QVarLengthArray>
38#include <QScrollBar>
39#include <QDesktopWidget>
40#include <QTextLayout>
41#include <QTextLine>
42#include <QLibrary>
43
44using namespace Scintilla;
45
46namespace Scintilla::Internal {
47
48//----------------------------------------------------------------------
49
50// Convert from a Scintilla characterSet value to a Qt codec name.
51const char *CharacterSetID(CharacterSet characterSet)
52{
53 switch (characterSet) {
54 //case CharacterSet::Ansi:
55 // return "";
56 case CharacterSet::Default:
57 return "ISO 8859-1";
58 case CharacterSet::Baltic:
59 return "ISO 8859-13";
60 case CharacterSet::ChineseBig5:
61 return "Big5";
62 case CharacterSet::EastEurope:
63 return "ISO 8859-2";
64 case CharacterSet::GB2312:
65 return "GB18030-0";
66 case CharacterSet::Greek:
67 return "ISO 8859-7";
68 case CharacterSet::Hangul:
69 return "CP949";
70 case CharacterSet::Mac:
71 return "Apple Roman";
72 //case SC_CHARSET_OEM:
73 // return "ASCII";
74 case CharacterSet::Russian:
75 return "KOI8-R";
76 case CharacterSet::Cyrillic:
77 return "Windows-1251";
78 case CharacterSet::ShiftJis:
79 return "Shift-JIS";
80 //case SC_CHARSET_SYMBOL:
81 // return "";
82 case CharacterSet::Turkish:
83 return "ISO 8859-9";
84 //case SC_CHARSET_JOHAB:
85 // return "CP1361";
86 case CharacterSet::Hebrew:
87 return "ISO 8859-8";
88 case CharacterSet::Arabic:
89 return "ISO 8859-6";
90 case CharacterSet::Vietnamese:
91 return "Windows-1258";
92 case CharacterSet::Thai:
93 return "TIS-620";
94 case CharacterSet::Iso8859_15:
95 return "ISO 8859-15";
96 default:
97 return "ISO 8859-1";
98 }
99}
100
101QString UnicodeFromText(QTextCodec *codec, std::string_view text) {
102 return codec->toUnicode(text.data(), static_cast<int>(text.length()));
103}
104
105static QFont::StyleStrategy ChooseStrategy(FontQuality eff)
106{
107 switch (eff) {
108 case FontQuality::QualityDefault: return QFont::PreferDefault;
109 case FontQuality::QualityNonAntialiased: return QFont::NoAntialias;
110 case FontQuality::QualityAntialiased: return QFont::PreferAntialias;
111 case FontQuality::QualityLcdOptimized: return QFont::PreferAntialias;
112 default: return QFont::PreferDefault;
113 }
114}
115
116class FontAndCharacterSet : public Font {
117public:
118 CharacterSet characterSet = CharacterSet::Ansi;
119 std::unique_ptr<QFont> pfont;
120 explicit FontAndCharacterSet(const FontParameters &fp) : characterSet(fp.characterSet) {
121 pfont = std::make_unique<QFont>();
122 pfont->setStyleStrategy(ChooseStrategy(fp.extraFontFlag));
123 pfont->setFamily(QString::fromUtf8(fp.faceName));
124 pfont->setPointSizeF(fp.size);
125 pfont->setBold(static_cast<int>(fp.weight) > 500);
126 pfont->setItalic(fp.italic);
127 }
128};
129
130namespace {
131
132const Supports SupportsQt[] = {
133 Supports::LineDrawsFinal,
134 Supports::FractionalStrokeWidth,
135 Supports::TranslucentStroke,
136 Supports::PixelModification,
137};
138
139const FontAndCharacterSet *AsFontAndCharacterSet(const Font *f) {
140 return dynamic_cast<const FontAndCharacterSet *>(f);
141}
142
143QFont *FontPointer(const Font *f)
144{
145 return AsFontAndCharacterSet(f)->pfont.get();
146}
147
148}
149
150std::shared_ptr<Font> Font::Allocate(const FontParameters &fp)
151{
152 return std::make_shared<FontAndCharacterSet>(fp);
153}
154
155SurfaceImpl::SurfaceImpl() = default;
156
157SurfaceImpl::SurfaceImpl(int width, int height, SurfaceMode mode_)
158{
159 if (width < 1) width = 1;
160 if (height < 1) height = 1;
161 deviceOwned = true;
162 device = new QPixmap(width, height);
163 mode = mode_;
164}
165
166SurfaceImpl::~SurfaceImpl()
167{
168 Clear();
169}
170
171void SurfaceImpl::Clear()
172{
173 if (painterOwned && painter) {
174 delete painter;
175 }
176
177 if (deviceOwned && device) {
178 delete device;
179 }
180 device = nullptr;
181 painter = nullptr;
182 deviceOwned = false;
183 painterOwned = false;
184}
185
186void SurfaceImpl::Init(WindowID wid)
187{
188 Release();
189 device = static_cast<QWidget *>(wid);
190}
191
192void SurfaceImpl::Init(SurfaceID sid, WindowID /*wid*/)
193{
194 Release();
195 device = static_cast<QPaintDevice *>(sid);
196}
197
198std::unique_ptr<Surface> SurfaceImpl::AllocatePixMap(int width, int height)
199{
200 return std::make_unique<SurfaceImpl>(width, height, mode);
201}
202
203void SurfaceImpl::SetMode(SurfaceMode mode_)
204{
205 mode = mode_;
206}
207
208void SurfaceImpl::Release() noexcept
209{
210 Clear();
211}
212
213int SurfaceImpl::SupportsFeature(Supports feature) noexcept
214{
215 for (const Supports f : SupportsQt) {
216 if (f == feature)
217 return 1;
218 }
219 return 0;
220}
221
222bool SurfaceImpl::Initialised()
223{
224 return device != nullptr;
225}
226
227void SurfaceImpl::PenColour(ColourRGBA fore)
228{
229 QPen penOutline(QColorFromColourRGBA(fore));
230 penOutline.setCapStyle(Qt::FlatCap);
231 GetPainter()->setPen(penOutline);
232}
233
234void SurfaceImpl::PenColourWidth(ColourRGBA fore, XYPOSITION strokeWidth) {
235 QPen penOutline(QColorFromColourRGBA(fore));
236 penOutline.setCapStyle(Qt::FlatCap);
237 penOutline.setJoinStyle(Qt::MiterJoin);
238 penOutline.setWidthF(strokeWidth);
239 GetPainter()->setPen(penOutline);
240}
241
242void SurfaceImpl::BrushColour(ColourRGBA back)
243{
244 GetPainter()->setBrush(QBrush(QColorFromColourRGBA(back)));
245}
246
247void SurfaceImpl::SetCodec(const Font *font)
248{
249 const FontAndCharacterSet *pfacs = AsFontAndCharacterSet(font);
250 if (pfacs && pfacs->pfont) {
251 const char *csid = "UTF-8";
252 if (!(mode.codePage == SC_CP_UTF8))
253 csid = CharacterSetID(pfacs->characterSet);
254 if (csid != codecName) {
255 codecName = csid;
256 codec = QTextCodec::codecForName(csid);
257 }
258 }
259}
260
261void SurfaceImpl::SetFont(const Font *font)
262{
263 const FontAndCharacterSet *pfacs = AsFontAndCharacterSet(font);
264 if (pfacs && pfacs->pfont) {
265 GetPainter()->setFont(*(pfacs->pfont));
266 SetCodec(font);
267 }
268}
269
270int SurfaceImpl::LogPixelsY()
271{
272 return device->logicalDpiY();
273}
274
275int SurfaceImpl::PixelDivisions()
276{
277 // Qt uses device pixels.
278 return 1;
279}
280
281int SurfaceImpl::DeviceHeightFont(int points)
282{
283 return points;
284}
285
286void SurfaceImpl::LineDraw(Point start, Point end, Stroke stroke)
287{
288 PenColourWidth(stroke.colour, stroke.width);
289 QLineF line(start.x, start.y, end.x, end.y);
290 GetPainter()->drawLine(line);
291}
292
293void SurfaceImpl::PolyLine(const Point *pts, size_t npts, Stroke stroke)
294{
295 // TODO: set line joins and caps
296 PenColourWidth(stroke.colour, stroke.width);
297 std::vector<QPointF> qpts;
298 std::transform(pts, pts + npts, std::back_inserter(qpts), QPointFFromPoint);
299 GetPainter()->drawPolyline(&qpts[0], static_cast<int>(npts));
300}
301
302void SurfaceImpl::Polygon(const Point *pts, size_t npts, FillStroke fillStroke)
303{
304 PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
305 BrushColour(fillStroke.fill.colour);
306
307 std::vector<QPointF> qpts;
308 std::transform(pts, pts + npts, std::back_inserter(qpts), QPointFFromPoint);
309
310 GetPainter()->drawPolygon(&qpts[0], static_cast<int>(npts));
311}
312
313void SurfaceImpl::RectangleDraw(PRectangle rc, FillStroke fillStroke)
314{
315 PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
316 BrushColour(fillStroke.fill.colour);
317 const QRectF rect = QRectFFromPRect(rc.Inset(fillStroke.stroke.width / 2));
318 GetPainter()->drawRect(rect);
319}
320
321void SurfaceImpl::RectangleFrame(PRectangle rc, Stroke stroke) {
322 PenColourWidth(stroke.colour, stroke.width);
323 // Default QBrush is Qt::NoBrush so does not fill
324 GetPainter()->setBrush(QBrush());
325 const QRectF rect = QRectFFromPRect(rc.Inset(stroke.width / 2));
326 GetPainter()->drawRect(rect);
327}
328
329void SurfaceImpl::FillRectangle(PRectangle rc, Fill fill)
330{
331 GetPainter()->fillRect(QRectFFromPRect(rc), QColorFromColourRGBA(fill.colour));
332}
333
334void SurfaceImpl::FillRectangleAligned(PRectangle rc, Fill fill)
335{
336 FillRectangle(PixelAlign(rc, 1), fill);
337}
338
339void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern)
340{
341 // Tile pattern over rectangle
342 SurfaceImpl *surface = dynamic_cast<SurfaceImpl *>(&surfacePattern);
343 // Currently assumes 8x8 pattern
344 int widthPat = 8;
345 int heightPat = 8;
346 for (int xTile = rc.left; xTile < rc.right; xTile += widthPat) {
347 int widthx = (xTile + widthPat > rc.right) ? rc.right - xTile : widthPat;
348 for (int yTile = rc.top; yTile < rc.bottom; yTile += heightPat) {
349 int heighty = (yTile + heightPat > rc.bottom) ? rc.bottom - yTile : heightPat;
350 QRect source(0, 0, widthx, heighty);
351 QRect target(xTile, yTile, widthx, heighty);
352 QPixmap *pixmap = static_cast<QPixmap *>(surface->GetPaintDevice());
353 GetPainter()->drawPixmap(target, *pixmap, source);
354 }
355 }
356}
357
358void SurfaceImpl::RoundedRectangle(PRectangle rc, FillStroke fillStroke)
359{
360 PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
361 BrushColour(fillStroke.fill.colour);
362 GetPainter()->drawRoundedRect(QRectFFromPRect(rc), 3.0f, 3.0f);
363}
364
365void SurfaceImpl::AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke)
366{
367 QColor qFill = QColorFromColourRGBA(fillStroke.fill.colour);
368 QBrush brushFill(qFill);
369 GetPainter()->setBrush(brushFill);
370 if (fillStroke.fill.colour == fillStroke.stroke.colour) {
371 painter->setPen(Qt::NoPen);
372 QRectF rect = QRectFFromPRect(rc);
373 if (cornerSize > 0.0f) {
374 // A radius of 1 shows no curve so add 1
375 qreal radius = cornerSize+1;
376 GetPainter()->drawRoundedRect(rect, radius, radius);
377 } else {
378 GetPainter()->fillRect(rect, brushFill);
379 }
380 } else {
381 QColor qOutline = QColorFromColourRGBA(fillStroke.stroke.colour);
382 QPen penOutline(qOutline);
383 penOutline.setWidthF(fillStroke.stroke.width);
384 GetPainter()->setPen(penOutline);
385
386 QRectF rect = QRectFFromPRect(rc.Inset(fillStroke.stroke.width / 2));
387 if (cornerSize > 0.0f) {
388 // A radius of 1 shows no curve so add 1
389 qreal radius = cornerSize+1;
390 GetPainter()->drawRoundedRect(rect, radius, radius);
391 } else {
392 GetPainter()->drawRect(rect);
393 }
394 }
395}
396
397void SurfaceImpl::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) {
398 QRectF rect = QRectFFromPRect(rc);
399 QLinearGradient linearGradient;
400 switch (options) {
401 case GradientOptions::leftToRight:
402 linearGradient = QLinearGradient(rc.left, rc.top, rc.right, rc.top);
403 break;
404 case GradientOptions::topToBottom:
405 default:
406 linearGradient = QLinearGradient(rc.left, rc.top, rc.left, rc.bottom);
407 break;
408 }
409 linearGradient.setSpread(QGradient::RepeatSpread);
410 for (const ColourStop &stop : stops) {
411 linearGradient.setColorAt(stop.position, QColorFromColourRGBA(stop.colour));
412 }
413 QBrush brush = QBrush(linearGradient);
414 GetPainter()->fillRect(rect, brush);
415}
416
417static std::vector<unsigned char> ImageByteSwapped(int width, int height, const unsigned char *pixelsImage)
418{
419 // Input is RGBA, but Format_ARGB32 is BGRA, so swap the red bytes and blue bytes
420 size_t bytes = width * height * 4;
421 std::vector<unsigned char> imageBytes(pixelsImage, pixelsImage+bytes);
422 for (size_t i=0; i<bytes; i+=4)
423 std::swap(imageBytes[i], imageBytes[i+2]);
424 return imageBytes;
425}
426
427void SurfaceImpl::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage)
428{
429 std::vector<unsigned char> imageBytes = ImageByteSwapped(width, height, pixelsImage);
430 QImage image(&imageBytes[0], width, height, QImage::Format_ARGB32);
431 QPoint pt(rc.left, rc.top);
432 GetPainter()->drawImage(pt, image);
433}
434
435void SurfaceImpl::Ellipse(PRectangle rc, FillStroke fillStroke)
436{
437 PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
438 BrushColour(fillStroke.fill.colour);
439 const QRectF rect = QRectFFromPRect(rc.Inset(fillStroke.stroke.width / 2));
440 GetPainter()->drawEllipse(rect);
441}
442
443void SurfaceImpl::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) {
444 const XYPOSITION halfStroke = fillStroke.stroke.width / 2.0f;
445 const XYPOSITION radius = rc.Height() / 2.0f - halfStroke;
446 PRectangle rcInner = rc;
447 rcInner.left += radius;
448 rcInner.right -= radius;
449 const XYPOSITION arcHeight = rc.Height() - fillStroke.stroke.width;
450
451 PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
452 BrushColour(fillStroke.fill.colour);
453
454 QPainterPath path;
455
456 const Ends leftSide = static_cast<Ends>(static_cast<unsigned int>(ends) & 0xfu);
457 const Ends rightSide = static_cast<Ends>(static_cast<unsigned int>(ends) & 0xf0u);
458 switch (leftSide) {
459 case Ends::leftFlat:
460 path.moveTo(rc.left + halfStroke, rc.top + halfStroke);
461 path.lineTo(rc.left + halfStroke, rc.bottom - halfStroke);
462 break;
463 case Ends::leftAngle:
464 path.moveTo(rcInner.left + halfStroke, rc.top + halfStroke);
465 path.lineTo(rc.left + halfStroke, rc.Centre().y);
466 path.lineTo(rcInner.left + halfStroke, rc.bottom - halfStroke);
467 break;
468 case Ends::semiCircles:
469 default:
470 path.moveTo(rcInner.left + halfStroke, rc.top + halfStroke);
471 QRectF rectangleArc(rc.left + halfStroke, rc.top + halfStroke,
472 arcHeight, arcHeight);
473 path.arcTo(rectangleArc, 90, 180);
474 break;
475 }
476
477 switch (rightSide) {
478 case Ends::rightFlat:
479 path.lineTo(rc.right - halfStroke, rc.bottom - halfStroke);
480 path.lineTo(rc.right - halfStroke, rc.top + halfStroke);
481 break;
482 case Ends::rightAngle:
483 path.lineTo(rcInner.right - halfStroke, rc.bottom - halfStroke);
484 path.lineTo(rc.right - halfStroke, rc.Centre().y);
485 path.lineTo(rcInner.right - halfStroke, rc.top + halfStroke);
486 break;
487 case Ends::semiCircles:
488 default:
489 path.lineTo(rcInner.right - halfStroke, rc.bottom - halfStroke);
490 QRectF rectangleArc(rc.right - arcHeight - halfStroke, rc.top + halfStroke,
491 arcHeight, arcHeight);
492 path.arcTo(rectangleArc, 270, 180);
493 break;
494 }
495
496 // Close the path to enclose it for stroking and for filling, then draw it
497 path.closeSubpath();
498 GetPainter()->drawPath(path);
499}
500
501void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource)
502{
503 SurfaceImpl *source = dynamic_cast<SurfaceImpl *>(&surfaceSource);
504 QPixmap *pixmap = static_cast<QPixmap *>(source->GetPaintDevice());
505
506 GetPainter()->drawPixmap(rc.left, rc.top, *pixmap, from.x, from.y, -1, -1);
507}
508
509std::unique_ptr<IScreenLineLayout> SurfaceImpl::Layout(const IScreenLine *)
510{
511 return {};
512}
513
514void SurfaceImpl::DrawTextNoClip(PRectangle rc,
515 const Font *font,
516 XYPOSITION ybase,
517 std::string_view text,
518 ColourRGBA fore,
519 ColourRGBA back)
520{
521 SetFont(font);
522 PenColour(fore);
523
524 GetPainter()->setBackground(QColorFromColourRGBA(back));
525 GetPainter()->setBackgroundMode(Qt::OpaqueMode);
526 QString su = UnicodeFromText(codec, text);
527 GetPainter()->drawText(QPointF(rc.left, ybase), su);
528}
529
530void SurfaceImpl::DrawTextClipped(PRectangle rc,
531 const Font *font,
532 XYPOSITION ybase,
533 std::string_view text,
534 ColourRGBA fore,
535 ColourRGBA back)
536{
537 SetClip(rc);
538 DrawTextNoClip(rc, font, ybase, text, fore, back);
539 PopClip();
540}
541
542void SurfaceImpl::DrawTextTransparent(PRectangle rc,
543 const Font *font,
544 XYPOSITION ybase,
545 std::string_view text,
546 ColourRGBA fore)
547{
548 SetFont(font);
549 PenColour(fore);
550
551 GetPainter()->setBackgroundMode(Qt::TransparentMode);
552 QString su = UnicodeFromText(codec, text);
553 GetPainter()->drawText(QPointF(rc.left, ybase), su);
554}
555
556void SurfaceImpl::SetClip(PRectangle rc)
557{
558 GetPainter()->save();
559 GetPainter()->setClipRect(QRectFFromPRect(rc));
560}
561
562void SurfaceImpl::PopClip()
563{
564 GetPainter()->restore();
565}
566
567void SurfaceImpl::MeasureWidths(const Font *font,
568 std::string_view text,
569 XYPOSITION *positions)
570{
571 if (!font)
572 return;
573 SetCodec(font);
574 QString su = UnicodeFromText(codec, text);
575 QTextLayout tlay(su, *FontPointer(font), GetPaintDevice());
576 tlay.beginLayout();
577 QTextLine tl = tlay.createLine();
578 tlay.endLayout();
579 if (mode.codePage == SC_CP_UTF8) {
580 int fit = su.size();
581 int ui=0;
582 size_t i=0;
583 while (ui<fit) {
584 const unsigned char uch = text[i];
585 const unsigned int byteCount = UTF8BytesOfLead[uch];
586 const int codeUnits = UTF16LengthFromUTF8ByteCount(byteCount);
587 qreal xPosition = tl.cursorToX(ui+codeUnits);
588 for (size_t bytePos=0; (bytePos<byteCount) && (i<text.length()); bytePos++) {
589 positions[i++] = xPosition;
590 }
591 ui += codeUnits;
592 }
593 XYPOSITION lastPos = 0;
594 if (i > 0)
595 lastPos = positions[i-1];
596 while (i<text.length()) {
597 positions[i++] = lastPos;
598 }
599 } else if (mode.codePage) {
600 // DBCS
601 int ui = 0;
602 for (size_t i=0; i<text.length();) {
603 size_t lenChar = DBCSIsLeadByte(mode.codePage, text[i]) ? 2 : 1;
604 qreal xPosition = tl.cursorToX(ui+1);
605 for (unsigned int bytePos=0; (bytePos<lenChar) && (i<text.length()); bytePos++) {
606 positions[i++] = xPosition;
607 }
608 ui++;
609 }
610 } else {
611 // Single byte encoding
612 for (int i=0; i<static_cast<int>(text.length()); i++) {
613 positions[i] = tl.cursorToX(i+1);
614 }
615 }
616}
617
618XYPOSITION SurfaceImpl::WidthText(const Font *font, std::string_view text)
619{
620 QFontMetricsF metrics(*FontPointer(font), device);
621 SetCodec(font);
622 QString su = UnicodeFromText(codec, text);
623 return metrics.width(su);
624}
625
626void SurfaceImpl::DrawTextNoClipUTF8(PRectangle rc,
627 const Font *font,
628 XYPOSITION ybase,
629 std::string_view text,
630 ColourRGBA fore,
631 ColourRGBA back)
632{
633 SetFont(font);
634 PenColour(fore);
635
636 GetPainter()->setBackground(QColorFromColourRGBA(back));
637 GetPainter()->setBackgroundMode(Qt::OpaqueMode);
638 QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length()));
639 GetPainter()->drawText(QPointF(rc.left, ybase), su);
640}
641
642void SurfaceImpl::DrawTextClippedUTF8(PRectangle rc,
643 const Font *font,
644 XYPOSITION ybase,
645 std::string_view text,
646 ColourRGBA fore,
647 ColourRGBA back)
648{
649 SetClip(rc);
650 DrawTextNoClip(rc, font, ybase, text, fore, back);
651 PopClip();
652}
653
654void SurfaceImpl::DrawTextTransparentUTF8(PRectangle rc,
655 const Font *font,
656 XYPOSITION ybase,
657 std::string_view text,
658 ColourRGBA fore)
659{
660 SetFont(font);
661 PenColour(fore);
662
663 GetPainter()->setBackgroundMode(Qt::TransparentMode);
664 QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length()));
665 GetPainter()->drawText(QPointF(rc.left, ybase), su);
666}
667
668void SurfaceImpl::MeasureWidthsUTF8(const Font *font,
669 std::string_view text,
670 XYPOSITION *positions)
671{
672 if (!font)
673 return;
674 QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length()));
675 QTextLayout tlay(su, *FontPointer(font), GetPaintDevice());
676 tlay.beginLayout();
677 QTextLine tl = tlay.createLine();
678 tlay.endLayout();
679 int fit = su.size();
680 int ui=0;
681 size_t i=0;
682 while (ui<fit) {
683 const unsigned char uch = text[i];
684 const unsigned int byteCount = UTF8BytesOfLead[uch];
685 const int codeUnits = UTF16LengthFromUTF8ByteCount(byteCount);
686 qreal xPosition = tl.cursorToX(ui+codeUnits);
687 for (size_t bytePos=0; (bytePos<byteCount) && (i<text.length()); bytePos++) {
688 positions[i++] = xPosition;
689 }
690 ui += codeUnits;
691 }
692 XYPOSITION lastPos = 0;
693 if (i > 0)
694 lastPos = positions[i-1];
695 while (i<text.length()) {
696 positions[i++] = lastPos;
697 }
698}
699
700XYPOSITION SurfaceImpl::WidthTextUTF8(const Font *font, std::string_view text)
701{
702 QFontMetricsF metrics(*FontPointer(font), device);
703 QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length()));
704 return metrics.width(su);
705}
706
707XYPOSITION SurfaceImpl::Ascent(const Font *font)
708{
709 QFontMetricsF metrics(*FontPointer(font), device);
710 return metrics.ascent();
711}
712
713XYPOSITION SurfaceImpl::Descent(const Font *font)
714{
715 QFontMetricsF metrics(*FontPointer(font), device);
716 // Qt returns 1 less than true descent
717 // See: QFontEngineWin::descent which says:
718 // ### we subtract 1 to even out the historical +1 in QFontMetrics's
719 // ### height=asc+desc+1 equation. Fix in Qt5.
720 return metrics.descent() + 1;
721}
722
723XYPOSITION SurfaceImpl::InternalLeading(const Font * /* font */)
724{
725 return 0;
726}
727
728XYPOSITION SurfaceImpl::Height(const Font *font)
729{
730 QFontMetricsF metrics(*FontPointer(font), device);
731 return metrics.height();
732}
733
734XYPOSITION SurfaceImpl::AverageCharWidth(const Font *font)
735{
736 QFontMetricsF metrics(*FontPointer(font), device);
737 return metrics.averageCharWidth();
738}
739
740void SurfaceImpl::FlushCachedState()
741{
742 if (device->paintingActive()) {
743 GetPainter()->setPen(QPen());
744 GetPainter()->setBrush(QBrush());
745 }
746}
747
748void SurfaceImpl::FlushDrawing()
749{
750}
751
752QPaintDevice *SurfaceImpl::GetPaintDevice()
753{
754 return device;
755}
756
757QPainter *SurfaceImpl::GetPainter()
758{
759 Q_ASSERT(device);
760 if (!painter) {
761 if (device->paintingActive()) {
762 painter = device->paintEngine()->painter();
763 } else {
764 painterOwned = true;
765 painter = new QPainter(device);
766 }
767
768 // Set text antialiasing unconditionally.
769 // The font's style strategy will override.
770 painter->setRenderHint(QPainter::TextAntialiasing, true);
771
772 painter->setRenderHint(QPainter::Antialiasing, true);
773 }
774
775 return painter;
776}
777
778std::unique_ptr<Surface> Surface::Allocate(Technology)
779{
780 return std::make_unique<SurfaceImpl>();
781}
782
783
784//----------------------------------------------------------------------
785
786namespace {
787
788QWidget *window(WindowID wid) noexcept
789{
790 return static_cast<QWidget *>(wid);
791}
792
793QRect ScreenRectangleForPoint(QPoint posGlobal)
794{
795#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
796 const QScreen *screen = QGuiApplication::screenAt(posGlobal);
797 return screen->availableGeometry();
798#else
799 const QDesktopWidget *desktop = QApplication::desktop();
800 return desktop->availableGeometry(posGlobal);
801#endif
802}
803
804}
805
806Window::~Window() noexcept = default;
807
808void Window::Destroy() noexcept
809{
810 if (wid)
811 delete window(wid);
812 wid = nullptr;
813}
814PRectangle Window::GetPosition() const
815{
816 // Before any size allocated pretend its 1000 wide so not scrolled
817 return wid ? PRectFromQRect(window(wid)->frameGeometry()) : PRectangle(0, 0, 1000, 1000);
818}
819
820void Window::SetPosition(PRectangle rc)
821{
822 if (wid)
823 window(wid)->setGeometry(QRectFromPRect(rc));
824}
825
826void Window::SetPositionRelative(PRectangle rc, const Window *relativeTo)
827{
828 QPoint oPos = window(relativeTo->wid)->mapToGlobal(QPoint(0,0));
829 int ox = oPos.x();
830 int oy = oPos.y();
831 ox += rc.left;
832 oy += rc.top;
833
834 const QRect rectDesk = ScreenRectangleForPoint(QPoint(ox, oy));
835 /* do some corrections to fit into screen */
836 int sizex = rc.right - rc.left;
837 int sizey = rc.bottom - rc.top;
838 int screenWidth = rectDesk.width();
839 if (ox < rectDesk.x())
840 ox = rectDesk.x();
841 if (sizex > screenWidth)
842 ox = rectDesk.x(); /* the best we can do */
843 else if (ox + sizex > rectDesk.right())
844 ox = rectDesk.right() - sizex;
845 if (oy + sizey > rectDesk.bottom())
846 oy = rectDesk.bottom() - sizey;
847
848 Q_ASSERT(wid);
849 window(wid)->move(ox, oy);
850 window(wid)->resize(sizex, sizey);
851}
852
853PRectangle Window::GetClientPosition() const
854{
855 // The client position is the window position
856 return GetPosition();
857}
858
859void Window::Show(bool show)
860{
861 if (wid)
862 window(wid)->setVisible(show);
863}
864
865void Window::InvalidateAll()
866{
867 if (wid)
868 window(wid)->update();
869}
870
871void Window::InvalidateRectangle(PRectangle rc)
872{
873 if (wid)
874 window(wid)->update(QRectFromPRect(rc));
875}
876
877void Window::SetCursor(Cursor curs)
878{
879 if (wid) {
880 Qt::CursorShape shape;
881
882 switch (curs) {
883 case Cursor::text: shape = Qt::IBeamCursor; break;
884 case Cursor::arrow: shape = Qt::ArrowCursor; break;
885 case Cursor::up: shape = Qt::UpArrowCursor; break;
886 case Cursor::wait: shape = Qt::WaitCursor; break;
887 case Cursor::horizontal: shape = Qt::SizeHorCursor; break;
888 case Cursor::vertical: shape = Qt::SizeVerCursor; break;
889 case Cursor::hand: shape = Qt::PointingHandCursor; break;
890 default: shape = Qt::ArrowCursor; break;
891 }
892
893 QCursor cursor = QCursor(shape);
894
895 if (curs != cursorLast) {
896 window(wid)->setCursor(cursor);
897 cursorLast = curs;
898 }
899 }
900}
901
902/* Returns rectangle of monitor pt is on, both rect and pt are in Window's
903 window coordinates */
904PRectangle Window::GetMonitorRect(Point pt)
905{
906 const QPoint posGlobal = window(wid)->mapToGlobal(QPoint(pt.x, pt.y));
907 const QPoint originGlobal = window(wid)->mapToGlobal(QPoint(0, 0));
908 QRect rectScreen = ScreenRectangleForPoint(posGlobal);
909 rectScreen.translate(-originGlobal.x(), -originGlobal.y());
910 return PRectFromQRect(rectScreen);
911}
912
913//----------------------------------------------------------------------
914class ListWidget : public QListWidget {
915public:
916 explicit ListWidget(QWidget *parent);
917
918 void setDelegate(IListBoxDelegate *lbDelegate);
919
920 int currentSelection();
921
922protected:
923 void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
924 void mouseDoubleClickEvent(QMouseEvent *event) override;
925 QStyleOptionViewItem viewOptions() const override;
926
927private:
928 IListBoxDelegate *delegate;
929};
930
931class ListBoxImpl : public ListBox {
932public:
933 ListBoxImpl() noexcept;
934
935 void SetFont(const Font *font) override;
936 void Create(Window &parent, int ctrlID, Point location,
937 int lineHeight, bool unicodeMode_, Technology technology) override;
938 void SetAverageCharWidth(int width) override;
939 void SetVisibleRows(int rows) override;
940 int GetVisibleRows() const override;
941 PRectangle GetDesiredRect() override;
942 int CaretFromEdge() override;
943 void Clear() noexcept override;
944 void Append(char *s, int type) override;
945 int Length() override;
946 void Select(int n) override;
947 int GetSelection() override;
948 int Find(const char *prefix) override;
949 std::string GetValue(int n) override;
950 void RegisterImage(int type, const char *xpmData) override;
951 void RegisterRGBAImage(int type, int width, int height,
952 const unsigned char *pixelsImage) override;
953 virtual void RegisterQPixmapImage(int type, const QPixmap& pm);
954 void ClearRegisteredImages() override;
955 void SetDelegate(IListBoxDelegate *lbDelegate) override;
956 void SetList(const char *list, char separator, char typesep) override;
957 void SetOptions(ListOptions options_) override;
958
959 [[nodiscard]] ListWidget *GetWidget() const noexcept;
960private:
961 bool unicodeMode{false};
962 int visibleRows{5};
963 QMap<int,QPixmap> images;
964};
965ListBoxImpl::ListBoxImpl() noexcept = default;
966
967void ListBoxImpl::Create(Window &parent,
968 int /*ctrlID*/,
969 Point location,
970 int /*lineHeight*/,
971 bool unicodeMode_,
972 Technology)
973{
974 unicodeMode = unicodeMode_;
975
976 QWidget *qparent = static_cast<QWidget *>(parent.GetID());
977 ListWidget *list = new ListWidget(qparent);
978
979#if defined(Q_OS_WIN)
980 // On Windows, Qt::ToolTip causes a crash when the list is clicked on
981 // so Qt::Tool is used.
982 list->setParent(nullptr, Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint
983#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
984 | Qt::WindowDoesNotAcceptFocus
985#endif
986 );
987#else
988 // On OS X, Qt::Tool takes focus so main window loses focus so
989 // keyboard stops working. Qt::ToolTip works but its only really
990 // documented for tooltips.
991 // On Linux / X this setting allows clicking on list items.
992 list->setParent(nullptr, static_cast<Qt::WindowFlags>(Qt::ToolTip | Qt::FramelessWindowHint));
993#endif
994 list->setAttribute(Qt::WA_ShowWithoutActivating);
995 list->setFocusPolicy(Qt::NoFocus);
996 list->setUniformItemSizes(true);
997 list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
998 list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
999 list->move(location.x, location.y);
1000
1001 int maxIconWidth = 0;
1002 int maxIconHeight = 0;
1003 foreach (QPixmap im, images) {
1004 if (maxIconWidth < im.width())
1005 maxIconWidth = im.width();
1006 if (maxIconHeight < im.height())
1007 maxIconHeight = im.height();
1008 }
1009 list->setIconSize(QSize(maxIconWidth, maxIconHeight));
1010
1011 wid = list;
1012}
1013void ListBoxImpl::SetFont(const Font *font)
1014{
1015 ListWidget *list = GetWidget();
1016 const FontAndCharacterSet *pfacs = AsFontAndCharacterSet(font);
1017 if (pfacs && pfacs->pfont) {
1018 list->setFont(*(pfacs->pfont));
1019 }
1020}
1021void ListBoxImpl::SetAverageCharWidth(int /*width*/) {}
1022
1023void ListBoxImpl::SetVisibleRows(int rows)
1024{
1025 visibleRows = rows;
1026}
1027
1028int ListBoxImpl::GetVisibleRows() const
1029{
1030 return visibleRows;
1031}
1032PRectangle ListBoxImpl::GetDesiredRect()
1033{
1034 ListWidget *list = GetWidget();
1035 int rows = Length();
1036 if (rows == 0 || rows > visibleRows) {
1037 rows = visibleRows;
1038 }
1039 int rowHeight = list->sizeHintForRow(0);
1040 int height = (rows * rowHeight) + (2 * list->frameWidth());
1041
1042 QStyle *style = QApplication::style();
1043 int width = list->sizeHintForColumn(0) + (2 * list->frameWidth());
1044 if (Length() > rows) {
1045 width += style->pixelMetric(QStyle::PM_ScrollBarExtent);
1046 }
1047
1048 return PRectangle(0, 0, width, height);
1049}
1050int ListBoxImpl::CaretFromEdge()
1051{
1052 ListWidget *list = GetWidget();
1053 int maxIconWidth = 0;
1054 foreach (QPixmap im, images) {
1055 if (maxIconWidth < im.width())
1056 maxIconWidth = im.width();
1057 }
1058
1059 int extra;
1060 // The 12 is from trial and error on OS X and the 7
1061 // is from trial and error on Windows - there may be
1062 // a better programmatic way to find any padding factors.
1063#ifdef Q_OS_DARWIN
1064 extra = 12;
1065#else
1066 extra = 7;
1067#endif
1068 return maxIconWidth + (2 * list->frameWidth()) + extra;
1069}
1070void ListBoxImpl::Clear() noexcept
1071{
1072 ListWidget *list = GetWidget();
1073 list->clear();
1074}
1075void ListBoxImpl::Append(char *s, int type)
1076{
1077 ListWidget *list = GetWidget();
1078 QString str = unicodeMode ? QString::fromUtf8(s) : QString::fromLocal8Bit(s);
1079 QIcon icon;
1080 if (type >= 0) {
1081 Q_ASSERT(images.contains(type));
1082 icon = images.value(type);
1083 }
1084 new QListWidgetItem(icon, str, list);
1085}
1086int ListBoxImpl::Length()
1087{
1088 ListWidget *list = GetWidget();
1089 return list->count();
1090}
1091void ListBoxImpl::Select(int n)
1092{
1093 ListWidget *list = GetWidget();
1094 QModelIndex index = list->model()->index(n, 0);
1095 if (index.isValid()) {
1096 QRect row_rect = list->visualRect(index);
1097 if (!list->viewport()->rect().contains(row_rect)) {
1098 list->scrollTo(index, QAbstractItemView::PositionAtTop);
1099 }
1100 }
1101 list->setCurrentRow(n);
1102}
1103int ListBoxImpl::GetSelection()
1104{
1105 ListWidget *list = GetWidget();
1106 return list->currentSelection();
1107}
1108int ListBoxImpl::Find(const char *prefix)
1109{
1110 ListWidget *list = GetWidget();
1111 QString sPrefix = unicodeMode ? QString::fromUtf8(prefix) : QString::fromLocal8Bit(prefix);
1112 QList<QListWidgetItem *> ms = list->findItems(sPrefix, Qt::MatchStartsWith);
1113 int result = -1;
1114 if (!ms.isEmpty()) {
1115 result = list->row(ms.first());
1116 }
1117
1118 return result;
1119}
1120std::string ListBoxImpl::GetValue(int n)
1121{
1122 ListWidget *list = GetWidget();
1123 QListWidgetItem *item = list->item(n);
1124 QString str = item->data(Qt::DisplayRole).toString();
1125 QByteArray bytes = unicodeMode ? str.toUtf8() : str.toLocal8Bit();
1126 return std::string(bytes.constData());
1127}
1128
1129void ListBoxImpl::RegisterQPixmapImage(int type, const QPixmap& pm)
1130{
1131 images[type] = pm;
1132 ListWidget *list = GetWidget();
1133 if (list) {
1134 QSize iconSize = list->iconSize();
1135 if (pm.width() > iconSize.width() || pm.height() > iconSize.height())
1136 list->setIconSize(QSize(qMax(pm.width(), iconSize.width()),
1137 qMax(pm.height(), iconSize.height())));
1138 }
1139
1140}
1141
1142void ListBoxImpl::RegisterImage(int type, const char *xpmData)
1143{
1144 RegisterQPixmapImage(type, QPixmap(reinterpret_cast<const char * const *>(xpmData)));
1145}
1146
1147void ListBoxImpl::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage)
1148{
1149 std::vector<unsigned char> imageBytes = ImageByteSwapped(width, height, pixelsImage);
1150 QImage image(&imageBytes[0], width, height, QImage::Format_ARGB32);
1151 RegisterQPixmapImage(type, QPixmap::fromImage(image));
1152}
1153
1154void ListBoxImpl::ClearRegisteredImages()
1155{
1156 images.clear();
1157 ListWidget *list = GetWidget();
1158 if (list)
1159 list->setIconSize(QSize(0, 0));
1160}
1161void ListBoxImpl::SetDelegate(IListBoxDelegate *lbDelegate)
1162{
1163 ListWidget *list = GetWidget();
1164 list->setDelegate(lbDelegate);
1165}
1166void ListBoxImpl::SetList(const char *list, char separator, char typesep)
1167{
1168 // This method is *not* platform dependent.
1169 // It is borrowed from the GTK implementation.
1170 Clear();
1171 size_t count = strlen(list) + 1;
1172 std::vector<char> words(list, list+count);
1173 char *startword = &words[0];
1174 char *numword = nullptr;
1175 int i = 0;
1176 for (; words[i]; i++) {
1177 if (words[i] == separator) {
1178 words[i] = '\0';
1179 if (numword)
1180 *numword = '\0';
1181 Append(startword, numword?atoi(numword + 1):-1);
1182 startword = &words[0] + i + 1;
1183 numword = nullptr;
1184 } else if (words[i] == typesep) {
1185 numword = &words[0] + i;
1186 }
1187 }
1188 if (startword) {
1189 if (numword)
1190 *numword = '\0';
1191 Append(startword, numword?atoi(numword + 1):-1);
1192 }
1193}
1194void ListBoxImpl::SetOptions(ListOptions)
1195{
1196}
1197ListWidget *ListBoxImpl::GetWidget() const noexcept
1198{
1199 return static_cast<ListWidget *>(wid);
1200}
1201
1202ListBox::ListBox() noexcept = default;
1203ListBox::~ListBox() noexcept = default;
1204
1205std::unique_ptr<ListBox> ListBox::Allocate()
1206{
1207 return std::make_unique<ListBoxImpl>();
1208}
1209ListWidget::ListWidget(QWidget *parent)
1210: QListWidget(parent), delegate(nullptr)
1211{}
1212
1213void ListWidget::setDelegate(IListBoxDelegate *lbDelegate)
1214{
1215 delegate = lbDelegate;
1216}
1217
1218void ListWidget::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
1219 QListWidget::selectionChanged(selected, deselected);
1220 if (delegate) {
1221 const int selection = currentSelection();
1222 if (selection >= 0) {
1223 ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
1224 delegate->ListNotify(&event);
1225 }
1226 }
1227}
1228
1229int ListWidget::currentSelection() {
1230 const QModelIndexList indices = selectionModel()->selectedRows();
1231 foreach (const QModelIndex ind, indices) {
1232 return ind.row();
1233 }
1234 return -1;
1235}
1236
1237void ListWidget::mouseDoubleClickEvent(QMouseEvent * /* event */)
1238{
1239 if (delegate) {
1240 ListBoxEvent event(ListBoxEvent::EventType::doubleClick);
1241 delegate->ListNotify(&event);
1242 }
1243}
1244
1245QStyleOptionViewItem ListWidget::viewOptions() const
1246{
1247 QStyleOptionViewItem result = QListWidget::viewOptions();
1248 result.state |= QStyle::State_Active;
1249 return result;
1250}
1251//----------------------------------------------------------------------
1252Menu::Menu() noexcept : mid(nullptr) {}
1253void Menu::CreatePopUp()
1254{
1255 Destroy();
1256 mid = new QMenu();
1257}
1258
1259void Menu::Destroy() noexcept
1260{
1261 if (mid) {
1262 QMenu *menu = static_cast<QMenu *>(mid);
1263 delete menu;
1264 }
1265 mid = nullptr;
1266}
1267void Menu::Show(Point pt, const Window & /*w*/)
1268{
1269 QMenu *menu = static_cast<QMenu *>(mid);
1270 menu->exec(QPoint(pt.x, pt.y));
1271 Destroy();
1272}
1273
1274//----------------------------------------------------------------------
1275
1276ColourRGBA Platform::Chrome()
1277{
1278 QColor c(Qt::gray);
1279 return ColourRGBA(c.red(), c.green(), c.blue());
1280}
1281
1282ColourRGBA Platform::ChromeHighlight()
1283{
1284 QColor c(Qt::lightGray);
1285 return ColourRGBA(c.red(), c.green(), c.blue());
1286}
1287
1288const char *Platform::DefaultFont()
1289{
1290 static char fontNameDefault[200] = "";
1291 if (!fontNameDefault[0]) {
1292 QFont font = QApplication::font();
1293 strcpy(fontNameDefault, font.family().toUtf8());
1294 }
1295 return fontNameDefault;
1296}
1297
1298int Platform::DefaultFontSize()
1299{
1300 QFont font = QApplication::font();
1301 return font.pointSize();
1302}
1303
1304unsigned int Platform::DoubleClickTime()
1305{
1306 return QApplication::doubleClickInterval();
1307}
1308
1309void Platform::DebugDisplay(const char *s) noexcept
1310{
1311 qWarning("Scintilla: %s", s);
1312}
1313
1314void Platform::DebugPrintf(const char *format, ...) noexcept
1315{
1316 char buffer[2000];
1317 va_list pArguments{};
1318 va_start(pArguments, format);
1319 vsprintf(buffer, format, pArguments);
1320 va_end(pArguments);
1321 Platform::DebugDisplay(buffer);
1322}
1323
1324bool Platform::ShowAssertionPopUps(bool /*assertionPopUps*/) noexcept
1325{
1326 return false;
1327}
1328
1329void Platform::Assert(const char *c, const char *file, int line) noexcept
1330{
1331 char buffer[2000];
1332 sprintf(buffer, "Assertion [%s] failed at %s %d", c, file, line);
1333 if (Platform::ShowAssertionPopUps(false)) {
1334 QMessageBox mb("Assertion Failure", buffer, QMessageBox::NoIcon,
1335 QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton);
1336 mb.exec();
1337 } else {
1338 strcat(buffer, "\n");
1339 Platform::DebugDisplay(buffer);
1340 }
1341}
1342
1343}
1344