Christian Grasser 43182e1d04 Update to Scintilla 5.3.3 and Lexilla 5.2.2
update to https://www.scintilla.org/scintilla533.zip with:

   1. Released 8 February 2023.
   2. Fix SCI_LINESJOIN bug where carriage returns were incorrectly retained. Bug #2372.
   3. Fix SCI_VERTICALCENTRECARET to update the vertical scroll position.
   4. When an autocompletion list is shown in response to SCN_CHARADDED, do not process character as fill-up or stop. This avoids closing immediately when a character may both trigger and finish autocompletion.
   5. On Cocoa fix character input bug where dotless 'i' and some other extended Latin characters could not be entered. The change also stops SCI_ASSIGNCMDKEY from working with these characters on Cocoa. Bug #2374.
   6. On GTK, support IME context. Feature #1476.
   7. On GTK on Win32, fix scrolling speed to not be too fast. Bug #2375.
   8. On Qt, fix indicator drawing past left of text pane over margin. Bug #2373, Bug #1956.
   9. On Qt, allow scrolling with mouse wheel when scroll bar hidden.

and https://www.scintilla.org/lexilla522.zip with

   1. Released 8 February 2023.
   2. C++: Fix keywords that start with non-ASCII. Also affects other lexers. Issue #130.
   3. Matlab: Include more prefix and suffix characters in numeric literals. Issue #120.
   4. Matlab: More accurate treatment of line ends inside strings. Matlab and Octave are different here. Issue #18.
   5. Modula-3: Don't treat identifier suffix that matches keyword as keyword. Issue #129.
   6. Modula-3: Fix endless loop in folder. Issue #128.
   7. Modula-3: Fix access to lines beyond document end in folder. Issue #131.
   8. Python: Don't highlight match and case as keywords in contexts where they probably aren't used as keywords. Pull request #122.
   9. X12: Support empty envelopes. Bug #2369.

update CMakeLists.txt to latest changes within vcxproj file

Close #13082
2023-02-12 17:11:57 +01:00

1360 lines
37 KiB
C++

