Merge pull request #3648 from techee/nim_lexer
[geany-mirror.git] / scintilla / gtk / PlatGTK.cxx
blob692735f450acbf72d6bc29a7b0e86c13b93b3b31
1 // Scintilla source code edit control
2 // PlatGTK.cxx - implementation of platform facilities on GTK+/Linux
3 // Copyright 1998-2004 by Neil Hodgson <neilh@scintilla.org>
4 // The License.txt file describes the conditions under which this software may be distributed.
6 #include <cstddef>
7 #include <cstdlib>
8 #include <cstring>
9 #include <cstdio>
10 #include <cmath>
12 #include <string>
13 #include <string_view>
14 #include <vector>
15 #include <map>
16 #include <optional>
17 #include <algorithm>
18 #include <memory>
19 #include <sstream>
21 #include <glib.h>
22 #include <gmodule.h>
23 #include <gdk/gdk.h>
24 #include <gtk/gtk.h>
25 #include <gdk/gdkkeysyms.h>
26 #if defined(GDK_WINDOWING_WAYLAND)
27 #include <gdk/gdkwayland.h>
28 #endif
30 #include "ScintillaTypes.h"
31 #include "ScintillaMessages.h"
33 #include "Debugging.h"
34 #include "Geometry.h"
35 #include "Platform.h"
37 #include "Scintilla.h"
38 #include "ScintillaWidget.h"
39 #include "XPM.h"
40 #include "UniConversion.h"
42 #include "Wrappers.h"
43 #include "Converter.h"
45 #ifdef _MSC_VER
46 // Ignore unreferenced local functions in GTK+ headers
47 #pragma warning(disable: 4505)
48 #endif
50 using namespace Scintilla;
51 using namespace Scintilla::Internal;
53 namespace {
55 constexpr double kPi = 3.14159265358979323846;
57 constexpr double degrees = kPi / 180.0;
59 struct IntegerRectangle {
60 int left;
61 int top;
62 int right;
63 int bottom;
65 explicit IntegerRectangle(PRectangle rc) noexcept :
66 left(static_cast<int>(rc.left)), top(static_cast<int>(rc.top)),
67 right(static_cast<int>(rc.right)), bottom(static_cast<int>(rc.bottom)) {
69 int Width() const noexcept { return right - left; }
70 int Height() const noexcept { return bottom - top; }
73 GtkWidget *PWidget(WindowID wid) noexcept {
74 return static_cast<GtkWidget *>(wid);
77 void SetFractionalPositions([[maybe_unused]] PangoContext *pcontext) noexcept {
78 #if PANGO_VERSION_CHECK(1,44,3)
79 pango_context_set_round_glyph_positions(pcontext, FALSE);
80 #endif
83 void LayoutSetText(PangoLayout *layout, std::string_view text) noexcept {
84 pango_layout_set_text(layout, text.data(), static_cast<int>(text.length()));
87 enum class EncodingType { singleByte, utf8, dbcs };
89 // Holds a PangoFontDescription*.
90 class FontHandle : public Font {
91 public:
92 UniquePangoFontDescription fd;
93 CharacterSet characterSet;
94 explicit FontHandle(const FontParameters &fp) :
95 fd(pango_font_description_new()), characterSet(fp.characterSet) {
96 if (fd) {
97 pango_font_description_set_family(fd.get(),
98 (fp.faceName[0] == '!') ? fp.faceName + 1 : fp.faceName);
99 pango_font_description_set_size(fd.get(), pango_units_from_double(fp.size));
100 pango_font_description_set_weight(fd.get(), static_cast<PangoWeight>(fp.weight));
101 pango_font_description_set_style(fd.get(), fp.italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
104 ~FontHandle() override = default;
107 // X has a 16 bit coordinate space, so stop drawing here to avoid wrapping
108 constexpr int maxCoordinate = 32000;
110 const FontHandle *PFont(const Font *f) noexcept {
111 return dynamic_cast<const FontHandle *>(f);
116 std::shared_ptr<Font> Font::Allocate(const FontParameters &fp) {
117 return std::make_shared<FontHandle>(fp);
120 namespace Scintilla {
122 // SurfaceID is a cairo_t*
123 class SurfaceImpl : public Surface {
124 SurfaceMode mode;
125 EncodingType et= EncodingType::singleByte;
126 WindowID widSave = nullptr;
127 cairo_t *context = nullptr;
128 UniqueCairo cairoOwned;
129 UniqueCairoSurface surf;
130 bool inited = false;
131 UniquePangoContext pcontext;
132 double resolution = 1.0;
133 PangoDirection direction = PANGO_DIRECTION_LTR;
134 const cairo_font_options_t *fontOptions = nullptr;
135 PangoLanguage *language = nullptr;
136 UniquePangoLayout layout;
137 Converter conv;
138 CharacterSet characterSet = static_cast<CharacterSet>(-1);
140 void PenColourAlpha(ColourRGBA fore) noexcept;
141 void SetConverter(CharacterSet characterSet_);
142 void CairoRectangle(PRectangle rc) noexcept;
143 public:
144 SurfaceImpl() noexcept;
145 SurfaceImpl(cairo_t *context_, int width, int height, SurfaceMode mode_, WindowID wid) noexcept;
146 // Deleted so SurfaceImpl objects can not be copied.
147 SurfaceImpl(const SurfaceImpl&) = delete;
148 SurfaceImpl(SurfaceImpl&&) = delete;
149 SurfaceImpl&operator=(const SurfaceImpl&) = delete;
150 SurfaceImpl&operator=(SurfaceImpl&&) = delete;
151 ~SurfaceImpl() override = default;
153 void GetContextState() noexcept;
154 UniquePangoContext MeasuringContext();
156 void Init(WindowID wid) override;
157 void Init(SurfaceID sid, WindowID wid) override;
158 std::unique_ptr<Surface> AllocatePixMap(int width, int height) override;
160 void SetMode(SurfaceMode mode_) override;
162 void Release() noexcept override;
163 int SupportsFeature(Supports feature) noexcept override;
164 bool Initialised() override;
165 int LogPixelsY() override;
166 int PixelDivisions() override;
167 int DeviceHeightFont(int points) override;
168 void LineDraw(Point start, Point end, Stroke stroke) override;
169 void PolyLine(const Point *pts, size_t npts, Stroke stroke) override;
170 void Polygon(const Point *pts, size_t npts, FillStroke fillStroke) override;
171 void RectangleDraw(PRectangle rc, FillStroke fillStroke) override;
172 void RectangleFrame(PRectangle rc, Stroke stroke) override;
173 void FillRectangle(PRectangle rc, Fill fill) override;
174 void FillRectangleAligned(PRectangle rc, Fill fill) override;
175 void FillRectangle(PRectangle rc, Surface &surfacePattern) override;
176 void RoundedRectangle(PRectangle rc, FillStroke fillStroke) override;
177 void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) override;
178 void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override;
179 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override;
180 void Ellipse(PRectangle rc, FillStroke fillStroke) override;
181 void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) override;
182 void Copy(PRectangle rc, Point from, Surface &surfaceSource) override;
184 std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override;
186 void DrawTextBase(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore);
187 void DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
188 void DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
189 void DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
190 void MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) override;
191 XYPOSITION WidthText(const Font *font_, std::string_view text) override;
193 void DrawTextBaseUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore);
194 void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
195 void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
196 void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
197 void MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) override;
198 XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) override;
200 XYPOSITION Ascent(const Font *font_) override;
201 XYPOSITION Descent(const Font *font_) override;
202 XYPOSITION InternalLeading(const Font *font_) override;
203 XYPOSITION Height(const Font *font_) override;
204 XYPOSITION AverageCharWidth(const Font *font_) override;
206 void SetClip(PRectangle rc) override;
207 void PopClip() override;
208 void FlushCachedState() override;
209 void FlushDrawing() override;
212 const Supports SupportsGTK[] = {
213 Supports::LineDrawsFinal,
214 Supports::FractionalStrokeWidth,
215 Supports::TranslucentStroke,
216 Supports::PixelModification,
217 Supports::ThreadSafeMeasureWidths,
222 const char *CharacterSetID(CharacterSet characterSet) noexcept {
223 switch (characterSet) {
224 case CharacterSet::Ansi:
225 return "";
226 case CharacterSet::Default:
227 return "ISO-8859-1";
228 case CharacterSet::Baltic:
229 return "ISO-8859-13";
230 case CharacterSet::ChineseBig5:
231 return "BIG-5";
232 case CharacterSet::EastEurope:
233 return "ISO-8859-2";
234 case CharacterSet::GB2312:
235 return "CP936";
236 case CharacterSet::Greek:
237 return "ISO-8859-7";
238 case CharacterSet::Hangul:
239 return "CP949";
240 case CharacterSet::Mac:
241 return "MACINTOSH";
242 case CharacterSet::Oem:
243 return "ASCII";
244 case CharacterSet::Russian:
245 return "KOI8-R";
246 case CharacterSet::Oem866:
247 return "CP866";
248 case CharacterSet::Cyrillic:
249 return "CP1251";
250 case CharacterSet::ShiftJis:
251 return "SHIFT-JIS";
252 case CharacterSet::Symbol:
253 return "";
254 case CharacterSet::Turkish:
255 return "ISO-8859-9";
256 case CharacterSet::Johab:
257 return "CP1361";
258 case CharacterSet::Hebrew:
259 return "ISO-8859-8";
260 case CharacterSet::Arabic:
261 return "ISO-8859-6";
262 case CharacterSet::Vietnamese:
263 return "";
264 case CharacterSet::Thai:
265 return "ISO-8859-11";
266 case CharacterSet::Iso8859_15:
267 return "ISO-8859-15";
268 default:
269 return "";
273 void SurfaceImpl::PenColourAlpha(ColourRGBA fore) noexcept {
274 if (context) {
275 cairo_set_source_rgba(context,
276 fore.GetRedComponent(),
277 fore.GetGreenComponent(),
278 fore.GetBlueComponent(),
279 fore.GetAlphaComponent());
283 void SurfaceImpl::SetConverter(CharacterSet characterSet_) {
284 if (characterSet != characterSet_) {
285 characterSet = characterSet_;
286 conv.Open("UTF-8", CharacterSetID(characterSet), false);
290 void SurfaceImpl::CairoRectangle(PRectangle rc) noexcept {
291 cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
294 SurfaceImpl::SurfaceImpl() noexcept {
297 SurfaceImpl::SurfaceImpl(cairo_t *context_, int width, int height, SurfaceMode mode_, WindowID wid) noexcept {
298 if (height > 0 && width > 0) {
299 cairo_surface_t *psurfContext = cairo_get_target(context_);
300 surf.reset(cairo_surface_create_similar(
301 psurfContext,
302 CAIRO_CONTENT_COLOR_ALPHA, width, height));
303 cairoOwned.reset(cairo_create(surf.get()));
304 context = cairoOwned.get();
305 pcontext.reset(gtk_widget_create_pango_context(PWidget(wid)));
306 PLATFORM_ASSERT(pcontext);
307 SetFractionalPositions(pcontext.get());
308 GetContextState();
309 layout.reset(pango_layout_new(pcontext.get()));
310 PLATFORM_ASSERT(layout);
311 cairo_rectangle(context, 0, 0, width, height);
312 cairo_set_source_rgb(context, 1.0, 0, 0);
313 cairo_fill(context);
314 cairo_set_line_width(context, 1);
315 inited = true;
316 mode = mode_;
320 void SurfaceImpl::Release() noexcept {
321 et = EncodingType::singleByte;
322 cairoOwned.reset();
323 context = nullptr;
324 surf.reset();
325 layout.reset();
326 // fontOptions and language are owned by original context and don't need to be freed
327 fontOptions = nullptr;
328 language = nullptr;
329 pcontext.reset();
330 conv.Close();
331 characterSet = static_cast<CharacterSet>(-1);
332 inited = false;
335 bool SurfaceImpl::Initialised() {
336 if (inited && context) {
337 if (cairo_status(context) == CAIRO_STATUS_SUCCESS) {
338 // Even when status is success, the target surface may have been
339 // finished which may cause an assertion to fail crashing the application.
340 // The cairo_surface_has_show_text_glyphs call checks the finished flag
341 // and when set, sets the status to CAIRO_STATUS_SURFACE_FINISHED
342 // which leads to warning messages instead of crashes.
343 // Performing the check in this method as it is called rarely and has no
344 // other side effects.
345 cairo_surface_t *psurfContext = cairo_get_target(context);
346 if (psurfContext) {
347 cairo_surface_has_show_text_glyphs(psurfContext);
350 return cairo_status(context) == CAIRO_STATUS_SUCCESS;
352 return inited;
355 void SurfaceImpl::GetContextState() noexcept {
356 resolution = pango_cairo_context_get_resolution(pcontext.get());
357 direction = pango_context_get_base_dir(pcontext.get());
358 fontOptions = pango_cairo_context_get_font_options(pcontext.get());
359 language = pango_context_get_language(pcontext.get());
362 UniquePangoContext SurfaceImpl::MeasuringContext() {
363 UniquePangoFontMap fmMeasure(pango_cairo_font_map_get_default());
364 PLATFORM_ASSERT(fmMeasure);
365 UniquePangoContext contextMeasure(pango_font_map_create_context(fmMeasure.release()));
366 PLATFORM_ASSERT(contextMeasure);
367 SetFractionalPositions(contextMeasure.get());
369 pango_cairo_context_set_resolution(contextMeasure.get(), resolution);
370 pango_context_set_base_dir(contextMeasure.get(), direction);
371 pango_cairo_context_set_font_options(contextMeasure.get(), fontOptions);
372 pango_context_set_language(contextMeasure.get(), language);
374 return contextMeasure;
377 void SurfaceImpl::Init(WindowID wid) {
378 widSave = wid;
379 Release();
380 PLATFORM_ASSERT(wid);
381 // if we are only created from a window ID, we can't perform drawing
382 context = nullptr;
383 pcontext.reset(gtk_widget_create_pango_context(PWidget(wid)));
384 PLATFORM_ASSERT(pcontext);
385 SetFractionalPositions(pcontext.get());
386 GetContextState();
387 layout.reset(pango_layout_new(pcontext.get()));
388 PLATFORM_ASSERT(layout);
389 inited = true;
392 void SurfaceImpl::Init(SurfaceID sid, WindowID wid) {
393 widSave = wid;
394 PLATFORM_ASSERT(sid);
395 Release();
396 PLATFORM_ASSERT(wid);
397 cairoOwned.reset(cairo_reference(static_cast<cairo_t *>(sid)));
398 context = cairoOwned.get();
399 pcontext.reset(gtk_widget_create_pango_context(PWidget(wid)));
400 SetFractionalPositions(pcontext.get());
401 // update the Pango context in case sid isn't the widget's surface
402 pango_cairo_update_context(context, pcontext.get());
403 GetContextState();
404 layout.reset(pango_layout_new(pcontext.get()));
405 cairo_set_line_width(context, 1);
406 inited = true;
409 std::unique_ptr<Surface> SurfaceImpl::AllocatePixMap(int width, int height) {
410 // widSave must be alive now so safe for creating a PangoContext
411 return std::make_unique<SurfaceImpl>(context, width, height, mode, widSave);
414 void SurfaceImpl::SetMode(SurfaceMode mode_) {
415 mode = mode_;
416 if (mode.codePage == SC_CP_UTF8) {
417 et = EncodingType::utf8;
418 } else if (mode.codePage) {
419 et = EncodingType::dbcs;
420 } else {
421 et = EncodingType::singleByte;
425 int SurfaceImpl::SupportsFeature(Supports feature) noexcept {
426 for (const Supports f : SupportsGTK) {
427 if (f == feature)
428 return 1;
430 return 0;
433 int SurfaceImpl::LogPixelsY() {
434 return 72;
437 int SurfaceImpl::PixelDivisions() {
438 // GTK uses device pixels.
439 return 1;
442 int SurfaceImpl::DeviceHeightFont(int points) {
443 const int logPix = LogPixelsY();
444 return (points * logPix + logPix / 2) / 72;
447 void SurfaceImpl::LineDraw(Point start, Point end, Stroke stroke) {
448 PLATFORM_ASSERT(context);
449 if (!context)
450 return;
451 PenColourAlpha(stroke.colour);
452 cairo_set_line_width(context, stroke.width);
453 cairo_move_to(context, start.x, start.y);
454 cairo_line_to(context, end.x, end.y);
455 cairo_stroke(context);
458 void SurfaceImpl::PolyLine(const Point *pts, size_t npts, Stroke stroke) {
459 // TODO: set line joins and caps
460 PLATFORM_ASSERT(context && npts > 1);
461 if (!context)
462 return;
463 PenColourAlpha(stroke.colour);
464 cairo_set_line_width(context, stroke.width);
465 cairo_move_to(context, pts[0].x, pts[0].y);
466 for (size_t i = 1; i < npts; i++) {
467 cairo_line_to(context, pts[i].x, pts[i].y);
469 cairo_stroke(context);
472 void SurfaceImpl::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) {
473 PLATFORM_ASSERT(context);
474 PenColourAlpha(fillStroke.fill.colour);
475 cairo_move_to(context, pts[0].x, pts[0].y);
476 for (size_t i = 1; i < npts; i++) {
477 cairo_line_to(context, pts[i].x, pts[i].y);
479 cairo_close_path(context);
480 cairo_fill_preserve(context);
481 PenColourAlpha(fillStroke.stroke.colour);
482 cairo_set_line_width(context, fillStroke.stroke.width);
483 cairo_stroke(context);
486 void SurfaceImpl::RectangleDraw(PRectangle rc, FillStroke fillStroke) {
487 if (context) {
488 CairoRectangle(rc.Inset(fillStroke.stroke.width / 2));
489 PenColourAlpha(fillStroke.fill.colour);
490 cairo_fill_preserve(context);
491 PenColourAlpha(fillStroke.stroke.colour);
492 cairo_set_line_width(context, fillStroke.stroke.width);
493 cairo_stroke(context);
497 void SurfaceImpl::RectangleFrame(PRectangle rc, Stroke stroke) {
498 if (context) {
499 CairoRectangle(rc.Inset(stroke.width / 2));
500 PenColourAlpha(stroke.colour);
501 cairo_set_line_width(context, stroke.width);
502 cairo_stroke(context);
506 void SurfaceImpl::FillRectangle(PRectangle rc, Fill fill) {
507 PenColourAlpha(fill.colour);
508 if (context && (rc.left < maxCoordinate)) { // Protect against out of range
509 CairoRectangle(rc);
510 cairo_fill(context);
514 void SurfaceImpl::FillRectangleAligned(PRectangle rc, Fill fill) {
515 FillRectangle(PixelAlign(rc, 1), fill);
518 void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
519 SurfaceImpl &surfi = dynamic_cast<SurfaceImpl &>(surfacePattern);
520 if (context && surfi.surf) {
521 // Tile pattern over rectangle
522 cairo_set_source_surface(context, surfi.surf.get(), rc.left, rc.top);
523 cairo_pattern_set_extend(cairo_get_source(context), CAIRO_EXTEND_REPEAT);
524 cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
525 cairo_fill(context);
529 void SurfaceImpl::RoundedRectangle(PRectangle rc, FillStroke fillStroke) {
530 if (((rc.right - rc.left) > 4) && ((rc.bottom - rc.top) > 4)) {
531 // Approximate a round rect with some cut off corners
532 Point pts[] = {
533 Point(rc.left + 2, rc.top),
534 Point(rc.right - 2, rc.top),
535 Point(rc.right, rc.top + 2),
536 Point(rc.right, rc.bottom - 2),
537 Point(rc.right - 2, rc.bottom),
538 Point(rc.left + 2, rc.bottom),
539 Point(rc.left, rc.bottom - 2),
540 Point(rc.left, rc.top + 2),
542 Polygon(pts, std::size(pts), fillStroke);
543 } else {
544 RectangleDraw(rc, fillStroke);
548 static void PathRoundRectangle(cairo_t *context, double left, double top, double width, double height, double radius) noexcept {
549 cairo_new_sub_path(context);
550 cairo_arc(context, left + width - radius, top + radius, radius, -90 * degrees, 0 * degrees);
551 cairo_arc(context, left + width - radius, top + height - radius, radius, 0 * degrees, 90 * degrees);
552 cairo_arc(context, left + radius, top + height - radius, radius, 90 * degrees, 180 * degrees);
553 cairo_arc(context, left + radius, top + radius, radius, 180 * degrees, 270 * degrees);
554 cairo_close_path(context);
557 void SurfaceImpl::AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) {
558 if (context && rc.Width() > 0) {
559 const XYPOSITION halfStroke = fillStroke.stroke.width / 2.0;
560 const XYPOSITION doubleStroke = fillStroke.stroke.width * 2.0;
561 PenColourAlpha(fillStroke.fill.colour);
562 if (cornerSize > 0)
563 PathRoundRectangle(context, rc.left + fillStroke.stroke.width, rc.top + fillStroke.stroke.width,
564 rc.Width() - doubleStroke, rc.Height() - doubleStroke, cornerSize);
565 else
566 cairo_rectangle(context, rc.left + fillStroke.stroke.width, rc.top + fillStroke.stroke.width,
567 rc.Width() - doubleStroke, rc.Height() - doubleStroke);
568 cairo_fill(context);
570 PenColourAlpha(fillStroke.stroke.colour);
571 if (cornerSize > 0)
572 PathRoundRectangle(context, rc.left + halfStroke, rc.top + halfStroke,
573 rc.Width() - fillStroke.stroke.width, rc.Height() - fillStroke.stroke.width, cornerSize);
574 else
575 cairo_rectangle(context, rc.left + halfStroke, rc.top + halfStroke,
576 rc.Width() - fillStroke.stroke.width, rc.Height() - fillStroke.stroke.width);
577 cairo_set_line_width(context, fillStroke.stroke.width);
578 cairo_stroke(context);
582 void SurfaceImpl::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) {
583 if (context) {
584 cairo_pattern_t *pattern;
585 switch (options) {
586 case GradientOptions::leftToRight:
587 pattern = cairo_pattern_create_linear(rc.left, rc.top, rc.right, rc.top);
588 break;
589 case GradientOptions::topToBottom:
590 default:
591 pattern = cairo_pattern_create_linear(rc.left, rc.top, rc.left, rc.bottom);
592 break;
594 for (const ColourStop &stop : stops) {
595 cairo_pattern_add_color_stop_rgba(pattern, stop.position,
596 stop.colour.GetRedComponent(),
597 stop.colour.GetGreenComponent(),
598 stop.colour.GetBlueComponent(),
599 stop.colour.GetAlphaComponent());
601 cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
602 cairo_set_source(context, pattern);
603 cairo_fill(context);
604 cairo_pattern_destroy(pattern);
608 void SurfaceImpl::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
609 PLATFORM_ASSERT(context);
610 if (width == 0)
611 return;
612 if (rc.Width() > width)
613 rc.left += (rc.Width() - width) / 2;
614 rc.right = rc.left + width;
615 if (rc.Height() > height)
616 rc.top += (rc.Height() - height) / 2;
617 rc.bottom = rc.top + height;
619 const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
620 const int ucs = stride * height;
621 std::vector<unsigned char> image(ucs);
622 for (ptrdiff_t iy=0; iy<height; iy++) {
623 unsigned char *pixel = &image[0] + iy*stride;
624 RGBAImage::BGRAFromRGBA(pixel, pixelsImage, width);
625 pixelsImage += RGBAImage::bytesPerPixel * width;
628 UniqueCairoSurface surfImage(cairo_image_surface_create_for_data(&image[0], CAIRO_FORMAT_ARGB32, width, height, stride));
629 cairo_set_source_surface(context, surfImage.get(), rc.left, rc.top);
630 cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
631 cairo_fill(context);
634 void SurfaceImpl::Ellipse(PRectangle rc, FillStroke fillStroke) {
635 PLATFORM_ASSERT(context);
636 PenColourAlpha(fillStroke.fill.colour);
637 cairo_arc(context, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2,
638 (std::min(rc.Width(), rc.Height()) - fillStroke.stroke.width) / 2, 0, 2*kPi);
639 cairo_fill_preserve(context);
640 PenColourAlpha(fillStroke.stroke.colour);
641 cairo_set_line_width(context, fillStroke.stroke.width);
642 cairo_stroke(context);
645 void SurfaceImpl::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) {
646 const XYPOSITION midLine = rc.Centre().y;
647 const XYPOSITION halfStroke = fillStroke.stroke.width / 2.0f;
648 const XYPOSITION radius = rc.Height() / 2.0f - halfStroke;
649 PRectangle rcInner = rc;
650 rcInner.left += radius;
651 rcInner.right -= radius;
653 cairo_new_sub_path(context);
655 const Ends leftSide = static_cast<Ends>(static_cast<int>(ends) & 0xf);
656 const Ends rightSide = static_cast<Ends>(static_cast<int>(ends) & 0xf0);
657 switch (leftSide) {
658 case Ends::leftFlat:
659 cairo_move_to(context, rc.left + halfStroke, rc.top + halfStroke);
660 cairo_line_to(context, rc.left + halfStroke, rc.bottom - halfStroke);
661 break;
662 case Ends::leftAngle:
663 cairo_move_to(context, rcInner.left + halfStroke, rc.top + halfStroke);
664 cairo_line_to(context, rc.left + halfStroke, rc.Centre().y);
665 cairo_line_to(context, rcInner.left + halfStroke, rc.bottom - halfStroke);
666 break;
667 case Ends::semiCircles:
668 default:
669 cairo_move_to(context, rcInner.left + halfStroke, rc.top + halfStroke);
670 cairo_arc_negative(context, rcInner.left + halfStroke, midLine, radius,
671 270 * degrees, 90 * degrees);
672 break;
675 switch (rightSide) {
676 case Ends::rightFlat:
677 cairo_line_to(context, rc.right - halfStroke, rc.bottom - halfStroke);
678 cairo_line_to(context, rc.right - halfStroke, rc.top + halfStroke);
679 break;
680 case Ends::rightAngle:
681 cairo_line_to(context, rcInner.right - halfStroke, rc.bottom - halfStroke);
682 cairo_line_to(context, rc.right - halfStroke, rc.Centre().y);
683 cairo_line_to(context, rcInner.right - halfStroke, rc.top + halfStroke);
684 break;
685 case Ends::semiCircles:
686 default:
687 cairo_line_to(context, rcInner.right - halfStroke, rc.bottom - halfStroke);
688 cairo_arc_negative(context, rcInner.right - halfStroke, midLine, radius,
689 90 * degrees, 270 * degrees);
690 break;
693 // Close the path to enclose it for stroking and for filling, then draw it
694 cairo_close_path(context);
695 PenColourAlpha(fillStroke.fill.colour);
696 cairo_fill_preserve(context);
698 PenColourAlpha(fillStroke.stroke.colour);
699 cairo_set_line_width(context, fillStroke.stroke.width);
700 cairo_stroke(context);
703 void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
704 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfaceSource);
705 const bool canDraw = surfi.surf != nullptr;
706 if (canDraw) {
707 PLATFORM_ASSERT(context);
708 cairo_set_source_surface(context, surfi.surf.get(),
709 rc.left - from.x, rc.top - from.y);
710 cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
711 cairo_fill(context);
715 std::unique_ptr<IScreenLineLayout> SurfaceImpl::Layout(const IScreenLine *) {
716 return {};
719 std::string UTF8FromLatin1(std::string_view text) {
720 std::string utfForm(text.length()*2 + 1, '\0');
721 size_t lenU = 0;
722 for (const char ch : text) {
723 const unsigned char uch = ch;
724 if (uch < 0x80) {
725 utfForm[lenU++] = uch;
726 } else {
727 utfForm[lenU++] = static_cast<char>(0xC0 | (uch >> 6));
728 utfForm[lenU++] = static_cast<char>(0x80 | (uch & 0x3f));
731 utfForm.resize(lenU);
732 return utfForm;
735 namespace {
737 std::string UTF8FromIconv(const Converter &conv, std::string_view text) {
738 if (conv) {
739 std::string utfForm(text.length()*3+1, '\0');
740 char *pin = const_cast<char *>(text.data());
741 gsize inLeft = text.length();
742 char *putf = &utfForm[0];
743 char *pout = putf;
744 gsize outLeft = text.length()*3+1;
745 const gsize conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
746 if (conversions != sizeFailure) {
747 *pout = '\0';
748 utfForm.resize(pout - putf);
749 return utfForm;
752 return std::string();
755 // Work out how many bytes are in a character by trying to convert using iconv,
756 // returning the first length that succeeds.
757 size_t MultiByteLenFromIconv(const Converter &conv, const char *s, size_t len) noexcept {
758 for (size_t lenMB=1; (lenMB<4) && (lenMB <= len); lenMB++) {
759 char wcForm[2] {};
760 char *pin = const_cast<char *>(s);
761 gsize inLeft = lenMB;
762 char *pout = wcForm;
763 gsize outLeft = 2;
764 const gsize conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
765 if (conversions != sizeFailure) {
766 return lenMB;
769 return 1;
774 void SurfaceImpl::DrawTextBase(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
775 ColourRGBA fore) {
776 if (context) {
777 PenColourAlpha(fore);
778 const XYPOSITION xText = rc.left;
779 if (PFont(font_)->fd) {
780 if (et == EncodingType::utf8) {
781 LayoutSetText(layout.get(), text);
782 } else {
783 SetConverter(PFont(font_)->characterSet);
784 std::string utfForm = UTF8FromIconv(conv, text);
785 if (utfForm.empty()) { // iconv failed so treat as Latin1
786 utfForm = UTF8FromLatin1(text);
788 LayoutSetText(layout.get(), utfForm);
790 pango_layout_set_font_description(layout.get(), PFont(font_)->fd.get());
791 pango_cairo_update_layout(context, layout.get());
792 PangoLayoutLine *pll = pango_layout_get_line_readonly(layout.get(), 0);
793 cairo_move_to(context, xText, ybase);
794 pango_cairo_show_layout_line(context, pll);
799 void SurfaceImpl::DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
800 ColourRGBA fore, ColourRGBA back) {
801 FillRectangleAligned(rc, back);
802 DrawTextBase(rc, font_, ybase, text, fore);
805 // On GTK+, exactly same as DrawTextNoClip
806 void SurfaceImpl::DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
807 ColourRGBA fore, ColourRGBA back) {
808 FillRectangleAligned(rc, back);
809 DrawTextBase(rc, font_, ybase, text, fore);
812 void SurfaceImpl::DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
813 ColourRGBA fore) {
814 // Avoid drawing spaces in transparent mode
815 for (size_t i=0; i<text.length(); i++) {
816 if (text[i] != ' ') {
817 DrawTextBase(rc, font_, ybase, text, fore);
818 return;
823 namespace {
825 class ClusterIterator {
826 UniquePangoLayoutIter iter;
827 PangoRectangle pos {};
828 int lenPositions;
829 public:
830 bool finished = false;
831 XYPOSITION positionStart = 0.0;
832 XYPOSITION position = 0.0;
833 XYPOSITION distance = 0.0;
834 int curIndex = 0;
835 ClusterIterator(PangoLayout *layout, std::string_view text) noexcept :
836 lenPositions(static_cast<int>(text.length())) {
837 LayoutSetText(layout, text);
838 iter.reset(pango_layout_get_iter(layout));
839 curIndex = pango_layout_iter_get_index(iter.get());
840 pango_layout_iter_get_cluster_extents(iter.get(), nullptr, &pos);
843 void Next() noexcept {
844 positionStart = position;
845 if (pango_layout_iter_next_cluster(iter.get())) {
846 pango_layout_iter_get_cluster_extents(iter.get(), nullptr, &pos);
847 position = pango_units_to_double(pos.x);
848 curIndex = pango_layout_iter_get_index(iter.get());
849 } else {
850 finished = true;
851 position = pango_units_to_double(pos.x + pos.width);
852 curIndex = pango_layout_iter_get_index(iter.get());
854 distance = position - positionStart;
858 // Something has gone wrong so set all the characters as equally spaced.
859 void EquallySpaced(PangoLayout *layout, XYPOSITION *positions, size_t lenPositions) {
860 int widthLayout = 0;
861 pango_layout_get_size(layout, &widthLayout, nullptr);
862 const XYPOSITION widthTotal = pango_units_to_double(widthLayout);
863 for (size_t bytePos=0; bytePos<lenPositions; bytePos++) {
864 positions[bytePos] = widthTotal / lenPositions * (bytePos + 1);
870 void SurfaceImpl::MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) {
871 if (PFont(font_)->fd) {
872 UniquePangoContext contextMeasure = MeasuringContext();
873 UniquePangoLayout layoutMeasure(pango_layout_new(contextMeasure.get()));
874 PLATFORM_ASSERT(layoutMeasure);
876 pango_layout_set_font_description(layoutMeasure.get(), PFont(font_)->fd.get());
877 if (et == EncodingType::utf8) {
878 // Simple and direct as UTF-8 is native Pango encoding
879 ClusterIterator iti(layoutMeasure.get(), text);
880 int i = iti.curIndex;
881 if (i != 0) {
882 // Unexpected start to iteration, could be bidirectional text
883 EquallySpaced(layoutMeasure.get(), positions, text.length());
884 return;
886 while (!iti.finished) {
887 iti.Next();
888 const int places = iti.curIndex - i;
889 while (i < iti.curIndex) {
890 // Evenly distribute space among bytes of this cluster.
891 // Would be better to find number of characters and then
892 // divide evenly between characters with each byte of a character
893 // being at the same position.
894 positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places;
895 i++;
898 PLATFORM_ASSERT(static_cast<size_t>(i) == text.length());
899 } else {
900 int positionsCalculated = 0;
901 const char *charSetID = CharacterSetID(PFont(font_)->characterSet);
902 std::string utfForm;
904 gsize bytesRead = 0;
905 gsize bytesWritten = 0;
906 GError *error = nullptr;
907 UniqueStr textInUTF8(g_convert(text.data(), text.length(),
908 "UTF-8", charSetID,
909 &bytesRead,
910 &bytesWritten,
911 &error));
912 if ((bytesWritten > 0) && (bytesRead == text.length()) && !error) {
913 // Extra allocation here but avoiding it makes code more complex
914 utfForm.assign(textInUTF8.get(), bytesWritten);
916 if (error) {
917 #ifdef DEBUG
918 fprintf(stderr, "MeasureWidths: %s.\n", error->message);
919 #endif
920 g_error_free(error);
923 if (et == EncodingType::dbcs) {
924 if (!utfForm.empty()) {
925 // Convert to UTF-8 so can ask Pango for widths, then
926 // Loop through UTF-8 and DBCS forms, taking account of different
927 // character byte lengths.
928 Converter convMeasure("UCS-2", charSetID, false);
929 int i = 0;
930 ClusterIterator iti(layoutMeasure.get(), utfForm);
931 int clusterStart = iti.curIndex;
932 if (clusterStart != 0) {
933 // Unexpected start to iteration, could be bidirectional text
934 EquallySpaced(layoutMeasure.get(), positions, text.length());
935 return;
937 while (!iti.finished) {
938 iti.Next();
939 const int clusterEnd = iti.curIndex;
940 const int places = g_utf8_strlen(utfForm.data() + clusterStart, clusterEnd - clusterStart);
941 int place = 1;
942 while (clusterStart < clusterEnd) {
943 size_t lenChar = MultiByteLenFromIconv(convMeasure, text.data()+i, text.length()-i);
944 while (lenChar--) {
945 positions[i++] = iti.position - (places - place) * iti.distance / places;
946 positionsCalculated++;
948 clusterStart += UTF8BytesOfLead[static_cast<unsigned char>(utfForm[clusterStart])];
949 place++;
952 PLATFORM_ASSERT(static_cast<size_t>(i) == text.length());
955 if (positionsCalculated < 1) {
956 const size_t lenPositions = text.length();
957 // Either 8-bit or DBCS conversion failed so treat as 8-bit.
958 const bool rtlCheck = PFont(font_)->characterSet == CharacterSet::Hebrew ||
959 PFont(font_)->characterSet == CharacterSet::Arabic;
960 if (utfForm.empty()) {
961 utfForm = UTF8FromLatin1(text);
962 #ifdef DEBUG
963 fprintf(stderr, "MeasureWidths: Fall back to Latin1 [%s]\n", utfForm.c_str());
964 #endif
966 size_t i = 0;
967 // Each 8-bit input character may take 1 or 2 bytes in UTF-8
968 // and groups of up to 3 may be represented as ligatures.
969 ClusterIterator iti(layoutMeasure.get(), utfForm);
970 int clusterStart = iti.curIndex;
971 if (clusterStart != 0) {
972 // Unexpected start to iteration, could be bidirectional text
973 EquallySpaced(layoutMeasure.get(), positions, lenPositions);
974 return;
976 while (!iti.finished) {
977 iti.Next();
978 const int clusterEnd = iti.curIndex;
979 const int ligatureLength = g_utf8_strlen(utfForm.data() + clusterStart, clusterEnd - clusterStart);
980 if (((i + ligatureLength) > lenPositions) ||
981 (rtlCheck && ((clusterEnd <= clusterStart) || (ligatureLength == 0) || (ligatureLength > 3)))) {
982 // Something has gone wrong: exit quickly but pretend all the characters are equally spaced:
983 #ifdef DEBUG
984 fprintf(stderr, "MeasureWidths: result too long.\n");
985 #endif
986 EquallySpaced(layoutMeasure.get(), positions, lenPositions);
987 return;
989 PLATFORM_ASSERT(ligatureLength > 0 && ligatureLength <= 3);
990 for (int charInLig=0; charInLig<ligatureLength; charInLig++) {
991 positions[i++] = iti.position - (ligatureLength - 1 - charInLig) * iti.distance / ligatureLength;
993 clusterStart = clusterEnd;
995 while (i < lenPositions) {
996 // If something failed, fill in rest of the positions
997 positions[i++] = clusterStart;
999 PLATFORM_ASSERT(i == text.length());
1002 } else {
1003 // No font so return an ascending range of values
1004 for (size_t i = 0; i < text.length(); i++) {
1005 positions[i] = i + 1.0;
1010 XYPOSITION SurfaceImpl::WidthText(const Font *font_, std::string_view text) {
1011 if (PFont(font_)->fd) {
1012 pango_layout_set_font_description(layout.get(), PFont(font_)->fd.get());
1013 if (et == EncodingType::utf8) {
1014 LayoutSetText(layout.get(), text);
1015 } else {
1016 SetConverter(PFont(font_)->characterSet);
1017 std::string utfForm = UTF8FromIconv(conv, text);
1018 if (utfForm.empty()) { // iconv failed so treat as Latin1
1019 utfForm = UTF8FromLatin1(text);
1021 LayoutSetText(layout.get(), utfForm);
1023 PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout.get(), 0);
1024 PangoRectangle pos {};
1025 pango_layout_line_get_extents(pangoLine, nullptr, &pos);
1026 return pango_units_to_double(pos.width);
1028 return 1;
1031 void SurfaceImpl::DrawTextBaseUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
1032 ColourRGBA fore) {
1033 if (context) {
1034 PenColourAlpha(fore);
1035 const XYPOSITION xText = rc.left;
1036 if (PFont(font_)->fd) {
1037 LayoutSetText(layout.get(), text);
1038 pango_layout_set_font_description(layout.get(), PFont(font_)->fd.get());
1039 pango_cairo_update_layout(context, layout.get());
1040 PangoLayoutLine *pll = pango_layout_get_line_readonly(layout.get(), 0);
1041 cairo_move_to(context, xText, ybase);
1042 pango_cairo_show_layout_line(context, pll);
1047 void SurfaceImpl::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
1048 ColourRGBA fore, ColourRGBA back) {
1049 FillRectangleAligned(rc, back);
1050 DrawTextBaseUTF8(rc, font_, ybase, text, fore);
1053 // On GTK+, exactly same as DrawTextNoClip
1054 void SurfaceImpl::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
1055 ColourRGBA fore, ColourRGBA back) {
1056 FillRectangleAligned(rc, back);
1057 DrawTextBaseUTF8(rc, font_, ybase, text, fore);
1060 void SurfaceImpl::DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
1061 ColourRGBA fore) {
1062 // Avoid drawing spaces in transparent mode
1063 for (size_t i = 0; i < text.length(); i++) {
1064 if (text[i] != ' ') {
1065 DrawTextBaseUTF8(rc, font_, ybase, text, fore);
1066 return;
1071 void SurfaceImpl::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) {
1072 if (PFont(font_)->fd) {
1073 UniquePangoContext contextMeasure = MeasuringContext();
1074 UniquePangoLayout layoutMeasure(pango_layout_new(contextMeasure.get()));
1075 PLATFORM_ASSERT(layoutMeasure);
1077 pango_layout_set_font_description(layoutMeasure.get(), PFont(font_)->fd.get());
1078 // Simple and direct as UTF-8 is native Pango encoding
1079 ClusterIterator iti(layoutMeasure.get(), text);
1080 int i = iti.curIndex;
1081 if (i != 0) {
1082 // Unexpected start to iteration, could be bidirectional text
1083 EquallySpaced(layoutMeasure.get(), positions, text.length());
1084 return;
1086 while (!iti.finished) {
1087 iti.Next();
1088 const int places = iti.curIndex - i;
1089 while (i < iti.curIndex) {
1090 // Evenly distribute space among bytes of this cluster.
1091 // Would be better to find number of characters and then
1092 // divide evenly between characters with each byte of a character
1093 // being at the same position.
1094 positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places;
1095 i++;
1098 PLATFORM_ASSERT(static_cast<size_t>(i) == text.length());
1099 } else {
1100 // No font so return an ascending range of values
1101 for (size_t i = 0; i < text.length(); i++) {
1102 positions[i] = i + 1.0;
1107 XYPOSITION SurfaceImpl::WidthTextUTF8(const Font *font_, std::string_view text) {
1108 if (PFont(font_)->fd) {
1109 pango_layout_set_font_description(layout.get(), PFont(font_)->fd.get());
1110 LayoutSetText(layout.get(), text);
1111 PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout.get(), 0);
1112 PangoRectangle pos{};
1113 pango_layout_line_get_extents(pangoLine, nullptr, &pos);
1114 return pango_units_to_double(pos.width);
1116 return 1;
1119 // Ascent and descent determined by Pango font metrics.
1121 XYPOSITION SurfaceImpl::Ascent(const Font *font_) {
1122 if (!PFont(font_)->fd) {
1123 return 1.0;
1125 UniquePangoFontMetrics metrics(pango_context_get_metrics(pcontext.get(),
1126 PFont(font_)->fd.get(), language));
1127 return std::max(1.0, std::ceil(pango_units_to_double(
1128 pango_font_metrics_get_ascent(metrics.get()))));
1131 XYPOSITION SurfaceImpl::Descent(const Font *font_) {
1132 if (!PFont(font_)->fd) {
1133 return 0.0;
1135 UniquePangoFontMetrics metrics(pango_context_get_metrics(pcontext.get(),
1136 PFont(font_)->fd.get(), language));
1137 return std::ceil(pango_units_to_double(pango_font_metrics_get_descent(metrics.get())));
1140 XYPOSITION SurfaceImpl::InternalLeading(const Font *) {
1141 return 0;
1144 XYPOSITION SurfaceImpl::Height(const Font *font_) {
1145 return Ascent(font_) + Descent(font_);
1148 XYPOSITION SurfaceImpl::AverageCharWidth(const Font *font_) {
1149 return WidthText(font_, "n");
1152 void SurfaceImpl::SetClip(PRectangle rc) {
1153 PLATFORM_ASSERT(context);
1154 cairo_save(context);
1155 CairoRectangle(rc);
1156 cairo_clip(context);
1159 void SurfaceImpl::PopClip() {
1160 PLATFORM_ASSERT(context);
1161 cairo_restore(context);
1164 void SurfaceImpl::FlushCachedState() {}
1166 void SurfaceImpl::FlushDrawing() {
1169 std::unique_ptr<Surface> Surface::Allocate(Technology) {
1170 return std::make_unique<SurfaceImpl>();
1173 Window::~Window() noexcept {}
1175 void Window::Destroy() noexcept {
1176 if (wid) {
1177 ListBox *listbox = dynamic_cast<ListBox *>(this);
1178 if (listbox) {
1179 gtk_widget_hide(GTK_WIDGET(wid));
1180 // clear up window content
1181 listbox->Clear();
1182 // resize the window to the smallest possible size for it to adapt
1183 // to future content
1184 gtk_window_resize(GTK_WINDOW(wid), 1, 1);
1185 } else {
1186 gtk_widget_destroy(GTK_WIDGET(wid));
1188 wid = nullptr;
1192 PRectangle Window::GetPosition() const {
1193 // Before any size allocated pretend its 1000 wide so not scrolled
1194 PRectangle rc(0, 0, 1000, 1000);
1195 if (wid) {
1196 GtkAllocation allocation;
1197 gtk_widget_get_allocation(PWidget(wid), &allocation);
1198 rc.left = static_cast<XYPOSITION>(allocation.x);
1199 rc.top = static_cast<XYPOSITION>(allocation.y);
1200 if (allocation.width > 20) {
1201 rc.right = rc.left + allocation.width;
1202 rc.bottom = rc.top + allocation.height;
1205 return rc;
1208 void Window::SetPosition(PRectangle rc) {
1209 GtkAllocation alloc {};
1210 alloc.x = static_cast<int>(rc.left);
1211 alloc.y = static_cast<int>(rc.top);
1212 alloc.width = static_cast<int>(rc.Width());
1213 alloc.height = static_cast<int>(rc.Height());
1214 gtk_widget_size_allocate(PWidget(wid), &alloc);
1217 namespace {
1219 GdkRectangle MonitorRectangleForWidget(GtkWidget *wid) noexcept {
1220 GdkWindow *wnd = WindowFromWidget(wid);
1221 GdkRectangle rcScreen = GdkRectangle();
1222 #if GTK_CHECK_VERSION(3,22,0)
1223 GdkDisplay *pdisplay = gtk_widget_get_display(wid);
1224 GdkMonitor *monitor = gdk_display_get_monitor_at_window(pdisplay, wnd);
1225 gdk_monitor_get_geometry(monitor, &rcScreen);
1226 #if defined(GDK_WINDOWING_WAYLAND)
1227 if (GDK_IS_WAYLAND_DISPLAY(pdisplay)) {
1228 // The GDK behavior on Wayland is not self-consistent, we must correct the display coordinates to match
1229 // the coordinate space used in gtk_window_move. See also https://sourceforge.net/p/scintilla/bugs/2296/
1230 rcScreen.x = 0;
1231 rcScreen.y = 0;
1233 #endif
1234 #else
1235 GdkScreen *screen = gtk_widget_get_screen(wid);
1236 const gint monitor_num = gdk_screen_get_monitor_at_window(screen, wnd);
1237 gdk_screen_get_monitor_geometry(screen, monitor_num, &rcScreen);
1238 #endif
1239 return rcScreen;
1244 void Window::SetPositionRelative(PRectangle rc, const Window *relativeTo) {
1245 const IntegerRectangle irc(rc);
1246 int ox = 0;
1247 int oy = 0;
1248 GdkWindow *wndRelativeTo = WindowFromWidget(PWidget(relativeTo->wid));
1249 gdk_window_get_origin(wndRelativeTo, &ox, &oy);
1250 ox += irc.left;
1251 oy += irc.top;
1253 const GdkRectangle rcMonitor = MonitorRectangleForWidget(PWidget(relativeTo->wid));
1255 /* do some corrections to fit into screen */
1256 const int sizex = irc.Width();
1257 const int sizey = irc.Height();
1258 if (sizex > rcMonitor.width || ox < rcMonitor.x)
1259 ox = rcMonitor.x; /* the best we can do */
1260 else if (ox + sizex > rcMonitor.x + rcMonitor.width)
1261 ox = rcMonitor.x + rcMonitor.width - sizex;
1262 if (sizey > rcMonitor.height || oy < rcMonitor.y)
1263 oy = rcMonitor.y;
1264 else if (oy + sizey > rcMonitor.y + rcMonitor.height)
1265 oy = rcMonitor.y + rcMonitor.height - sizey;
1267 gtk_window_move(GTK_WINDOW(PWidget(wid)), ox, oy);
1269 gtk_window_resize(GTK_WINDOW(wid), sizex, sizey);
1272 PRectangle Window::GetClientPosition() const {
1273 // On GTK+, the client position is the window position
1274 return GetPosition();
1277 void Window::Show(bool show) {
1278 if (show)
1279 gtk_widget_show(PWidget(wid));
1282 void Window::InvalidateAll() {
1283 if (wid) {
1284 gtk_widget_queue_draw(PWidget(wid));
1288 void Window::InvalidateRectangle(PRectangle rc) {
1289 if (wid) {
1290 const IntegerRectangle irc(rc);
1291 gtk_widget_queue_draw_area(PWidget(wid),
1292 irc.left, irc.top,
1293 irc.Width(), irc.Height());
1297 void Window::SetCursor(Cursor curs) {
1298 // We don't set the cursor to same value numerous times under gtk because
1299 // it stores the cursor in the window once it's set
1300 if (curs == cursorLast)
1301 return;
1303 cursorLast = curs;
1304 GdkDisplay *pdisplay = gtk_widget_get_display(PWidget(wid));
1306 GdkCursor *gdkCurs;
1307 switch (curs) {
1308 case Cursor::text:
1309 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_XTERM);
1310 break;
1311 case Cursor::arrow:
1312 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
1313 break;
1314 case Cursor::up:
1315 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_CENTER_PTR);
1316 break;
1317 case Cursor::wait:
1318 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_WATCH);
1319 break;
1320 case Cursor::hand:
1321 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_HAND2);
1322 break;
1323 case Cursor::reverseArrow:
1324 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_RIGHT_PTR);
1325 break;
1326 default:
1327 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
1328 cursorLast = Cursor::arrow;
1329 break;
1332 if (WindowFromWidget(PWidget(wid)))
1333 gdk_window_set_cursor(WindowFromWidget(PWidget(wid)), gdkCurs);
1334 UnRefCursor(gdkCurs);
1337 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1338 gdk window coordinates */
1339 PRectangle Window::GetMonitorRect(Point pt) {
1340 gint x_offset, y_offset;
1342 gdk_window_get_origin(WindowFromWidget(PWidget(wid)), &x_offset, &y_offset);
1344 GdkRectangle rect {};
1346 #if GTK_CHECK_VERSION(3,22,0)
1347 GdkDisplay *pdisplay = gtk_widget_get_display(PWidget(wid));
1348 GdkMonitor *monitor = gdk_display_get_monitor_at_point(pdisplay,
1349 pt.x + x_offset, pt.y + y_offset);
1350 gdk_monitor_get_geometry(monitor, &rect);
1351 #else
1352 GdkScreen *screen = gtk_widget_get_screen(PWidget(wid));
1353 const gint monitor_num = gdk_screen_get_monitor_at_point(screen,
1354 static_cast<gint>(pt.x) + x_offset, static_cast<gint>(pt.y) + y_offset);
1355 gdk_screen_get_monitor_geometry(screen, monitor_num, &rect);
1356 #endif
1357 rect.x -= x_offset;
1358 rect.y -= y_offset;
1359 return PRectangle::FromInts(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
1362 typedef std::map<int, RGBAImage *> ImageMap;
1364 struct ListImage {
1365 const RGBAImage *rgba_data;
1366 GdkPixbuf *pixbuf;
1369 static void list_image_free(gpointer, gpointer value, gpointer) noexcept {
1370 ListImage *list_image = static_cast<ListImage *>(value);
1371 if (list_image->pixbuf)
1372 g_object_unref(list_image->pixbuf);
1373 g_free(list_image);
1376 ListBox::ListBox() noexcept {
1379 ListBox::~ListBox() noexcept {
1382 enum {
1383 PIXBUF_COLUMN,
1384 TEXT_COLUMN,
1385 N_COLUMNS
1388 class ListBoxX : public ListBox {
1389 WindowID widCached;
1390 WindowID frame;
1391 WindowID list;
1392 WindowID scroller;
1393 GHashTable *pixhash;
1394 GtkCellRenderer *pixbuf_renderer;
1395 GtkCellRenderer *renderer;
1396 RGBAImageSet images;
1397 int desiredVisibleRows;
1398 unsigned int maxItemCharacters;
1399 unsigned int aveCharWidth;
1400 #if GTK_CHECK_VERSION(3,0,0)
1401 std::unique_ptr<GtkCssProvider, GObjectReleaser> cssProvider;
1402 #endif
1403 public:
1404 IListBoxDelegate *delegate;
1406 ListBoxX() noexcept : widCached(nullptr), frame(nullptr), list(nullptr), scroller(nullptr),
1407 pixhash(nullptr), pixbuf_renderer(nullptr),
1408 renderer(nullptr),
1409 desiredVisibleRows(5), maxItemCharacters(0),
1410 aveCharWidth(1),
1411 delegate(nullptr) {
1413 // Deleted so ListBoxX objects can not be copied.
1414 ListBoxX(const ListBoxX&) = delete;
1415 ListBoxX(ListBoxX&&) = delete;
1416 ListBoxX&operator=(const ListBoxX&) = delete;
1417 ListBoxX&operator=(ListBoxX&&) = delete;
1418 ~ListBoxX() noexcept override {
1419 if (pixhash) {
1420 g_hash_table_foreach(pixhash, list_image_free, nullptr);
1421 g_hash_table_destroy(pixhash);
1423 if (widCached) {
1424 gtk_widget_destroy(GTK_WIDGET(widCached));
1425 wid = widCached = nullptr;
1428 void SetFont(const Font *font) override;
1429 void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, Technology technology_) override;
1430 void SetAverageCharWidth(int width) override;
1431 void SetVisibleRows(int rows) override;
1432 int GetVisibleRows() const override;
1433 int GetRowHeight();
1434 PRectangle GetDesiredRect() override;
1435 int CaretFromEdge() override;
1436 void Clear() noexcept override;
1437 void Append(char *s, int type = -1) override;
1438 int Length() override;
1439 void Select(int n) override;
1440 int GetSelection() override;
1441 int Find(const char *prefix) override;
1442 std::string GetValue(int n) override;
1443 void RegisterRGBA(int type, std::unique_ptr<RGBAImage> image);
1444 void RegisterImage(int type, const char *xpm_data) override;
1445 void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) override;
1446 void ClearRegisteredImages() override;
1447 void SetDelegate(IListBoxDelegate *lbDelegate) override;
1448 void SetList(const char *listText, char separator, char typesep) override;
1449 void SetOptions(ListOptions options_) override;
1452 std::unique_ptr<ListBox> ListBox::Allocate() {
1453 return std::make_unique<ListBoxX>();
1456 static int treeViewGetRowHeight(GtkTreeView *view) {
1457 #if GTK_CHECK_VERSION(3,0,0)
1458 // This version sometimes reports erroneous results on GTK2, but the GTK2
1459 // version is inaccurate for GTK 3.14.
1460 GdkRectangle rect;
1461 GtkTreePath *path = gtk_tree_path_new_first();
1462 gtk_tree_view_get_background_area(view, path, nullptr, &rect);
1463 gtk_tree_path_free(path);
1464 return rect.height;
1465 #else
1466 int row_height=0;
1467 int vertical_separator=0;
1468 int expander_size=0;
1469 GtkTreeViewColumn *column = gtk_tree_view_get_column(view, 0);
1470 gtk_tree_view_column_cell_get_size(column, nullptr, nullptr, nullptr, nullptr, &row_height);
1471 gtk_widget_style_get(GTK_WIDGET(view),
1472 "vertical-separator", &vertical_separator,
1473 "expander-size", &expander_size, nullptr);
1474 row_height += vertical_separator;
1475 row_height = std::max(row_height, expander_size);
1476 return row_height;
1477 #endif
1480 // SmallScroller, a GtkScrolledWindow that can shrink very small, as
1481 // gtk_widget_set_size_request() cannot shrink widgets on GTK3
1482 typedef struct {
1483 GtkScrolledWindow parent;
1484 /* Workaround ABI issue with Windows GTK2 bundle and GCC > 3.
1485 See http://lists.geany.org/pipermail/devel/2015-April/thread.html#9379
1487 GtkScrolledWindow contains a bitfield, and GCC 3.4 and 4.8 don't agree
1488 on the size of the structure (regardless of -mms-bitfields):
1489 - GCC 3.4 has sizeof(GtkScrolledWindow)=88
1490 - GCC 4.8 has sizeof(GtkScrolledWindow)=84
1491 As Windows GTK2 bundle is built with GCC 3, it requires types derived
1492 from GtkScrolledWindow to be at least 88 bytes, which means we need to
1493 add some fake padding to fill in the extra 4 bytes.
1494 There is however no other issue with the layout difference as we never
1495 access any GtkScrolledWindow fields ourselves. */
1496 int padding;
1497 } SmallScroller;
1498 typedef GtkScrolledWindowClass SmallScrollerClass;
1500 G_DEFINE_TYPE(SmallScroller, small_scroller, GTK_TYPE_SCROLLED_WINDOW)
1502 #if GTK_CHECK_VERSION(3,0,0)
1503 static void small_scroller_get_preferred_height(GtkWidget *widget, gint *min, gint *nat) {
1504 GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
1505 if (GTK_IS_TREE_VIEW(child)) {
1506 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(child));
1507 int n_rows = gtk_tree_model_iter_n_children(model, nullptr);
1508 int row_height = treeViewGetRowHeight(GTK_TREE_VIEW(child));
1510 *min = MAX(1, row_height);
1511 *nat = MAX(*min, n_rows * row_height);
1512 } else {
1513 GTK_WIDGET_CLASS(small_scroller_parent_class)->get_preferred_height(widget, min, nat);
1514 if (*min > 1)
1515 *min = 1;
1518 #else
1519 static void small_scroller_size_request(GtkWidget *widget, GtkRequisition *req) {
1520 GTK_WIDGET_CLASS(small_scroller_parent_class)->size_request(widget, req);
1521 req->height = 1;
1523 #endif
1525 static void small_scroller_class_init(SmallScrollerClass *klass) {
1526 #if GTK_CHECK_VERSION(3,0,0)
1527 GTK_WIDGET_CLASS(klass)->get_preferred_height = small_scroller_get_preferred_height;
1528 #else
1529 GTK_WIDGET_CLASS(klass)->size_request = small_scroller_size_request;
1530 #endif
1533 static void small_scroller_init(SmallScroller *) {}
1535 static gboolean ButtonPress(GtkWidget *, const GdkEventButton *ev, gpointer p) {
1536 try {
1537 ListBoxX *lb = static_cast<ListBoxX *>(p);
1538 if (ev->type == GDK_2BUTTON_PRESS && lb->delegate) {
1539 ListBoxEvent event(ListBoxEvent::EventType::doubleClick);
1540 lb->delegate->ListNotify(&event);
1541 return TRUE;
1544 } catch (...) {
1545 // No pointer back to Scintilla to save status
1547 return FALSE;
1550 static gboolean ButtonRelease(GtkWidget *, const GdkEventButton *ev, gpointer p) {
1551 try {
1552 ListBoxX *lb = static_cast<ListBoxX *>(p);
1553 if (ev->type != GDK_2BUTTON_PRESS && lb->delegate) {
1554 ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
1555 lb->delegate->ListNotify(&event);
1556 return TRUE;
1558 } catch (...) {
1559 // No pointer back to Scintilla to save status
1561 return FALSE;
1564 /* Change the active colour to the selected colour so the listbox uses the colour
1565 scheme that it would use if it had the focus. */
1566 static void StyleSet(GtkWidget *w, GtkStyle *, void *) {
1568 g_return_if_fail(w != nullptr);
1570 /* Copy the selected colour to active. Note that the modify calls will cause
1571 recursive calls to this function after the value is updated and w->style to
1572 be set to a new object */
1574 #if GTK_CHECK_VERSION(3,16,0)
1575 // On recent releases of GTK+, it does not appear necessary to set the list box colours.
1576 // This may be because of common themes and may be needed with other themes.
1577 // The *override* calls are deprecated now, so only call them for older versions of GTK+.
1578 #elif GTK_CHECK_VERSION(3,0,0)
1579 GtkStyleContext *styleContext = gtk_widget_get_style_context(w);
1580 if (styleContext == nullptr)
1581 return;
1583 GdkRGBA colourForeSelected;
1584 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourForeSelected);
1585 GdkRGBA colourForeActive;
1586 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourForeActive);
1587 if (!gdk_rgba_equal(&colourForeSelected, &colourForeActive))
1588 gtk_widget_override_color(w, GTK_STATE_FLAG_ACTIVE, &colourForeSelected);
1590 styleContext = gtk_widget_get_style_context(w);
1591 if (styleContext == nullptr)
1592 return;
1594 GdkRGBA colourBaseSelected;
1595 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourBaseSelected);
1596 GdkRGBA colourBaseActive;
1597 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourBaseActive);
1598 if (!gdk_rgba_equal(&colourBaseSelected, &colourBaseActive))
1599 gtk_widget_override_background_color(w, GTK_STATE_FLAG_ACTIVE, &colourBaseSelected);
1600 #else
1601 GtkStyle *style = gtk_widget_get_style(w);
1602 if (style == nullptr)
1603 return;
1604 if (!gdk_color_equal(&style->base[GTK_STATE_SELECTED], &style->base[GTK_STATE_ACTIVE]))
1605 gtk_widget_modify_base(w, GTK_STATE_ACTIVE, &style->base[GTK_STATE_SELECTED]);
1606 style = gtk_widget_get_style(w);
1607 if (style == nullptr)
1608 return;
1609 if (!gdk_color_equal(&style->text[GTK_STATE_SELECTED], &style->text[GTK_STATE_ACTIVE]))
1610 gtk_widget_modify_text(w, GTK_STATE_ACTIVE, &style->text[GTK_STATE_SELECTED]);
1611 #endif
1614 void ListBoxX::Create(Window &parent, int, Point, int, bool, Technology) {
1615 if (widCached != nullptr) {
1616 wid = widCached;
1617 return;
1620 #if GTK_CHECK_VERSION(3,0,0)
1621 if (!cssProvider) {
1622 cssProvider.reset(gtk_css_provider_new());
1624 #endif
1626 wid = widCached = gtk_window_new(GTK_WINDOW_POPUP);
1627 gtk_window_set_type_hint(GTK_WINDOW(wid), GDK_WINDOW_TYPE_HINT_POPUP_MENU);
1629 frame = gtk_frame_new(nullptr);
1630 gtk_widget_show(PWidget(frame));
1631 gtk_container_add(GTK_CONTAINER(GetID()), PWidget(frame));
1632 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1633 gtk_container_set_border_width(GTK_CONTAINER(frame), 0);
1635 scroller = g_object_new(small_scroller_get_type(), nullptr);
1636 gtk_container_set_border_width(GTK_CONTAINER(scroller), 0);
1637 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
1638 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1639 gtk_container_add(GTK_CONTAINER(frame), PWidget(scroller));
1640 gtk_widget_show(PWidget(scroller));
1642 /* Tree and its model */
1643 GtkListStore *store =
1644 gtk_list_store_new(N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING);
1646 list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1647 g_signal_connect(G_OBJECT(list), "style-set", G_CALLBACK(StyleSet), nullptr);
1649 #if GTK_CHECK_VERSION(3,0,0)
1650 GtkStyleContext *styleContext = gtk_widget_get_style_context(GTK_WIDGET(list));
1651 if (styleContext) {
1652 gtk_style_context_add_provider(styleContext, GTK_STYLE_PROVIDER(cssProvider.get()),
1653 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1655 #endif
1657 GtkTreeSelection *selection =
1658 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1659 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1660 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
1661 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list), FALSE);
1663 /* Columns */
1664 GtkTreeViewColumn *column = gtk_tree_view_column_new();
1665 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1666 gtk_tree_view_column_set_title(column, "Autocomplete");
1668 pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
1669 gtk_cell_renderer_set_fixed_size(pixbuf_renderer, 0, -1);
1670 gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE);
1671 gtk_tree_view_column_add_attribute(column, pixbuf_renderer,
1672 "pixbuf", PIXBUF_COLUMN);
1674 renderer = gtk_cell_renderer_text_new();
1675 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1676 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1677 gtk_tree_view_column_add_attribute(column, renderer,
1678 "text", TEXT_COLUMN);
1680 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
1681 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list), "fixed-height-mode"))
1682 g_object_set(G_OBJECT(list), "fixed-height-mode", TRUE, nullptr);
1684 GtkWidget *widget = PWidget(list); // No code inside the G_OBJECT macro
1685 gtk_container_add(GTK_CONTAINER(PWidget(scroller)), widget);
1686 gtk_widget_show(widget);
1687 g_signal_connect(G_OBJECT(widget), "button_press_event",
1688 G_CALLBACK(ButtonPress), this);
1689 g_signal_connect(G_OBJECT(widget), "button_release_event",
1690 G_CALLBACK(ButtonRelease), this);
1692 GtkWidget *top = gtk_widget_get_toplevel(static_cast<GtkWidget *>(parent.GetID()));
1693 gtk_window_set_transient_for(GTK_WINDOW(static_cast<GtkWidget *>(wid)),
1694 GTK_WINDOW(top));
1697 void ListBoxX::SetFont(const Font *font) {
1698 // Only do for Pango font as there have been crashes for GDK fonts
1699 if (Created() && PFont(font)->fd) {
1700 // Current font is Pango font
1701 #if GTK_CHECK_VERSION(3,0,0)
1702 if (cssProvider) {
1703 PangoFontDescription *pfd = PFont(font)->fd.get();
1704 std::ostringstream ssFontSetting;
1705 ssFontSetting << "GtkTreeView, treeview { ";
1706 ssFontSetting << "font-family: " << pango_font_description_get_family(pfd) << "; ";
1707 ssFontSetting << "font-size:";
1708 ssFontSetting << static_cast<double>(pango_font_description_get_size(pfd)) / PANGO_SCALE;
1709 // On GTK < 3.21.0 the units are incorrectly parsed, so a font size in points
1710 // need to use the "px" unit. Normally we only get fonts in points here, so
1711 // don't bother to handle the case the font is actually in pixels on < 3.21.0.
1712 if (gtk_check_version(3, 21, 0) != nullptr || // on < 3.21.0
1713 pango_font_description_get_size_is_absolute(pfd)) {
1714 ssFontSetting << "px; ";
1715 } else {
1716 ssFontSetting << "pt; ";
1718 ssFontSetting << "font-weight:"<< pango_font_description_get_weight(pfd) << "; ";
1719 ssFontSetting << "}";
1720 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(cssProvider.get()),
1721 ssFontSetting.str().c_str(), -1, nullptr);
1723 #else
1724 gtk_widget_modify_font(PWidget(list), PFont(font)->fd.get());
1725 #endif
1726 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), -1);
1727 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1731 void ListBoxX::SetAverageCharWidth(int width) {
1732 aveCharWidth = width;
1735 void ListBoxX::SetVisibleRows(int rows) {
1736 desiredVisibleRows = rows;
1739 int ListBoxX::GetVisibleRows() const {
1740 return desiredVisibleRows;
1743 int ListBoxX::GetRowHeight() {
1744 return treeViewGetRowHeight(GTK_TREE_VIEW(list));
1747 PRectangle ListBoxX::GetDesiredRect() {
1748 // Before any size allocated pretend its 100 wide so not scrolled
1749 PRectangle rc(0, 0, 100, 100);
1750 if (wid) {
1751 int rows = Length();
1752 if ((rows == 0) || (rows > desiredVisibleRows))
1753 rows = desiredVisibleRows;
1755 GtkRequisition req;
1756 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1757 // returns reasonable values.
1758 #if GTK_CHECK_VERSION(3,0,0)
1759 gtk_widget_get_preferred_size(GTK_WIDGET(frame), nullptr, &req);
1760 #else
1761 gtk_widget_size_request(GTK_WIDGET(frame), &req);
1762 #endif
1763 int height;
1765 // First calculate height of the clist for our desired visible
1766 // row count otherwise it tries to expand to the total # of rows
1767 // Get cell height
1768 const int row_height = GetRowHeight();
1769 #if GTK_CHECK_VERSION(3,0,0)
1770 GtkStyleContext *styleContextFrame = gtk_widget_get_style_context(PWidget(frame));
1771 GtkStateFlags stateFlagsFrame = gtk_style_context_get_state(styleContextFrame);
1772 GtkBorder padding, border, border_border = { 0, 0, 0, 0 };
1773 gtk_style_context_get_padding(styleContextFrame, stateFlagsFrame, &padding);
1774 gtk_style_context_get_border(styleContextFrame, stateFlagsFrame, &border);
1776 # if GTK_CHECK_VERSION(3,20,0)
1777 // on GTK 3.20 the frame border is in a sub-node "border".
1778 // Unfortunately we need to be built against 3.20 to be able to support this, as it requires
1779 // new API.
1780 GtkStyleContext *styleContextFrameBorder = gtk_style_context_new();
1781 GtkWidgetPath *widget_path = gtk_widget_path_copy(gtk_style_context_get_path(styleContextFrame));
1782 gtk_widget_path_append_type(widget_path, GTK_TYPE_BORDER); // dummy type
1783 gtk_widget_path_iter_set_object_name(widget_path, -1, "border");
1784 gtk_style_context_set_path(styleContextFrameBorder, widget_path);
1785 gtk_widget_path_free(widget_path);
1786 gtk_style_context_get_border(styleContextFrameBorder, stateFlagsFrame, &border_border);
1787 g_object_unref(styleContextFrameBorder);
1788 # else // < 3.20
1789 if (gtk_check_version(3, 20, 0) == nullptr) {
1790 // default to 1px all around as it's likely what it is, and so we don't miss 2px height
1791 // on GTK 3.20 when built against an earlier version.
1792 border_border.top = border_border.bottom = border_border.left = border_border.right = 1;
1794 # endif
1796 height = (rows * row_height
1797 + padding.top + padding.bottom
1798 + border.top + border.bottom
1799 + border_border.top + border_border.bottom
1800 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
1801 #else
1802 height = (rows * row_height
1803 + 2 * (PWidget(frame)->style->ythickness
1804 + GTK_CONTAINER(PWidget(list))->border_width));
1805 #endif
1806 rc.bottom = height;
1808 const unsigned int width = std::max(maxItemCharacters, 12U);
1809 rc.right = width * (aveCharWidth + aveCharWidth / 3);
1810 // Add horizontal padding and borders
1811 int horizontal_separator=0;
1812 gtk_widget_style_get(PWidget(list),
1813 "horizontal-separator", &horizontal_separator, nullptr);
1814 rc.right += horizontal_separator;
1815 #if GTK_CHECK_VERSION(3,0,0)
1816 rc.right += (padding.left + padding.right
1817 + border.left + border.right
1818 + border_border.left + border_border.right
1819 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
1820 #else
1821 rc.right += 2 * (PWidget(frame)->style->xthickness
1822 + GTK_CONTAINER(PWidget(list))->border_width);
1823 #endif
1824 if (Length() > rows) {
1825 // Add the width of the scrollbar
1826 GtkWidget *vscrollbar =
1827 gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(scroller));
1828 #if GTK_CHECK_VERSION(3,0,0)
1829 gtk_widget_get_preferred_size(vscrollbar, nullptr, &req);
1830 #else
1831 gtk_widget_size_request(vscrollbar, &req);
1832 #endif
1833 rc.right += req.width;
1836 return rc;
1839 int ListBoxX::CaretFromEdge() {
1840 gint renderer_width, renderer_height;
1841 gtk_cell_renderer_get_fixed_size(pixbuf_renderer, &renderer_width,
1842 &renderer_height);
1843 return 4 + renderer_width;
1846 void ListBoxX::Clear() noexcept {
1847 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1848 gtk_list_store_clear(GTK_LIST_STORE(model));
1849 maxItemCharacters = 0;
1852 static void init_pixmap(ListImage *list_image) noexcept {
1853 if (list_image->rgba_data) {
1854 // Drop any existing pixmap/bitmap as data may have changed
1855 if (list_image->pixbuf)
1856 g_object_unref(list_image->pixbuf);
1857 list_image->pixbuf =
1858 gdk_pixbuf_new_from_data(list_image->rgba_data->Pixels(),
1859 GDK_COLORSPACE_RGB,
1860 TRUE,
1862 list_image->rgba_data->GetWidth(),
1863 list_image->rgba_data->GetHeight(),
1864 list_image->rgba_data->GetWidth() * 4,
1865 nullptr,
1866 nullptr);
1870 #define SPACING 5
1872 void ListBoxX::Append(char *s, int type) {
1873 ListImage *list_image = nullptr;
1874 if ((type >= 0) && pixhash) {
1875 list_image = static_cast<ListImage *>(g_hash_table_lookup(pixhash,
1876 GINT_TO_POINTER(type)));
1878 GtkTreeIter iter {};
1879 GtkListStore *store =
1880 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
1881 gtk_list_store_append(GTK_LIST_STORE(store), &iter);
1882 if (list_image) {
1883 if (nullptr == list_image->pixbuf)
1884 init_pixmap(list_image);
1885 if (list_image->pixbuf) {
1886 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1887 PIXBUF_COLUMN, list_image->pixbuf,
1888 TEXT_COLUMN, s, -1);
1890 const gint pixbuf_width = gdk_pixbuf_get_width(list_image->pixbuf);
1891 gint renderer_height, renderer_width;
1892 gtk_cell_renderer_get_fixed_size(pixbuf_renderer,
1893 &renderer_width, &renderer_height);
1894 if (pixbuf_width > renderer_width)
1895 gtk_cell_renderer_set_fixed_size(pixbuf_renderer,
1896 pixbuf_width, -1);
1897 } else {
1898 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1899 TEXT_COLUMN, s, -1);
1901 } else {
1902 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1903 TEXT_COLUMN, s, -1);
1905 const unsigned int len = static_cast<unsigned int>(strlen(s));
1906 if (maxItemCharacters < len)
1907 maxItemCharacters = len;
1910 int ListBoxX::Length() {
1911 if (wid)
1912 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1913 (GTK_TREE_VIEW(list)), nullptr);
1914 return 0;
1917 void ListBoxX::Select(int n) {
1918 GtkTreeIter iter {};
1919 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1920 GtkTreeSelection *selection =
1921 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1923 if (n < 0) {
1924 gtk_tree_selection_unselect_all(selection);
1925 return;
1928 const bool valid = gtk_tree_model_iter_nth_child(model, &iter, nullptr, n) != FALSE;
1929 if (valid) {
1930 gtk_tree_selection_select_iter(selection, &iter);
1932 // Move the scrollbar to show the selection.
1933 const int total = Length();
1934 #if GTK_CHECK_VERSION(3,0,0)
1935 GtkAdjustment *adj =
1936 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list));
1937 #else
1938 GtkAdjustment *adj =
1939 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list));
1940 #endif
1941 gdouble value = (static_cast<gdouble>(n) / total) * (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_lower(adj))
1942 + gtk_adjustment_get_lower(adj) - gtk_adjustment_get_page_size(adj) / 2;
1943 // Get cell height
1944 const int row_height = GetRowHeight();
1946 int rows = Length();
1947 if ((rows == 0) || (rows > desiredVisibleRows))
1948 rows = desiredVisibleRows;
1949 if (rows & 0x1) {
1950 // Odd rows to display -- We are now in the middle.
1951 // Align it so that we don't chop off rows.
1952 value += static_cast<gfloat>(row_height) / 2.0f;
1954 // Clamp it.
1955 value = (value < 0)? 0 : value;
1956 value = (value > (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)))?
1957 (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)) : value;
1959 // Set it.
1960 gtk_adjustment_set_value(adj, value);
1961 } else {
1962 gtk_tree_selection_unselect_all(selection);
1965 if (delegate) {
1966 ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
1967 delegate->ListNotify(&event);
1971 int ListBoxX::GetSelection() {
1972 int index = -1;
1973 GtkTreeIter iter {};
1974 GtkTreeModel *model {};
1975 GtkTreeSelection *selection;
1976 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1977 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1978 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1979 const int *indices = gtk_tree_path_get_indices(path);
1980 // Don't free indices.
1981 if (indices)
1982 index = indices[0];
1983 gtk_tree_path_free(path);
1985 return index;
1988 int ListBoxX::Find(const char *prefix) {
1989 GtkTreeIter iter {};
1990 GtkTreeModel *model =
1991 gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1992 bool valid = gtk_tree_model_get_iter_first(model, &iter) != FALSE;
1993 int i = 0;
1994 while (valid) {
1995 gchar *s = nullptr;
1996 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &s, -1);
1997 if (s && (0 == strncmp(prefix, s, strlen(prefix)))) {
1998 g_free(s);
1999 return i;
2001 g_free(s);
2002 valid = gtk_tree_model_iter_next(model, &iter) != FALSE;
2003 i++;
2005 return -1;
2008 std::string ListBoxX::GetValue(int n) {
2009 char *text = nullptr;
2010 GtkTreeIter iter {};
2011 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
2012 const bool valid = gtk_tree_model_iter_nth_child(model, &iter, nullptr, n) != FALSE;
2013 if (valid) {
2014 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &text, -1);
2016 std::string value;
2017 if (text) {
2018 value = text;
2020 g_free(text);
2021 return value;
2024 // g_return_if_fail causes unnecessary compiler warning in release compile.
2025 #ifdef _MSC_VER
2026 #pragma warning(disable: 4127)
2027 #endif
2029 void ListBoxX::RegisterRGBA(int type, std::unique_ptr<RGBAImage> image) {
2030 images.AddImage(type, std::move(image));
2031 const RGBAImage * const observe = images.Get(type);
2033 if (!pixhash) {
2034 pixhash = g_hash_table_new(g_direct_hash, g_direct_equal);
2036 ListImage *list_image = static_cast<ListImage *>(g_hash_table_lookup(pixhash,
2037 GINT_TO_POINTER(type)));
2038 if (list_image) {
2039 // Drop icon already registered
2040 if (list_image->pixbuf)
2041 g_object_unref(list_image->pixbuf);
2042 list_image->pixbuf = nullptr;
2043 list_image->rgba_data = observe;
2044 } else {
2045 list_image = g_new0(ListImage, 1);
2046 list_image->rgba_data = observe;
2047 g_hash_table_insert(pixhash, GINT_TO_POINTER(type),
2048 (gpointer) list_image);
2052 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
2053 g_return_if_fail(xpm_data);
2054 XPM xpmImage(xpm_data);
2055 RegisterRGBA(type, std::make_unique<RGBAImage>(xpmImage));
2058 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
2059 RegisterRGBA(type, std::make_unique<RGBAImage>(width, height, 1.0f, pixelsImage));
2062 void ListBoxX::ClearRegisteredImages() {
2063 images.Clear();
2066 void ListBoxX::SetDelegate(IListBoxDelegate *lbDelegate) {
2067 delegate = lbDelegate;
2070 void ListBoxX::SetList(const char *listText, char separator, char typesep) {
2071 Clear();
2072 const size_t count = strlen(listText) + 1;
2073 std::vector<char> words(listText, listText+count);
2074 char *startword = &words[0];
2075 char *numword = nullptr;
2076 int i = 0;
2077 for (; words[i]; i++) {
2078 if (words[i] == separator) {
2079 words[i] = '\0';
2080 if (numword)
2081 *numword = '\0';
2082 Append(startword, numword?atoi(numword + 1):-1);
2083 startword = &words[0] + i + 1;
2084 numword = nullptr;
2085 } else if (words[i] == typesep) {
2086 numword = &words[0] + i;
2089 if (startword) {
2090 if (numword)
2091 *numword = '\0';
2092 Append(startword, numword?atoi(numword + 1):-1);
2096 void ListBoxX::SetOptions(ListOptions) {
2099 Menu::Menu() noexcept : mid(nullptr) {}
2101 void Menu::CreatePopUp() {
2102 Destroy();
2103 mid = gtk_menu_new();
2104 g_object_ref_sink(G_OBJECT(mid));
2107 void Menu::Destroy() noexcept {
2108 if (mid)
2109 g_object_unref(G_OBJECT(mid));
2110 mid = nullptr;
2113 #if !GTK_CHECK_VERSION(3,22,0)
2114 static void MenuPositionFunc(GtkMenu *, gint *x, gint *y, gboolean *, gpointer userData) noexcept {
2115 const gint intFromPointer = GPOINTER_TO_INT(userData);
2116 *x = intFromPointer & 0xffff;
2117 *y = intFromPointer >> 16;
2119 #endif
2121 void Menu::Show(Point pt, const Window &w) {
2122 GtkMenu *widget = static_cast<GtkMenu *>(mid);
2123 gtk_widget_show_all(GTK_WIDGET(widget));
2124 #if GTK_CHECK_VERSION(3,22,0)
2125 // Rely on GTK+ to do the right thing with positioning
2126 gtk_menu_popup_at_pointer(widget, nullptr);
2127 #else
2128 const GdkRectangle rcMonitor = MonitorRectangleForWidget(PWidget(w.GetID()));
2129 GtkRequisition requisition;
2130 #if GTK_CHECK_VERSION(3,0,0)
2131 gtk_widget_get_preferred_size(GTK_WIDGET(widget), nullptr, &requisition);
2132 #else
2133 gtk_widget_size_request(GTK_WIDGET(widget), &requisition);
2134 #endif
2135 if ((pt.x + requisition.width) > rcMonitor.x + rcMonitor.width) {
2136 pt.x = rcMonitor.x + rcMonitor.width - requisition.width;
2138 if ((pt.y + requisition.height) > rcMonitor.y + rcMonitor.height) {
2139 pt.y = rcMonitor.y + rcMonitor.height - requisition.height;
2141 gtk_menu_popup(widget, nullptr, nullptr, MenuPositionFunc,
2142 GINT_TO_POINTER((static_cast<int>(pt.y) << 16) | static_cast<int>(pt.x)), 0,
2143 gtk_get_current_event_time());
2144 #endif
2147 ColourRGBA Platform::Chrome() {
2148 return ColourRGBA(0xe0, 0xe0, 0xe0);
2151 ColourRGBA Platform::ChromeHighlight() {
2152 return ColourRGBA(0xff, 0xff, 0xff);
2155 const char *Platform::DefaultFont() {
2156 #ifdef G_OS_WIN32
2157 return "Lucida Console";
2158 #else
2159 return "!Sans";
2160 #endif
2163 int Platform::DefaultFontSize() {
2164 #ifdef G_OS_WIN32
2165 return 10;
2166 #else
2167 return 12;
2168 #endif
2171 unsigned int Platform::DoubleClickTime() {
2172 return 500; // Half a second
2175 void Platform::DebugDisplay(const char *s) noexcept {
2176 fprintf(stderr, "%s", s);
2179 //#define TRACE
2181 #ifdef TRACE
2182 void Platform::DebugPrintf(const char *format, ...) noexcept {
2183 char buffer[2000];
2184 va_list pArguments;
2185 va_start(pArguments, format);
2186 vsnprintf(buffer, std::size(buffer), format, pArguments);
2187 va_end(pArguments);
2188 Platform::DebugDisplay(buffer);
2190 #else
2191 void Platform::DebugPrintf(const char *, ...) noexcept {}
2193 #endif
2195 // Not supported for GTK+
2196 static bool assertionPopUps = true;
2198 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) noexcept {
2199 const bool ret = assertionPopUps;
2200 assertionPopUps = assertionPopUps_;
2201 return ret;
2204 void Platform::Assert(const char *c, const char *file, int line) noexcept {
2205 char buffer[2000];
2206 g_snprintf(buffer, sizeof(buffer), "Assertion [%s] failed at %s %d\r\n", c, file, line);
2207 Platform::DebugDisplay(buffer);
2208 abort();
2211 void Platform_Initialise() {
2214 void Platform_Finalise() {