// @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 #include "PlatQt.h" #include "Scintilla.h" #include "UniConversion.h" #include "DBCS.h" #include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(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 pfont; explicit FontAndCharacterSet(const FontParameters &fp) : characterSet(fp.characterSet) { pfont = std::make_unique(); pfont->setStyleStrategy(ChooseStrategy(fp.extraFontFlag)); pfont->setFamily(QString::fromUtf8(fp.faceName)); pfont->setPointSizeF(fp.size); pfont->setBold(static_cast(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(f); } QFont *FontPointer(const Font *f) { return AsFontAndCharacterSet(f)->pfont.get(); } } std::shared_ptr Font::Allocate(const FontParameters &fp) { return std::make_shared(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(wid); } void SurfaceImpl::Init(SurfaceID sid, WindowID /*wid*/) { Release(); device = static_cast(sid); } std::unique_ptr SurfaceImpl::AllocatePixMap(int width, int height) { return std::make_unique(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 qpts; std::transform(pts, pts + npts, std::back_inserter(qpts), QPointFFromPoint); GetPainter()->drawPolyline(&qpts[0], static_cast(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 qpts; std::transform(pts, pts + npts, std::back_inserter(qpts), QPointFFromPoint); GetPainter()->drawPolygon(&qpts[0], static_cast(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(&surfacePattern); // Currently assumes 8x8 pattern int widthPat = 8; int heightPat = 8; for (int xTile = rc.left; xTile < rc.right; xTile += widthPat) { int widthx = (xTile + widthPat > rc.right) ? rc.right - xTile : widthPat; for (int yTile = rc.top; yTile < rc.bottom; yTile += heightPat) { int heighty = (yTile + heightPat > rc.bottom) ? rc.bottom - yTile : heightPat; QRect source(0, 0, widthx, heighty); QRect target(xTile, yTile, widthx, heighty); QPixmap *pixmap = static_cast(surface->GetPaintDevice()); GetPainter()->drawPixmap(target, *pixmap, source); } } } 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 &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 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 imageBytes(pixelsImage, pixelsImage+bytes); for (size_t i=0; i 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(static_cast(ends) & 0xfu); const Ends rightSide = static_cast(static_cast(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(&surfaceSource); QPixmap *pixmap = static_cast(source->GetPaintDevice()); GetPainter()->drawPixmap(rc.left, rc.top, *pixmap, from.x, from.y, -1, -1); } std::unique_ptr 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)); } 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 0) lastPos = positions[i-1]; while (i(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(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(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(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 0) lastPos = positions[i-1]; while (i(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::Allocate(Technology) { return std::make_unique(); } //---------------------------------------------------------------------- namespace { QWidget *window(WindowID wid) noexcept { return static_cast(wid); } QRect ScreenRectangleForPoint(QPoint posGlobal) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) const QScreen *screen = QGuiApplication::screenAt(posGlobal); 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; 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 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(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::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 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) { RegisterQPixmapImage(type, QPixmap(reinterpret_cast(xpmData))); } void ListBoxImpl::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) { std::vector 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 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(wid); } ListBox::ListBox() noexcept = default; ListBox::~ListBox() noexcept = default; std::unique_ptr ListBox::Allocate() { return std::make_unique(); } 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(mid); delete menu; } mid = nullptr; } void Menu::Show(Point pt, const Window & /*w*/) { QMenu *menu = static_cast(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); } } }