// @file PlatQt.cpp
// Copyright (c) 1990-2011, Scientific Toolworks, Inc.
//
// The License.txt file describes the conditions under which this software may be distributed.
//
// Author: Jason Haslam
//
// Additions Copyright (c) 2011 Archaeopteryx Software, Inc. d/b/a Wingware
// Scintilla platform layer for Qt
#include <cstdio>
#include "PlatQt.h"
#include "Scintilla.h"
#include "XPM.h"
#include "UniConversion.h"
#include "DBCS.h"
#include <QApplication>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QScreen>
#endif
#include <QFont>
#include <QColor>
#include <QRect>
#include <QPaintDevice>
#include <QPaintEngine>
#include <QWidget>
#include <QPixmap>
#include <QPainter>
#include <QPainterPath>
#include <QMenu>
#include <QAction>
#include <QTime>
#include <QMessageBox>
#include <QTextCodec>
#include <QListWidget>
#include <QVarLengthArray>
#include <QScrollBar>
#include <QTextLayout>
#include <QTextLine>
#include <QLibrary>
using namespace Scintilla;
namespace Scintilla::Internal {
//----------------------------------------------------------------------
// Convert from a Scintilla characterSet value to a Qt codec name.
const char *CharacterSetID(CharacterSet characterSet)
{
switch (characterSet) {
//case CharacterSet::Ansi:
// return "";
case CharacterSet::Default:
return "ISO 8859-1";
case CharacterSet::Baltic:
return "ISO 8859-13";
case CharacterSet::ChineseBig5:
return "Big5";
case CharacterSet::EastEurope:
return "ISO 8859-2";
case CharacterSet::GB2312:
return "GB18030-0";
case CharacterSet::Greek:
return "ISO 8859-7";
case CharacterSet::Hangul:
return "CP949";
case CharacterSet::Mac:
return "Apple Roman";
//case SC_CHARSET_OEM:
// return "ASCII";
case CharacterSet::Russian:
return "KOI8-R";
case CharacterSet::Cyrillic:
return "Windows-1251";
case CharacterSet::ShiftJis:
return "Shift-JIS";
//case SC_CHARSET_SYMBOL:
// return "";
case CharacterSet::Turkish:
return "ISO 8859-9";
//case SC_CHARSET_JOHAB:
// return "CP1361";
case CharacterSet::Hebrew:
return "ISO 8859-8";
case CharacterSet::Arabic:
return "ISO 8859-6";
case CharacterSet::Vietnamese:
return "Windows-1258";
case CharacterSet::Thai:
return "TIS-620";
case CharacterSet::Iso8859_15:
return "ISO 8859-15";
default:
return "ISO 8859-1";
}
}
QString UnicodeFromText(QTextCodec *codec, std::string_view text) {
return codec->toUnicode(text.data(), static_cast<int>(text.length()));
}
static QFont::StyleStrategy ChooseStrategy(FontQuality eff)
{
switch (eff) {
case FontQuality::QualityDefault: return QFont::PreferDefault;
case FontQuality::QualityNonAntialiased: return QFont::NoAntialias;
case FontQuality::QualityAntialiased: return QFont::PreferAntialias;
case FontQuality::QualityLcdOptimized: return QFont::PreferAntialias;
default: return QFont::PreferDefault;
}
}
class FontAndCharacterSet : public Font {
public:
CharacterSet characterSet = CharacterSet::Ansi;
std::unique_ptr<QFont> pfont;
explicit FontAndCharacterSet(const FontParameters &fp) : characterSet(fp.characterSet) {
pfont = std::make_unique<QFont>();
pfont->setStyleStrategy(ChooseStrategy(fp.extraFontFlag));
pfont->setFamily(QString::fromUtf8(fp.faceName));
pfont->setPointSizeF(fp.size);
pfont->setBold(static_cast<int>(fp.weight) > 500);
pfont->setItalic(fp.italic);
}
};
namespace {
const Supports SupportsQt[] = {
Supports::LineDrawsFinal,
Supports::FractionalStrokeWidth,
Supports::TranslucentStroke,
Supports::PixelModification,
};
const FontAndCharacterSet *AsFontAndCharacterSet(const Font *f) {
return dynamic_cast<const FontAndCharacterSet *>(f);
}
QFont *FontPointer(const Font *f)
{
return AsFontAndCharacterSet(f)->pfont.get();
}
}
std::shared_ptr<Font> Font::Allocate(const FontParameters &fp)
{
return std::make_shared<FontAndCharacterSet>(fp);
}
SurfaceImpl::SurfaceImpl() = default;
SurfaceImpl::SurfaceImpl(int width, int height, SurfaceMode mode_)
{
if (width < 1) width = 1;
if (height < 1) height = 1;
deviceOwned = true;
device = new QPixmap(width, height);
mode = mode_;
}
SurfaceImpl::~SurfaceImpl()
{
Clear();
}
void SurfaceImpl::Clear()
{
if (painterOwned && painter) {
delete painter;
}
if (deviceOwned && device) {
delete device;
}
device = nullptr;
painter = nullptr;
deviceOwned = false;
painterOwned = false;
}
void SurfaceImpl::Init(WindowID wid)
{
Release();
device = static_cast<QWidget *>(wid);
}
void SurfaceImpl::Init(SurfaceID sid, WindowID /*wid*/)
{
Release();
device = static_cast<QPaintDevice *>(sid);
}
std::unique_ptr<Surface> SurfaceImpl::AllocatePixMap(int width, int height)
{
return std::make_unique<SurfaceImpl>(width, height, mode);
}
void SurfaceImpl::SetMode(SurfaceMode mode_)
{
mode = mode_;
}
void SurfaceImpl::Release() noexcept
{
Clear();
}
int SurfaceImpl::SupportsFeature(Supports feature) noexcept
{
for (const Supports f : SupportsQt) {
if (f == feature)
return 1;
}
return 0;
}
bool SurfaceImpl::Initialised()
{
return device != nullptr;
}
void SurfaceImpl::PenColour(ColourRGBA fore)
{
QPen penOutline(QColorFromColourRGBA(fore));
penOutline.setCapStyle(Qt::FlatCap);
GetPainter()->setPen(penOutline);
}
void SurfaceImpl::PenColourWidth(ColourRGBA fore, XYPOSITION strokeWidth) {
QPen penOutline(QColorFromColourRGBA(fore));
penOutline.setCapStyle(Qt::FlatCap);
penOutline.setJoinStyle(Qt::MiterJoin);
penOutline.setWidthF(strokeWidth);
GetPainter()->setPen(penOutline);
}
void SurfaceImpl::BrushColour(ColourRGBA back)
{
GetPainter()->setBrush(QBrush(QColorFromColourRGBA(back)));
}
void SurfaceImpl::SetCodec(const Font *font)
{
const FontAndCharacterSet *pfacs = AsFontAndCharacterSet(font);
if (pfacs && pfacs->pfont) {
const char *csid = "UTF-8";
if (!(mode.codePage == SC_CP_UTF8))
csid = CharacterSetID(pfacs->characterSet);
if (csid != codecName) {
codecName = csid;
codec = QTextCodec::codecForName(csid);
}
}
}
void SurfaceImpl::SetFont(const Font *font)
{
const FontAndCharacterSet *pfacs = AsFontAndCharacterSet(font);
if (pfacs && pfacs->pfont) {
GetPainter()->setFont(*(pfacs->pfont));
SetCodec(font);
}
}
int SurfaceImpl::LogPixelsY()
{
return device->logicalDpiY();
}
int SurfaceImpl::PixelDivisions()
{
// Qt uses device pixels.
return 1;
}
int SurfaceImpl::DeviceHeightFont(int points)
{
return points;
}
void SurfaceImpl::LineDraw(Point start, Point end, Stroke stroke)
{
PenColourWidth(stroke.colour, stroke.width);
QLineF line(start.x, start.y, end.x, end.y);
GetPainter()->drawLine(line);
}
void SurfaceImpl::PolyLine(const Point *pts, size_t npts, Stroke stroke)
{
// TODO: set line joins and caps
PenColourWidth(stroke.colour, stroke.width);
std::vector<QPointF> qpts;
std::transform(pts, pts + npts, std::back_inserter(qpts), QPointFFromPoint);
GetPainter()->drawPolyline(&qpts[0], static_cast<int>(npts));
}
void SurfaceImpl::Polygon(const Point *pts, size_t npts, FillStroke fillStroke)
{
PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
BrushColour(fillStroke.fill.colour);
std::vector<QPointF> qpts;
std::transform(pts, pts + npts, std::back_inserter(qpts), QPointFFromPoint);
GetPainter()->drawPolygon(&qpts[0], static_cast<int>(npts));
}
void SurfaceImpl::RectangleDraw(PRectangle rc, FillStroke fillStroke)
{
PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
BrushColour(fillStroke.fill.colour);
const QRectF rect = QRectFFromPRect(rc.Inset(fillStroke.stroke.width / 2));
GetPainter()->drawRect(rect);
}
void SurfaceImpl::RectangleFrame(PRectangle rc, Stroke stroke) {
PenColourWidth(stroke.colour, stroke.width);
// Default QBrush is Qt::NoBrush so does not fill
GetPainter()->setBrush(QBrush());
const QRectF rect = QRectFFromPRect(rc.Inset(stroke.width / 2));
GetPainter()->drawRect(rect);
}
void SurfaceImpl::FillRectangle(PRectangle rc, Fill fill)
{
GetPainter()->fillRect(QRectFFromPRect(rc), QColorFromColourRGBA(fill.colour));
}
void SurfaceImpl::FillRectangleAligned(PRectangle rc, Fill fill)
{
FillRectangle(PixelAlign(rc, 1), fill);
}
void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern)
{
// Tile pattern over rectangle
SurfaceImpl *surface = dynamic_cast<SurfaceImpl *>(&surfacePattern);
const QPixmap *pixmap = static_cast<QPixmap *>(surface->GetPaintDevice());
GetPainter()->drawTiledPixmap(QRectFromPRect(rc), *pixmap);
}
void SurfaceImpl::RoundedRectangle(PRectangle rc, FillStroke fillStroke)
{
PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
BrushColour(fillStroke.fill.colour);
GetPainter()->drawRoundedRect(QRectFFromPRect(rc), 3.0f, 3.0f);
}
void SurfaceImpl::AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke)
{
QColor qFill = QColorFromColourRGBA(fillStroke.fill.colour);
QBrush brushFill(qFill);
GetPainter()->setBrush(brushFill);
if (fillStroke.fill.colour == fillStroke.stroke.colour) {
painter->setPen(Qt::NoPen);
QRectF rect = QRectFFromPRect(rc);
if (cornerSize > 0.0f) {
// A radius of 1 shows no curve so add 1
qreal radius = cornerSize+1;
GetPainter()->drawRoundedRect(rect, radius, radius);
} else {
GetPainter()->fillRect(rect, brushFill);
}
} else {
QColor qOutline = QColorFromColourRGBA(fillStroke.stroke.colour);
QPen penOutline(qOutline);
penOutline.setWidthF(fillStroke.stroke.width);
GetPainter()->setPen(penOutline);
QRectF rect = QRectFFromPRect(rc.Inset(fillStroke.stroke.width / 2));
if (cornerSize > 0.0f) {
// A radius of 1 shows no curve so add 1
qreal radius = cornerSize+1;
GetPainter()->drawRoundedRect(rect, radius, radius);
} else {
GetPainter()->drawRect(rect);
}
}
}
void SurfaceImpl::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) {
QRectF rect = QRectFFromPRect(rc);
QLinearGradient linearGradient;
switch (options) {
case GradientOptions::leftToRight:
linearGradient = QLinearGradient(rc.left, rc.top, rc.right, rc.top);
break;
case GradientOptions::topToBottom:
default:
linearGradient = QLinearGradient(rc.left, rc.top, rc.left, rc.bottom);
break;
}
linearGradient.setSpread(QGradient::RepeatSpread);
for (const ColourStop &stop : stops) {
linearGradient.setColorAt(stop.position, QColorFromColourRGBA(stop.colour));
}
QBrush brush = QBrush(linearGradient);
GetPainter()->fillRect(rect, brush);
}
static std::vector<unsigned char> ImageByteSwapped(int width, int height, const unsigned char *pixelsImage)
{
// Input is RGBA, but Format_ARGB32 is BGRA, so swap the red bytes and blue bytes
size_t bytes = width * height * 4;
std::vector<unsigned char> imageBytes(pixelsImage, pixelsImage+bytes);
for (size_t i=0; i<bytes; i+=4)
std::swap(imageBytes[i], imageBytes[i+2]);
return imageBytes;
}
void SurfaceImpl::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage)
{
std::vector<unsigned char> imageBytes = ImageByteSwapped(width, height, pixelsImage);
QImage image(&imageBytes[0], width, height, QImage::Format_ARGB32);
QPoint pt(rc.left, rc.top);
GetPainter()->drawImage(pt, image);
}
void SurfaceImpl::Ellipse(PRectangle rc, FillStroke fillStroke)
{
PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
BrushColour(fillStroke.fill.colour);
const QRectF rect = QRectFFromPRect(rc.Inset(fillStroke.stroke.width / 2));
GetPainter()->drawEllipse(rect);
}
void SurfaceImpl::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) {
const XYPOSITION halfStroke = fillStroke.stroke.width / 2.0f;
const XYPOSITION radius = rc.Height() / 2.0f - halfStroke;
PRectangle rcInner = rc;
rcInner.left += radius;
rcInner.right -= radius;
const XYPOSITION arcHeight = rc.Height() - fillStroke.stroke.width;
PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
BrushColour(fillStroke.fill.colour);
QPainterPath path;
const Ends leftSide = static_cast<Ends>(static_cast<unsigned int>(ends) & 0xfu);
const Ends rightSide = static_cast<Ends>(static_cast<unsigned int>(ends) & 0xf0u);
switch (leftSide) {
case Ends::leftFlat:
path.moveTo(rc.left + halfStroke, rc.top + halfStroke);
path.lineTo(rc.left + halfStroke, rc.bottom - halfStroke);
break;
case Ends::leftAngle:
path.moveTo(rcInner.left + halfStroke, rc.top + halfStroke);
path.lineTo(rc.left + halfStroke, rc.Centre().y);
path.lineTo(rcInner.left + halfStroke, rc.bottom - halfStroke);
break;
case Ends::semiCircles:
default:
path.moveTo(rcInner.left + halfStroke, rc.top + halfStroke);
QRectF rectangleArc(rc.left + halfStroke, rc.top + halfStroke,
arcHeight, arcHeight);
path.arcTo(rectangleArc, 90, 180);
break;
}
switch (rightSide) {
case Ends::rightFlat:
path.lineTo(rc.right - halfStroke, rc.bottom - halfStroke);
path.lineTo(rc.right - halfStroke, rc.top + halfStroke);
break;
case Ends::rightAngle:
path.lineTo(rcInner.right - halfStroke, rc.bottom - halfStroke);
path.lineTo(rc.right - halfStroke, rc.Centre().y);
path.lineTo(rcInner.right - halfStroke, rc.top + halfStroke);
break;
case Ends::semiCircles:
default:
path.lineTo(rcInner.right - halfStroke, rc.bottom - halfStroke);
QRectF rectangleArc(rc.right - arcHeight - halfStroke, rc.top + halfStroke,
arcHeight, arcHeight);
path.arcTo(rectangleArc, 270, 180);
break;
}
// Close the path to enclose it for stroking and for filling, then draw it
path.closeSubpath();
GetPainter()->drawPath(path);
}
void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource)
{
SurfaceImpl *source = dynamic_cast<SurfaceImpl *>(&surfaceSource);
QPixmap *pixmap = static_cast<QPixmap *>(source->GetPaintDevice());
GetPainter()->drawPixmap(rc.left, rc.top, *pixmap, from.x, from.y, -1, -1);
}
std::unique_ptr<IScreenLineLayout> SurfaceImpl::Layout(const IScreenLine *)
{
return {};
}
void SurfaceImpl::DrawTextNoClip(PRectangle rc,
const Font *font,
XYPOSITION ybase,
std::string_view text,
ColourRGBA fore,
ColourRGBA back)
{
SetFont(font);
PenColour(fore);
GetPainter()->setBackground(QColorFromColourRGBA(back));
GetPainter()->setBackgroundMode(Qt::OpaqueMode);
QString su = UnicodeFromText(codec, text);
GetPainter()->drawText(QPointF(rc.left, ybase), su);
}
void SurfaceImpl::DrawTextClipped(PRectangle rc,
const Font *font,
XYPOSITION ybase,
std::string_view text,
ColourRGBA fore,
ColourRGBA back)
{
SetClip(rc);
DrawTextNoClip(rc, font, ybase, text, fore, back);
PopClip();
}
void SurfaceImpl::DrawTextTransparent(PRectangle rc,
const Font *font,
XYPOSITION ybase,
std::string_view text,
ColourRGBA fore)
{
SetFont(font);
PenColour(fore);
GetPainter()->setBackgroundMode(Qt::TransparentMode);
QString su = UnicodeFromText(codec, text);
GetPainter()->drawText(QPointF(rc.left, ybase), su);
}
void SurfaceImpl::SetClip(PRectangle rc)
{
GetPainter()->save();
GetPainter()->setClipRect(QRectFFromPRect(rc), Qt::IntersectClip);
}
void SurfaceImpl::PopClip()
{
GetPainter()->restore();
}
void SurfaceImpl::MeasureWidths(const Font *font,
std::string_view text,
XYPOSITION *positions)
{
if (!font)
return;
SetCodec(font);
QString su = UnicodeFromText(codec, text);
QTextLayout tlay(su, *FontPointer(font), GetPaintDevice());
tlay.beginLayout();
QTextLine tl = tlay.createLine();
tlay.endLayout();
if (mode.codePage == SC_CP_UTF8) {
int fit = su.size();
int ui=0;
size_t i=0;
while (ui<fit) {
const unsigned char uch = text[i];
const unsigned int byteCount = UTF8BytesOfLead[uch];
const int codeUnits = UTF16LengthFromUTF8ByteCount(byteCount);
qreal xPosition = tl.cursorToX(ui+codeUnits);
for (size_t bytePos=0; (bytePos<byteCount) && (i<text.length()); bytePos++) {
positions[i++] = xPosition;
}
ui += codeUnits;
}
XYPOSITION lastPos = 0;
if (i > 0)
lastPos = positions[i-1];
while (i<text.length()) {
positions[i++] = lastPos;
}
} else if (mode.codePage) {
// DBCS
int ui = 0;
for (size_t i=0; i<text.length();) {
size_t lenChar = DBCSIsLeadByte(mode.codePage, text[i]) ? 2 : 1;
qreal xPosition = tl.cursorToX(ui+1);
for (unsigned int bytePos=0; (bytePos<lenChar) && (i<text.length()); bytePos++) {
positions[i++] = xPosition;
}
ui++;
}
} else {
// Single byte encoding
for (int i=0; i<static_cast<int>(text.length()); i++) {
positions[i] = tl.cursorToX(i+1);
}
}
}
XYPOSITION SurfaceImpl::WidthText(const Font *font, std::string_view text)
{
QFontMetricsF metrics(*FontPointer(font), device);
SetCodec(font);
QString su = UnicodeFromText(codec, text);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return metrics.horizontalAdvance(su);
#else
return metrics.width(su);
#endif
}
void SurfaceImpl::DrawTextNoClipUTF8(PRectangle rc,
const Font *font,
XYPOSITION ybase,
std::string_view text,
ColourRGBA fore,
ColourRGBA back)
{
SetFont(font);
PenColour(fore);
GetPainter()->setBackground(QColorFromColourRGBA(back));
GetPainter()->setBackgroundMode(Qt::OpaqueMode);
QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length()));
GetPainter()->drawText(QPointF(rc.left, ybase), su);
}
void SurfaceImpl::DrawTextClippedUTF8(PRectangle rc,
const Font *font,
XYPOSITION ybase,
std::string_view text,
ColourRGBA fore,
ColourRGBA back)
{
SetClip(rc);
DrawTextNoClip(rc, font, ybase, text, fore, back);
PopClip();
}
void SurfaceImpl::DrawTextTransparentUTF8(PRectangle rc,
const Font *font,
XYPOSITION ybase,
std::string_view text,
ColourRGBA fore)
{
SetFont(font);
PenColour(fore);
GetPainter()->setBackgroundMode(Qt::TransparentMode);
QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length()));
GetPainter()->drawText(QPointF(rc.left, ybase), su);
}
void SurfaceImpl::MeasureWidthsUTF8(const Font *font,
std::string_view text,
XYPOSITION *positions)
{
if (!font)
return;
QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length()));
QTextLayout tlay(su, *FontPointer(font), GetPaintDevice());
tlay.beginLayout();
QTextLine tl = tlay.createLine();
tlay.endLayout();
int fit = su.size();
int ui=0;
size_t i=0;
while (ui<fit) {
const unsigned char uch = text[i];
const unsigned int byteCount = UTF8BytesOfLead[uch];
const int codeUnits = UTF16LengthFromUTF8ByteCount(byteCount);
qreal xPosition = tl.cursorToX(ui+codeUnits);
for (size_t bytePos=0; (bytePos<byteCount) && (i<text.length()); bytePos++) {
positions[i++] = xPosition;
}
ui += codeUnits;
}
XYPOSITION lastPos = 0;
if (i > 0)
lastPos = positions[i-1];
while (i<text.length()) {
positions[i++] = lastPos;
}
}
XYPOSITION SurfaceImpl::WidthTextUTF8(const Font *font, std::string_view text)
{
QFontMetricsF metrics(*FontPointer(font), device);
QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length()));
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return metrics.horizontalAdvance(su);
#else
return metrics.width(su);
#endif
}
XYPOSITION SurfaceImpl::Ascent(const Font *font)
{
QFontMetricsF metrics(*FontPointer(font), device);
return metrics.ascent();
}
XYPOSITION SurfaceImpl::Descent(const Font *font)
{
QFontMetricsF metrics(*FontPointer(font), device);
// Qt returns 1 less than true descent
// See: QFontEngineWin::descent which says:
// ### we subtract 1 to even out the historical +1 in QFontMetrics's
// ### height=asc+desc+1 equation. Fix in Qt5.
return metrics.descent() + 1;
}
XYPOSITION SurfaceImpl::InternalLeading(const Font * /* font */)
{
return 0;
}
XYPOSITION SurfaceImpl::Height(const Font *font)
{
QFontMetricsF metrics(*FontPointer(font), device);
return metrics.height();
}
XYPOSITION SurfaceImpl::AverageCharWidth(const Font *font)
{
QFontMetricsF metrics(*FontPointer(font), device);
return metrics.averageCharWidth();
}
void SurfaceImpl::FlushCachedState()
{
if (device->paintingActive()) {
GetPainter()->setPen(QPen());
GetPainter()->setBrush(QBrush());
}
}
void SurfaceImpl::FlushDrawing()
{
}
QPaintDevice *SurfaceImpl::GetPaintDevice()
{
return device;
}
QPainter *SurfaceImpl::GetPainter()
{
Q_ASSERT(device);
if (!painter) {
if (device->paintingActive()) {
painter = device->paintEngine()->painter();
} else {
painterOwned = true;
painter = new QPainter(device);
}
// Set text antialiasing unconditionally.
// The font's style strategy will override.
painter->setRenderHint(QPainter::TextAntialiasing, true);
painter->setRenderHint(QPainter::Antialiasing, true);
}
return painter;
}
std::unique_ptr<Surface> Surface::Allocate(Technology)
{
return std::make_unique<SurfaceImpl>();
}
//----------------------------------------------------------------------
namespace {
QWidget *window(WindowID wid) noexcept
{
return static_cast<QWidget *>(wid);
}
QRect ScreenRectangleForPoint(QPoint posGlobal)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
const QScreen *screen = QGuiApplication::screenAt(posGlobal);
if (!screen) {
screen = QGuiApplication::primaryScreen();
}
return screen->availableGeometry();
#else
const QDesktopWidget *desktop = QApplication::desktop();
return desktop->availableGeometry(posGlobal);
#endif
}
}
Window::~Window() noexcept = default;
void Window::Destroy() noexcept
{
if (wid)
delete window(wid);
wid = nullptr;
}
PRectangle Window::GetPosition() const
{
// Before any size allocated pretend its 1000 wide so not scrolled
return wid ? PRectFromQRect(window(wid)->frameGeometry()) : PRectangle(0, 0, 1000, 1000);
}
void Window::SetPosition(PRectangle rc)
{
if (wid)
window(wid)->setGeometry(QRectFromPRect(rc));
}
void Window::SetPositionRelative(PRectangle rc, const Window *relativeTo)
{
QPoint oPos = window(relativeTo->wid)->mapToGlobal(QPoint(0,0));
int ox = oPos.x();
int oy = oPos.y();
ox += rc.left;
oy += rc.top;
const QRect rectDesk = ScreenRectangleForPoint(QPoint(ox, oy));
/* do some corrections to fit into screen */
int sizex = rc.right - rc.left;
int sizey = rc.bottom - rc.top;
int screenWidth = rectDesk.width();
if (ox < rectDesk.x())
ox = rectDesk.x();
if (sizex > screenWidth)
ox = rectDesk.x(); /* the best we can do */
else if (ox + sizex > rectDesk.right())
ox = rectDesk.right() - sizex;
if (oy + sizey > rectDesk.bottom())
oy = rectDesk.bottom() - sizey;
if (oy < rectDesk.top())
oy = rectDesk.top();
Q_ASSERT(wid);
window(wid)->move(ox, oy);
window(wid)->resize(sizex, sizey);
}
PRectangle Window::GetClientPosition() const
{
// The client position is the window position
return GetPosition();
}
void Window::Show(bool show)
{
if (wid)
window(wid)->setVisible(show);
}
void Window::InvalidateAll()
{
if (wid)
window(wid)->update();
}
void Window::InvalidateRectangle(PRectangle rc)
{
if (wid)
window(wid)->update(QRectFromPRect(rc));
}
void Window::SetCursor(Cursor curs)
{
if (wid) {
Qt::CursorShape shape;
switch (curs) {
case Cursor::text: shape = Qt::IBeamCursor; break;
case Cursor::arrow: shape = Qt::ArrowCursor; break;
case Cursor::up: shape = Qt::UpArrowCursor; break;
case Cursor::wait: shape = Qt::WaitCursor; break;
case Cursor::horizontal: shape = Qt::SizeHorCursor; break;
case Cursor::vertical: shape = Qt::SizeVerCursor; break;
case Cursor::hand: shape = Qt::PointingHandCursor; break;
default: shape = Qt::ArrowCursor; break;
}
QCursor cursor = QCursor(shape);
if (curs != cursorLast) {
window(wid)->setCursor(cursor);
cursorLast = curs;
}
}
}
/* Returns rectangle of monitor pt is on, both rect and pt are in Window's
window coordinates */
PRectangle Window::GetMonitorRect(Point pt)
{
const QPoint posGlobal = window(wid)->mapToGlobal(QPoint(pt.x, pt.y));
const QPoint originGlobal = window(wid)->mapToGlobal(QPoint(0, 0));
QRect rectScreen = ScreenRectangleForPoint(posGlobal);
rectScreen.translate(-originGlobal.x(), -originGlobal.y());
return PRectFromQRect(rectScreen);
}
//----------------------------------------------------------------------
class ListWidget : public QListWidget {
public:
explicit ListWidget(QWidget *parent);
void setDelegate(IListBoxDelegate *lbDelegate);
int currentSelection();
protected:
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void initViewItemOption(QStyleOptionViewItem *option) const override;
#else
QStyleOptionViewItem viewOptions() const override;
#endif
private:
IListBoxDelegate *delegate;
};
class ListBoxImpl : public ListBox {
public:
ListBoxImpl() noexcept;
void SetFont(const Font *font) override;
void Create(Window &parent, int ctrlID, Point location,
int lineHeight, bool unicodeMode_, Technology technology) override;
void SetAverageCharWidth(int width) override;
void SetVisibleRows(int rows) override;
int GetVisibleRows() const override;
PRectangle GetDesiredRect() override;
int CaretFromEdge() override;
void Clear() noexcept override;
void Append(char *s, int type) override;
int Length() override;
void Select(int n) override;
int GetSelection() override;
int Find(const char *prefix) override;
std::string GetValue(int n) override;
void RegisterImage(int type, const char *xpmData) override;
void RegisterRGBAImage(int type, int width, int height,
const unsigned char *pixelsImage) override;
virtual void RegisterQPixmapImage(int type, const QPixmap& pm);
void ClearRegisteredImages() override;
void SetDelegate(IListBoxDelegate *lbDelegate) override;
void SetList(const char *list, char separator, char typesep) override;
void SetOptions(ListOptions options_) override;
[[nodiscard]] ListWidget *GetWidget() const noexcept;
private:
bool unicodeMode{false};
int visibleRows{5};
QMap<int,QPixmap> images;
};
ListBoxImpl::ListBoxImpl() noexcept = default;
void ListBoxImpl::Create(Window &parent,
int /*ctrlID*/,
Point location,
int /*lineHeight*/,
bool unicodeMode_,
Technology)
{
unicodeMode = unicodeMode_;
QWidget *qparent = static_cast<QWidget *>(parent.GetID());
ListWidget *list = new ListWidget(qparent);
#if defined(Q_OS_WIN)
// On Windows, Qt::ToolTip causes a crash when the list is clicked on
// so Qt::Tool is used.
list->setParent(nullptr, Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
| Qt::WindowDoesNotAcceptFocus
#endif
);
#else
// On macOS, Qt::Tool takes focus so main window loses focus so
// keyboard stops working. Qt::ToolTip works but its only really
// documented for tooltips.
// On Linux / X this setting allows clicking on list items.
list->setParent(nullptr, static_cast<Qt::WindowFlags>(Qt::ToolTip | Qt::FramelessWindowHint));
#endif
list->setAttribute(Qt::WA_ShowWithoutActivating);
list->setFocusPolicy(Qt::NoFocus);
list->setUniformItemSizes(true);
list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
list->move(location.x, location.y);
int maxIconWidth = 0;
int maxIconHeight = 0;
foreach (QPixmap im, images) {
if (maxIconWidth < im.width())
maxIconWidth = im.width();
if (maxIconHeight < im.height())
maxIconHeight = im.height();
}
list->setIconSize(QSize(maxIconWidth, maxIconHeight));
wid = list;
}
void ListBoxImpl::SetFont(const Font *font)
{
ListWidget *list = GetWidget();
const FontAndCharacterSet *pfacs = AsFontAndCharacterSet(font);
if (pfacs && pfacs->pfont) {
list->setFont(*(pfacs->pfont));
}
}
void ListBoxImpl::SetAverageCharWidth(int /*width*/) {}
void ListBoxImpl::SetVisibleRows(int rows)
{
visibleRows = rows;
}
int ListBoxImpl::GetVisibleRows() const
{
return visibleRows;
}
PRectangle ListBoxImpl::GetDesiredRect()
{
ListWidget *list = GetWidget();
int rows = Length();
if (rows == 0 || rows > visibleRows) {
rows = visibleRows;
}
int rowHeight = list->sizeHintForRow(0);
int height = (rows * rowHeight) + (2 * list->frameWidth());
QStyle *style = QApplication::style();
int width = list->sizeHintForColumn(0) + (2 * list->frameWidth());
if (Length() > rows) {
width += style->pixelMetric(QStyle::PM_ScrollBarExtent);
}
return PRectangle(0, 0, width, height);
}
int ListBoxImpl::CaretFromEdge()
{
ListWidget *list = GetWidget();
int maxIconWidth = 0;
foreach (QPixmap im, images) {
if (maxIconWidth < im.width())
maxIconWidth = im.width();
}
int extra;
// The 12 is from trial and error on macOS and the 7
// is from trial and error on Windows - there may be
// a better programmatic way to find any padding factors.
#ifdef Q_OS_DARWIN
extra = 12;
#else
extra = 7;
#endif
return maxIconWidth + (2 * list->frameWidth()) + extra;
}
void ListBoxImpl::Clear() noexcept
{
ListWidget *list = GetWidget();
list->clear();
}
void ListBoxImpl::Append(char *s, int type)
{
ListWidget *list = GetWidget();
QString str = unicodeMode ? QString::fromUtf8(s) : QString::fromLocal8Bit(s);
QIcon icon;
if (type >= 0) {
Q_ASSERT(images.contains(type));
icon = images.value(type);
}
new QListWidgetItem(icon, str, list);
}
int ListBoxImpl::Length()
{
ListWidget *list = GetWidget();
return list->count();
}
void ListBoxImpl::Select(int n)
{
ListWidget *list = GetWidget();
QModelIndex index = list->model()->index(n, 0);
if (index.isValid()) {
QRect row_rect = list->visualRect(index);
if (!list->viewport()->rect().contains(row_rect)) {
list->scrollTo(index, QAbstractItemView::PositionAtTop);
}
}
list->setCurrentRow(n);
}
int ListBoxImpl::GetSelection()
{
ListWidget *list = GetWidget();
return list->currentSelection();
}
int ListBoxImpl::Find(const char *prefix)
{
ListWidget *list = GetWidget();
QString sPrefix = unicodeMode ? QString::fromUtf8(prefix) : QString::fromLocal8Bit(prefix);
QList<QListWidgetItem *> ms = list->findItems(sPrefix, Qt::MatchStartsWith);
int result = -1;
if (!ms.isEmpty()) {
result = list->row(ms.first());
}
return result;
}
std::string ListBoxImpl::GetValue(int n)
{
ListWidget *list = GetWidget();
QListWidgetItem *item = list->item(n);
QString str = item->data(Qt::DisplayRole).toString();
QByteArray bytes = unicodeMode ? str.toUtf8() : str.toLocal8Bit();
return std::string(bytes.constData());
}
void ListBoxImpl::RegisterQPixmapImage(int type, const QPixmap& pm)
{
images[type] = pm;
ListWidget *list = GetWidget();
if (list) {
QSize iconSize = list->iconSize();
if (pm.width() > iconSize.width() || pm.height() > iconSize.height())
list->setIconSize(QSize(qMax(pm.width(), iconSize.width()),
qMax(pm.height(), iconSize.height())));
}
}
void ListBoxImpl::RegisterImage(int type, const char *xpmData)
{
XPM xpmImage(xpmData);
RGBAImage rgbaImage(xpmImage);
RegisterRGBAImage(type, rgbaImage.GetWidth(), rgbaImage.GetHeight(), rgbaImage.Pixels());
}
void ListBoxImpl::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage)
{
std::vector<unsigned char> imageBytes = ImageByteSwapped(width, height, pixelsImage);
QImage image(&imageBytes[0], width, height, QImage::Format_ARGB32);
RegisterQPixmapImage(type, QPixmap::fromImage(image));
}
void ListBoxImpl::ClearRegisteredImages()
{
images.clear();
ListWidget *list = GetWidget();
if (list)
list->setIconSize(QSize(0, 0));
}
void ListBoxImpl::SetDelegate(IListBoxDelegate *lbDelegate)
{
ListWidget *list = GetWidget();
list->setDelegate(lbDelegate);
}
void ListBoxImpl::SetList(const char *list, char separator, char typesep)
{
// This method is *not* platform dependent.
// It is borrowed from the GTK implementation.
Clear();
size_t count = strlen(list) + 1;
std::vector<char> words(list, list+count);
char *startword = &words[0];
char *numword = nullptr;
int i = 0;
for (; words[i]; i++) {
if (words[i] == separator) {
words[i] = '\0';
if (numword)
*numword = '\0';
Append(startword, numword?atoi(numword + 1):-1);
startword = &words[0] + i + 1;
numword = nullptr;
} else if (words[i] == typesep) {
numword = &words[0] + i;
}
}
if (startword) {
if (numword)
*numword = '\0';
Append(startword, numword?atoi(numword + 1):-1);
}
}
void ListBoxImpl::SetOptions(ListOptions)
{
}
ListWidget *ListBoxImpl::GetWidget() const noexcept
{
return static_cast<ListWidget *>(wid);
}
ListBox::ListBox() noexcept = default;
ListBox::~ListBox() noexcept = default;
std::unique_ptr<ListBox> ListBox::Allocate()
{
return std::make_unique<ListBoxImpl>();
}
ListWidget::ListWidget(QWidget *parent)
: QListWidget(parent), delegate(nullptr)
{}
void ListWidget::setDelegate(IListBoxDelegate *lbDelegate)
{
delegate = lbDelegate;
}
void ListWidget::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
QListWidget::selectionChanged(selected, deselected);
if (delegate) {
const int selection = currentSelection();
if (selection >= 0) {
ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
delegate->ListNotify(&event);
}
}
}
int ListWidget::currentSelection() {
const QModelIndexList indices = selectionModel()->selectedRows();
foreach (const QModelIndex ind, indices) {
return ind.row();
}
return -1;
}
void ListWidget::mouseDoubleClickEvent(QMouseEvent * /* event */)
{
if (delegate) {
ListBoxEvent event(ListBoxEvent::EventType::doubleClick);
delegate->ListNotify(&event);
}
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void ListWidget::initViewItemOption(QStyleOptionViewItem *option) const
{
QListWidget::initViewItemOption(option);
option->state |= QStyle::State_Active;
}
#else
QStyleOptionViewItem ListWidget::viewOptions() const
{
QStyleOptionViewItem result = QListWidget::viewOptions();
result.state |= QStyle::State_Active;
return result;
}
#endif
//----------------------------------------------------------------------
Menu::Menu() noexcept : mid(nullptr) {}
void Menu::CreatePopUp()
{
Destroy();
mid = new QMenu();
}
void Menu::Destroy() noexcept
{
if (mid) {
QMenu *menu = static_cast<QMenu *>(mid);
delete menu;
}
mid = nullptr;
}
void Menu::Show(Point pt, const Window & /*w*/)
{
QMenu *menu = static_cast<QMenu *>(mid);
menu->exec(QPoint(pt.x, pt.y));
Destroy();
}
//----------------------------------------------------------------------
ColourRGBA Platform::Chrome()
{
QColor c(Qt::gray);
return ColourRGBA(c.red(), c.green(), c.blue());
}
ColourRGBA Platform::ChromeHighlight()
{
QColor c(Qt::lightGray);
return ColourRGBA(c.red(), c.green(), c.blue());
}
const char *Platform::DefaultFont()
{
static char fontNameDefault[200] = "";
if (!fontNameDefault[0]) {
QFont font = QApplication::font();
strcpy(fontNameDefault, font.family().toUtf8());
}
return fontNameDefault;
}
int Platform::DefaultFontSize()
{
QFont font = QApplication::font();
return font.pointSize();
}
unsigned int Platform::DoubleClickTime()
{
return QApplication::doubleClickInterval();
}
void Platform::DebugDisplay(const char *s) noexcept
{
qWarning("Scintilla: %s", s);
}
void Platform::DebugPrintf(const char *format, ...) noexcept
{
char buffer[2000];
va_list pArguments{};
va_start(pArguments, format);
vsprintf(buffer, format, pArguments);
va_end(pArguments);
Platform::DebugDisplay(buffer);
}
bool Platform::ShowAssertionPopUps(bool /*assertionPopUps*/) noexcept
{
return false;
}
void Platform::Assert(const char *c, const char *file, int line) noexcept
{
char buffer[2000];
sprintf(buffer, "Assertion [%s] failed at %s %d", c, file, line);
if (Platform::ShowAssertionPopUps(false)) {
QMessageBox mb("Assertion Failure", buffer, QMessageBox::NoIcon,
QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton);
mb.exec();
} else {
strcat(buffer, "\n");
Platform::DebugDisplay(buffer);
}
}
}