2009-04-24 23:35:41 +00:00
|
|
|
// Scintilla source code edit control
|
|
|
|
// PlatGTK.cxx - implementation of platform facilities on GTK+/Linux
|
|
|
|
// Copyright 1998-2004 by Neil Hodgson <neilh@scintilla.org>
|
|
|
|
// The License.txt file describes the conditions under which this software may be distributed.
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
#include <cstddef>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <cstring>
|
|
|
|
#include <cstdio>
|
|
|
|
#include <cmath>
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
#include <string>
|
2019-05-04 18:14:48 +00:00
|
|
|
#include <string_view>
|
2013-08-28 00:44:27 +00:00
|
|
|
#include <vector>
|
|
|
|
#include <map>
|
2022-01-04 23:07:50 +00:00
|
|
|
#include <optional>
|
2019-07-21 13:26:02 +00:00
|
|
|
#include <algorithm>
|
2019-05-04 18:14:48 +00:00
|
|
|
#include <memory>
|
|
|
|
#include <sstream>
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
#include <glib.h>
|
|
|
|
#include <gmodule.h>
|
|
|
|
#include <gdk/gdk.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#include <gdk/gdkkeysyms.h>
|
2022-01-04 23:07:50 +00:00
|
|
|
#if defined(GDK_WINDOWING_WAYLAND)
|
|
|
|
#include <gdk/gdkwayland.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "ScintillaTypes.h"
|
|
|
|
#include "ScintillaMessages.h"
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
#include "Debugging.h"
|
|
|
|
#include "Geometry.h"
|
2009-04-24 23:35:41 +00:00
|
|
|
#include "Platform.h"
|
|
|
|
|
|
|
|
#include "Scintilla.h"
|
|
|
|
#include "ScintillaWidget.h"
|
Updated to Scintilla 5.4.2 & Lexilla 5.3.1
https://www.scintilla.org/scintilla542.zip
Release 5.4.2
Released 5 March 2024.
Significantly reduce memory used for undo actions, often to a half or quarter of previous versions. Feature #1458.
Add APIs for saving and restoring undo history.
For GTK, when laying out text, detect runs with both left-to-right and right-to-left ranges and divide into an ASCII prefix and more complex suffix. Lay out the ASCII prefix in the standard manner but, for the suffix, measure the whole width and spread that over the suffix bytes. This produces more usable results where the caret moves over the ASCII prefix correctly and over the suffix reasonably but not accurately.
For ScintillaEdit on Qt, fix reference from ScintillaDocument to Document to match change in 5.4.1 using IDocumentEditable for SCI_GETDOCPOINTER and SCI_SETDOCPOINTER.
For Direct2D on Win32, use the multi-threaded option to avoid crashes when Scintilla instances created on different threads. There may be more problems with this scenario so it should be avoided. Bug #2420.
For Win32, ensure keyboard-initiated context menu appears in multi-screen situations.
https://www.scintilla.org/lexilla531.zip
Release 5.3.1
Released 5 March 2024.
Assembler: After comments, treat \r\n line ends the same as \n. This makes testing easier.
Bash: Fix folding when line changed to/from comment and previous line is comment. Issue #224.
Batch: Fix handling ':' next to keywords. Issue #222.
JavaScript: in cpp lexer, add lexer.cpp.backquoted.strings=2 mode to treat ` back-quoted strings as template literals which allow embedded ${expressions}. Issue #94.
Python: fix lexing of rb'' and rf'' strings. Issue #223, Pull request #227.
Ruby: fix lexing of methods on numeric literals like '3.times' so the '.' and method name do not appear in numeric style. Issue #225.
2024-03-06 21:05:54 +00:00
|
|
|
#include "CharacterType.h"
|
2009-04-24 23:35:41 +00:00
|
|
|
#include "XPM.h"
|
2015-06-07 21:19:26 +00:00
|
|
|
#include "UniConversion.h"
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
#include "Wrappers.h"
|
2009-04-24 23:35:41 +00:00
|
|
|
#include "Converter.h"
|
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
#ifdef _MSC_VER
|
|
|
|
// Ignore unreferenced local functions in GTK+ headers
|
|
|
|
#pragma warning(disable: 4505)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
using namespace Scintilla;
|
2022-01-04 23:07:50 +00:00
|
|
|
using namespace Scintilla::Internal;
|
2019-07-21 13:26:02 +00:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
constexpr double kPi = 3.14159265358979323846;
|
2015-06-07 21:19:26 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
constexpr double degrees = kPi / 180.0;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
struct IntegerRectangle {
|
|
|
|
int left;
|
|
|
|
int top;
|
|
|
|
int right;
|
|
|
|
int bottom;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
explicit IntegerRectangle(PRectangle rc) noexcept :
|
|
|
|
left(static_cast<int>(rc.left)), top(static_cast<int>(rc.top)),
|
|
|
|
right(static_cast<int>(rc.right)), bottom(static_cast<int>(rc.bottom)) {
|
|
|
|
}
|
|
|
|
int Width() const noexcept { return right - left; }
|
|
|
|
int Height() const noexcept { return bottom - top; }
|
|
|
|
};
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
GtkWidget *PWidget(WindowID wid) noexcept {
|
|
|
|
return static_cast<GtkWidget *>(wid);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
2011-03-22 00:16:49 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SetFractionalPositions([[maybe_unused]] PangoContext *pcontext) noexcept {
|
|
|
|
#if PANGO_VERSION_CHECK(1,44,3)
|
|
|
|
pango_context_set_round_glyph_positions(pcontext, FALSE);
|
|
|
|
#endif
|
2011-07-17 22:30:49 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void LayoutSetText(PangoLayout *layout, std::string_view text) noexcept {
|
|
|
|
pango_layout_set_text(layout, text.data(), static_cast<int>(text.length()));
|
2019-07-21 13:26:02 +00:00
|
|
|
}
|
2009-06-24 19:09:31 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
enum class EncodingType { singleByte, utf8, dbcs };
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
// Holds a PangoFontDescription*.
|
2022-01-04 23:07:50 +00:00
|
|
|
class FontHandle : public Font {
|
2009-04-24 23:35:41 +00:00
|
|
|
public:
|
2022-01-04 23:07:50 +00:00
|
|
|
UniquePangoFontDescription fd;
|
|
|
|
CharacterSet characterSet;
|
|
|
|
explicit FontHandle(const FontParameters &fp) :
|
|
|
|
fd(pango_font_description_new()), characterSet(fp.characterSet) {
|
|
|
|
if (fd) {
|
|
|
|
pango_font_description_set_family(fd.get(),
|
|
|
|
(fp.faceName[0] == '!') ? fp.faceName + 1 : fp.faceName);
|
|
|
|
pango_font_description_set_size(fd.get(), pango_units_from_double(fp.size));
|
|
|
|
pango_font_description_set_weight(fd.get(), static_cast<PangoWeight>(fp.weight));
|
|
|
|
pango_font_description_set_style(fd.get(), fp.italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
~FontHandle() override = default;
|
2009-04-24 23:35:41 +00:00
|
|
|
};
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
// X has a 16 bit coordinate space, so stop drawing here to avoid wrapping
|
2021-02-21 04:53:09 +00:00
|
|
|
constexpr int maxCoordinate = 32000;
|
2019-05-04 18:14:48 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
const FontHandle *PFont(const Font *f) noexcept {
|
|
|
|
return dynamic_cast<const FontHandle *>(f);
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
std::shared_ptr<Font> Font::Allocate(const FontParameters &fp) {
|
|
|
|
return std::make_shared<FontHandle>(fp);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2010-07-12 22:19:51 +00:00
|
|
|
namespace Scintilla {
|
2011-07-17 22:30:49 +00:00
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
// SurfaceID is a cairo_t*
|
2010-07-12 22:19:51 +00:00
|
|
|
class SurfaceImpl : public Surface {
|
2022-01-04 23:07:50 +00:00
|
|
|
SurfaceMode mode;
|
|
|
|
EncodingType et= EncodingType::singleByte;
|
|
|
|
WindowID widSave = nullptr;
|
|
|
|
cairo_t *context = nullptr;
|
|
|
|
UniqueCairo cairoOwned;
|
|
|
|
UniqueCairoSurface surf;
|
|
|
|
bool inited = false;
|
|
|
|
UniquePangoContext pcontext;
|
|
|
|
double resolution = 1.0;
|
|
|
|
PangoDirection direction = PANGO_DIRECTION_LTR;
|
|
|
|
const cairo_font_options_t *fontOptions = nullptr;
|
|
|
|
PangoLanguage *language = nullptr;
|
|
|
|
UniquePangoLayout layout;
|
2009-04-24 23:35:41 +00:00
|
|
|
Converter conv;
|
2022-01-04 23:07:50 +00:00
|
|
|
CharacterSet characterSet = static_cast<CharacterSet>(-1);
|
|
|
|
|
|
|
|
void PenColourAlpha(ColourRGBA fore) noexcept;
|
|
|
|
void SetConverter(CharacterSet characterSet_);
|
|
|
|
void CairoRectangle(PRectangle rc) noexcept;
|
2009-04-24 23:35:41 +00:00
|
|
|
public:
|
2019-07-21 13:26:02 +00:00
|
|
|
SurfaceImpl() noexcept;
|
2022-01-04 23:07:50 +00:00
|
|
|
SurfaceImpl(cairo_t *context_, int width, int height, SurfaceMode mode_, WindowID wid) noexcept;
|
2021-02-21 04:53:09 +00:00
|
|
|
// Deleted so SurfaceImpl objects can not be copied.
|
|
|
|
SurfaceImpl(const SurfaceImpl&) = delete;
|
|
|
|
SurfaceImpl(SurfaceImpl&&) = delete;
|
|
|
|
SurfaceImpl&operator=(const SurfaceImpl&) = delete;
|
|
|
|
SurfaceImpl&operator=(SurfaceImpl&&) = delete;
|
2022-01-04 23:07:50 +00:00
|
|
|
~SurfaceImpl() override = default;
|
|
|
|
|
|
|
|
void GetContextState() noexcept;
|
|
|
|
UniquePangoContext MeasuringContext();
|
2019-05-04 18:14:48 +00:00
|
|
|
|
|
|
|
void Init(WindowID wid) override;
|
|
|
|
void Init(SurfaceID sid, WindowID wid) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
std::unique_ptr<Surface> AllocatePixMap(int width, int height) override;
|
|
|
|
|
|
|
|
void SetMode(SurfaceMode mode_) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void Release() noexcept override;
|
|
|
|
int SupportsFeature(Supports feature) noexcept override;
|
2019-05-04 18:14:48 +00:00
|
|
|
bool Initialised() override;
|
|
|
|
int LogPixelsY() override;
|
2022-01-04 23:07:50 +00:00
|
|
|
int PixelDivisions() override;
|
2019-05-04 18:14:48 +00:00
|
|
|
int DeviceHeightFont(int points) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void LineDraw(Point start, Point end, Stroke stroke) override;
|
|
|
|
void PolyLine(const Point *pts, size_t npts, Stroke stroke) override;
|
|
|
|
void Polygon(const Point *pts, size_t npts, FillStroke fillStroke) override;
|
|
|
|
void RectangleDraw(PRectangle rc, FillStroke fillStroke) override;
|
|
|
|
void RectangleFrame(PRectangle rc, Stroke stroke) override;
|
|
|
|
void FillRectangle(PRectangle rc, Fill fill) override;
|
|
|
|
void FillRectangleAligned(PRectangle rc, Fill fill) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void FillRectangle(PRectangle rc, Surface &surfacePattern) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void RoundedRectangle(PRectangle rc, FillStroke fillStroke) override;
|
|
|
|
void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override;
|
|
|
|
void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void Ellipse(PRectangle rc, FillStroke fillStroke) override;
|
|
|
|
void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void Copy(PRectangle rc, Point from, Surface &surfaceSource) override;
|
|
|
|
|
|
|
|
std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override;
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void DrawTextBase(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore);
|
|
|
|
void DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
|
|
|
|
void DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
|
|
|
|
void DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
|
|
|
|
void MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) override;
|
|
|
|
XYPOSITION WidthText(const Font *font_, std::string_view text) override;
|
|
|
|
|
|
|
|
void DrawTextBaseUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore);
|
|
|
|
void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
|
|
|
|
void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
|
|
|
|
void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
|
|
|
|
void MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) override;
|
|
|
|
XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) override;
|
|
|
|
|
|
|
|
XYPOSITION Ascent(const Font *font_) override;
|
|
|
|
XYPOSITION Descent(const Font *font_) override;
|
|
|
|
XYPOSITION InternalLeading(const Font *font_) override;
|
|
|
|
XYPOSITION Height(const Font *font_) override;
|
|
|
|
XYPOSITION AverageCharWidth(const Font *font_) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
|
|
|
|
void SetClip(PRectangle rc) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void PopClip() override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void FlushCachedState() override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void FlushDrawing() override;
|
|
|
|
};
|
2019-05-04 18:14:48 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
const Supports SupportsGTK[] = {
|
|
|
|
Supports::LineDrawsFinal,
|
|
|
|
Supports::FractionalStrokeWidth,
|
|
|
|
Supports::TranslucentStroke,
|
|
|
|
Supports::PixelModification,
|
|
|
|
Supports::ThreadSafeMeasureWidths,
|
2009-04-24 23:35:41 +00:00
|
|
|
};
|
2022-01-04 23:07:50 +00:00
|
|
|
|
2010-07-12 22:19:51 +00:00
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
const char *CharacterSetID(CharacterSet characterSet) noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
switch (characterSet) {
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Ansi:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Default:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "ISO-8859-1";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Baltic:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "ISO-8859-13";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::ChineseBig5:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "BIG-5";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::EastEurope:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "ISO-8859-2";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::GB2312:
|
2010-09-05 22:56:27 +00:00
|
|
|
return "CP936";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Greek:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "ISO-8859-7";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Hangul:
|
2010-09-05 22:56:27 +00:00
|
|
|
return "CP949";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Mac:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "MACINTOSH";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Oem:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "ASCII";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Russian:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "KOI8-R";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Oem866:
|
2019-05-04 18:14:48 +00:00
|
|
|
return "CP866";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Cyrillic:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "CP1251";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::ShiftJis:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "SHIFT-JIS";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Symbol:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Turkish:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "ISO-8859-9";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Johab:
|
2010-09-05 22:56:27 +00:00
|
|
|
return "CP1361";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Hebrew:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "ISO-8859-8";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Arabic:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "ISO-8859-6";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Vietnamese:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Thai:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "ISO-8859-11";
|
2022-01-04 23:07:50 +00:00
|
|
|
case CharacterSet::Iso8859_15:
|
2009-04-24 23:35:41 +00:00
|
|
|
return "ISO-8859-15";
|
|
|
|
default:
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::PenColourAlpha(ColourRGBA fore) noexcept {
|
|
|
|
if (context) {
|
|
|
|
cairo_set_source_rgba(context,
|
|
|
|
fore.GetRedComponent(),
|
|
|
|
fore.GetGreenComponent(),
|
|
|
|
fore.GetBlueComponent(),
|
|
|
|
fore.GetAlphaComponent());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceImpl::SetConverter(CharacterSet characterSet_) {
|
2009-04-24 23:35:41 +00:00
|
|
|
if (characterSet != characterSet_) {
|
|
|
|
characterSet = characterSet_;
|
|
|
|
conv.Open("UTF-8", CharacterSetID(characterSet), false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::CairoRectangle(PRectangle rc) noexcept {
|
|
|
|
cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
SurfaceImpl::SurfaceImpl() noexcept {
|
|
|
|
}
|
|
|
|
|
|
|
|
SurfaceImpl::SurfaceImpl(cairo_t *context_, int width, int height, SurfaceMode mode_, WindowID wid) noexcept {
|
|
|
|
if (height > 0 && width > 0) {
|
|
|
|
cairo_surface_t *psurfContext = cairo_get_target(context_);
|
|
|
|
surf.reset(cairo_surface_create_similar(
|
|
|
|
psurfContext,
|
|
|
|
CAIRO_CONTENT_COLOR_ALPHA, width, height));
|
|
|
|
cairoOwned.reset(cairo_create(surf.get()));
|
|
|
|
context = cairoOwned.get();
|
|
|
|
pcontext.reset(gtk_widget_create_pango_context(PWidget(wid)));
|
|
|
|
PLATFORM_ASSERT(pcontext);
|
|
|
|
SetFractionalPositions(pcontext.get());
|
|
|
|
GetContextState();
|
|
|
|
layout.reset(pango_layout_new(pcontext.get()));
|
|
|
|
PLATFORM_ASSERT(layout);
|
|
|
|
cairo_rectangle(context, 0, 0, width, height);
|
|
|
|
cairo_set_source_rgb(context, 1.0, 0, 0);
|
|
|
|
cairo_fill(context);
|
|
|
|
cairo_set_line_width(context, 1);
|
|
|
|
inited = true;
|
|
|
|
mode = mode_;
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::Release() noexcept {
|
|
|
|
et = EncodingType::singleByte;
|
|
|
|
cairoOwned.reset();
|
2019-07-21 13:26:02 +00:00
|
|
|
context = nullptr;
|
2022-01-04 23:07:50 +00:00
|
|
|
surf.reset();
|
|
|
|
layout.reset();
|
|
|
|
// fontOptions and language are owned by original context and don't need to be freed
|
|
|
|
fontOptions = nullptr;
|
|
|
|
language = nullptr;
|
|
|
|
pcontext.reset();
|
2009-04-24 23:35:41 +00:00
|
|
|
conv.Close();
|
2022-01-04 23:07:50 +00:00
|
|
|
characterSet = static_cast<CharacterSet>(-1);
|
2009-04-24 23:35:41 +00:00
|
|
|
inited = false;
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
bool SurfaceImpl::Initialised() {
|
2015-06-07 21:19:26 +00:00
|
|
|
if (inited && context) {
|
|
|
|
if (cairo_status(context) == CAIRO_STATUS_SUCCESS) {
|
|
|
|
// Even when status is success, the target surface may have been
|
2021-02-21 04:53:09 +00:00
|
|
|
// finished which may cause an assertion to fail crashing the application.
|
2015-06-07 21:19:26 +00:00
|
|
|
// The cairo_surface_has_show_text_glyphs call checks the finished flag
|
|
|
|
// and when set, sets the status to CAIRO_STATUS_SURFACE_FINISHED
|
|
|
|
// which leads to warning messages instead of crashes.
|
|
|
|
// Performing the check in this method as it is called rarely and has no
|
|
|
|
// other side effects.
|
|
|
|
cairo_surface_t *psurfContext = cairo_get_target(context);
|
|
|
|
if (psurfContext) {
|
|
|
|
cairo_surface_has_show_text_glyphs(psurfContext);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return cairo_status(context) == CAIRO_STATUS_SUCCESS;
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
return inited;
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::GetContextState() noexcept {
|
|
|
|
resolution = pango_cairo_context_get_resolution(pcontext.get());
|
|
|
|
direction = pango_context_get_base_dir(pcontext.get());
|
|
|
|
fontOptions = pango_cairo_context_get_font_options(pcontext.get());
|
|
|
|
language = pango_context_get_language(pcontext.get());
|
|
|
|
}
|
|
|
|
|
|
|
|
UniquePangoContext SurfaceImpl::MeasuringContext() {
|
|
|
|
UniquePangoFontMap fmMeasure(pango_cairo_font_map_get_default());
|
|
|
|
PLATFORM_ASSERT(fmMeasure);
|
|
|
|
UniquePangoContext contextMeasure(pango_font_map_create_context(fmMeasure.release()));
|
|
|
|
PLATFORM_ASSERT(contextMeasure);
|
|
|
|
SetFractionalPositions(contextMeasure.get());
|
|
|
|
|
|
|
|
pango_cairo_context_set_resolution(contextMeasure.get(), resolution);
|
|
|
|
pango_context_set_base_dir(contextMeasure.get(), direction);
|
|
|
|
pango_cairo_context_set_font_options(contextMeasure.get(), fontOptions);
|
|
|
|
pango_context_set_language(contextMeasure.get(), language);
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
return contextMeasure;
|
|
|
|
}
|
|
|
|
|
2010-07-12 22:19:51 +00:00
|
|
|
void SurfaceImpl::Init(WindowID wid) {
|
2022-01-04 23:07:50 +00:00
|
|
|
widSave = wid;
|
2009-04-24 23:35:41 +00:00
|
|
|
Release();
|
|
|
|
PLATFORM_ASSERT(wid);
|
2015-06-07 21:19:26 +00:00
|
|
|
// if we are only created from a window ID, we can't perform drawing
|
2019-07-21 13:26:02 +00:00
|
|
|
context = nullptr;
|
2022-01-04 23:07:50 +00:00
|
|
|
pcontext.reset(gtk_widget_create_pango_context(PWidget(wid)));
|
2009-04-24 23:35:41 +00:00
|
|
|
PLATFORM_ASSERT(pcontext);
|
2022-01-04 23:07:50 +00:00
|
|
|
SetFractionalPositions(pcontext.get());
|
|
|
|
GetContextState();
|
|
|
|
layout.reset(pango_layout_new(pcontext.get()));
|
2009-04-24 23:35:41 +00:00
|
|
|
PLATFORM_ASSERT(layout);
|
|
|
|
inited = true;
|
|
|
|
}
|
|
|
|
|
2010-07-12 22:19:51 +00:00
|
|
|
void SurfaceImpl::Init(SurfaceID sid, WindowID wid) {
|
2022-01-04 23:07:50 +00:00
|
|
|
widSave = wid;
|
2009-04-24 23:35:41 +00:00
|
|
|
PLATFORM_ASSERT(sid);
|
|
|
|
Release();
|
|
|
|
PLATFORM_ASSERT(wid);
|
2022-01-04 23:07:50 +00:00
|
|
|
cairoOwned.reset(cairo_reference(static_cast<cairo_t *>(sid)));
|
|
|
|
context = cairoOwned.get();
|
|
|
|
pcontext.reset(gtk_widget_create_pango_context(PWidget(wid)));
|
|
|
|
SetFractionalPositions(pcontext.get());
|
2013-08-28 00:44:27 +00:00
|
|
|
// update the Pango context in case sid isn't the widget's surface
|
2022-01-04 23:07:50 +00:00
|
|
|
pango_cairo_update_context(context, pcontext.get());
|
|
|
|
GetContextState();
|
|
|
|
layout.reset(pango_layout_new(pcontext.get()));
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_set_line_width(context, 1);
|
2009-04-24 23:35:41 +00:00
|
|
|
inited = true;
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
std::unique_ptr<Surface> SurfaceImpl::AllocatePixMap(int width, int height) {
|
|
|
|
// widSave must be alive now so safe for creating a PangoContext
|
|
|
|
return std::make_unique<SurfaceImpl>(context, width, height, mode, widSave);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::SetMode(SurfaceMode mode_) {
|
|
|
|
mode = mode_;
|
|
|
|
if (mode.codePage == SC_CP_UTF8) {
|
|
|
|
et = EncodingType::utf8;
|
|
|
|
} else if (mode.codePage) {
|
|
|
|
et = EncodingType::dbcs;
|
|
|
|
} else {
|
|
|
|
et = EncodingType::singleByte;
|
2011-03-22 00:16:49 +00:00
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
int SurfaceImpl::SupportsFeature(Supports feature) noexcept {
|
|
|
|
for (const Supports f : SupportsGTK) {
|
|
|
|
if (f == feature)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
int SurfaceImpl::LogPixelsY() {
|
|
|
|
return 72;
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
int SurfaceImpl::PixelDivisions() {
|
|
|
|
// GTK uses device pixels.
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
int SurfaceImpl::DeviceHeightFont(int points) {
|
2019-07-21 13:26:02 +00:00
|
|
|
const int logPix = LogPixelsY();
|
2009-04-24 23:35:41 +00:00
|
|
|
return (points * logPix + logPix / 2) / 72;
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::LineDraw(Point start, Point end, Stroke stroke) {
|
|
|
|
PLATFORM_ASSERT(context);
|
|
|
|
if (!context)
|
|
|
|
return;
|
|
|
|
PenColourAlpha(stroke.colour);
|
|
|
|
cairo_set_line_width(context, stroke.width);
|
|
|
|
cairo_move_to(context, start.x, start.y);
|
|
|
|
cairo_line_to(context, end.x, end.y);
|
|
|
|
cairo_stroke(context);
|
2011-03-22 00:16:49 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::PolyLine(const Point *pts, size_t npts, Stroke stroke) {
|
|
|
|
// TODO: set line joins and caps
|
|
|
|
PLATFORM_ASSERT(context && npts > 1);
|
|
|
|
if (!context)
|
|
|
|
return;
|
|
|
|
PenColourAlpha(stroke.colour);
|
|
|
|
cairo_set_line_width(context, stroke.width);
|
|
|
|
cairo_move_to(context, pts[0].x, pts[0].y);
|
|
|
|
for (size_t i = 1; i < npts; i++) {
|
|
|
|
cairo_line_to(context, pts[i].x, pts[i].y);
|
2011-03-22 00:16:49 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
cairo_stroke(context);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) {
|
2015-06-07 21:19:26 +00:00
|
|
|
PLATFORM_ASSERT(context);
|
2022-01-04 23:07:50 +00:00
|
|
|
PenColourAlpha(fillStroke.fill.colour);
|
|
|
|
cairo_move_to(context, pts[0].x, pts[0].y);
|
2019-05-04 18:14:48 +00:00
|
|
|
for (size_t i = 1; i < npts; i++) {
|
2022-01-04 23:07:50 +00:00
|
|
|
cairo_line_to(context, pts[i].x, pts[i].y);
|
2011-03-22 00:16:49 +00:00
|
|
|
}
|
|
|
|
cairo_close_path(context);
|
|
|
|
cairo_fill_preserve(context);
|
2022-01-04 23:07:50 +00:00
|
|
|
PenColourAlpha(fillStroke.stroke.colour);
|
|
|
|
cairo_set_line_width(context, fillStroke.stroke.width);
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_stroke(context);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::RectangleDraw(PRectangle rc, FillStroke fillStroke) {
|
2011-03-22 00:16:49 +00:00
|
|
|
if (context) {
|
2022-01-04 23:07:50 +00:00
|
|
|
CairoRectangle(rc.Inset(fillStroke.stroke.width / 2));
|
|
|
|
PenColourAlpha(fillStroke.fill.colour);
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_fill_preserve(context);
|
2022-01-04 23:07:50 +00:00
|
|
|
PenColourAlpha(fillStroke.stroke.colour);
|
|
|
|
cairo_set_line_width(context, fillStroke.stroke.width);
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_stroke(context);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::RectangleFrame(PRectangle rc, Stroke stroke) {
|
|
|
|
if (context) {
|
|
|
|
CairoRectangle(rc.Inset(stroke.width / 2));
|
|
|
|
PenColourAlpha(stroke.colour);
|
|
|
|
cairo_set_line_width(context, stroke.width);
|
|
|
|
cairo_stroke(context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceImpl::FillRectangle(PRectangle rc, Fill fill) {
|
|
|
|
PenColourAlpha(fill.colour);
|
2011-03-22 00:16:49 +00:00
|
|
|
if (context && (rc.left < maxCoordinate)) { // Protect against out of range
|
2022-01-04 23:07:50 +00:00
|
|
|
CairoRectangle(rc);
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_fill(context);
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::FillRectangleAligned(PRectangle rc, Fill fill) {
|
|
|
|
FillRectangle(PixelAlign(rc, 1), fill);
|
|
|
|
}
|
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
|
2021-02-21 04:53:09 +00:00
|
|
|
SurfaceImpl &surfi = dynamic_cast<SurfaceImpl &>(surfacePattern);
|
2022-01-04 23:07:50 +00:00
|
|
|
if (context && surfi.surf) {
|
2009-04-24 23:35:41 +00:00
|
|
|
// Tile pattern over rectangle
|
2022-01-04 23:07:50 +00:00
|
|
|
cairo_set_source_surface(context, surfi.surf.get(), rc.left, rc.top);
|
2021-02-21 04:53:09 +00:00
|
|
|
cairo_pattern_set_extend(cairo_get_source(context), CAIRO_EXTEND_REPEAT);
|
|
|
|
cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
|
|
|
|
cairo_fill(context);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::RoundedRectangle(PRectangle rc, FillStroke fillStroke) {
|
2009-04-24 23:35:41 +00:00
|
|
|
if (((rc.right - rc.left) > 4) && ((rc.bottom - rc.top) > 4)) {
|
|
|
|
// Approximate a round rect with some cut off corners
|
|
|
|
Point pts[] = {
|
2019-07-21 13:26:02 +00:00
|
|
|
Point(rc.left + 2, rc.top),
|
|
|
|
Point(rc.right - 2, rc.top),
|
|
|
|
Point(rc.right, rc.top + 2),
|
|
|
|
Point(rc.right, rc.bottom - 2),
|
|
|
|
Point(rc.right - 2, rc.bottom),
|
|
|
|
Point(rc.left + 2, rc.bottom),
|
|
|
|
Point(rc.left, rc.bottom - 2),
|
|
|
|
Point(rc.left, rc.top + 2),
|
|
|
|
};
|
2022-01-04 23:07:50 +00:00
|
|
|
Polygon(pts, std::size(pts), fillStroke);
|
2009-04-24 23:35:41 +00:00
|
|
|
} else {
|
2022-01-04 23:07:50 +00:00
|
|
|
RectangleDraw(rc, fillStroke);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
static void PathRoundRectangle(cairo_t *context, double left, double top, double width, double height, double radius) noexcept {
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_new_sub_path(context);
|
|
|
|
cairo_arc(context, left + width - radius, top + radius, radius, -90 * degrees, 0 * degrees);
|
|
|
|
cairo_arc(context, left + width - radius, top + height - radius, radius, 0 * degrees, 90 * degrees);
|
|
|
|
cairo_arc(context, left + radius, top + height - radius, radius, 90 * degrees, 180 * degrees);
|
|
|
|
cairo_arc(context, left + radius, top + radius, radius, 180 * degrees, 270 * degrees);
|
|
|
|
cairo_close_path(context);
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) {
|
2011-03-22 00:16:49 +00:00
|
|
|
if (context && rc.Width() > 0) {
|
2022-01-04 23:07:50 +00:00
|
|
|
const XYPOSITION halfStroke = fillStroke.stroke.width / 2.0;
|
|
|
|
const XYPOSITION doubleStroke = fillStroke.stroke.width * 2.0;
|
|
|
|
PenColourAlpha(fillStroke.fill.colour);
|
2013-08-28 00:44:27 +00:00
|
|
|
if (cornerSize > 0)
|
2022-01-04 23:07:50 +00:00
|
|
|
PathRoundRectangle(context, rc.left + fillStroke.stroke.width, rc.top + fillStroke.stroke.width,
|
|
|
|
rc.Width() - doubleStroke, rc.Height() - doubleStroke, cornerSize);
|
2013-08-28 00:44:27 +00:00
|
|
|
else
|
2022-01-04 23:07:50 +00:00
|
|
|
cairo_rectangle(context, rc.left + fillStroke.stroke.width, rc.top + fillStroke.stroke.width,
|
|
|
|
rc.Width() - doubleStroke, rc.Height() - doubleStroke);
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_fill(context);
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
PenColourAlpha(fillStroke.stroke.colour);
|
2013-08-28 00:44:27 +00:00
|
|
|
if (cornerSize > 0)
|
2022-01-04 23:07:50 +00:00
|
|
|
PathRoundRectangle(context, rc.left + halfStroke, rc.top + halfStroke,
|
|
|
|
rc.Width() - fillStroke.stroke.width, rc.Height() - fillStroke.stroke.width, cornerSize);
|
2013-08-28 00:44:27 +00:00
|
|
|
else
|
2022-01-04 23:07:50 +00:00
|
|
|
cairo_rectangle(context, rc.left + halfStroke, rc.top + halfStroke,
|
|
|
|
rc.Width() - fillStroke.stroke.width, rc.Height() - fillStroke.stroke.width);
|
|
|
|
cairo_set_line_width(context, fillStroke.stroke.width);
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_stroke(context);
|
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
void SurfaceImpl::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) {
|
|
|
|
if (context) {
|
|
|
|
cairo_pattern_t *pattern;
|
|
|
|
switch (options) {
|
|
|
|
case GradientOptions::leftToRight:
|
|
|
|
pattern = cairo_pattern_create_linear(rc.left, rc.top, rc.right, rc.top);
|
|
|
|
break;
|
|
|
|
case GradientOptions::topToBottom:
|
|
|
|
default:
|
|
|
|
pattern = cairo_pattern_create_linear(rc.left, rc.top, rc.left, rc.bottom);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
for (const ColourStop &stop : stops) {
|
|
|
|
cairo_pattern_add_color_stop_rgba(pattern, stop.position,
|
2019-07-21 13:26:02 +00:00
|
|
|
stop.colour.GetRedComponent(),
|
|
|
|
stop.colour.GetGreenComponent(),
|
|
|
|
stop.colour.GetBlueComponent(),
|
|
|
|
stop.colour.GetAlphaComponent());
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
|
|
|
|
cairo_set_source(context, pattern);
|
|
|
|
cairo_fill(context);
|
|
|
|
cairo_pattern_destroy(pattern);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
void SurfaceImpl::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
|
2015-06-07 21:19:26 +00:00
|
|
|
PLATFORM_ASSERT(context);
|
2023-09-22 09:32:35 +00:00
|
|
|
if (width == 0)
|
|
|
|
return;
|
2013-08-28 00:44:27 +00:00
|
|
|
if (rc.Width() > width)
|
|
|
|
rc.left += (rc.Width() - width) / 2;
|
|
|
|
rc.right = rc.left + width;
|
|
|
|
if (rc.Height() > height)
|
|
|
|
rc.top += (rc.Height() - height) / 2;
|
|
|
|
rc.bottom = rc.top + height;
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
|
2019-07-21 13:26:02 +00:00
|
|
|
const int ucs = stride * height;
|
2013-08-28 00:44:27 +00:00
|
|
|
std::vector<unsigned char> image(ucs);
|
2021-02-21 04:53:09 +00:00
|
|
|
for (ptrdiff_t iy=0; iy<height; iy++) {
|
|
|
|
unsigned char *pixel = &image[0] + iy*stride;
|
|
|
|
RGBAImage::BGRAFromRGBA(pixel, pixelsImage, width);
|
|
|
|
pixelsImage += RGBAImage::bytesPerPixel * width;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
UniqueCairoSurface surfImage(cairo_image_surface_create_for_data(&image[0], CAIRO_FORMAT_ARGB32, width, height, stride));
|
|
|
|
cairo_set_source_surface(context, surfImage.get(), rc.left, rc.top);
|
2021-02-21 04:53:09 +00:00
|
|
|
cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
|
2013-08-28 00:44:27 +00:00
|
|
|
cairo_fill(context);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2009-08-23 02:24:48 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::Ellipse(PRectangle rc, FillStroke fillStroke) {
|
2015-06-07 21:19:26 +00:00
|
|
|
PLATFORM_ASSERT(context);
|
2022-01-04 23:07:50 +00:00
|
|
|
PenColourAlpha(fillStroke.fill.colour);
|
2015-06-07 21:19:26 +00:00
|
|
|
cairo_arc(context, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2,
|
2022-01-04 23:07:50 +00:00
|
|
|
(std::min(rc.Width(), rc.Height()) - fillStroke.stroke.width) / 2, 0, 2*kPi);
|
|
|
|
cairo_fill_preserve(context);
|
|
|
|
PenColourAlpha(fillStroke.stroke.colour);
|
|
|
|
cairo_set_line_width(context, fillStroke.stroke.width);
|
|
|
|
cairo_stroke(context);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceImpl::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) {
|
|
|
|
const XYPOSITION midLine = rc.Centre().y;
|
|
|
|
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;
|
|
|
|
|
|
|
|
cairo_new_sub_path(context);
|
|
|
|
|
|
|
|
const Ends leftSide = static_cast<Ends>(static_cast<int>(ends) & 0xf);
|
|
|
|
const Ends rightSide = static_cast<Ends>(static_cast<int>(ends) & 0xf0);
|
|
|
|
switch (leftSide) {
|
|
|
|
case Ends::leftFlat:
|
|
|
|
cairo_move_to(context, rc.left + halfStroke, rc.top + halfStroke);
|
|
|
|
cairo_line_to(context, rc.left + halfStroke, rc.bottom - halfStroke);
|
|
|
|
break;
|
|
|
|
case Ends::leftAngle:
|
|
|
|
cairo_move_to(context, rcInner.left + halfStroke, rc.top + halfStroke);
|
|
|
|
cairo_line_to(context, rc.left + halfStroke, rc.Centre().y);
|
|
|
|
cairo_line_to(context, rcInner.left + halfStroke, rc.bottom - halfStroke);
|
|
|
|
break;
|
|
|
|
case Ends::semiCircles:
|
|
|
|
default:
|
|
|
|
cairo_move_to(context, rcInner.left + halfStroke, rc.top + halfStroke);
|
|
|
|
cairo_arc_negative(context, rcInner.left + halfStroke, midLine, radius,
|
|
|
|
270 * degrees, 90 * degrees);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (rightSide) {
|
|
|
|
case Ends::rightFlat:
|
|
|
|
cairo_line_to(context, rc.right - halfStroke, rc.bottom - halfStroke);
|
|
|
|
cairo_line_to(context, rc.right - halfStroke, rc.top + halfStroke);
|
|
|
|
break;
|
|
|
|
case Ends::rightAngle:
|
|
|
|
cairo_line_to(context, rcInner.right - halfStroke, rc.bottom - halfStroke);
|
|
|
|
cairo_line_to(context, rc.right - halfStroke, rc.Centre().y);
|
|
|
|
cairo_line_to(context, rcInner.right - halfStroke, rc.top + halfStroke);
|
|
|
|
break;
|
|
|
|
case Ends::semiCircles:
|
|
|
|
default:
|
|
|
|
cairo_line_to(context, rcInner.right - halfStroke, rc.bottom - halfStroke);
|
|
|
|
cairo_arc_negative(context, rcInner.right - halfStroke, midLine, radius,
|
|
|
|
90 * degrees, 270 * degrees);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the path to enclose it for stroking and for filling, then draw it
|
|
|
|
cairo_close_path(context);
|
|
|
|
PenColourAlpha(fillStroke.fill.colour);
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_fill_preserve(context);
|
2022-01-04 23:07:50 +00:00
|
|
|
|
|
|
|
PenColourAlpha(fillStroke.stroke.colour);
|
|
|
|
cairo_set_line_width(context, fillStroke.stroke.width);
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_stroke(context);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
|
2011-03-22 00:16:49 +00:00
|
|
|
SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfaceSource);
|
2022-01-04 23:07:50 +00:00
|
|
|
const bool canDraw = surfi.surf != nullptr;
|
2011-03-22 00:16:49 +00:00
|
|
|
if (canDraw) {
|
2015-06-07 21:19:26 +00:00
|
|
|
PLATFORM_ASSERT(context);
|
2022-01-04 23:07:50 +00:00
|
|
|
cairo_set_source_surface(context, surfi.surf.get(),
|
2019-07-21 13:26:02 +00:00
|
|
|
rc.left - from.x, rc.top - from.y);
|
2021-02-21 04:53:09 +00:00
|
|
|
cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_fill(context);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
std::unique_ptr<IScreenLineLayout> SurfaceImpl::Layout(const IScreenLine *) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string UTF8FromLatin1(std::string_view text) {
|
|
|
|
std::string utfForm(text.length()*2 + 1, '\0');
|
2009-04-24 23:35:41 +00:00
|
|
|
size_t lenU = 0;
|
2021-02-21 04:53:09 +00:00
|
|
|
for (const char ch : text) {
|
2019-07-21 13:26:02 +00:00
|
|
|
const unsigned char uch = ch;
|
2009-04-24 23:35:41 +00:00
|
|
|
if (uch < 0x80) {
|
|
|
|
utfForm[lenU++] = uch;
|
|
|
|
} else {
|
|
|
|
utfForm[lenU++] = static_cast<char>(0xC0 | (uch >> 6));
|
|
|
|
utfForm[lenU++] = static_cast<char>(0x80 | (uch & 0x3f));
|
|
|
|
}
|
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
utfForm.resize(lenU);
|
2009-04-24 23:35:41 +00:00
|
|
|
return utfForm;
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
std::string UTF8FromIconv(const Converter &conv, std::string_view text) {
|
2009-04-24 23:35:41 +00:00
|
|
|
if (conv) {
|
2019-05-04 18:14:48 +00:00
|
|
|
std::string utfForm(text.length()*3+1, '\0');
|
|
|
|
char *pin = const_cast<char *>(text.data());
|
|
|
|
gsize inLeft = text.length();
|
2013-08-28 00:44:27 +00:00
|
|
|
char *putf = &utfForm[0];
|
|
|
|
char *pout = putf;
|
2019-05-04 18:14:48 +00:00
|
|
|
gsize outLeft = text.length()*3+1;
|
2019-07-21 13:26:02 +00:00
|
|
|
const gsize conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
|
2019-05-04 18:14:48 +00:00
|
|
|
if (conversions != sizeFailure) {
|
2009-04-24 23:35:41 +00:00
|
|
|
*pout = '\0';
|
2013-08-28 00:44:27 +00:00
|
|
|
utfForm.resize(pout - putf);
|
2009-04-24 23:35:41 +00:00
|
|
|
return utfForm;
|
|
|
|
}
|
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
return std::string();
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Work out how many bytes are in a character by trying to convert using iconv,
|
|
|
|
// returning the first length that succeeds.
|
2021-02-21 04:53:09 +00:00
|
|
|
size_t MultiByteLenFromIconv(const Converter &conv, const char *s, size_t len) noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
for (size_t lenMB=1; (lenMB<4) && (lenMB <= len); lenMB++) {
|
2021-02-21 04:53:09 +00:00
|
|
|
char wcForm[2] {};
|
2009-04-24 23:35:41 +00:00
|
|
|
char *pin = const_cast<char *>(s);
|
2019-05-04 18:14:48 +00:00
|
|
|
gsize inLeft = lenMB;
|
2009-04-24 23:35:41 +00:00
|
|
|
char *pout = wcForm;
|
2019-05-04 18:14:48 +00:00
|
|
|
gsize outLeft = 2;
|
2019-07-21 13:26:02 +00:00
|
|
|
const gsize conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
|
2019-05-04 18:14:48 +00:00
|
|
|
if (conversions != sizeFailure) {
|
2009-04-24 23:35:41 +00:00
|
|
|
return lenMB;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::DrawTextBase(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore) {
|
2011-03-22 00:16:49 +00:00
|
|
|
if (context) {
|
2022-01-04 23:07:50 +00:00
|
|
|
PenColourAlpha(fore);
|
2019-07-21 13:26:02 +00:00
|
|
|
const XYPOSITION xText = rc.left;
|
2022-01-04 23:07:50 +00:00
|
|
|
if (PFont(font_)->fd) {
|
|
|
|
if (et == EncodingType::utf8) {
|
|
|
|
LayoutSetText(layout.get(), text);
|
2009-04-24 23:35:41 +00:00
|
|
|
} else {
|
2013-08-28 00:44:27 +00:00
|
|
|
SetConverter(PFont(font_)->characterSet);
|
2022-08-14 10:23:34 +00:00
|
|
|
std::string utfForm = UTF8FromIconv(conv, text);
|
2013-08-28 00:44:27 +00:00
|
|
|
if (utfForm.empty()) { // iconv failed so treat as Latin1
|
2019-05-04 18:14:48 +00:00
|
|
|
utfForm = UTF8FromLatin1(text);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
LayoutSetText(layout.get(), utfForm);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
pango_layout_set_font_description(layout.get(), PFont(font_)->fd.get());
|
|
|
|
pango_cairo_update_layout(context, layout.get());
|
|
|
|
PangoLayoutLine *pll = pango_layout_get_line_readonly(layout.get(), 0);
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_move_to(context, xText, ybase);
|
|
|
|
pango_cairo_show_layout_line(context, pll);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore, ColourRGBA back) {
|
|
|
|
FillRectangleAligned(rc, back);
|
2019-05-04 18:14:48 +00:00
|
|
|
DrawTextBase(rc, font_, ybase, text, fore);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// On GTK+, exactly same as DrawTextNoClip
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore, ColourRGBA back) {
|
|
|
|
FillRectangleAligned(rc, back);
|
2019-05-04 18:14:48 +00:00
|
|
|
DrawTextBase(rc, font_, ybase, text, fore);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore) {
|
2009-04-24 23:35:41 +00:00
|
|
|
// Avoid drawing spaces in transparent mode
|
2019-05-04 18:14:48 +00:00
|
|
|
for (size_t i=0; i<text.length(); i++) {
|
|
|
|
if (text[i] != ' ') {
|
|
|
|
DrawTextBase(rc, font_, ybase, text, fore);
|
2009-04-24 23:35:41 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-13 11:10:12 +00:00
|
|
|
namespace {
|
|
|
|
|
2010-07-12 22:19:51 +00:00
|
|
|
class ClusterIterator {
|
2022-01-04 23:07:50 +00:00
|
|
|
UniquePangoLayoutIter iter;
|
|
|
|
PangoRectangle pos {};
|
|
|
|
int lenPositions;
|
2010-07-12 22:19:51 +00:00
|
|
|
public:
|
2022-01-04 23:07:50 +00:00
|
|
|
bool finished = false;
|
|
|
|
XYPOSITION positionStart = 0.0;
|
|
|
|
XYPOSITION position = 0.0;
|
|
|
|
XYPOSITION distance = 0.0;
|
|
|
|
int curIndex = 0;
|
|
|
|
ClusterIterator(PangoLayout *layout, std::string_view text) noexcept :
|
|
|
|
lenPositions(static_cast<int>(text.length())) {
|
|
|
|
LayoutSetText(layout, text);
|
|
|
|
iter.reset(pango_layout_get_iter(layout));
|
2022-04-13 11:10:12 +00:00
|
|
|
curIndex = pango_layout_iter_get_index(iter.get());
|
2022-01-04 23:07:50 +00:00
|
|
|
pango_layout_iter_get_cluster_extents(iter.get(), nullptr, &pos);
|
2010-07-12 22:19:51 +00:00
|
|
|
}
|
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
void Next() noexcept {
|
2010-07-12 22:19:51 +00:00
|
|
|
positionStart = position;
|
2022-01-04 23:07:50 +00:00
|
|
|
if (pango_layout_iter_next_cluster(iter.get())) {
|
|
|
|
pango_layout_iter_get_cluster_extents(iter.get(), nullptr, &pos);
|
|
|
|
position = pango_units_to_double(pos.x);
|
|
|
|
curIndex = pango_layout_iter_get_index(iter.get());
|
2010-07-12 22:19:51 +00:00
|
|
|
} else {
|
|
|
|
finished = true;
|
2022-01-04 23:07:50 +00:00
|
|
|
position = pango_units_to_double(pos.x + pos.width);
|
2022-04-13 11:10:12 +00:00
|
|
|
curIndex = pango_layout_iter_get_index(iter.get());
|
2010-07-12 22:19:51 +00:00
|
|
|
}
|
|
|
|
distance = position - positionStart;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-04-13 11:10:12 +00:00
|
|
|
// Something has gone wrong so set all the characters as equally spaced.
|
|
|
|
void EquallySpaced(PangoLayout *layout, XYPOSITION *positions, size_t lenPositions) {
|
|
|
|
int widthLayout = 0;
|
|
|
|
pango_layout_get_size(layout, &widthLayout, nullptr);
|
|
|
|
const XYPOSITION widthTotal = pango_units_to_double(widthLayout);
|
|
|
|
for (size_t bytePos=0; bytePos<lenPositions; bytePos++) {
|
|
|
|
positions[bytePos] = widthTotal / lenPositions * (bytePos + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) {
|
|
|
|
if (PFont(font_)->fd) {
|
|
|
|
UniquePangoContext contextMeasure = MeasuringContext();
|
|
|
|
UniquePangoLayout layoutMeasure(pango_layout_new(contextMeasure.get()));
|
|
|
|
PLATFORM_ASSERT(layoutMeasure);
|
|
|
|
|
|
|
|
pango_layout_set_font_description(layoutMeasure.get(), PFont(font_)->fd.get());
|
|
|
|
if (et == EncodingType::utf8) {
|
|
|
|
// Simple and direct as UTF-8 is native Pango encoding
|
|
|
|
ClusterIterator iti(layoutMeasure.get(), text);
|
2022-04-13 11:10:12 +00:00
|
|
|
int i = iti.curIndex;
|
|
|
|
if (i != 0) {
|
|
|
|
// Unexpected start to iteration, could be bidirectional text
|
|
|
|
EquallySpaced(layoutMeasure.get(), positions, text.length());
|
|
|
|
return;
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
while (!iti.finished) {
|
|
|
|
iti.Next();
|
|
|
|
const int places = iti.curIndex - i;
|
|
|
|
while (i < iti.curIndex) {
|
|
|
|
// Evenly distribute space among bytes of this cluster.
|
|
|
|
// Would be better to find number of characters and then
|
|
|
|
// divide evenly between characters with each byte of a character
|
|
|
|
// being at the same position.
|
|
|
|
positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places;
|
|
|
|
i++;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
}
|
|
|
|
PLATFORM_ASSERT(static_cast<size_t>(i) == text.length());
|
|
|
|
} else {
|
|
|
|
int positionsCalculated = 0;
|
2022-08-14 10:23:34 +00:00
|
|
|
const char *charSetID = CharacterSetID(PFont(font_)->characterSet);
|
|
|
|
std::string utfForm;
|
|
|
|
{
|
|
|
|
gsize bytesRead = 0;
|
|
|
|
gsize bytesWritten = 0;
|
|
|
|
GError *error = nullptr;
|
|
|
|
UniqueStr textInUTF8(g_convert(text.data(), text.length(),
|
|
|
|
"UTF-8", charSetID,
|
|
|
|
&bytesRead,
|
|
|
|
&bytesWritten,
|
|
|
|
&error));
|
|
|
|
if ((bytesWritten > 0) && (bytesRead == text.length()) && !error) {
|
|
|
|
// Extra allocation here but avoiding it makes code more complex
|
|
|
|
utfForm.assign(textInUTF8.get(), bytesWritten);
|
|
|
|
}
|
|
|
|
if (error) {
|
|
|
|
#ifdef DEBUG
|
|
|
|
fprintf(stderr, "MeasureWidths: %s.\n", error->message);
|
|
|
|
#endif
|
|
|
|
g_error_free(error);
|
|
|
|
}
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
if (et == EncodingType::dbcs) {
|
|
|
|
if (!utfForm.empty()) {
|
|
|
|
// Convert to UTF-8 so can ask Pango for widths, then
|
|
|
|
// Loop through UTF-8 and DBCS forms, taking account of different
|
|
|
|
// character byte lengths.
|
2022-08-14 10:23:34 +00:00
|
|
|
Converter convMeasure("UCS-2", charSetID, false);
|
2022-01-04 23:07:50 +00:00
|
|
|
int i = 0;
|
|
|
|
ClusterIterator iti(layoutMeasure.get(), utfForm);
|
2022-04-13 11:10:12 +00:00
|
|
|
int clusterStart = iti.curIndex;
|
|
|
|
if (clusterStart != 0) {
|
|
|
|
// Unexpected start to iteration, could be bidirectional text
|
|
|
|
EquallySpaced(layoutMeasure.get(), positions, text.length());
|
|
|
|
return;
|
|
|
|
}
|
2010-07-12 22:19:51 +00:00
|
|
|
while (!iti.finished) {
|
|
|
|
iti.Next();
|
2019-07-21 13:26:02 +00:00
|
|
|
const int clusterEnd = iti.curIndex;
|
2022-08-14 10:23:34 +00:00
|
|
|
const int places = g_utf8_strlen(utfForm.data() + clusterStart, clusterEnd - clusterStart);
|
2022-01-04 23:07:50 +00:00
|
|
|
int place = 1;
|
|
|
|
while (clusterStart < clusterEnd) {
|
|
|
|
size_t lenChar = MultiByteLenFromIconv(convMeasure, text.data()+i, text.length()-i);
|
|
|
|
while (lenChar--) {
|
|
|
|
positions[i++] = iti.position - (places - place) * iti.distance / places;
|
|
|
|
positionsCalculated++;
|
2015-06-07 21:19:26 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
clusterStart += UTF8BytesOfLead[static_cast<unsigned char>(utfForm[clusterStart])];
|
|
|
|
place++;
|
2015-06-07 21:19:26 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
}
|
|
|
|
PLATFORM_ASSERT(static_cast<size_t>(i) == text.length());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (positionsCalculated < 1) {
|
|
|
|
const size_t lenPositions = text.length();
|
|
|
|
// Either 8-bit or DBCS conversion failed so treat as 8-bit.
|
|
|
|
const bool rtlCheck = PFont(font_)->characterSet == CharacterSet::Hebrew ||
|
|
|
|
PFont(font_)->characterSet == CharacterSet::Arabic;
|
|
|
|
if (utfForm.empty()) {
|
|
|
|
utfForm = UTF8FromLatin1(text);
|
2022-08-14 10:23:34 +00:00
|
|
|
#ifdef DEBUG
|
|
|
|
fprintf(stderr, "MeasureWidths: Fall back to Latin1 [%s]\n", utfForm.c_str());
|
|
|
|
#endif
|
2022-01-04 23:07:50 +00:00
|
|
|
}
|
|
|
|
size_t i = 0;
|
|
|
|
// Each 8-bit input character may take 1 or 2 bytes in UTF-8
|
|
|
|
// and groups of up to 3 may be represented as ligatures.
|
|
|
|
ClusterIterator iti(layoutMeasure.get(), utfForm);
|
2022-04-13 11:10:12 +00:00
|
|
|
int clusterStart = iti.curIndex;
|
|
|
|
if (clusterStart != 0) {
|
|
|
|
// Unexpected start to iteration, could be bidirectional text
|
|
|
|
EquallySpaced(layoutMeasure.get(), positions, lenPositions);
|
|
|
|
return;
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
while (!iti.finished) {
|
|
|
|
iti.Next();
|
|
|
|
const int clusterEnd = iti.curIndex;
|
2022-08-14 10:23:34 +00:00
|
|
|
const int ligatureLength = g_utf8_strlen(utfForm.data() + clusterStart, clusterEnd - clusterStart);
|
|
|
|
if (((i + ligatureLength) > lenPositions) ||
|
|
|
|
(rtlCheck && ((clusterEnd <= clusterStart) || (ligatureLength == 0) || (ligatureLength > 3)))) {
|
2022-01-04 23:07:50 +00:00
|
|
|
// Something has gone wrong: exit quickly but pretend all the characters are equally spaced:
|
2022-08-14 10:23:34 +00:00
|
|
|
#ifdef DEBUG
|
|
|
|
fprintf(stderr, "MeasureWidths: result too long.\n");
|
|
|
|
#endif
|
2022-04-13 11:10:12 +00:00
|
|
|
EquallySpaced(layoutMeasure.get(), positions, lenPositions);
|
2022-01-04 23:07:50 +00:00
|
|
|
return;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
PLATFORM_ASSERT(ligatureLength > 0 && ligatureLength <= 3);
|
|
|
|
for (int charInLig=0; charInLig<ligatureLength; charInLig++) {
|
|
|
|
positions[i++] = iti.position - (ligatureLength - 1 - charInLig) * iti.distance / ligatureLength;
|
2015-06-07 21:19:26 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
clusterStart = clusterEnd;
|
|
|
|
}
|
|
|
|
while (i < lenPositions) {
|
|
|
|
// If something failed, fill in rest of the positions
|
|
|
|
positions[i++] = clusterStart;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
PLATFORM_ASSERT(i == text.length());
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// No font so return an ascending range of values
|
2019-05-04 18:14:48 +00:00
|
|
|
for (size_t i = 0; i < text.length(); i++) {
|
2022-01-04 23:07:50 +00:00
|
|
|
positions[i] = i + 1.0;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION SurfaceImpl::WidthText(const Font *font_, std::string_view text) {
|
|
|
|
if (PFont(font_)->fd) {
|
|
|
|
pango_layout_set_font_description(layout.get(), PFont(font_)->fd.get());
|
|
|
|
if (et == EncodingType::utf8) {
|
|
|
|
LayoutSetText(layout.get(), text);
|
|
|
|
} else {
|
|
|
|
SetConverter(PFont(font_)->characterSet);
|
2022-08-14 10:23:34 +00:00
|
|
|
std::string utfForm = UTF8FromIconv(conv, text);
|
2022-01-04 23:07:50 +00:00
|
|
|
if (utfForm.empty()) { // iconv failed so treat as Latin1
|
|
|
|
utfForm = UTF8FromLatin1(text);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
LayoutSetText(layout.get(), utfForm);
|
|
|
|
}
|
|
|
|
PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout.get(), 0);
|
|
|
|
PangoRectangle pos {};
|
|
|
|
pango_layout_line_get_extents(pangoLine, nullptr, &pos);
|
|
|
|
return pango_units_to_double(pos.width);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceImpl::DrawTextBaseUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore) {
|
|
|
|
if (context) {
|
|
|
|
PenColourAlpha(fore);
|
|
|
|
const XYPOSITION xText = rc.left;
|
|
|
|
if (PFont(font_)->fd) {
|
|
|
|
LayoutSetText(layout.get(), text);
|
|
|
|
pango_layout_set_font_description(layout.get(), PFont(font_)->fd.get());
|
|
|
|
pango_cairo_update_layout(context, layout.get());
|
|
|
|
PangoLayoutLine *pll = pango_layout_get_line_readonly(layout.get(), 0);
|
|
|
|
cairo_move_to(context, xText, ybase);
|
|
|
|
pango_cairo_show_layout_line(context, pll);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceImpl::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore, ColourRGBA back) {
|
|
|
|
FillRectangleAligned(rc, back);
|
|
|
|
DrawTextBaseUTF8(rc, font_, ybase, text, fore);
|
|
|
|
}
|
|
|
|
|
|
|
|
// On GTK+, exactly same as DrawTextNoClip
|
|
|
|
void SurfaceImpl::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore, ColourRGBA back) {
|
|
|
|
FillRectangleAligned(rc, back);
|
|
|
|
DrawTextBaseUTF8(rc, font_, ybase, text, fore);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceImpl::DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore) {
|
|
|
|
// Avoid drawing spaces in transparent mode
|
|
|
|
for (size_t i = 0; i < text.length(); i++) {
|
|
|
|
if (text[i] != ' ') {
|
|
|
|
DrawTextBaseUTF8(rc, font_, ybase, text, fore);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceImpl::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) {
|
|
|
|
if (PFont(font_)->fd) {
|
|
|
|
UniquePangoContext contextMeasure = MeasuringContext();
|
|
|
|
UniquePangoLayout layoutMeasure(pango_layout_new(contextMeasure.get()));
|
|
|
|
PLATFORM_ASSERT(layoutMeasure);
|
|
|
|
|
|
|
|
pango_layout_set_font_description(layoutMeasure.get(), PFont(font_)->fd.get());
|
|
|
|
// Simple and direct as UTF-8 is native Pango encoding
|
|
|
|
ClusterIterator iti(layoutMeasure.get(), text);
|
2022-04-13 11:10:12 +00:00
|
|
|
int i = iti.curIndex;
|
|
|
|
if (i != 0) {
|
|
|
|
// Unexpected start to iteration, could be bidirectional text
|
|
|
|
EquallySpaced(layoutMeasure.get(), positions, text.length());
|
|
|
|
return;
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
while (!iti.finished) {
|
|
|
|
iti.Next();
|
Updated to Scintilla 5.4.2 & Lexilla 5.3.1
https://www.scintilla.org/scintilla542.zip
Release 5.4.2
Released 5 March 2024.
Significantly reduce memory used for undo actions, often to a half or quarter of previous versions. Feature #1458.
Add APIs for saving and restoring undo history.
For GTK, when laying out text, detect runs with both left-to-right and right-to-left ranges and divide into an ASCII prefix and more complex suffix. Lay out the ASCII prefix in the standard manner but, for the suffix, measure the whole width and spread that over the suffix bytes. This produces more usable results where the caret moves over the ASCII prefix correctly and over the suffix reasonably but not accurately.
For ScintillaEdit on Qt, fix reference from ScintillaDocument to Document to match change in 5.4.1 using IDocumentEditable for SCI_GETDOCPOINTER and SCI_SETDOCPOINTER.
For Direct2D on Win32, use the multi-threaded option to avoid crashes when Scintilla instances created on different threads. There may be more problems with this scenario so it should be avoided. Bug #2420.
For Win32, ensure keyboard-initiated context menu appears in multi-screen situations.
https://www.scintilla.org/lexilla531.zip
Release 5.3.1
Released 5 March 2024.
Assembler: After comments, treat \r\n line ends the same as \n. This makes testing easier.
Bash: Fix folding when line changed to/from comment and previous line is comment. Issue #224.
Batch: Fix handling ':' next to keywords. Issue #222.
JavaScript: in cpp lexer, add lexer.cpp.backquoted.strings=2 mode to treat ` back-quoted strings as template literals which allow embedded ${expressions}. Issue #94.
Python: fix lexing of rb'' and rf'' strings. Issue #223, Pull request #227.
Ruby: fix lexing of methods on numeric literals like '3.times' so the '.' and method name do not appear in numeric style. Issue #225.
2024-03-06 21:05:54 +00:00
|
|
|
if (iti.curIndex < i) {
|
|
|
|
// Backwards movement indicater bidirectional.
|
|
|
|
// Divide into ASCII prefix and non-ASCII suffix as this is common case
|
|
|
|
// and produces accurate positions for the ASCII prefix.
|
|
|
|
size_t lenASCII=0;
|
|
|
|
while (lenASCII<text.length() && IsASCII(text[lenASCII])) {
|
|
|
|
lenASCII++;
|
|
|
|
}
|
|
|
|
const std::string_view asciiPrefix = text.substr(0, lenASCII);
|
|
|
|
const std::string_view bidiSuffix = text.substr(lenASCII);
|
|
|
|
// Recurse for ASCII prefix.
|
|
|
|
MeasureWidthsUTF8(font_, asciiPrefix, positions);
|
|
|
|
// Measure the whole bidiSuffix and spread its width evenly
|
|
|
|
const XYPOSITION endASCII = positions[lenASCII-1];
|
|
|
|
const XYPOSITION widthBidi = WidthText(font_, bidiSuffix);
|
|
|
|
const XYPOSITION widthByteBidi = widthBidi / bidiSuffix.length();
|
|
|
|
for (size_t bidiPos=0; bidiPos<bidiSuffix.length(); bidiPos++) {
|
|
|
|
positions[bidiPos+lenASCII] = endASCII + widthByteBidi * (bidiPos + 1);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
const int places = iti.curIndex - i;
|
|
|
|
while (i < iti.curIndex) {
|
|
|
|
// Evenly distribute space among bytes of this cluster.
|
|
|
|
// Would be better to find number of characters and then
|
|
|
|
// divide evenly between characters with each byte of a character
|
|
|
|
// being at the same position.
|
|
|
|
positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PLATFORM_ASSERT(static_cast<size_t>(i) == text.length());
|
2009-04-24 23:35:41 +00:00
|
|
|
} else {
|
2022-01-04 23:07:50 +00:00
|
|
|
// No font so return an ascending range of values
|
|
|
|
for (size_t i = 0; i < text.length(); i++) {
|
|
|
|
positions[i] = i + 1.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
XYPOSITION SurfaceImpl::WidthTextUTF8(const Font *font_, std::string_view text) {
|
|
|
|
if (PFont(font_)->fd) {
|
|
|
|
pango_layout_set_font_description(layout.get(), PFont(font_)->fd.get());
|
|
|
|
LayoutSetText(layout.get(), text);
|
|
|
|
PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout.get(), 0);
|
|
|
|
PangoRectangle pos{};
|
|
|
|
pango_layout_line_get_extents(pangoLine, nullptr, &pos);
|
|
|
|
return pango_units_to_double(pos.width);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
return 1;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
// Ascent and descent determined by Pango font metrics.
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION SurfaceImpl::Ascent(const Font *font_) {
|
|
|
|
if (!PFont(font_)->fd) {
|
|
|
|
return 1.0;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
UniquePangoFontMetrics metrics(pango_context_get_metrics(pcontext.get(),
|
|
|
|
PFont(font_)->fd.get(), language));
|
|
|
|
return std::max(1.0, std::ceil(pango_units_to_double(
|
|
|
|
pango_font_metrics_get_ascent(metrics.get()))));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION SurfaceImpl::Descent(const Font *font_) {
|
|
|
|
if (!PFont(font_)->fd) {
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
UniquePangoFontMetrics metrics(pango_context_get_metrics(pcontext.get(),
|
|
|
|
PFont(font_)->fd.get(), language));
|
|
|
|
return std::ceil(pango_units_to_double(pango_font_metrics_get_descent(metrics.get())));
|
|
|
|
}
|
|
|
|
|
|
|
|
XYPOSITION SurfaceImpl::InternalLeading(const Font *) {
|
2009-04-24 23:35:41 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION SurfaceImpl::Height(const Font *font_) {
|
2009-04-24 23:35:41 +00:00
|
|
|
return Ascent(font_) + Descent(font_);
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION SurfaceImpl::AverageCharWidth(const Font *font_) {
|
2019-05-04 18:14:48 +00:00
|
|
|
return WidthText(font_, "n");
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceImpl::SetClip(PRectangle rc) {
|
2015-06-07 21:19:26 +00:00
|
|
|
PLATFORM_ASSERT(context);
|
2022-01-04 23:07:50 +00:00
|
|
|
cairo_save(context);
|
|
|
|
CairoRectangle(rc);
|
2011-03-22 00:16:49 +00:00
|
|
|
cairo_clip(context);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::PopClip() {
|
|
|
|
PLATFORM_ASSERT(context);
|
|
|
|
cairo_restore(context);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::FlushCachedState() {}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceImpl::FlushDrawing() {
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
std::unique_ptr<Surface> Surface::Allocate(Technology) {
|
|
|
|
return std::make_unique<SurfaceImpl>();
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
Window::~Window() noexcept {}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void Window::Destroy() noexcept {
|
2015-06-07 21:19:26 +00:00
|
|
|
if (wid) {
|
2019-07-21 13:26:02 +00:00
|
|
|
ListBox *listbox = dynamic_cast<ListBox *>(this);
|
2015-06-07 21:19:26 +00:00
|
|
|
if (listbox) {
|
|
|
|
gtk_widget_hide(GTK_WIDGET(wid));
|
|
|
|
// clear up window content
|
|
|
|
listbox->Clear();
|
|
|
|
// resize the window to the smallest possible size for it to adapt
|
|
|
|
// to future content
|
|
|
|
gtk_window_resize(GTK_WINDOW(wid), 1, 1);
|
|
|
|
} else {
|
|
|
|
gtk_widget_destroy(GTK_WIDGET(wid));
|
|
|
|
}
|
2019-07-21 13:26:02 +00:00
|
|
|
wid = nullptr;
|
2015-06-07 21:19:26 +00:00
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
PRectangle Window::GetPosition() const {
|
2009-04-24 23:35:41 +00:00
|
|
|
// Before any size allocated pretend its 1000 wide so not scrolled
|
|
|
|
PRectangle rc(0, 0, 1000, 1000);
|
2009-08-23 02:24:48 +00:00
|
|
|
if (wid) {
|
2011-07-17 22:30:49 +00:00
|
|
|
GtkAllocation allocation;
|
|
|
|
gtk_widget_get_allocation(PWidget(wid), &allocation);
|
2019-07-21 13:26:02 +00:00
|
|
|
rc.left = static_cast<XYPOSITION>(allocation.x);
|
|
|
|
rc.top = static_cast<XYPOSITION>(allocation.y);
|
2011-07-17 22:30:49 +00:00
|
|
|
if (allocation.width > 20) {
|
|
|
|
rc.right = rc.left + allocation.width;
|
|
|
|
rc.bottom = rc.top + allocation.height;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Window::SetPosition(PRectangle rc) {
|
2022-01-04 23:07:50 +00:00
|
|
|
GtkAllocation alloc {};
|
2019-07-21 13:26:02 +00:00
|
|
|
alloc.x = static_cast<int>(rc.left);
|
|
|
|
alloc.y = static_cast<int>(rc.top);
|
|
|
|
alloc.width = static_cast<int>(rc.Width());
|
|
|
|
alloc.height = static_cast<int>(rc.Height());
|
2009-08-23 02:24:48 +00:00
|
|
|
gtk_widget_size_allocate(PWidget(wid), &alloc);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
namespace {
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
GdkRectangle MonitorRectangleForWidget(GtkWidget *wid) noexcept {
|
2019-05-04 18:14:48 +00:00
|
|
|
GdkWindow *wnd = WindowFromWidget(wid);
|
|
|
|
GdkRectangle rcScreen = GdkRectangle();
|
|
|
|
#if GTK_CHECK_VERSION(3,22,0)
|
|
|
|
GdkDisplay *pdisplay = gtk_widget_get_display(wid);
|
|
|
|
GdkMonitor *monitor = gdk_display_get_monitor_at_window(pdisplay, wnd);
|
|
|
|
gdk_monitor_get_geometry(monitor, &rcScreen);
|
2022-01-04 23:07:50 +00:00
|
|
|
#if defined(GDK_WINDOWING_WAYLAND)
|
|
|
|
if (GDK_IS_WAYLAND_DISPLAY(pdisplay)) {
|
|
|
|
// The GDK behavior on Wayland is not self-consistent, we must correct the display coordinates to match
|
|
|
|
// the coordinate space used in gtk_window_move. See also https://sourceforge.net/p/scintilla/bugs/2296/
|
|
|
|
rcScreen.x = 0;
|
|
|
|
rcScreen.y = 0;
|
|
|
|
}
|
|
|
|
#endif
|
2019-05-04 18:14:48 +00:00
|
|
|
#else
|
2019-07-21 13:26:02 +00:00
|
|
|
GdkScreen *screen = gtk_widget_get_screen(wid);
|
|
|
|
const gint monitor_num = gdk_screen_get_monitor_at_window(screen, wnd);
|
2019-05-04 18:14:48 +00:00
|
|
|
gdk_screen_get_monitor_geometry(screen, monitor_num, &rcScreen);
|
|
|
|
#endif
|
|
|
|
return rcScreen;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Window::SetPositionRelative(PRectangle rc, const Window *relativeTo) {
|
2019-07-21 13:26:02 +00:00
|
|
|
const IntegerRectangle irc(rc);
|
2009-04-24 23:35:41 +00:00
|
|
|
int ox = 0;
|
|
|
|
int oy = 0;
|
2019-05-04 18:14:48 +00:00
|
|
|
GdkWindow *wndRelativeTo = WindowFromWidget(PWidget(relativeTo->wid));
|
|
|
|
gdk_window_get_origin(wndRelativeTo, &ox, &oy);
|
2019-07-21 13:26:02 +00:00
|
|
|
ox += irc.left;
|
|
|
|
oy += irc.top;
|
2019-05-04 18:14:48 +00:00
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
const GdkRectangle rcMonitor = MonitorRectangleForWidget(PWidget(relativeTo->wid));
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
/* do some corrections to fit into screen */
|
2019-07-21 13:26:02 +00:00
|
|
|
const int sizex = irc.Width();
|
|
|
|
const int sizey = irc.Height();
|
2019-05-04 18:14:48 +00:00
|
|
|
if (sizex > rcMonitor.width || ox < rcMonitor.x)
|
|
|
|
ox = rcMonitor.x; /* the best we can do */
|
|
|
|
else if (ox + sizex > rcMonitor.x + rcMonitor.width)
|
|
|
|
ox = rcMonitor.x + rcMonitor.width - sizex;
|
|
|
|
if (sizey > rcMonitor.height || oy < rcMonitor.y)
|
|
|
|
oy = rcMonitor.y;
|
|
|
|
else if (oy + sizey > rcMonitor.y + rcMonitor.height)
|
|
|
|
oy = rcMonitor.y + rcMonitor.height - sizey;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2009-08-23 02:24:48 +00:00
|
|
|
gtk_window_move(GTK_WINDOW(PWidget(wid)), ox, oy);
|
2009-06-24 19:09:31 +00:00
|
|
|
|
2015-06-07 21:19:26 +00:00
|
|
|
gtk_window_resize(GTK_WINDOW(wid), sizex, sizey);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
PRectangle Window::GetClientPosition() const {
|
2009-04-24 23:35:41 +00:00
|
|
|
// On GTK+, the client position is the window position
|
|
|
|
return GetPosition();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Window::Show(bool show) {
|
|
|
|
if (show)
|
2009-08-23 02:24:48 +00:00
|
|
|
gtk_widget_show(PWidget(wid));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Window::InvalidateAll() {
|
2009-08-23 02:24:48 +00:00
|
|
|
if (wid) {
|
|
|
|
gtk_widget_queue_draw(PWidget(wid));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Window::InvalidateRectangle(PRectangle rc) {
|
2009-08-23 02:24:48 +00:00
|
|
|
if (wid) {
|
2019-07-21 13:26:02 +00:00
|
|
|
const IntegerRectangle irc(rc);
|
2009-08-23 02:24:48 +00:00
|
|
|
gtk_widget_queue_draw_area(PWidget(wid),
|
2019-07-21 13:26:02 +00:00
|
|
|
irc.left, irc.top,
|
|
|
|
irc.Width(), irc.Height());
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Window::SetCursor(Cursor curs) {
|
|
|
|
// We don't set the cursor to same value numerous times under gtk because
|
|
|
|
// it stores the cursor in the window once it's set
|
|
|
|
if (curs == cursorLast)
|
|
|
|
return;
|
|
|
|
|
|
|
|
cursorLast = curs;
|
2019-05-04 18:14:48 +00:00
|
|
|
GdkDisplay *pdisplay = gtk_widget_get_display(PWidget(wid));
|
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
GdkCursor *gdkCurs;
|
|
|
|
switch (curs) {
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::text:
|
2019-05-04 18:14:48 +00:00
|
|
|
gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_XTERM);
|
2009-04-24 23:35:41 +00:00
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::arrow:
|
2019-05-04 18:14:48 +00:00
|
|
|
gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
|
2009-04-24 23:35:41 +00:00
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::up:
|
2019-05-04 18:14:48 +00:00
|
|
|
gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_CENTER_PTR);
|
2009-04-24 23:35:41 +00:00
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::wait:
|
2019-05-04 18:14:48 +00:00
|
|
|
gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_WATCH);
|
2009-04-24 23:35:41 +00:00
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::hand:
|
2019-05-04 18:14:48 +00:00
|
|
|
gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_HAND2);
|
2009-04-24 23:35:41 +00:00
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::reverseArrow:
|
2019-05-04 18:14:48 +00:00
|
|
|
gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_RIGHT_PTR);
|
2009-04-24 23:35:41 +00:00
|
|
|
break;
|
|
|
|
default:
|
2019-05-04 18:14:48 +00:00
|
|
|
gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
|
2022-01-04 23:07:50 +00:00
|
|
|
cursorLast = Cursor::arrow;
|
2009-04-24 23:35:41 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-07-17 22:30:49 +00:00
|
|
|
if (WindowFromWidget(PWidget(wid)))
|
|
|
|
gdk_window_set_cursor(WindowFromWidget(PWidget(wid)), gdkCurs);
|
2022-01-04 23:07:50 +00:00
|
|
|
UnRefCursor(gdkCurs);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Returns rectangle of monitor pt is on, both rect and pt are in Window's
|
|
|
|
gdk window coordinates */
|
|
|
|
PRectangle Window::GetMonitorRect(Point pt) {
|
|
|
|
gint x_offset, y_offset;
|
|
|
|
|
2011-07-17 22:30:49 +00:00
|
|
|
gdk_window_get_origin(WindowFromWidget(PWidget(wid)), &x_offset, &y_offset);
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
GdkRectangle rect {};
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
#if GTK_CHECK_VERSION(3,22,0)
|
|
|
|
GdkDisplay *pdisplay = gtk_widget_get_display(PWidget(wid));
|
|
|
|
GdkMonitor *monitor = gdk_display_get_monitor_at_point(pdisplay,
|
2019-07-21 13:26:02 +00:00
|
|
|
pt.x + x_offset, pt.y + y_offset);
|
2019-05-04 18:14:48 +00:00
|
|
|
gdk_monitor_get_geometry(monitor, &rect);
|
|
|
|
#else
|
2019-07-21 13:26:02 +00:00
|
|
|
GdkScreen *screen = gtk_widget_get_screen(PWidget(wid));
|
|
|
|
const gint monitor_num = gdk_screen_get_monitor_at_point(screen,
|
2022-01-04 23:07:50 +00:00
|
|
|
static_cast<gint>(pt.x) + x_offset, static_cast<gint>(pt.y) + y_offset);
|
2013-08-28 00:44:27 +00:00
|
|
|
gdk_screen_get_monitor_geometry(screen, monitor_num, &rect);
|
2019-05-04 18:14:48 +00:00
|
|
|
#endif
|
2013-08-28 00:44:27 +00:00
|
|
|
rect.x -= x_offset;
|
|
|
|
rect.y -= y_offset;
|
2019-07-21 13:26:02 +00:00
|
|
|
return PRectangle::FromInts(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
typedef std::map<int, RGBAImage *> ImageMap;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
struct ListImage {
|
2013-08-28 00:44:27 +00:00
|
|
|
const RGBAImage *rgba_data;
|
2009-04-24 23:35:41 +00:00
|
|
|
GdkPixbuf *pixbuf;
|
|
|
|
};
|
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
static void list_image_free(gpointer, gpointer value, gpointer) noexcept {
|
2013-08-28 00:44:27 +00:00
|
|
|
ListImage *list_image = static_cast<ListImage *>(value);
|
2009-04-24 23:35:41 +00:00
|
|
|
if (list_image->pixbuf)
|
2011-07-17 22:30:49 +00:00
|
|
|
g_object_unref(list_image->pixbuf);
|
2009-04-24 23:35:41 +00:00
|
|
|
g_free(list_image);
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
ListBox::ListBox() noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
ListBox::~ListBox() noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
enum {
|
|
|
|
PIXBUF_COLUMN,
|
|
|
|
TEXT_COLUMN,
|
|
|
|
N_COLUMNS
|
|
|
|
};
|
|
|
|
|
|
|
|
class ListBoxX : public ListBox {
|
2015-06-07 21:19:26 +00:00
|
|
|
WindowID widCached;
|
|
|
|
WindowID frame;
|
2009-04-24 23:35:41 +00:00
|
|
|
WindowID list;
|
|
|
|
WindowID scroller;
|
2022-08-14 10:23:34 +00:00
|
|
|
GHashTable *pixhash;
|
2019-07-21 13:26:02 +00:00
|
|
|
GtkCellRenderer *pixbuf_renderer;
|
|
|
|
GtkCellRenderer *renderer;
|
2013-08-28 00:44:27 +00:00
|
|
|
RGBAImageSet images;
|
2009-04-24 23:35:41 +00:00
|
|
|
int desiredVisibleRows;
|
|
|
|
unsigned int maxItemCharacters;
|
|
|
|
unsigned int aveCharWidth;
|
2019-05-04 18:14:48 +00:00
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
2022-01-04 23:07:50 +00:00
|
|
|
std::unique_ptr<GtkCssProvider, GObjectReleaser> cssProvider;
|
2019-05-04 18:14:48 +00:00
|
|
|
#endif
|
2009-04-24 23:35:41 +00:00
|
|
|
public:
|
2019-05-04 18:14:48 +00:00
|
|
|
IListBoxDelegate *delegate;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
ListBoxX() noexcept : widCached(nullptr), frame(nullptr), list(nullptr), scroller(nullptr),
|
|
|
|
pixhash(nullptr), pixbuf_renderer(nullptr),
|
|
|
|
renderer(nullptr),
|
2010-07-12 22:19:51 +00:00
|
|
|
desiredVisibleRows(5), maxItemCharacters(0),
|
2019-05-04 18:14:48 +00:00
|
|
|
aveCharWidth(1),
|
|
|
|
delegate(nullptr) {
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
// Deleted so ListBoxX objects can not be copied.
|
|
|
|
ListBoxX(const ListBoxX&) = delete;
|
|
|
|
ListBoxX(ListBoxX&&) = delete;
|
|
|
|
ListBoxX&operator=(const ListBoxX&) = delete;
|
|
|
|
ListBoxX&operator=(ListBoxX&&) = delete;
|
2022-01-04 23:07:50 +00:00
|
|
|
~ListBoxX() noexcept override {
|
2009-04-24 23:35:41 +00:00
|
|
|
if (pixhash) {
|
2022-08-14 10:23:34 +00:00
|
|
|
g_hash_table_foreach(pixhash, list_image_free, nullptr);
|
|
|
|
g_hash_table_destroy(pixhash);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2015-06-07 21:19:26 +00:00
|
|
|
if (widCached) {
|
|
|
|
gtk_widget_destroy(GTK_WIDGET(widCached));
|
2019-07-21 13:26:02 +00:00
|
|
|
wid = widCached = nullptr;
|
2015-06-07 21:19:26 +00:00
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
void SetFont(const Font *font) override;
|
|
|
|
void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, Technology technology_) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void SetAverageCharWidth(int width) override;
|
|
|
|
void SetVisibleRows(int rows) override;
|
|
|
|
int GetVisibleRows() const override;
|
2015-06-07 21:19:26 +00:00
|
|
|
int GetRowHeight();
|
2019-05-04 18:14:48 +00:00
|
|
|
PRectangle GetDesiredRect() override;
|
|
|
|
int CaretFromEdge() override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void Clear() noexcept override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void Append(char *s, int type = -1) override;
|
|
|
|
int Length() override;
|
|
|
|
void Select(int n) override;
|
|
|
|
int GetSelection() override;
|
|
|
|
int Find(const char *prefix) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
std::string GetValue(int n) override;
|
|
|
|
void RegisterRGBA(int type, std::unique_ptr<RGBAImage> image);
|
2019-05-04 18:14:48 +00:00
|
|
|
void RegisterImage(int type, const char *xpm_data) override;
|
|
|
|
void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) override;
|
|
|
|
void ClearRegisteredImages() override;
|
|
|
|
void SetDelegate(IListBoxDelegate *lbDelegate) override;
|
|
|
|
void SetList(const char *listText, char separator, char typesep) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void SetOptions(ListOptions options_) override;
|
2009-04-24 23:35:41 +00:00
|
|
|
};
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
std::unique_ptr<ListBox> ListBox::Allocate() {
|
|
|
|
return std::make_unique<ListBoxX>();
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
static int treeViewGetRowHeight(GtkTreeView *view) {
|
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
|
|
|
// This version sometimes reports erroneous results on GTK2, but the GTK2
|
|
|
|
// version is inaccurate for GTK 3.14.
|
|
|
|
GdkRectangle rect;
|
|
|
|
GtkTreePath *path = gtk_tree_path_new_first();
|
2019-07-21 13:26:02 +00:00
|
|
|
gtk_tree_view_get_background_area(view, path, nullptr, &rect);
|
2019-05-04 18:14:48 +00:00
|
|
|
gtk_tree_path_free(path);
|
|
|
|
return rect.height;
|
|
|
|
#else
|
|
|
|
int row_height=0;
|
|
|
|
int vertical_separator=0;
|
|
|
|
int expander_size=0;
|
|
|
|
GtkTreeViewColumn *column = gtk_tree_view_get_column(view, 0);
|
2019-07-21 13:26:02 +00:00
|
|
|
gtk_tree_view_column_cell_get_size(column, nullptr, nullptr, nullptr, nullptr, &row_height);
|
2019-05-04 18:14:48 +00:00
|
|
|
gtk_widget_style_get(GTK_WIDGET(view),
|
2019-07-21 13:26:02 +00:00
|
|
|
"vertical-separator", &vertical_separator,
|
|
|
|
"expander-size", &expander_size, nullptr);
|
2019-05-04 18:14:48 +00:00
|
|
|
row_height += vertical_separator;
|
|
|
|
row_height = std::max(row_height, expander_size);
|
|
|
|
return row_height;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2015-06-07 21:19:26 +00:00
|
|
|
// SmallScroller, a GtkScrolledWindow that can shrink very small, as
|
|
|
|
// gtk_widget_set_size_request() cannot shrink widgets on GTK3
|
|
|
|
typedef struct {
|
|
|
|
GtkScrolledWindow parent;
|
|
|
|
/* Workaround ABI issue with Windows GTK2 bundle and GCC > 3.
|
|
|
|
See http://lists.geany.org/pipermail/devel/2015-April/thread.html#9379
|
|
|
|
|
|
|
|
GtkScrolledWindow contains a bitfield, and GCC 3.4 and 4.8 don't agree
|
|
|
|
on the size of the structure (regardless of -mms-bitfields):
|
|
|
|
- GCC 3.4 has sizeof(GtkScrolledWindow)=88
|
|
|
|
- GCC 4.8 has sizeof(GtkScrolledWindow)=84
|
|
|
|
As Windows GTK2 bundle is built with GCC 3, it requires types derived
|
|
|
|
from GtkScrolledWindow to be at least 88 bytes, which means we need to
|
|
|
|
add some fake padding to fill in the extra 4 bytes.
|
|
|
|
There is however no other issue with the layout difference as we never
|
|
|
|
access any GtkScrolledWindow fields ourselves. */
|
|
|
|
int padding;
|
|
|
|
} SmallScroller;
|
|
|
|
typedef GtkScrolledWindowClass SmallScrollerClass;
|
|
|
|
|
|
|
|
G_DEFINE_TYPE(SmallScroller, small_scroller, GTK_TYPE_SCROLLED_WINDOW)
|
|
|
|
|
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
|
|
|
static void small_scroller_get_preferred_height(GtkWidget *widget, gint *min, gint *nat) {
|
2019-05-04 18:14:48 +00:00
|
|
|
GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
|
|
|
|
if (GTK_IS_TREE_VIEW(child)) {
|
|
|
|
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(child));
|
2019-07-21 13:26:02 +00:00
|
|
|
int n_rows = gtk_tree_model_iter_n_children(model, nullptr);
|
2019-05-04 18:14:48 +00:00
|
|
|
int row_height = treeViewGetRowHeight(GTK_TREE_VIEW(child));
|
|
|
|
|
|
|
|
*min = MAX(1, row_height);
|
|
|
|
*nat = MAX(*min, n_rows * row_height);
|
|
|
|
} else {
|
|
|
|
GTK_WIDGET_CLASS(small_scroller_parent_class)->get_preferred_height(widget, min, nat);
|
|
|
|
if (*min > 1)
|
|
|
|
*min = 1;
|
|
|
|
}
|
2015-06-07 21:19:26 +00:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
static void small_scroller_size_request(GtkWidget *widget, GtkRequisition *req) {
|
|
|
|
GTK_WIDGET_CLASS(small_scroller_parent_class)->size_request(widget, req);
|
|
|
|
req->height = 1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static void small_scroller_class_init(SmallScrollerClass *klass) {
|
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
|
|
|
GTK_WIDGET_CLASS(klass)->get_preferred_height = small_scroller_get_preferred_height;
|
|
|
|
#else
|
|
|
|
GTK_WIDGET_CLASS(klass)->size_request = small_scroller_size_request;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
static void small_scroller_init(SmallScroller *) {}
|
2015-06-07 21:19:26 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
static gboolean ButtonPress(GtkWidget *, const GdkEventButton *ev, gpointer p) {
|
2009-08-23 02:24:48 +00:00
|
|
|
try {
|
2019-07-21 13:26:02 +00:00
|
|
|
ListBoxX *lb = static_cast<ListBoxX *>(p);
|
2019-05-04 18:14:48 +00:00
|
|
|
if (ev->type == GDK_2BUTTON_PRESS && lb->delegate) {
|
|
|
|
ListBoxEvent event(ListBoxEvent::EventType::doubleClick);
|
|
|
|
lb->delegate->ListNotify(&event);
|
2009-08-23 02:24:48 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2009-08-23 02:24:48 +00:00
|
|
|
} catch (...) {
|
|
|
|
// No pointer back to Scintilla to save status
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
static gboolean ButtonRelease(GtkWidget *, const GdkEventButton *ev, gpointer p) {
|
2019-05-04 18:14:48 +00:00
|
|
|
try {
|
2019-07-21 13:26:02 +00:00
|
|
|
ListBoxX *lb = static_cast<ListBoxX *>(p);
|
2019-05-04 18:14:48 +00:00
|
|
|
if (ev->type != GDK_2BUTTON_PRESS && lb->delegate) {
|
|
|
|
ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
|
|
|
|
lb->delegate->ListNotify(&event);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
} catch (...) {
|
|
|
|
// No pointer back to Scintilla to save status
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
/* Change the active colour to the selected colour so the listbox uses the colour
|
2009-04-24 23:35:41 +00:00
|
|
|
scheme that it would use if it had the focus. */
|
2019-07-21 13:26:02 +00:00
|
|
|
static void StyleSet(GtkWidget *w, GtkStyle *, void *) {
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
g_return_if_fail(w != nullptr);
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
/* Copy the selected colour to active. Note that the modify calls will cause
|
2009-04-24 23:35:41 +00:00
|
|
|
recursive calls to this function after the value is updated and w->style to
|
|
|
|
be set to a new object */
|
2011-07-17 22:30:49 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
#if GTK_CHECK_VERSION(3,16,0)
|
|
|
|
// On recent releases of GTK+, it does not appear necessary to set the list box colours.
|
|
|
|
// This may be because of common themes and may be needed with other themes.
|
|
|
|
// The *override* calls are deprecated now, so only call them for older versions of GTK+.
|
|
|
|
#elif GTK_CHECK_VERSION(3,0,0)
|
2011-07-17 22:30:49 +00:00
|
|
|
GtkStyleContext *styleContext = gtk_widget_get_style_context(w);
|
2019-07-21 13:26:02 +00:00
|
|
|
if (styleContext == nullptr)
|
2011-07-17 22:30:49 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
GdkRGBA colourForeSelected;
|
|
|
|
gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourForeSelected);
|
|
|
|
GdkRGBA colourForeActive;
|
|
|
|
gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourForeActive);
|
|
|
|
if (!gdk_rgba_equal(&colourForeSelected, &colourForeActive))
|
|
|
|
gtk_widget_override_color(w, GTK_STATE_FLAG_ACTIVE, &colourForeSelected);
|
|
|
|
|
|
|
|
styleContext = gtk_widget_get_style_context(w);
|
2019-07-21 13:26:02 +00:00
|
|
|
if (styleContext == nullptr)
|
2011-07-17 22:30:49 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
GdkRGBA colourBaseSelected;
|
|
|
|
gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourBaseSelected);
|
|
|
|
GdkRGBA colourBaseActive;
|
|
|
|
gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourBaseActive);
|
|
|
|
if (!gdk_rgba_equal(&colourBaseSelected, &colourBaseActive))
|
|
|
|
gtk_widget_override_background_color(w, GTK_STATE_FLAG_ACTIVE, &colourBaseSelected);
|
|
|
|
#else
|
|
|
|
GtkStyle *style = gtk_widget_get_style(w);
|
2019-07-21 13:26:02 +00:00
|
|
|
if (style == nullptr)
|
2009-04-24 23:35:41 +00:00
|
|
|
return;
|
|
|
|
if (!gdk_color_equal(&style->base[GTK_STATE_SELECTED], &style->base[GTK_STATE_ACTIVE]))
|
|
|
|
gtk_widget_modify_base(w, GTK_STATE_ACTIVE, &style->base[GTK_STATE_SELECTED]);
|
|
|
|
style = gtk_widget_get_style(w);
|
2019-07-21 13:26:02 +00:00
|
|
|
if (style == nullptr)
|
2009-04-24 23:35:41 +00:00
|
|
|
return;
|
|
|
|
if (!gdk_color_equal(&style->text[GTK_STATE_SELECTED], &style->text[GTK_STATE_ACTIVE]))
|
|
|
|
gtk_widget_modify_text(w, GTK_STATE_ACTIVE, &style->text[GTK_STATE_SELECTED]);
|
2011-07-17 22:30:49 +00:00
|
|
|
#endif
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void ListBoxX::Create(Window &parent, int, Point, int, bool, Technology) {
|
2019-07-21 13:26:02 +00:00
|
|
|
if (widCached != nullptr) {
|
2015-06-07 21:19:26 +00:00
|
|
|
wid = widCached;
|
|
|
|
return;
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
|
|
|
if (!cssProvider) {
|
2022-01-04 23:07:50 +00:00
|
|
|
cssProvider.reset(gtk_css_provider_new());
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2015-06-07 21:19:26 +00:00
|
|
|
wid = widCached = gtk_window_new(GTK_WINDOW_POPUP);
|
2023-09-22 09:32:35 +00:00
|
|
|
gtk_window_set_type_hint(GTK_WINDOW(wid), GDK_WINDOW_TYPE_HINT_POPUP_MENU);
|
2015-06-07 21:19:26 +00:00
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
frame = gtk_frame_new(nullptr);
|
2015-06-07 21:19:26 +00:00
|
|
|
gtk_widget_show(PWidget(frame));
|
|
|
|
gtk_container_add(GTK_CONTAINER(GetID()), PWidget(frame));
|
2009-04-24 23:35:41 +00:00
|
|
|
gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
|
|
|
|
gtk_container_set_border_width(GTK_CONTAINER(frame), 0);
|
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
scroller = g_object_new(small_scroller_get_type(), nullptr);
|
2009-04-24 23:35:41 +00:00
|
|
|
gtk_container_set_border_width(GTK_CONTAINER(scroller), 0);
|
|
|
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
|
2019-07-21 13:26:02 +00:00
|
|
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
2009-04-24 23:35:41 +00:00
|
|
|
gtk_container_add(GTK_CONTAINER(frame), PWidget(scroller));
|
|
|
|
gtk_widget_show(PWidget(scroller));
|
|
|
|
|
|
|
|
/* Tree and its model */
|
|
|
|
GtkListStore *store =
|
|
|
|
gtk_list_store_new(N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING);
|
|
|
|
|
|
|
|
list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
|
2019-07-21 13:26:02 +00:00
|
|
|
g_signal_connect(G_OBJECT(list), "style-set", G_CALLBACK(StyleSet), nullptr);
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
|
|
|
GtkStyleContext *styleContext = gtk_widget_get_style_context(GTK_WIDGET(list));
|
|
|
|
if (styleContext) {
|
2022-01-04 23:07:50 +00:00
|
|
|
gtk_style_context_add_provider(styleContext, GTK_STYLE_PROVIDER(cssProvider.get()),
|
2019-07-21 13:26:02 +00:00
|
|
|
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
GtkTreeSelection *selection =
|
|
|
|
gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
|
|
|
|
gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
|
|
|
|
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
|
|
|
|
gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list), FALSE);
|
|
|
|
|
|
|
|
/* Columns */
|
|
|
|
GtkTreeViewColumn *column = gtk_tree_view_column_new();
|
|
|
|
gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
|
|
|
|
gtk_tree_view_column_set_title(column, "Autocomplete");
|
|
|
|
|
2009-08-23 02:24:48 +00:00
|
|
|
pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
|
|
|
|
gtk_cell_renderer_set_fixed_size(pixbuf_renderer, 0, -1);
|
|
|
|
gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE);
|
|
|
|
gtk_tree_view_column_add_attribute(column, pixbuf_renderer,
|
2019-07-21 13:26:02 +00:00
|
|
|
"pixbuf", PIXBUF_COLUMN);
|
2010-07-12 22:19:51 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
renderer = gtk_cell_renderer_text_new();
|
2009-04-24 23:35:41 +00:00
|
|
|
gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
|
|
|
|
gtk_tree_view_column_pack_start(column, renderer, TRUE);
|
|
|
|
gtk_tree_view_column_add_attribute(column, renderer,
|
2019-07-21 13:26:02 +00:00
|
|
|
"text", TEXT_COLUMN);
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
|
|
|
|
if (g_object_class_find_property(G_OBJECT_GET_CLASS(list), "fixed-height-mode"))
|
2019-07-21 13:26:02 +00:00
|
|
|
g_object_set(G_OBJECT(list), "fixed-height-mode", TRUE, nullptr);
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2015-06-07 21:19:26 +00:00
|
|
|
GtkWidget *widget = PWidget(list); // No code inside the G_OBJECT macro
|
|
|
|
gtk_container_add(GTK_CONTAINER(PWidget(scroller)), widget);
|
|
|
|
gtk_widget_show(widget);
|
|
|
|
g_signal_connect(G_OBJECT(widget), "button_press_event",
|
2019-07-21 13:26:02 +00:00
|
|
|
G_CALLBACK(ButtonPress), this);
|
2019-05-04 18:14:48 +00:00
|
|
|
g_signal_connect(G_OBJECT(widget), "button_release_event",
|
2019-07-21 13:26:02 +00:00
|
|
|
G_CALLBACK(ButtonRelease), this);
|
2019-05-04 18:14:48 +00:00
|
|
|
|
|
|
|
GtkWidget *top = gtk_widget_get_toplevel(static_cast<GtkWidget *>(parent.GetID()));
|
|
|
|
gtk_window_set_transient_for(GTK_WINDOW(static_cast<GtkWidget *>(wid)),
|
2019-07-21 13:26:02 +00:00
|
|
|
GTK_WINDOW(top));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void ListBoxX::SetFont(const Font *font) {
|
2009-04-24 23:35:41 +00:00
|
|
|
// Only do for Pango font as there have been crashes for GDK fonts
|
2022-01-04 23:07:50 +00:00
|
|
|
if (Created() && PFont(font)->fd) {
|
2009-04-24 23:35:41 +00:00
|
|
|
// Current font is Pango font
|
2011-07-17 22:30:49 +00:00
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
2019-05-04 18:14:48 +00:00
|
|
|
if (cssProvider) {
|
2022-01-04 23:07:50 +00:00
|
|
|
PangoFontDescription *pfd = PFont(font)->fd.get();
|
2019-05-04 18:14:48 +00:00
|
|
|
std::ostringstream ssFontSetting;
|
|
|
|
ssFontSetting << "GtkTreeView, treeview { ";
|
|
|
|
ssFontSetting << "font-family: " << pango_font_description_get_family(pfd) << "; ";
|
|
|
|
ssFontSetting << "font-size:";
|
|
|
|
ssFontSetting << static_cast<double>(pango_font_description_get_size(pfd)) / PANGO_SCALE;
|
|
|
|
// On GTK < 3.21.0 the units are incorrectly parsed, so a font size in points
|
|
|
|
// need to use the "px" unit. Normally we only get fonts in points here, so
|
|
|
|
// don't bother to handle the case the font is actually in pixels on < 3.21.0.
|
2019-07-21 13:26:02 +00:00
|
|
|
if (gtk_check_version(3, 21, 0) != nullptr || // on < 3.21.0
|
|
|
|
pango_font_description_get_size_is_absolute(pfd)) {
|
2019-05-04 18:14:48 +00:00
|
|
|
ssFontSetting << "px; ";
|
|
|
|
} else {
|
|
|
|
ssFontSetting << "pt; ";
|
|
|
|
}
|
|
|
|
ssFontSetting << "font-weight:"<< pango_font_description_get_weight(pfd) << "; ";
|
|
|
|
ssFontSetting << "}";
|
2022-01-04 23:07:50 +00:00
|
|
|
gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(cssProvider.get()),
|
2019-07-21 13:26:02 +00:00
|
|
|
ssFontSetting.str().c_str(), -1, nullptr);
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
2011-07-17 22:30:49 +00:00
|
|
|
#else
|
2022-01-04 23:07:50 +00:00
|
|
|
gtk_widget_modify_font(PWidget(list), PFont(font)->fd.get());
|
2011-07-17 22:30:49 +00:00
|
|
|
#endif
|
2019-05-04 18:14:48 +00:00
|
|
|
gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), -1);
|
|
|
|
gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::SetAverageCharWidth(int width) {
|
|
|
|
aveCharWidth = width;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::SetVisibleRows(int rows) {
|
|
|
|
desiredVisibleRows = rows;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ListBoxX::GetVisibleRows() const {
|
|
|
|
return desiredVisibleRows;
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
int ListBoxX::GetRowHeight() {
|
|
|
|
return treeViewGetRowHeight(GTK_TREE_VIEW(list));
|
2015-06-07 21:19:26 +00:00
|
|
|
}
|
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
PRectangle ListBoxX::GetDesiredRect() {
|
|
|
|
// Before any size allocated pretend its 100 wide so not scrolled
|
|
|
|
PRectangle rc(0, 0, 100, 100);
|
2009-08-23 02:24:48 +00:00
|
|
|
if (wid) {
|
2009-04-24 23:35:41 +00:00
|
|
|
int rows = Length();
|
|
|
|
if ((rows == 0) || (rows > desiredVisibleRows))
|
|
|
|
rows = desiredVisibleRows;
|
|
|
|
|
|
|
|
GtkRequisition req;
|
2013-08-28 00:44:27 +00:00
|
|
|
// This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
|
2015-06-07 21:19:26 +00:00
|
|
|
// returns reasonable values.
|
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
2019-07-21 13:26:02 +00:00
|
|
|
gtk_widget_get_preferred_size(GTK_WIDGET(frame), nullptr, &req);
|
2015-06-07 21:19:26 +00:00
|
|
|
#else
|
|
|
|
gtk_widget_size_request(GTK_WIDGET(frame), &req);
|
2013-08-28 00:44:27 +00:00
|
|
|
#endif
|
2009-04-24 23:35:41 +00:00
|
|
|
int height;
|
|
|
|
|
|
|
|
// First calculate height of the clist for our desired visible
|
|
|
|
// row count otherwise it tries to expand to the total # of rows
|
|
|
|
// Get cell height
|
2019-07-21 13:26:02 +00:00
|
|
|
const int row_height = GetRowHeight();
|
2011-07-17 22:30:49 +00:00
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
2015-06-07 21:19:26 +00:00
|
|
|
GtkStyleContext *styleContextFrame = gtk_widget_get_style_context(PWidget(frame));
|
2019-05-04 18:14:48 +00:00
|
|
|
GtkStateFlags stateFlagsFrame = gtk_style_context_get_state(styleContextFrame);
|
|
|
|
GtkBorder padding, border, border_border = { 0, 0, 0, 0 };
|
|
|
|
gtk_style_context_get_padding(styleContextFrame, stateFlagsFrame, &padding);
|
|
|
|
gtk_style_context_get_border(styleContextFrame, stateFlagsFrame, &border);
|
|
|
|
|
|
|
|
# if GTK_CHECK_VERSION(3,20,0)
|
|
|
|
// on GTK 3.20 the frame border is in a sub-node "border".
|
|
|
|
// Unfortunately we need to be built against 3.20 to be able to support this, as it requires
|
|
|
|
// new API.
|
|
|
|
GtkStyleContext *styleContextFrameBorder = gtk_style_context_new();
|
|
|
|
GtkWidgetPath *widget_path = gtk_widget_path_copy(gtk_style_context_get_path(styleContextFrame));
|
|
|
|
gtk_widget_path_append_type(widget_path, GTK_TYPE_BORDER); // dummy type
|
|
|
|
gtk_widget_path_iter_set_object_name(widget_path, -1, "border");
|
|
|
|
gtk_style_context_set_path(styleContextFrameBorder, widget_path);
|
|
|
|
gtk_widget_path_free(widget_path);
|
|
|
|
gtk_style_context_get_border(styleContextFrameBorder, stateFlagsFrame, &border_border);
|
|
|
|
g_object_unref(styleContextFrameBorder);
|
|
|
|
# else // < 3.20
|
2019-07-21 13:26:02 +00:00
|
|
|
if (gtk_check_version(3, 20, 0) == nullptr) {
|
2019-05-04 18:14:48 +00:00
|
|
|
// default to 1px all around as it's likely what it is, and so we don't miss 2px height
|
|
|
|
// on GTK 3.20 when built against an earlier version.
|
|
|
|
border_border.top = border_border.bottom = border_border.left = border_border.right = 1;
|
|
|
|
}
|
|
|
|
# endif
|
|
|
|
|
2011-07-17 22:30:49 +00:00
|
|
|
height = (rows * row_height
|
2019-07-21 13:26:02 +00:00
|
|
|
+ padding.top + padding.bottom
|
|
|
|
+ border.top + border.bottom
|
|
|
|
+ border_border.top + border_border.bottom
|
|
|
|
+ 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
|
2011-07-17 22:30:49 +00:00
|
|
|
#else
|
2009-04-24 23:35:41 +00:00
|
|
|
height = (rows * row_height
|
2019-07-21 13:26:02 +00:00
|
|
|
+ 2 * (PWidget(frame)->style->ythickness
|
|
|
|
+ GTK_CONTAINER(PWidget(list))->border_width));
|
2011-07-17 22:30:49 +00:00
|
|
|
#endif
|
2015-06-07 21:19:26 +00:00
|
|
|
rc.bottom = height;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
const unsigned int width = std::max(maxItemCharacters, 12U);
|
2009-04-24 23:35:41 +00:00
|
|
|
rc.right = width * (aveCharWidth + aveCharWidth / 3);
|
2015-06-07 21:19:26 +00:00
|
|
|
// Add horizontal padding and borders
|
|
|
|
int horizontal_separator=0;
|
|
|
|
gtk_widget_style_get(PWidget(list),
|
2019-07-21 13:26:02 +00:00
|
|
|
"horizontal-separator", &horizontal_separator, nullptr);
|
2015-06-07 21:19:26 +00:00
|
|
|
rc.right += horizontal_separator;
|
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
|
|
|
rc.right += (padding.left + padding.right
|
2019-07-21 13:26:02 +00:00
|
|
|
+ border.left + border.right
|
|
|
|
+ border_border.left + border_border.right
|
|
|
|
+ 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
|
2015-06-07 21:19:26 +00:00
|
|
|
#else
|
|
|
|
rc.right += 2 * (PWidget(frame)->style->xthickness
|
2019-07-21 13:26:02 +00:00
|
|
|
+ GTK_CONTAINER(PWidget(list))->border_width);
|
2015-06-07 21:19:26 +00:00
|
|
|
#endif
|
|
|
|
if (Length() > rows) {
|
|
|
|
// Add the width of the scrollbar
|
|
|
|
GtkWidget *vscrollbar =
|
|
|
|
gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(scroller));
|
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
2019-07-21 13:26:02 +00:00
|
|
|
gtk_widget_get_preferred_size(vscrollbar, nullptr, &req);
|
2015-06-07 21:19:26 +00:00
|
|
|
#else
|
|
|
|
gtk_widget_size_request(vscrollbar, &req);
|
|
|
|
#endif
|
|
|
|
rc.right += req.width;
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ListBoxX::CaretFromEdge() {
|
2009-08-23 02:24:48 +00:00
|
|
|
gint renderer_width, renderer_height;
|
|
|
|
gtk_cell_renderer_get_fixed_size(pixbuf_renderer, &renderer_width,
|
2019-07-21 13:26:02 +00:00
|
|
|
&renderer_height);
|
2009-08-23 02:24:48 +00:00
|
|
|
return 4 + renderer_width;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void ListBoxX::Clear() noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
|
|
|
|
gtk_list_store_clear(GTK_LIST_STORE(model));
|
|
|
|
maxItemCharacters = 0;
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
static void init_pixmap(ListImage *list_image) noexcept {
|
2013-08-28 00:44:27 +00:00
|
|
|
if (list_image->rgba_data) {
|
|
|
|
// Drop any existing pixmap/bitmap as data may have changed
|
|
|
|
if (list_image->pixbuf)
|
|
|
|
g_object_unref(list_image->pixbuf);
|
|
|
|
list_image->pixbuf =
|
|
|
|
gdk_pixbuf_new_from_data(list_image->rgba_data->Pixels(),
|
2019-07-21 13:26:02 +00:00
|
|
|
GDK_COLORSPACE_RGB,
|
|
|
|
TRUE,
|
|
|
|
8,
|
|
|
|
list_image->rgba_data->GetWidth(),
|
|
|
|
list_image->rgba_data->GetHeight(),
|
|
|
|
list_image->rgba_data->GetWidth() * 4,
|
|
|
|
nullptr,
|
|
|
|
nullptr);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#define SPACING 5
|
|
|
|
|
|
|
|
void ListBoxX::Append(char *s, int type) {
|
2019-07-21 13:26:02 +00:00
|
|
|
ListImage *list_image = nullptr;
|
2009-04-24 23:35:41 +00:00
|
|
|
if ((type >= 0) && pixhash) {
|
2022-08-14 10:23:34 +00:00
|
|
|
list_image = static_cast<ListImage *>(g_hash_table_lookup(pixhash,
|
2019-07-21 13:26:02 +00:00
|
|
|
GINT_TO_POINTER(type)));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
GtkTreeIter iter {};
|
2009-04-24 23:35:41 +00:00
|
|
|
GtkListStore *store =
|
|
|
|
GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
|
|
|
|
gtk_list_store_append(GTK_LIST_STORE(store), &iter);
|
|
|
|
if (list_image) {
|
2019-07-21 13:26:02 +00:00
|
|
|
if (nullptr == list_image->pixbuf)
|
2009-04-24 23:35:41 +00:00
|
|
|
init_pixmap(list_image);
|
|
|
|
if (list_image->pixbuf) {
|
|
|
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter,
|
2019-07-21 13:26:02 +00:00
|
|
|
PIXBUF_COLUMN, list_image->pixbuf,
|
|
|
|
TEXT_COLUMN, s, -1);
|
2009-08-23 02:24:48 +00:00
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
const gint pixbuf_width = gdk_pixbuf_get_width(list_image->pixbuf);
|
2009-08-23 02:24:48 +00:00
|
|
|
gint renderer_height, renderer_width;
|
2010-07-12 22:19:51 +00:00
|
|
|
gtk_cell_renderer_get_fixed_size(pixbuf_renderer,
|
2019-07-21 13:26:02 +00:00
|
|
|
&renderer_width, &renderer_height);
|
2009-08-23 02:24:48 +00:00
|
|
|
if (pixbuf_width > renderer_width)
|
|
|
|
gtk_cell_renderer_set_fixed_size(pixbuf_renderer,
|
2019-07-21 13:26:02 +00:00
|
|
|
pixbuf_width, -1);
|
2009-04-24 23:35:41 +00:00
|
|
|
} else {
|
|
|
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter,
|
2019-07-21 13:26:02 +00:00
|
|
|
TEXT_COLUMN, s, -1);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
} else {
|
2019-07-21 13:26:02 +00:00
|
|
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter,
|
|
|
|
TEXT_COLUMN, s, -1);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
const unsigned int len = static_cast<unsigned int>(strlen(s));
|
2009-04-24 23:35:41 +00:00
|
|
|
if (maxItemCharacters < len)
|
|
|
|
maxItemCharacters = len;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ListBoxX::Length() {
|
2009-08-23 02:24:48 +00:00
|
|
|
if (wid)
|
2009-04-24 23:35:41 +00:00
|
|
|
return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
|
2019-07-21 13:26:02 +00:00
|
|
|
(GTK_TREE_VIEW(list)), nullptr);
|
2009-04-24 23:35:41 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::Select(int n) {
|
2021-02-21 04:53:09 +00:00
|
|
|
GtkTreeIter iter {};
|
2009-04-24 23:35:41 +00:00
|
|
|
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
|
|
|
|
GtkTreeSelection *selection =
|
|
|
|
gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
|
|
|
|
|
|
|
|
if (n < 0) {
|
|
|
|
gtk_tree_selection_unselect_all(selection);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
const bool valid = gtk_tree_model_iter_nth_child(model, &iter, nullptr, n) != FALSE;
|
2009-04-24 23:35:41 +00:00
|
|
|
if (valid) {
|
|
|
|
gtk_tree_selection_select_iter(selection, &iter);
|
|
|
|
|
|
|
|
// Move the scrollbar to show the selection.
|
2019-07-21 13:26:02 +00:00
|
|
|
const int total = Length();
|
2011-07-17 22:30:49 +00:00
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
|
|
|
GtkAdjustment *adj =
|
|
|
|
gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list));
|
|
|
|
#else
|
2009-04-24 23:35:41 +00:00
|
|
|
GtkAdjustment *adj =
|
|
|
|
gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list));
|
2011-07-17 22:30:49 +00:00
|
|
|
#endif
|
2022-01-04 23:07:50 +00:00
|
|
|
gdouble value = (static_cast<gdouble>(n) / total) * (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_lower(adj))
|
2019-07-21 13:26:02 +00:00
|
|
|
+ gtk_adjustment_get_lower(adj) - gtk_adjustment_get_page_size(adj) / 2;
|
2009-04-24 23:35:41 +00:00
|
|
|
// Get cell height
|
2019-07-21 13:26:02 +00:00
|
|
|
const int row_height = GetRowHeight();
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
int rows = Length();
|
|
|
|
if ((rows == 0) || (rows > desiredVisibleRows))
|
|
|
|
rows = desiredVisibleRows;
|
|
|
|
if (rows & 0x1) {
|
|
|
|
// Odd rows to display -- We are now in the middle.
|
|
|
|
// Align it so that we don't chop off rows.
|
2019-07-21 13:26:02 +00:00
|
|
|
value += static_cast<gfloat>(row_height) / 2.0f;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
// Clamp it.
|
|
|
|
value = (value < 0)? 0 : value;
|
2011-07-17 22:30:49 +00:00
|
|
|
value = (value > (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)))?
|
2019-07-21 13:26:02 +00:00
|
|
|
(gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)) : value;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
// Set it.
|
|
|
|
gtk_adjustment_set_value(adj, value);
|
|
|
|
} else {
|
|
|
|
gtk_tree_selection_unselect_all(selection);
|
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
|
|
|
|
if (delegate) {
|
|
|
|
ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
|
|
|
|
delegate->ListNotify(&event);
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int ListBoxX::GetSelection() {
|
2015-06-07 21:19:26 +00:00
|
|
|
int index = -1;
|
2021-02-21 04:53:09 +00:00
|
|
|
GtkTreeIter iter {};
|
|
|
|
GtkTreeModel *model {};
|
2009-04-24 23:35:41 +00:00
|
|
|
GtkTreeSelection *selection;
|
|
|
|
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
|
|
|
|
if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
|
|
|
|
GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
|
2019-07-21 13:26:02 +00:00
|
|
|
const int *indices = gtk_tree_path_get_indices(path);
|
2009-04-24 23:35:41 +00:00
|
|
|
// Don't free indices.
|
|
|
|
if (indices)
|
2015-06-07 21:19:26 +00:00
|
|
|
index = indices[0];
|
|
|
|
gtk_tree_path_free(path);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2015-06-07 21:19:26 +00:00
|
|
|
return index;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int ListBoxX::Find(const char *prefix) {
|
2021-02-21 04:53:09 +00:00
|
|
|
GtkTreeIter iter {};
|
2009-04-24 23:35:41 +00:00
|
|
|
GtkTreeModel *model =
|
|
|
|
gtk_tree_view_get_model(GTK_TREE_VIEW(list));
|
|
|
|
bool valid = gtk_tree_model_get_iter_first(model, &iter) != FALSE;
|
|
|
|
int i = 0;
|
2019-07-21 13:26:02 +00:00
|
|
|
while (valid) {
|
2021-02-21 04:53:09 +00:00
|
|
|
gchar *s = nullptr;
|
2009-04-24 23:35:41 +00:00
|
|
|
gtk_tree_model_get(model, &iter, TEXT_COLUMN, &s, -1);
|
|
|
|
if (s && (0 == strncmp(prefix, s, strlen(prefix)))) {
|
2010-08-21 23:59:56 +00:00
|
|
|
g_free(s);
|
2009-04-24 23:35:41 +00:00
|
|
|
return i;
|
|
|
|
}
|
2010-08-21 23:59:56 +00:00
|
|
|
g_free(s);
|
2009-04-24 23:35:41 +00:00
|
|
|
valid = gtk_tree_model_iter_next(model, &iter) != FALSE;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
std::string ListBoxX::GetValue(int n) {
|
2019-07-21 13:26:02 +00:00
|
|
|
char *text = nullptr;
|
2021-02-21 04:53:09 +00:00
|
|
|
GtkTreeIter iter {};
|
2009-04-24 23:35:41 +00:00
|
|
|
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
|
2019-07-21 13:26:02 +00:00
|
|
|
const bool valid = gtk_tree_model_iter_nth_child(model, &iter, nullptr, n) != FALSE;
|
2009-04-24 23:35:41 +00:00
|
|
|
if (valid) {
|
|
|
|
gtk_tree_model_get(model, &iter, TEXT_COLUMN, &text, -1);
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
std::string value;
|
|
|
|
if (text) {
|
|
|
|
value = text;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2010-08-21 23:59:56 +00:00
|
|
|
g_free(text);
|
2022-01-04 23:07:50 +00:00
|
|
|
return value;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// g_return_if_fail causes unnecessary compiler warning in release compile.
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
#pragma warning(disable: 4127)
|
|
|
|
#endif
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void ListBoxX::RegisterRGBA(int type, std::unique_ptr<RGBAImage> image) {
|
|
|
|
images.AddImage(type, std::move(image));
|
|
|
|
const RGBAImage * const observe = images.Get(type);
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
if (!pixhash) {
|
|
|
|
pixhash = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
|
|
}
|
2022-08-14 10:23:34 +00:00
|
|
|
ListImage *list_image = static_cast<ListImage *>(g_hash_table_lookup(pixhash,
|
2019-07-21 13:26:02 +00:00
|
|
|
GINT_TO_POINTER(type)));
|
2009-04-24 23:35:41 +00:00
|
|
|
if (list_image) {
|
|
|
|
// Drop icon already registered
|
|
|
|
if (list_image->pixbuf)
|
2010-09-05 22:56:27 +00:00
|
|
|
g_object_unref(list_image->pixbuf);
|
2019-07-21 13:26:02 +00:00
|
|
|
list_image->pixbuf = nullptr;
|
2022-01-04 23:07:50 +00:00
|
|
|
list_image->rgba_data = observe;
|
2009-04-24 23:35:41 +00:00
|
|
|
} else {
|
|
|
|
list_image = g_new0(ListImage, 1);
|
2022-01-04 23:07:50 +00:00
|
|
|
list_image->rgba_data = observe;
|
2022-08-14 10:23:34 +00:00
|
|
|
g_hash_table_insert(pixhash, GINT_TO_POINTER(type),
|
2019-07-21 13:26:02 +00:00
|
|
|
(gpointer) list_image);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
void ListBoxX::RegisterImage(int type, const char *xpm_data) {
|
|
|
|
g_return_if_fail(xpm_data);
|
|
|
|
XPM xpmImage(xpm_data);
|
2022-01-04 23:07:50 +00:00
|
|
|
RegisterRGBA(type, std::make_unique<RGBAImage>(xpmImage));
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
|
2022-01-04 23:07:50 +00:00
|
|
|
RegisterRGBA(type, std::make_unique<RGBAImage>(width, height, 1.0f, pixelsImage));
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
void ListBoxX::ClearRegisteredImages() {
|
2013-08-28 00:44:27 +00:00
|
|
|
images.Clear();
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
void ListBoxX::SetDelegate(IListBoxDelegate *lbDelegate) {
|
|
|
|
delegate = lbDelegate;
|
|
|
|
}
|
|
|
|
|
2009-04-25 23:38:15 +00:00
|
|
|
void ListBoxX::SetList(const char *listText, char separator, char typesep) {
|
2009-04-24 23:35:41 +00:00
|
|
|
Clear();
|
2019-07-21 13:26:02 +00:00
|
|
|
const size_t count = strlen(listText) + 1;
|
2013-08-28 00:44:27 +00:00
|
|
|
std::vector<char> words(listText, listText+count);
|
|
|
|
char *startword = &words[0];
|
2019-07-21 13:26:02 +00:00
|
|
|
char *numword = nullptr;
|
2013-08-28 00:44:27 +00:00
|
|
|
int i = 0;
|
|
|
|
for (; words[i]; i++) {
|
|
|
|
if (words[i] == separator) {
|
|
|
|
words[i] = '\0';
|
2009-04-24 23:35:41 +00:00
|
|
|
if (numword)
|
|
|
|
*numword = '\0';
|
|
|
|
Append(startword, numword?atoi(numword + 1):-1);
|
2013-08-28 00:44:27 +00:00
|
|
|
startword = &words[0] + i + 1;
|
2019-07-21 13:26:02 +00:00
|
|
|
numword = nullptr;
|
2013-08-28 00:44:27 +00:00
|
|
|
} else if (words[i] == typesep) {
|
|
|
|
numword = &words[0] + i;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
if (startword) {
|
|
|
|
if (numword)
|
|
|
|
*numword = '\0';
|
|
|
|
Append(startword, numword?atoi(numword + 1):-1);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void ListBoxX::SetOptions(ListOptions) {
|
|
|
|
}
|
|
|
|
|
2019-07-21 13:26:02 +00:00
|
|
|
Menu::Menu() noexcept : mid(nullptr) {}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
void Menu::CreatePopUp() {
|
|
|
|
Destroy();
|
2010-09-05 22:56:27 +00:00
|
|
|
mid = gtk_menu_new();
|
2019-05-04 18:14:48 +00:00
|
|
|
g_object_ref_sink(G_OBJECT(mid));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void Menu::Destroy() noexcept {
|
2009-08-23 02:24:48 +00:00
|
|
|
if (mid)
|
|
|
|
g_object_unref(G_OBJECT(mid));
|
2019-07-21 13:26:02 +00:00
|
|
|
mid = nullptr;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
#if !GTK_CHECK_VERSION(3,22,0)
|
2021-02-21 04:53:09 +00:00
|
|
|
static void MenuPositionFunc(GtkMenu *, gint *x, gint *y, gboolean *, gpointer userData) noexcept {
|
2022-01-04 23:07:50 +00:00
|
|
|
const gint intFromPointer = GPOINTER_TO_INT(userData);
|
2010-09-05 22:56:27 +00:00
|
|
|
*x = intFromPointer & 0xffff;
|
|
|
|
*y = intFromPointer >> 16;
|
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
#endif
|
2010-09-05 22:56:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void Menu::Show(Point pt, const Window &w) {
|
2019-05-04 18:14:48 +00:00
|
|
|
GtkMenu *widget = static_cast<GtkMenu *>(mid);
|
2010-09-05 22:56:27 +00:00
|
|
|
gtk_widget_show_all(GTK_WIDGET(widget));
|
2019-05-04 18:14:48 +00:00
|
|
|
#if GTK_CHECK_VERSION(3,22,0)
|
|
|
|
// Rely on GTK+ to do the right thing with positioning
|
2019-07-21 13:26:02 +00:00
|
|
|
gtk_menu_popup_at_pointer(widget, nullptr);
|
2019-05-04 18:14:48 +00:00
|
|
|
#else
|
2019-07-21 13:26:02 +00:00
|
|
|
const GdkRectangle rcMonitor = MonitorRectangleForWidget(PWidget(w.GetID()));
|
2009-04-24 23:35:41 +00:00
|
|
|
GtkRequisition requisition;
|
2011-07-17 22:30:49 +00:00
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
2019-07-21 13:26:02 +00:00
|
|
|
gtk_widget_get_preferred_size(GTK_WIDGET(widget), nullptr, &requisition);
|
2011-07-17 22:30:49 +00:00
|
|
|
#else
|
2010-09-05 22:56:27 +00:00
|
|
|
gtk_widget_size_request(GTK_WIDGET(widget), &requisition);
|
2011-07-17 22:30:49 +00:00
|
|
|
#endif
|
2019-05-04 18:14:48 +00:00
|
|
|
if ((pt.x + requisition.width) > rcMonitor.x + rcMonitor.width) {
|
|
|
|
pt.x = rcMonitor.x + rcMonitor.width - requisition.width;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
if ((pt.y + requisition.height) > rcMonitor.y + rcMonitor.height) {
|
|
|
|
pt.y = rcMonitor.y + rcMonitor.height - requisition.height;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2019-07-21 13:26:02 +00:00
|
|
|
gtk_menu_popup(widget, nullptr, nullptr, MenuPositionFunc,
|
|
|
|
GINT_TO_POINTER((static_cast<int>(pt.y) << 16) | static_cast<int>(pt.x)), 0,
|
|
|
|
gtk_get_current_event_time());
|
2019-05-04 18:14:48 +00:00
|
|
|
#endif
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
ColourRGBA Platform::Chrome() {
|
|
|
|
return ColourRGBA(0xe0, 0xe0, 0xe0);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
ColourRGBA Platform::ChromeHighlight() {
|
2023-11-05 17:24:41 +00:00
|
|
|
return white;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const char *Platform::DefaultFont() {
|
|
|
|
#ifdef G_OS_WIN32
|
|
|
|
return "Lucida Console";
|
|
|
|
#else
|
|
|
|
return "!Sans";
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
int Platform::DefaultFontSize() {
|
|
|
|
#ifdef G_OS_WIN32
|
|
|
|
return 10;
|
|
|
|
#else
|
|
|
|
return 12;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int Platform::DoubleClickTime() {
|
|
|
|
return 500; // Half a second
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void Platform::DebugDisplay(const char *s) noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
fprintf(stderr, "%s", s);
|
|
|
|
}
|
|
|
|
|
|
|
|
//#define TRACE
|
|
|
|
|
|
|
|
#ifdef TRACE
|
2022-01-04 23:07:50 +00:00
|
|
|
void Platform::DebugPrintf(const char *format, ...) noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
char buffer[2000];
|
|
|
|
va_list pArguments;
|
|
|
|
va_start(pArguments, format);
|
Update scintilla 5.3.4 and lexilla 5.2.4 with:
https://www.scintilla.org/scintilla534.zip
Released 8 March 2023.
Add multithreaded wrap to significantly improve performance of wrapping large files.
More typesafe bindings of *Full APIs in ScintillaCall. Feature #1477.
Fix overlapping of text with line end wrap marker. Bug #2378.
Fix clipping of line end wrap symbol for SC_WRAPVISUALFLAGLOC_END_BY_TEXT.
Where a multi-byte character contains multiple styles, display each byte as a representation. This makes it easier to see and fix lexers that change styles mid-character, commonly because they use fixed size buffers.
Fix a potential crash with autocompletion list fill-ups where a SCN_CHARADDED handler retriggered an autocompletion list, but with no items that match the typed character.
lexilla523
Released 8 March 2023.
Add scripts/PromoteNew.bat script to promote .new files after checking.
Makefile: Remove 1024-byte line length limit..
Ruby: Add new lexical classes for % literals SCE_RB_STRING_W (%w non-interpolable string array), SCE_RB_STRING_I (%i non-interpolable symbol array), SCE_RB_STRING_QI (%I interpolable symbol array), and SCE_RB_STRING_QS (%s symbol). Issue #124.
Ruby: Disambiguate %= which may be a quote or modulo assignment. Issue #124, Bug #1255, Bug #2182.
Ruby: Fix additional fold level for single character in SCE_RB_STRING_QW. Issue #132.
Ruby: Set SCE_RB_HERE_QQ for unquoted and double-quoted heredocs and SCE_RB_HERE_QX for backticks-quoted heredocs. Issue #134.
Ruby: Recognise #{} inside SCE_RB_HERE_QQ and SCE_RB_HERE_QX. Issue #134.
Ruby: Improve regex and heredoc recognition. Issue #136.
Ruby: Highlight #@, #@@ and #$ style interpolation. Issue #140.
Ruby: Fix folding for multiple heredocs started on one line. Fix folding when there is a space after heredoc opening delimiter. Issue #135.
YAML: Remove 1024-byte line length limit.
https://www.scintilla.org/lexilla524.zip
Released 13 March 2023.
C++: Fix failure to recognize keywords containing upper case. Issue #149.
GDScript: Support % and $ node paths. Issue #145, Pull request #146.
Close #13338
2023-03-10 02:37:21 +00:00
|
|
|
vsnprintf(buffer, std::size(buffer), format, pArguments);
|
2009-04-24 23:35:41 +00:00
|
|
|
va_end(pArguments);
|
|
|
|
Platform::DebugDisplay(buffer);
|
|
|
|
}
|
|
|
|
#else
|
2022-01-04 23:07:50 +00:00
|
|
|
void Platform::DebugPrintf(const char *, ...) noexcept {}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Not supported for GTK+
|
|
|
|
static bool assertionPopUps = true;
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
bool Platform::ShowAssertionPopUps(bool assertionPopUps_) noexcept {
|
2019-07-21 13:26:02 +00:00
|
|
|
const bool ret = assertionPopUps;
|
2009-04-24 23:35:41 +00:00
|
|
|
assertionPopUps = assertionPopUps_;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void Platform::Assert(const char *c, const char *file, int line) noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
char buffer[2000];
|
2015-06-07 21:19:26 +00:00
|
|
|
g_snprintf(buffer, sizeof(buffer), "Assertion [%s] failed at %s %d\r\n", c, file, line);
|
2009-04-24 23:35:41 +00:00
|
|
|
Platform::DebugDisplay(buffer);
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Platform_Initialise() {
|
|
|
|
}
|
|
|
|
|
|
|
|
void Platform_Finalise() {
|
|
|
|
}
|