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.
5
6 #include <cstddef>
7 #include <cstdlib>
8 #include <cstring>
9 #include <cstdio>
10 #include <cmath>
11
12 #include <string>
13 #include <string_view>
14 #include <vector>
15 #include <map>
16 #include <algorithm>
17 #include <memory>
18 #include <sstream>
19
20 #include <glib.h>
21 #include <gmodule.h>
22 #include <gdk/gdk.h>
23 #include <gtk/gtk.h>
24 #include <gdk/gdkkeysyms.h>
25
26 #include "Platform.h"
27
28 #include "Scintilla.h"
29 #include "ScintillaWidget.h"
30 #include "IntegerRectangle.h"
31 #include "XPM.h"
32 #include "UniConversion.h"
33
34 #include "Converter.h"
35
36 #ifdef _MSC_VER
37 // Ignore unreferenced local functions in GTK+ headers
38 #pragma warning(disable: 4505)
39 #endif
40
41 using namespace Scintilla;
42
43 namespace {
44
45 constexpr double kPi = 3.14159265358979323846;
46
47 // The Pango version guard for pango_units_from_double and pango_units_to_double
48 // is more complex than simply implementing these here.
49
pangoUnitsFromDouble(double d)50 constexpr int pangoUnitsFromDouble(double d) noexcept {
51 return static_cast<int>(d * PANGO_SCALE + 0.5);
52 }
53
floatFromPangoUnits(int pu)54 constexpr float floatFromPangoUnits(int pu) noexcept {
55 return static_cast<float>(pu) / PANGO_SCALE;
56 }
57
CreateSimilarSurface(GdkWindow * window,cairo_content_t content,int width,int height)58 cairo_surface_t *CreateSimilarSurface(GdkWindow *window, cairo_content_t content, int width, int height) noexcept {
59 return gdk_window_create_similar_surface(window, content, width, height);
60 }
61
WindowFromWidget(GtkWidget * w)62 GdkWindow *WindowFromWidget(GtkWidget *w) noexcept {
63 return gtk_widget_get_window(w);
64 }
65
PWidget(WindowID wid)66 GtkWidget *PWidget(WindowID wid) noexcept {
67 return static_cast<GtkWidget *>(wid);
68 }
69
70 enum encodingType { singleByte, UTF8, dbcs };
71
72 // Holds a PangoFontDescription*.
73 class FontHandle {
74 public:
75 PangoFontDescription *pfd;
76 int characterSet;
FontHandle()77 FontHandle() noexcept : pfd(nullptr), characterSet(-1) {
78 }
FontHandle(PangoFontDescription * pfd_,int characterSet_)79 FontHandle(PangoFontDescription *pfd_, int characterSet_) noexcept {
80 pfd = pfd_;
81 characterSet = characterSet_;
82 }
83 // Deleted so FontHandle objects can not be copied.
84 FontHandle(const FontHandle &) = delete;
85 FontHandle(FontHandle &&) = delete;
86 FontHandle &operator=(const FontHandle &) = delete;
87 FontHandle &operator=(FontHandle &&) = delete;
~FontHandle()88 ~FontHandle() {
89 if (pfd)
90 pango_font_description_free(pfd);
91 pfd = nullptr;
92 }
93 static FontHandle *CreateNewFont(const FontParameters &fp);
94 };
95
CreateNewFont(const FontParameters & fp)96 FontHandle *FontHandle::CreateNewFont(const FontParameters &fp) {
97 PangoFontDescription *pfd = pango_font_description_new();
98 if (pfd) {
99 pango_font_description_set_family(pfd,
100 (fp.faceName[0] == '!') ? fp.faceName+1 : fp.faceName);
101 pango_font_description_set_size(pfd, pangoUnitsFromDouble(fp.size));
102 pango_font_description_set_weight(pfd, static_cast<PangoWeight>(fp.weight));
103 pango_font_description_set_style(pfd, fp.italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
104 return new FontHandle(pfd, fp.characterSet);
105 }
106
107 return nullptr;
108 }
109
110 // X has a 16 bit coordinate space, so stop drawing here to avoid wrapping
111 constexpr int maxCoordinate = 32000;
112
PFont(const Font & f)113 FontHandle *PFont(const Font &f) noexcept {
114 return static_cast<FontHandle *>(f.GetID());
115 }
116
117 }
118
Font()119 Font::Font() noexcept : fid(nullptr) {}
120
~Font()121 Font::~Font() {}
122
Create(const FontParameters & fp)123 void Font::Create(const FontParameters &fp) {
124 Release();
125 fid = FontHandle::CreateNewFont(fp);
126 }
127
Release()128 void Font::Release() {
129 if (fid)
130 delete static_cast<FontHandle *>(fid);
131 fid = nullptr;
132 }
133
134 // Required on OS X
135 namespace Scintilla {
136
137 // SurfaceID is a cairo_t*
138 class SurfaceImpl : public Surface {
139 encodingType et;
140 cairo_t *context;
141 cairo_surface_t *psurf;
142 int x;
143 int y;
144 bool inited;
145 bool createdGC;
146 PangoContext *pcontext;
147 PangoLayout *layout;
148 Converter conv;
149 int characterSet;
150 void SetConverter(int characterSet_);
151 public:
152 SurfaceImpl() noexcept;
153 // Deleted so SurfaceImpl objects can not be copied.
154 SurfaceImpl(const SurfaceImpl&) = delete;
155 SurfaceImpl(SurfaceImpl&&) = delete;
156 SurfaceImpl&operator=(const SurfaceImpl&) = delete;
157 SurfaceImpl&operator=(SurfaceImpl&&) = delete;
158 ~SurfaceImpl() override;
159
160 void Init(WindowID wid) override;
161 void Init(SurfaceID sid, WindowID wid) override;
162 void InitPixMap(int width, int height, Surface *surface_, WindowID wid) override;
163
164 void Clear() noexcept;
165 void Release() override;
166 bool Initialised() override;
167 void PenColour(ColourDesired fore) override;
168 int LogPixelsY() override;
169 int DeviceHeightFont(int points) override;
170 void MoveTo(int x_, int y_) override;
171 void LineTo(int x_, int y_) override;
172 void Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) override;
173 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) override;
174 void FillRectangle(PRectangle rc, ColourDesired back) override;
175 void FillRectangle(PRectangle rc, Surface &surfacePattern) override;
176 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) override;
177 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
178 ColourDesired outline, int alphaOutline, int flags) override;
179 void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override;
180 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override;
181 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override;
182 void Copy(PRectangle rc, Point from, Surface &surfaceSource) override;
183
184 std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override;
185
186 void DrawTextBase(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore);
187 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override;
188 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override;
189 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override;
190 void MeasureWidths(Font &font_, std::string_view text, XYPOSITION *positions) override;
191 XYPOSITION WidthText(Font &font_, std::string_view text) override;
192 XYPOSITION Ascent(Font &font_) override;
193 XYPOSITION Descent(Font &font_) override;
194 XYPOSITION InternalLeading(Font &font_) override;
195 XYPOSITION Height(Font &font_) override;
196 XYPOSITION AverageCharWidth(Font &font_) override;
197
198 void SetClip(PRectangle rc) override;
199 void FlushCachedState() override;
200
201 void SetUnicodeMode(bool unicodeMode_) override;
202 void SetDBCSMode(int codePage) override;
203 void SetBidiR2L(bool bidiR2L_) override;
204 };
205 }
206
CharacterSetID(int characterSet)207 const char *CharacterSetID(int characterSet) noexcept {
208 switch (characterSet) {
209 case SC_CHARSET_ANSI:
210 return "";
211 case SC_CHARSET_DEFAULT:
212 return "ISO-8859-1";
213 case SC_CHARSET_BALTIC:
214 return "ISO-8859-13";
215 case SC_CHARSET_CHINESEBIG5:
216 return "BIG-5";
217 case SC_CHARSET_EASTEUROPE:
218 return "ISO-8859-2";
219 case SC_CHARSET_GB2312:
220 return "CP936";
221 case SC_CHARSET_GREEK:
222 return "ISO-8859-7";
223 case SC_CHARSET_HANGUL:
224 return "CP949";
225 case SC_CHARSET_MAC:
226 return "MACINTOSH";
227 case SC_CHARSET_OEM:
228 return "ASCII";
229 case SC_CHARSET_RUSSIAN:
230 return "KOI8-R";
231 case SC_CHARSET_OEM866:
232 return "CP866";
233 case SC_CHARSET_CYRILLIC:
234 return "CP1251";
235 case SC_CHARSET_SHIFTJIS:
236 return "SHIFT-JIS";
237 case SC_CHARSET_SYMBOL:
238 return "";
239 case SC_CHARSET_TURKISH:
240 return "ISO-8859-9";
241 case SC_CHARSET_JOHAB:
242 return "CP1361";
243 case SC_CHARSET_HEBREW:
244 return "ISO-8859-8";
245 case SC_CHARSET_ARABIC:
246 return "ISO-8859-6";
247 case SC_CHARSET_VIETNAMESE:
248 return "";
249 case SC_CHARSET_THAI:
250 return "ISO-8859-11";
251 case SC_CHARSET_8859_15:
252 return "ISO-8859-15";
253 default:
254 return "";
255 }
256 }
257
SetConverter(int characterSet_)258 void SurfaceImpl::SetConverter(int characterSet_) {
259 if (characterSet != characterSet_) {
260 characterSet = characterSet_;
261 conv.Open("UTF-8", CharacterSetID(characterSet), false);
262 }
263 }
264
SurfaceImpl()265 SurfaceImpl::SurfaceImpl() noexcept : et(singleByte),
266 context(nullptr),
267 psurf(nullptr),
268 x(0), y(0), inited(false), createdGC(false),
269 pcontext(nullptr), layout(nullptr), characterSet(-1) {
270 }
271
~SurfaceImpl()272 SurfaceImpl::~SurfaceImpl() {
273 Clear();
274 }
275
Clear()276 void SurfaceImpl::Clear() noexcept {
277 et = singleByte;
278 if (createdGC) {
279 createdGC = false;
280 cairo_destroy(context);
281 }
282 context = nullptr;
283 if (psurf)
284 cairo_surface_destroy(psurf);
285 psurf = nullptr;
286 if (layout)
287 g_object_unref(layout);
288 layout = nullptr;
289 if (pcontext)
290 g_object_unref(pcontext);
291 pcontext = nullptr;
292 conv.Close();
293 characterSet = -1;
294 x = 0;
295 y = 0;
296 inited = false;
297 createdGC = false;
298 }
299
Release()300 void SurfaceImpl::Release() {
301 Clear();
302 }
303
Initialised()304 bool SurfaceImpl::Initialised() {
305 if (inited && context) {
306 if (cairo_status(context) == CAIRO_STATUS_SUCCESS) {
307 // Even when status is success, the target surface may have been
308 // finished which may cause an assertion to fail crashing the application.
309 // The cairo_surface_has_show_text_glyphs call checks the finished flag
310 // and when set, sets the status to CAIRO_STATUS_SURFACE_FINISHED
311 // which leads to warning messages instead of crashes.
312 // Performing the check in this method as it is called rarely and has no
313 // other side effects.
314 cairo_surface_t *psurfContext = cairo_get_target(context);
315 if (psurfContext) {
316 cairo_surface_has_show_text_glyphs(psurfContext);
317 }
318 }
319 return cairo_status(context) == CAIRO_STATUS_SUCCESS;
320 }
321 return inited;
322 }
323
Init(WindowID wid)324 void SurfaceImpl::Init(WindowID wid) {
325 Release();
326 PLATFORM_ASSERT(wid);
327 // if we are only created from a window ID, we can't perform drawing
328 psurf = nullptr;
329 context = nullptr;
330 createdGC = false;
331 pcontext = gtk_widget_create_pango_context(PWidget(wid));
332 PLATFORM_ASSERT(pcontext);
333 layout = pango_layout_new(pcontext);
334 PLATFORM_ASSERT(layout);
335 inited = true;
336 }
337
Init(SurfaceID sid,WindowID wid)338 void SurfaceImpl::Init(SurfaceID sid, WindowID wid) {
339 PLATFORM_ASSERT(sid);
340 Release();
341 PLATFORM_ASSERT(wid);
342 context = cairo_reference(static_cast<cairo_t *>(sid));
343 pcontext = gtk_widget_create_pango_context(PWidget(wid));
344 // update the Pango context in case sid isn't the widget's surface
345 pango_cairo_update_context(context, pcontext);
346 layout = pango_layout_new(pcontext);
347 cairo_set_line_width(context, 1);
348 createdGC = true;
349 inited = true;
350 }
351
InitPixMap(int width,int height,Surface * surface_,WindowID wid)352 void SurfaceImpl::InitPixMap(int width, int height, Surface *surface_, WindowID wid) {
353 PLATFORM_ASSERT(surface_);
354 Release();
355 SurfaceImpl *surfImpl = dynamic_cast<SurfaceImpl *>(surface_);
356 PLATFORM_ASSERT(surfImpl);
357 PLATFORM_ASSERT(wid);
358 context = cairo_reference(surfImpl->context);
359 pcontext = gtk_widget_create_pango_context(PWidget(wid));
360 // update the Pango context in case surface_ isn't the widget's surface
361 pango_cairo_update_context(context, pcontext);
362 PLATFORM_ASSERT(pcontext);
363 layout = pango_layout_new(pcontext);
364 PLATFORM_ASSERT(layout);
365 if (height > 0 && width > 0)
366 psurf = CreateSimilarSurface(
367 WindowFromWidget(PWidget(wid)),
368 CAIRO_CONTENT_COLOR_ALPHA, width, height);
369 cairo_destroy(context);
370 context = cairo_create(psurf);
371 cairo_rectangle(context, 0, 0, width, height);
372 cairo_set_source_rgb(context, 1.0, 0, 0);
373 cairo_fill(context);
374 // This produces sharp drawing more similar to GDK:
375 //cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
376 cairo_set_line_width(context, 1);
377 createdGC = true;
378 inited = true;
379 et = surfImpl->et;
380 }
381
PenColour(ColourDesired fore)382 void SurfaceImpl::PenColour(ColourDesired fore) {
383 if (context) {
384 const ColourDesired cdFore(fore.AsInteger());
385 cairo_set_source_rgb(context,
386 cdFore.GetRed() / 255.0,
387 cdFore.GetGreen() / 255.0,
388 cdFore.GetBlue() / 255.0);
389 }
390 }
391
LogPixelsY()392 int SurfaceImpl::LogPixelsY() {
393 return 72;
394 }
395
DeviceHeightFont(int points)396 int SurfaceImpl::DeviceHeightFont(int points) {
397 const int logPix = LogPixelsY();
398 return (points * logPix + logPix / 2) / 72;
399 }
400
MoveTo(int x_,int y_)401 void SurfaceImpl::MoveTo(int x_, int y_) {
402 x = x_;
403 y = y_;
404 }
405
Delta(int difference)406 static int Delta(int difference) noexcept {
407 if (difference < 0)
408 return -1;
409 else if (difference > 0)
410 return 1;
411 else
412 return 0;
413 }
414
LineTo(int x_,int y_)415 void SurfaceImpl::LineTo(int x_, int y_) {
416 // cairo_line_to draws the end position, unlike Win32 or GDK with GDK_CAP_NOT_LAST.
417 // For simple cases, move back one pixel from end.
418 if (context) {
419 const int xDiff = x_ - x;
420 const int xDelta = Delta(xDiff);
421 const int yDiff = y_ - y;
422 const int yDelta = Delta(yDiff);
423 if ((xDiff == 0) || (yDiff == 0)) {
424 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
425 const int xEnd = x_ - xDelta;
426 const int left = std::min(x, xEnd);
427 const int width = std::abs(x - xEnd) + 1;
428 const int yEnd = y_ - yDelta;
429 const int top = std::min(y, yEnd);
430 const int height = std::abs(y - yEnd) + 1;
431 cairo_rectangle(context, left, top, width, height);
432 cairo_fill(context);
433 } else if ((std::abs(xDiff) == std::abs(yDiff))) {
434 // 45 degree slope
435 cairo_move_to(context, x + 0.5, y + 0.5);
436 cairo_line_to(context, x_ + 0.5 - xDelta, y_ + 0.5 - yDelta);
437 } else {
438 // Line has a different slope so difficult to avoid last pixel
439 cairo_move_to(context, x + 0.5, y + 0.5);
440 cairo_line_to(context, x_ + 0.5, y_ + 0.5);
441 }
442 cairo_stroke(context);
443 }
444 x = x_;
445 y = y_;
446 }
447
Polygon(Point * pts,size_t npts,ColourDesired fore,ColourDesired back)448 void SurfaceImpl::Polygon(Point *pts, size_t npts, ColourDesired fore,
449 ColourDesired back) {
450 PLATFORM_ASSERT(context);
451 PenColour(back);
452 cairo_move_to(context, pts[0].x + 0.5, pts[0].y + 0.5);
453 for (size_t i = 1; i < npts; i++) {
454 cairo_line_to(context, pts[i].x + 0.5, pts[i].y + 0.5);
455 }
456 cairo_close_path(context);
457 cairo_fill_preserve(context);
458 PenColour(fore);
459 cairo_stroke(context);
460 }
461
RectangleDraw(PRectangle rc,ColourDesired fore,ColourDesired back)462 void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
463 if (context) {
464 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5,
465 rc.Width() - 1, rc.Height() - 1);
466 PenColour(back);
467 cairo_fill_preserve(context);
468 PenColour(fore);
469 cairo_stroke(context);
470 }
471 }
472
FillRectangle(PRectangle rc,ColourDesired back)473 void SurfaceImpl::FillRectangle(PRectangle rc, ColourDesired back) {
474 PenColour(back);
475 if (context && (rc.left < maxCoordinate)) { // Protect against out of range
476 rc.left = std::round(rc.left);
477 rc.right = std::round(rc.right);
478 cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
479 cairo_fill(context);
480 }
481 }
482
FillRectangle(PRectangle rc,Surface & surfacePattern)483 void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
484 SurfaceImpl &surfi = dynamic_cast<SurfaceImpl &>(surfacePattern);
485 if (context && surfi.psurf) {
486 // Tile pattern over rectangle
487 cairo_set_source_surface(context, surfi.psurf, rc.left, rc.top);
488 cairo_pattern_set_extend(cairo_get_source(context), CAIRO_EXTEND_REPEAT);
489 cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
490 cairo_fill(context);
491 }
492 }
493
RoundedRectangle(PRectangle rc,ColourDesired fore,ColourDesired back)494 void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
495 if (((rc.right - rc.left) > 4) && ((rc.bottom - rc.top) > 4)) {
496 // Approximate a round rect with some cut off corners
497 Point pts[] = {
498 Point(rc.left + 2, rc.top),
499 Point(rc.right - 2, rc.top),
500 Point(rc.right, rc.top + 2),
501 Point(rc.right, rc.bottom - 2),
502 Point(rc.right - 2, rc.bottom),
503 Point(rc.left + 2, rc.bottom),
504 Point(rc.left, rc.bottom - 2),
505 Point(rc.left, rc.top + 2),
506 };
507 Polygon(pts, std::size(pts), fore, back);
508 } else {
509 RectangleDraw(rc, fore, back);
510 }
511 }
512
PathRoundRectangle(cairo_t * context,double left,double top,double width,double height,double radius)513 static void PathRoundRectangle(cairo_t *context, double left, double top, double width, double height, double radius) noexcept {
514 constexpr double degrees = kPi / 180.0;
515
516 cairo_new_sub_path(context);
517 cairo_arc(context, left + width - radius, top + radius, radius, -90 * degrees, 0 * degrees);
518 cairo_arc(context, left + width - radius, top + height - radius, radius, 0 * degrees, 90 * degrees);
519 cairo_arc(context, left + radius, top + height - radius, radius, 90 * degrees, 180 * degrees);
520 cairo_arc(context, left + radius, top + radius, radius, 180 * degrees, 270 * degrees);
521 cairo_close_path(context);
522 }
523
AlphaRectangle(PRectangle rc,int cornerSize,ColourDesired fill,int alphaFill,ColourDesired outline,int alphaOutline,int)524 void SurfaceImpl::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
525 ColourDesired outline, int alphaOutline, int /*flags*/) {
526 if (context && rc.Width() > 0) {
527 const ColourDesired cdFill(fill.AsInteger());
528 cairo_set_source_rgba(context,
529 cdFill.GetRed() / 255.0,
530 cdFill.GetGreen() / 255.0,
531 cdFill.GetBlue() / 255.0,
532 alphaFill / 255.0);
533 if (cornerSize > 0)
534 PathRoundRectangle(context, rc.left + 1.0, rc.top + 1.0, rc.Width() - 2.0, rc.Height() - 2.0, cornerSize);
535 else
536 cairo_rectangle(context, rc.left + 1.0, rc.top + 1.0, rc.Width() - 2.0, rc.Height() - 2.0);
537 cairo_fill(context);
538
539 const ColourDesired cdOutline(outline.AsInteger());
540 cairo_set_source_rgba(context,
541 cdOutline.GetRed() / 255.0,
542 cdOutline.GetGreen() / 255.0,
543 cdOutline.GetBlue() / 255.0,
544 alphaOutline / 255.0);
545 if (cornerSize > 0)
546 PathRoundRectangle(context, rc.left + 0.5, rc.top + 0.5, rc.Width() - 1, rc.Height() - 1, cornerSize);
547 else
548 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5, rc.Width() - 1, rc.Height() - 1);
549 cairo_stroke(context);
550 }
551 }
552
GradientRectangle(PRectangle rc,const std::vector<ColourStop> & stops,GradientOptions options)553 void SurfaceImpl::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) {
554 if (context) {
555 cairo_pattern_t *pattern;
556 switch (options) {
557 case GradientOptions::leftToRight:
558 pattern = cairo_pattern_create_linear(rc.left, rc.top, rc.right, rc.top);
559 break;
560 case GradientOptions::topToBottom:
561 default:
562 pattern = cairo_pattern_create_linear(rc.left, rc.top, rc.left, rc.bottom);
563 break;
564 }
565 for (const ColourStop &stop : stops) {
566 cairo_pattern_add_color_stop_rgba(pattern, stop.position,
567 stop.colour.GetRedComponent(),
568 stop.colour.GetGreenComponent(),
569 stop.colour.GetBlueComponent(),
570 stop.colour.GetAlphaComponent());
571 }
572 cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
573 cairo_set_source(context, pattern);
574 cairo_fill(context);
575 cairo_pattern_destroy(pattern);
576 }
577 }
578
DrawRGBAImage(PRectangle rc,int width,int height,const unsigned char * pixelsImage)579 void SurfaceImpl::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
580 PLATFORM_ASSERT(context);
581 if (rc.Width() > width)
582 rc.left += (rc.Width() - width) / 2;
583 rc.right = rc.left + width;
584 if (rc.Height() > height)
585 rc.top += (rc.Height() - height) / 2;
586 rc.bottom = rc.top + height;
587
588 const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
589 const int ucs = stride * height;
590 std::vector<unsigned char> image(ucs);
591 for (ptrdiff_t iy=0; iy<height; iy++) {
592 unsigned char *pixel = &image[0] + iy*stride;
593 RGBAImage::BGRAFromRGBA(pixel, pixelsImage, width);
594 pixelsImage += RGBAImage::bytesPerPixel * width;
595 }
596
597 cairo_surface_t *psurfImage = cairo_image_surface_create_for_data(&image[0], CAIRO_FORMAT_ARGB32, width, height, stride);
598 cairo_set_source_surface(context, psurfImage, rc.left, rc.top);
599 cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
600 cairo_fill(context);
601
602 cairo_surface_destroy(psurfImage);
603 }
604
Ellipse(PRectangle rc,ColourDesired fore,ColourDesired back)605 void SurfaceImpl::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
606 PLATFORM_ASSERT(context);
607 PenColour(back);
608 cairo_arc(context, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2,
609 std::min(rc.Width(), rc.Height()) / 2, 0, 2*kPi);
610 cairo_fill_preserve(context);
611 PenColour(fore);
612 cairo_stroke(context);
613 }
614
Copy(PRectangle rc,Point from,Surface & surfaceSource)615 void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
616 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfaceSource);
617 const bool canDraw = surfi.psurf != nullptr;
618 if (canDraw) {
619 PLATFORM_ASSERT(context);
620 cairo_set_source_surface(context, surfi.psurf,
621 rc.left - from.x, rc.top - from.y);
622 cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
623 cairo_fill(context);
624 }
625 }
626
Layout(const IScreenLine *)627 std::unique_ptr<IScreenLineLayout> SurfaceImpl::Layout(const IScreenLine *) {
628 return {};
629 }
630
UTF8FromLatin1(std::string_view text)631 std::string UTF8FromLatin1(std::string_view text) {
632 std::string utfForm(text.length()*2 + 1, '\0');
633 size_t lenU = 0;
634 for (const char ch : text) {
635 const unsigned char uch = ch;
636 if (uch < 0x80) {
637 utfForm[lenU++] = uch;
638 } else {
639 utfForm[lenU++] = static_cast<char>(0xC0 | (uch >> 6));
640 utfForm[lenU++] = static_cast<char>(0x80 | (uch & 0x3f));
641 }
642 }
643 utfForm.resize(lenU);
644 return utfForm;
645 }
646
647 namespace {
648
UTF8FromIconv(const Converter & conv,std::string_view text)649 std::string UTF8FromIconv(const Converter &conv, std::string_view text) {
650 if (conv) {
651 std::string utfForm(text.length()*3+1, '\0');
652 char *pin = const_cast<char *>(text.data());
653 gsize inLeft = text.length();
654 char *putf = &utfForm[0];
655 char *pout = putf;
656 gsize outLeft = text.length()*3+1;
657 const gsize conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
658 if (conversions != sizeFailure) {
659 *pout = '\0';
660 utfForm.resize(pout - putf);
661 return utfForm;
662 }
663 }
664 return std::string();
665 }
666
667 // Work out how many bytes are in a character by trying to convert using iconv,
668 // returning the first length that succeeds.
MultiByteLenFromIconv(const Converter & conv,const char * s,size_t len)669 size_t MultiByteLenFromIconv(const Converter &conv, const char *s, size_t len) noexcept {
670 for (size_t lenMB=1; (lenMB<4) && (lenMB <= len); lenMB++) {
671 char wcForm[2] {};
672 char *pin = const_cast<char *>(s);
673 gsize inLeft = lenMB;
674 char *pout = wcForm;
675 gsize outLeft = 2;
676 const gsize conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
677 if (conversions != sizeFailure) {
678 return lenMB;
679 }
680 }
681 return 1;
682 }
683
684 }
685
DrawTextBase(PRectangle rc,const Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore)686 void SurfaceImpl::DrawTextBase(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text,
687 ColourDesired fore) {
688 PenColour(fore);
689 if (context) {
690 const XYPOSITION xText = rc.left;
691 if (PFont(font_)->pfd) {
692 std::string utfForm;
693 if (et == UTF8) {
694 pango_layout_set_text(layout, text.data(), text.length());
695 } else {
696 SetConverter(PFont(font_)->characterSet);
697 utfForm = UTF8FromIconv(conv, text);
698 if (utfForm.empty()) { // iconv failed so treat as Latin1
699 utfForm = UTF8FromLatin1(text);
700 }
701 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
702 }
703 pango_layout_set_font_description(layout, PFont(font_)->pfd);
704 pango_cairo_update_layout(context, layout);
705 PangoLayoutLine *pll = pango_layout_get_line_readonly(layout, 0);
706 cairo_move_to(context, xText, ybase);
707 pango_cairo_show_layout_line(context, pll);
708 }
709 }
710 }
711
DrawTextNoClip(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore,ColourDesired back)712 void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text,
713 ColourDesired fore, ColourDesired back) {
714 FillRectangle(rc, back);
715 DrawTextBase(rc, font_, ybase, text, fore);
716 }
717
718 // On GTK+, exactly same as DrawTextNoClip
DrawTextClipped(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore,ColourDesired back)719 void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text,
720 ColourDesired fore, ColourDesired back) {
721 FillRectangle(rc, back);
722 DrawTextBase(rc, font_, ybase, text, fore);
723 }
724
DrawTextTransparent(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore)725 void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text,
726 ColourDesired fore) {
727 // Avoid drawing spaces in transparent mode
728 for (size_t i=0; i<text.length(); i++) {
729 if (text[i] != ' ') {
730 DrawTextBase(rc, font_, ybase, text, fore);
731 return;
732 }
733 }
734 }
735
736 class ClusterIterator {
737 PangoLayoutIter *iter;
738 PangoRectangle pos;
739 size_t lenPositions;
740 public:
741 bool finished;
742 XYPOSITION positionStart;
743 XYPOSITION position;
744 XYPOSITION distance;
745 int curIndex;
ClusterIterator(PangoLayout * layout,size_t len)746 ClusterIterator(PangoLayout *layout, size_t len) noexcept : lenPositions(len), finished(false),
747 positionStart(0), position(0), distance(0), curIndex(0) {
748 iter = pango_layout_get_iter(layout);
749 pango_layout_iter_get_cluster_extents(iter, nullptr, &pos);
750 }
751 // Deleted so ClusterIterator objects can not be copied.
752 ClusterIterator(const ClusterIterator&) = delete;
753 ClusterIterator(ClusterIterator&&) = delete;
754 ClusterIterator&operator=(const ClusterIterator&) = delete;
755 ClusterIterator&operator=(ClusterIterator&&) = delete;
756
~ClusterIterator()757 ~ClusterIterator() {
758 pango_layout_iter_free(iter);
759 }
760
Next()761 void Next() noexcept {
762 positionStart = position;
763 if (pango_layout_iter_next_cluster(iter)) {
764 pango_layout_iter_get_cluster_extents(iter, nullptr, &pos);
765 position = floatFromPangoUnits(pos.x);
766 curIndex = pango_layout_iter_get_index(iter);
767 } else {
768 finished = true;
769 position = floatFromPangoUnits(pos.x + pos.width);
770 curIndex = lenPositions;
771 }
772 distance = position - positionStart;
773 }
774 };
775
MeasureWidths(Font & font_,std::string_view text,XYPOSITION * positions)776 void SurfaceImpl::MeasureWidths(Font &font_, std::string_view text, XYPOSITION *positions) {
777 if (font_.GetID()) {
778 if (PFont(font_)->pfd) {
779 pango_layout_set_font_description(layout, PFont(font_)->pfd);
780 if (et == UTF8) {
781 // Simple and direct as UTF-8 is native Pango encoding
782 int i = 0;
783 pango_layout_set_text(layout, text.data(), text.length());
784 ClusterIterator iti(layout, text.length());
785 while (!iti.finished) {
786 iti.Next();
787 const int places = iti.curIndex - i;
788 while (i < iti.curIndex) {
789 // Evenly distribute space among bytes of this cluster.
790 // Would be better to find number of characters and then
791 // divide evenly between characters with each byte of a character
792 // being at the same position.
793 positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places;
794 i++;
795 }
796 }
797 PLATFORM_ASSERT(static_cast<size_t>(i) == text.length());
798 } else {
799 int positionsCalculated = 0;
800 if (et == dbcs) {
801 SetConverter(PFont(font_)->characterSet);
802 std::string utfForm = UTF8FromIconv(conv, text);
803 if (!utfForm.empty()) {
804 // Convert to UTF-8 so can ask Pango for widths, then
805 // Loop through UTF-8 and DBCS forms, taking account of different
806 // character byte lengths.
807 Converter convMeasure("UCS-2", CharacterSetID(characterSet), false);
808 pango_layout_set_text(layout, utfForm.c_str(), strlen(utfForm.c_str()));
809 int i = 0;
810 int clusterStart = 0;
811 ClusterIterator iti(layout, strlen(utfForm.c_str()));
812 while (!iti.finished) {
813 iti.Next();
814 const int clusterEnd = iti.curIndex;
815 const int places = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
816 int place = 1;
817 while (clusterStart < clusterEnd) {
818 size_t lenChar = MultiByteLenFromIconv(convMeasure, text.data()+i, text.length()-i);
819 while (lenChar--) {
820 positions[i++] = iti.position - (places - place) * iti.distance / places;
821 positionsCalculated++;
822 }
823 clusterStart += UTF8BytesOfLead[static_cast<unsigned char>(utfForm[clusterStart])];
824 place++;
825 }
826 }
827 PLATFORM_ASSERT(static_cast<size_t>(i) == text.length());
828 }
829 }
830 if (positionsCalculated < 1) {
831 const size_t lenPositions = text.length();
832 // Either 8-bit or DBCS conversion failed so treat as 8-bit.
833 SetConverter(PFont(font_)->characterSet);
834 const bool rtlCheck = PFont(font_)->characterSet == SC_CHARSET_HEBREW ||
835 PFont(font_)->characterSet == SC_CHARSET_ARABIC;
836 std::string utfForm = UTF8FromIconv(conv, text);
837 if (utfForm.empty()) {
838 utfForm = UTF8FromLatin1(text);
839 }
840 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
841 size_t i = 0;
842 int clusterStart = 0;
843 // Each 8-bit input character may take 1 or 2 bytes in UTF-8
844 // and groups of up to 3 may be represented as ligatures.
845 ClusterIterator iti(layout, utfForm.length());
846 while (!iti.finished) {
847 iti.Next();
848 const int clusterEnd = iti.curIndex;
849 const int ligatureLength = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
850 if (rtlCheck && ((clusterEnd <= clusterStart) || (ligatureLength == 0) || (ligatureLength > 3))) {
851 // Something has gone wrong: exit quickly but pretend all the characters are equally spaced:
852 int widthLayout = 0;
853 pango_layout_get_size(layout, &widthLayout, nullptr);
854 const XYPOSITION widthTotal = floatFromPangoUnits(widthLayout);
855 for (size_t bytePos=0; bytePos<lenPositions; bytePos++) {
856 positions[bytePos] = widthTotal / lenPositions * (bytePos + 1);
857 }
858 return;
859 }
860 PLATFORM_ASSERT(ligatureLength > 0 && ligatureLength <= 3);
861 for (int charInLig=0; charInLig<ligatureLength; charInLig++) {
862 positions[i++] = iti.position - (ligatureLength - 1 - charInLig) * iti.distance / ligatureLength;
863 }
864 clusterStart = clusterEnd;
865 }
866 while (i < lenPositions) {
867 // If something failed, fill in rest of the positions
868 positions[i++] = clusterStart;
869 }
870 PLATFORM_ASSERT(i == text.length());
871 }
872 }
873 }
874 } else {
875 // No font so return an ascending range of values
876 for (size_t i = 0; i < text.length(); i++) {
877 positions[i] = i + 1;
878 }
879 }
880 }
881
WidthText(Font & font_,std::string_view text)882 XYPOSITION SurfaceImpl::WidthText(Font &font_, std::string_view text) {
883 if (font_.GetID()) {
884 if (PFont(font_)->pfd) {
885 std::string utfForm;
886 pango_layout_set_font_description(layout, PFont(font_)->pfd);
887 if (et == UTF8) {
888 pango_layout_set_text(layout, text.data(), text.length());
889 } else {
890 SetConverter(PFont(font_)->characterSet);
891 utfForm = UTF8FromIconv(conv, text);
892 if (utfForm.empty()) { // iconv failed so treat as Latin1
893 utfForm = UTF8FromLatin1(text);
894 }
895 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
896 }
897 PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout, 0);
898 PangoRectangle pos {};
899 pango_layout_line_get_extents(pangoLine, nullptr, &pos);
900 return floatFromPangoUnits(pos.width);
901 }
902 return 1;
903 } else {
904 return 1;
905 }
906 }
907
908 // Ascent and descent determined by Pango font metrics.
909
Ascent(Font & font_)910 XYPOSITION SurfaceImpl::Ascent(Font &font_) {
911 if (!(font_.GetID()))
912 return 1;
913 XYPOSITION ascent = 0;
914 if (PFont(font_)->pfd) {
915 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
916 PFont(font_)->pfd, pango_context_get_language(pcontext));
917 ascent = std::round(floatFromPangoUnits(
918 pango_font_metrics_get_ascent(metrics)));
919 pango_font_metrics_unref(metrics);
920 }
921 if (ascent == 0) {
922 ascent = 1;
923 }
924 return ascent;
925 }
926
Descent(Font & font_)927 XYPOSITION SurfaceImpl::Descent(Font &font_) {
928 if (!(font_.GetID()))
929 return 1;
930 if (PFont(font_)->pfd) {
931 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
932 PFont(font_)->pfd, pango_context_get_language(pcontext));
933 const XYPOSITION descent = std::round(floatFromPangoUnits(
934 pango_font_metrics_get_descent(metrics)));
935 pango_font_metrics_unref(metrics);
936 return descent;
937 }
938 return 0;
939 }
940
InternalLeading(Font &)941 XYPOSITION SurfaceImpl::InternalLeading(Font &) {
942 return 0;
943 }
944
Height(Font & font_)945 XYPOSITION SurfaceImpl::Height(Font &font_) {
946 return Ascent(font_) + Descent(font_);
947 }
948
AverageCharWidth(Font & font_)949 XYPOSITION SurfaceImpl::AverageCharWidth(Font &font_) {
950 return WidthText(font_, "n");
951 }
952
SetClip(PRectangle rc)953 void SurfaceImpl::SetClip(PRectangle rc) {
954 PLATFORM_ASSERT(context);
955 cairo_rectangle(context, rc.left, rc.top, rc.Width(), rc.Height());
956 cairo_clip(context);
957 }
958
FlushCachedState()959 void SurfaceImpl::FlushCachedState() {}
960
SetUnicodeMode(bool unicodeMode_)961 void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
962 if (unicodeMode_)
963 et = UTF8;
964 }
965
SetDBCSMode(int codePage)966 void SurfaceImpl::SetDBCSMode(int codePage) {
967 if (codePage && (codePage != SC_CP_UTF8))
968 et = dbcs;
969 }
970
SetBidiR2L(bool)971 void SurfaceImpl::SetBidiR2L(bool) {
972 }
973
Allocate(int)974 Surface *Surface::Allocate(int) {
975 return new SurfaceImpl();
976 }
977
~Window()978 Window::~Window() {}
979
Destroy()980 void Window::Destroy() {
981 if (wid) {
982 ListBox *listbox = dynamic_cast<ListBox *>(this);
983 if (listbox) {
984 gtk_widget_hide(GTK_WIDGET(wid));
985 // clear up window content
986 listbox->Clear();
987 // resize the window to the smallest possible size for it to adapt
988 // to future content
989 gtk_window_resize(GTK_WINDOW(wid), 1, 1);
990 } else {
991 gtk_widget_destroy(GTK_WIDGET(wid));
992 }
993 wid = nullptr;
994 }
995 }
996
GetPosition() const997 PRectangle Window::GetPosition() const {
998 // Before any size allocated pretend its 1000 wide so not scrolled
999 PRectangle rc(0, 0, 1000, 1000);
1000 if (wid) {
1001 GtkAllocation allocation;
1002 gtk_widget_get_allocation(PWidget(wid), &allocation);
1003 rc.left = static_cast<XYPOSITION>(allocation.x);
1004 rc.top = static_cast<XYPOSITION>(allocation.y);
1005 if (allocation.width > 20) {
1006 rc.right = rc.left + allocation.width;
1007 rc.bottom = rc.top + allocation.height;
1008 }
1009 }
1010 return rc;
1011 }
1012
SetPosition(PRectangle rc)1013 void Window::SetPosition(PRectangle rc) {
1014 GtkAllocation alloc;
1015 alloc.x = static_cast<int>(rc.left);
1016 alloc.y = static_cast<int>(rc.top);
1017 alloc.width = static_cast<int>(rc.Width());
1018 alloc.height = static_cast<int>(rc.Height());
1019 gtk_widget_size_allocate(PWidget(wid), &alloc);
1020 }
1021
1022 namespace {
1023
MonitorRectangleForWidget(GtkWidget * wid)1024 GdkRectangle MonitorRectangleForWidget(GtkWidget *wid) noexcept {
1025 GdkWindow *wnd = WindowFromWidget(wid);
1026 GdkRectangle rcScreen = GdkRectangle();
1027 #if GTK_CHECK_VERSION(3,22,0)
1028 GdkDisplay *pdisplay = gtk_widget_get_display(wid);
1029 GdkMonitor *monitor = gdk_display_get_monitor_at_window(pdisplay, wnd);
1030 gdk_monitor_get_geometry(monitor, &rcScreen);
1031 #else
1032 GdkScreen *screen = gtk_widget_get_screen(wid);
1033 const gint monitor_num = gdk_screen_get_monitor_at_window(screen, wnd);
1034 gdk_screen_get_monitor_geometry(screen, monitor_num, &rcScreen);
1035 #endif
1036 return rcScreen;
1037 }
1038
1039 }
1040
SetPositionRelative(PRectangle rc,const Window * relativeTo)1041 void Window::SetPositionRelative(PRectangle rc, const Window *relativeTo) {
1042 const IntegerRectangle irc(rc);
1043 int ox = 0;
1044 int oy = 0;
1045 GdkWindow *wndRelativeTo = WindowFromWidget(PWidget(relativeTo->wid));
1046 gdk_window_get_origin(wndRelativeTo, &ox, &oy);
1047 ox += irc.left;
1048 oy += irc.top;
1049
1050 const GdkRectangle rcMonitor = MonitorRectangleForWidget(PWidget(relativeTo->wid));
1051
1052 /* do some corrections to fit into screen */
1053 const int sizex = irc.Width();
1054 const int sizey = irc.Height();
1055 if (sizex > rcMonitor.width || ox < rcMonitor.x)
1056 ox = rcMonitor.x; /* the best we can do */
1057 else if (ox + sizex > rcMonitor.x + rcMonitor.width)
1058 ox = rcMonitor.x + rcMonitor.width - sizex;
1059 if (sizey > rcMonitor.height || oy < rcMonitor.y)
1060 oy = rcMonitor.y;
1061 else if (oy + sizey > rcMonitor.y + rcMonitor.height)
1062 oy = rcMonitor.y + rcMonitor.height - sizey;
1063
1064 gtk_window_move(GTK_WINDOW(PWidget(wid)), ox, oy);
1065
1066 gtk_window_resize(GTK_WINDOW(wid), sizex, sizey);
1067 }
1068
GetClientPosition() const1069 PRectangle Window::GetClientPosition() const {
1070 // On GTK+, the client position is the window position
1071 return GetPosition();
1072 }
1073
Show(bool show)1074 void Window::Show(bool show) {
1075 if (show)
1076 gtk_widget_show(PWidget(wid));
1077 }
1078
InvalidateAll()1079 void Window::InvalidateAll() {
1080 if (wid) {
1081 gtk_widget_queue_draw(PWidget(wid));
1082 }
1083 }
1084
InvalidateRectangle(PRectangle rc)1085 void Window::InvalidateRectangle(PRectangle rc) {
1086 if (wid) {
1087 const IntegerRectangle irc(rc);
1088 gtk_widget_queue_draw_area(PWidget(wid),
1089 irc.left, irc.top,
1090 irc.Width(), irc.Height());
1091 }
1092 }
1093
SetFont(Font &)1094 void Window::SetFont(Font &) {
1095 // Can not be done generically but only needed for ListBox
1096 }
1097
SetCursor(Cursor curs)1098 void Window::SetCursor(Cursor curs) {
1099 // We don't set the cursor to same value numerous times under gtk because
1100 // it stores the cursor in the window once it's set
1101 if (curs == cursorLast)
1102 return;
1103
1104 cursorLast = curs;
1105 GdkDisplay *pdisplay = gtk_widget_get_display(PWidget(wid));
1106
1107 GdkCursor *gdkCurs;
1108 switch (curs) {
1109 case cursorText:
1110 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_XTERM);
1111 break;
1112 case cursorArrow:
1113 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
1114 break;
1115 case cursorUp:
1116 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_CENTER_PTR);
1117 break;
1118 case cursorWait:
1119 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_WATCH);
1120 break;
1121 case cursorHand:
1122 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_HAND2);
1123 break;
1124 case cursorReverseArrow:
1125 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_RIGHT_PTR);
1126 break;
1127 default:
1128 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
1129 cursorLast = cursorArrow;
1130 break;
1131 }
1132
1133 if (WindowFromWidget(PWidget(wid)))
1134 gdk_window_set_cursor(WindowFromWidget(PWidget(wid)), gdkCurs);
1135 #if GTK_CHECK_VERSION(3,0,0)
1136 g_object_unref(gdkCurs);
1137 #else
1138 gdk_cursor_unref(gdkCurs);
1139 #endif
1140 }
1141
1142 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1143 gdk window coordinates */
GetMonitorRect(Point pt)1144 PRectangle Window::GetMonitorRect(Point pt) {
1145 gint x_offset, y_offset;
1146
1147 gdk_window_get_origin(WindowFromWidget(PWidget(wid)), &x_offset, &y_offset);
1148
1149 GdkRectangle rect {};
1150
1151 #if GTK_CHECK_VERSION(3,22,0)
1152 GdkDisplay *pdisplay = gtk_widget_get_display(PWidget(wid));
1153 GdkMonitor *monitor = gdk_display_get_monitor_at_point(pdisplay,
1154 pt.x + x_offset, pt.y + y_offset);
1155 gdk_monitor_get_geometry(monitor, &rect);
1156 #else
1157 GdkScreen *screen = gtk_widget_get_screen(PWidget(wid));
1158 const gint monitor_num = gdk_screen_get_monitor_at_point(screen,
1159 pt.x + x_offset, pt.y + y_offset);
1160 gdk_screen_get_monitor_geometry(screen, monitor_num, &rect);
1161 #endif
1162 rect.x -= x_offset;
1163 rect.y -= y_offset;
1164 return PRectangle::FromInts(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
1165 }
1166
1167 typedef std::map<int, RGBAImage *> ImageMap;
1168
1169 struct ListImage {
1170 const RGBAImage *rgba_data;
1171 GdkPixbuf *pixbuf;
1172 };
1173
list_image_free(gpointer,gpointer value,gpointer)1174 static void list_image_free(gpointer, gpointer value, gpointer) noexcept {
1175 ListImage *list_image = static_cast<ListImage *>(value);
1176 if (list_image->pixbuf)
1177 g_object_unref(list_image->pixbuf);
1178 g_free(list_image);
1179 }
1180
ListBox()1181 ListBox::ListBox() noexcept {
1182 }
1183
~ListBox()1184 ListBox::~ListBox() {
1185 }
1186
1187 enum {
1188 PIXBUF_COLUMN,
1189 TEXT_COLUMN,
1190 N_COLUMNS
1191 };
1192
1193 class ListBoxX : public ListBox {
1194 WindowID widCached;
1195 WindowID frame;
1196 WindowID list;
1197 WindowID scroller;
1198 void *pixhash;
1199 GtkCellRenderer *pixbuf_renderer;
1200 GtkCellRenderer *renderer;
1201 RGBAImageSet images;
1202 int desiredVisibleRows;
1203 unsigned int maxItemCharacters;
1204 unsigned int aveCharWidth;
1205 #if GTK_CHECK_VERSION(3,0,0)
1206 GtkCssProvider *cssProvider;
1207 #endif
1208 public:
1209 IListBoxDelegate *delegate;
1210
ListBoxX()1211 ListBoxX() noexcept : widCached(nullptr), frame(nullptr), list(nullptr), scroller(nullptr),
1212 pixhash(nullptr), pixbuf_renderer(nullptr),
1213 renderer(nullptr),
1214 desiredVisibleRows(5), maxItemCharacters(0),
1215 aveCharWidth(1),
1216 #if GTK_CHECK_VERSION(3,0,0)
1217 cssProvider(nullptr),
1218 #endif
1219 delegate(nullptr) {
1220 }
1221 // Deleted so ListBoxX objects can not be copied.
1222 ListBoxX(const ListBoxX&) = delete;
1223 ListBoxX(ListBoxX&&) = delete;
1224 ListBoxX&operator=(const ListBoxX&) = delete;
1225 ListBoxX&operator=(ListBoxX&&) = delete;
~ListBoxX()1226 ~ListBoxX() override {
1227 if (pixhash) {
1228 g_hash_table_foreach((GHashTable *) pixhash, list_image_free, nullptr);
1229 g_hash_table_destroy((GHashTable *) pixhash);
1230 }
1231 if (widCached) {
1232 gtk_widget_destroy(GTK_WIDGET(widCached));
1233 wid = widCached = nullptr;
1234 }
1235 #if GTK_CHECK_VERSION(3,0,0)
1236 if (cssProvider) {
1237 g_object_unref(cssProvider);
1238 cssProvider = nullptr;
1239 }
1240 #endif
1241 }
1242 void SetFont(Font &font) override;
1243 void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_) override;
1244 void SetAverageCharWidth(int width) override;
1245 void SetVisibleRows(int rows) override;
1246 int GetVisibleRows() const override;
1247 int GetRowHeight();
1248 PRectangle GetDesiredRect() override;
1249 int CaretFromEdge() override;
1250 void Clear() override;
1251 void Append(char *s, int type = -1) override;
1252 int Length() override;
1253 void Select(int n) override;
1254 int GetSelection() override;
1255 int Find(const char *prefix) override;
1256 void GetValue(int n, char *value, int len) override;
1257 void RegisterRGBA(int type, RGBAImage *image);
1258 void RegisterImage(int type, const char *xpm_data) override;
1259 void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) override;
1260 void ClearRegisteredImages() override;
1261 void SetDelegate(IListBoxDelegate *lbDelegate) override;
1262 void SetList(const char *listText, char separator, char typesep) override;
1263 };
1264
Allocate()1265 ListBox *ListBox::Allocate() {
1266 ListBoxX *lb = new ListBoxX();
1267 return lb;
1268 }
1269
treeViewGetRowHeight(GtkTreeView * view)1270 static int treeViewGetRowHeight(GtkTreeView *view) {
1271 #if GTK_CHECK_VERSION(3,0,0)
1272 // This version sometimes reports erroneous results on GTK2, but the GTK2
1273 // version is inaccurate for GTK 3.14.
1274 GdkRectangle rect;
1275 GtkTreePath *path = gtk_tree_path_new_first();
1276 gtk_tree_view_get_background_area(view, path, nullptr, &rect);
1277 gtk_tree_path_free(path);
1278 return rect.height;
1279 #else
1280 int row_height=0;
1281 int vertical_separator=0;
1282 int expander_size=0;
1283 GtkTreeViewColumn *column = gtk_tree_view_get_column(view, 0);
1284 gtk_tree_view_column_cell_get_size(column, nullptr, nullptr, nullptr, nullptr, &row_height);
1285 gtk_widget_style_get(GTK_WIDGET(view),
1286 "vertical-separator", &vertical_separator,
1287 "expander-size", &expander_size, nullptr);
1288 row_height += vertical_separator;
1289 row_height = std::max(row_height, expander_size);
1290 return row_height;
1291 #endif
1292 }
1293
1294 // SmallScroller, a GtkScrolledWindow that can shrink very small, as
1295 // gtk_widget_set_size_request() cannot shrink widgets on GTK3
1296 typedef struct {
1297 GtkScrolledWindow parent;
1298 /* Workaround ABI issue with Windows GTK2 bundle and GCC > 3.
1299 See http://lists.geany.org/pipermail/devel/2015-April/thread.html#9379
1300
1301 GtkScrolledWindow contains a bitfield, and GCC 3.4 and 4.8 don't agree
1302 on the size of the structure (regardless of -mms-bitfields):
1303 - GCC 3.4 has sizeof(GtkScrolledWindow)=88
1304 - GCC 4.8 has sizeof(GtkScrolledWindow)=84
1305 As Windows GTK2 bundle is built with GCC 3, it requires types derived
1306 from GtkScrolledWindow to be at least 88 bytes, which means we need to
1307 add some fake padding to fill in the extra 4 bytes.
1308 There is however no other issue with the layout difference as we never
1309 access any GtkScrolledWindow fields ourselves. */
1310 int padding;
1311 } SmallScroller;
1312 typedef GtkScrolledWindowClass SmallScrollerClass;
1313
G_DEFINE_TYPE(SmallScroller,small_scroller,GTK_TYPE_SCROLLED_WINDOW)1314 G_DEFINE_TYPE(SmallScroller, small_scroller, GTK_TYPE_SCROLLED_WINDOW)
1315
1316 #if GTK_CHECK_VERSION(3,0,0)
1317 static void small_scroller_get_preferred_height(GtkWidget *widget, gint *min, gint *nat) {
1318 GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
1319 if (GTK_IS_TREE_VIEW(child)) {
1320 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(child));
1321 int n_rows = gtk_tree_model_iter_n_children(model, nullptr);
1322 int row_height = treeViewGetRowHeight(GTK_TREE_VIEW(child));
1323
1324 *min = MAX(1, row_height);
1325 *nat = MAX(*min, n_rows * row_height);
1326 } else {
1327 GTK_WIDGET_CLASS(small_scroller_parent_class)->get_preferred_height(widget, min, nat);
1328 if (*min > 1)
1329 *min = 1;
1330 }
1331 }
1332 #else
1333 static void small_scroller_size_request(GtkWidget *widget, GtkRequisition *req) {
1334 GTK_WIDGET_CLASS(small_scroller_parent_class)->size_request(widget, req);
1335 req->height = 1;
1336 }
1337 #endif
1338
small_scroller_class_init(SmallScrollerClass * klass)1339 static void small_scroller_class_init(SmallScrollerClass *klass) {
1340 #if GTK_CHECK_VERSION(3,0,0)
1341 GTK_WIDGET_CLASS(klass)->get_preferred_height = small_scroller_get_preferred_height;
1342 #else
1343 GTK_WIDGET_CLASS(klass)->size_request = small_scroller_size_request;
1344 #endif
1345 }
1346
small_scroller_init(SmallScroller *)1347 static void small_scroller_init(SmallScroller *) {}
1348
ButtonPress(GtkWidget *,GdkEventButton * ev,gpointer p)1349 static gboolean ButtonPress(GtkWidget *, GdkEventButton *ev, gpointer p) {
1350 try {
1351 ListBoxX *lb = static_cast<ListBoxX *>(p);
1352 if (ev->type == GDK_2BUTTON_PRESS && lb->delegate) {
1353 ListBoxEvent event(ListBoxEvent::EventType::doubleClick);
1354 lb->delegate->ListNotify(&event);
1355 return TRUE;
1356 }
1357
1358 } catch (...) {
1359 // No pointer back to Scintilla to save status
1360 }
1361 return FALSE;
1362 }
1363
ButtonRelease(GtkWidget *,GdkEventButton * ev,gpointer p)1364 static gboolean ButtonRelease(GtkWidget *, GdkEventButton *ev, gpointer p) {
1365 try {
1366 ListBoxX *lb = static_cast<ListBoxX *>(p);
1367 if (ev->type != GDK_2BUTTON_PRESS && lb->delegate) {
1368 ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
1369 lb->delegate->ListNotify(&event);
1370 return TRUE;
1371 }
1372 } catch (...) {
1373 // No pointer back to Scintilla to save status
1374 }
1375 return FALSE;
1376 }
1377
1378 /* Change the active colour to the selected colour so the listbox uses the colour
1379 scheme that it would use if it had the focus. */
StyleSet(GtkWidget * w,GtkStyle *,void *)1380 static void StyleSet(GtkWidget *w, GtkStyle *, void *) {
1381
1382 g_return_if_fail(w != nullptr);
1383
1384 /* Copy the selected colour to active. Note that the modify calls will cause
1385 recursive calls to this function after the value is updated and w->style to
1386 be set to a new object */
1387
1388 #if GTK_CHECK_VERSION(3,16,0)
1389 // On recent releases of GTK+, it does not appear necessary to set the list box colours.
1390 // This may be because of common themes and may be needed with other themes.
1391 // The *override* calls are deprecated now, so only call them for older versions of GTK+.
1392 #elif GTK_CHECK_VERSION(3,0,0)
1393 GtkStyleContext *styleContext = gtk_widget_get_style_context(w);
1394 if (styleContext == nullptr)
1395 return;
1396
1397 GdkRGBA colourForeSelected;
1398 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourForeSelected);
1399 GdkRGBA colourForeActive;
1400 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourForeActive);
1401 if (!gdk_rgba_equal(&colourForeSelected, &colourForeActive))
1402 gtk_widget_override_color(w, GTK_STATE_FLAG_ACTIVE, &colourForeSelected);
1403
1404 styleContext = gtk_widget_get_style_context(w);
1405 if (styleContext == nullptr)
1406 return;
1407
1408 GdkRGBA colourBaseSelected;
1409 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourBaseSelected);
1410 GdkRGBA colourBaseActive;
1411 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourBaseActive);
1412 if (!gdk_rgba_equal(&colourBaseSelected, &colourBaseActive))
1413 gtk_widget_override_background_color(w, GTK_STATE_FLAG_ACTIVE, &colourBaseSelected);
1414 #else
1415 GtkStyle *style = gtk_widget_get_style(w);
1416 if (style == nullptr)
1417 return;
1418 if (!gdk_color_equal(&style->base[GTK_STATE_SELECTED], &style->base[GTK_STATE_ACTIVE]))
1419 gtk_widget_modify_base(w, GTK_STATE_ACTIVE, &style->base[GTK_STATE_SELECTED]);
1420 style = gtk_widget_get_style(w);
1421 if (style == nullptr)
1422 return;
1423 if (!gdk_color_equal(&style->text[GTK_STATE_SELECTED], &style->text[GTK_STATE_ACTIVE]))
1424 gtk_widget_modify_text(w, GTK_STATE_ACTIVE, &style->text[GTK_STATE_SELECTED]);
1425 #endif
1426 }
1427
Create(Window & parent,int,Point,int,bool,int)1428 void ListBoxX::Create(Window &parent, int, Point, int, bool, int) {
1429 if (widCached != nullptr) {
1430 wid = widCached;
1431 return;
1432 }
1433
1434 #if GTK_CHECK_VERSION(3,0,0)
1435 if (!cssProvider) {
1436 cssProvider = gtk_css_provider_new();
1437 }
1438 #endif
1439
1440 wid = widCached = gtk_window_new(GTK_WINDOW_POPUP);
1441
1442 frame = gtk_frame_new(nullptr);
1443 gtk_widget_show(PWidget(frame));
1444 gtk_container_add(GTK_CONTAINER(GetID()), PWidget(frame));
1445 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1446 gtk_container_set_border_width(GTK_CONTAINER(frame), 0);
1447
1448 scroller = g_object_new(small_scroller_get_type(), nullptr);
1449 gtk_container_set_border_width(GTK_CONTAINER(scroller), 0);
1450 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
1451 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1452 gtk_container_add(GTK_CONTAINER(frame), PWidget(scroller));
1453 gtk_widget_show(PWidget(scroller));
1454
1455 /* Tree and its model */
1456 GtkListStore *store =
1457 gtk_list_store_new(N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING);
1458
1459 list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1460 g_signal_connect(G_OBJECT(list), "style-set", G_CALLBACK(StyleSet), nullptr);
1461
1462 #if GTK_CHECK_VERSION(3,0,0)
1463 GtkStyleContext *styleContext = gtk_widget_get_style_context(GTK_WIDGET(list));
1464 if (styleContext) {
1465 gtk_style_context_add_provider(styleContext, GTK_STYLE_PROVIDER(cssProvider),
1466 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1467 }
1468 #endif
1469
1470 GtkTreeSelection *selection =
1471 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1472 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1473 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
1474 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list), FALSE);
1475
1476 /* Columns */
1477 GtkTreeViewColumn *column = gtk_tree_view_column_new();
1478 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1479 gtk_tree_view_column_set_title(column, "Autocomplete");
1480
1481 pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
1482 gtk_cell_renderer_set_fixed_size(pixbuf_renderer, 0, -1);
1483 gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE);
1484 gtk_tree_view_column_add_attribute(column, pixbuf_renderer,
1485 "pixbuf", PIXBUF_COLUMN);
1486
1487 renderer = gtk_cell_renderer_text_new();
1488 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1489 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1490 gtk_tree_view_column_add_attribute(column, renderer,
1491 "text", TEXT_COLUMN);
1492
1493 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
1494 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list), "fixed-height-mode"))
1495 g_object_set(G_OBJECT(list), "fixed-height-mode", TRUE, nullptr);
1496
1497 GtkWidget *widget = PWidget(list); // No code inside the G_OBJECT macro
1498 gtk_container_add(GTK_CONTAINER(PWidget(scroller)), widget);
1499 gtk_widget_show(widget);
1500 g_signal_connect(G_OBJECT(widget), "button_press_event",
1501 G_CALLBACK(ButtonPress), this);
1502 g_signal_connect(G_OBJECT(widget), "button_release_event",
1503 G_CALLBACK(ButtonRelease), this);
1504
1505 GtkWidget *top = gtk_widget_get_toplevel(static_cast<GtkWidget *>(parent.GetID()));
1506 gtk_window_set_transient_for(GTK_WINDOW(static_cast<GtkWidget *>(wid)),
1507 GTK_WINDOW(top));
1508 }
1509
SetFont(Font & font)1510 void ListBoxX::SetFont(Font &font) {
1511 // Only do for Pango font as there have been crashes for GDK fonts
1512 if (Created() && PFont(font)->pfd) {
1513 // Current font is Pango font
1514 #if GTK_CHECK_VERSION(3,0,0)
1515 if (cssProvider) {
1516 PangoFontDescription *pfd = PFont(font)->pfd;
1517 std::ostringstream ssFontSetting;
1518 ssFontSetting << "GtkTreeView, treeview { ";
1519 ssFontSetting << "font-family: " << pango_font_description_get_family(pfd) << "; ";
1520 ssFontSetting << "font-size:";
1521 ssFontSetting << static_cast<double>(pango_font_description_get_size(pfd)) / PANGO_SCALE;
1522 // On GTK < 3.21.0 the units are incorrectly parsed, so a font size in points
1523 // need to use the "px" unit. Normally we only get fonts in points here, so
1524 // don't bother to handle the case the font is actually in pixels on < 3.21.0.
1525 if (gtk_check_version(3, 21, 0) != nullptr || // on < 3.21.0
1526 pango_font_description_get_size_is_absolute(pfd)) {
1527 ssFontSetting << "px; ";
1528 } else {
1529 ssFontSetting << "pt; ";
1530 }
1531 ssFontSetting << "font-weight:"<< pango_font_description_get_weight(pfd) << "; ";
1532 ssFontSetting << "}";
1533 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(cssProvider),
1534 ssFontSetting.str().c_str(), -1, nullptr);
1535 }
1536 #else
1537 gtk_widget_modify_font(PWidget(list), PFont(font)->pfd);
1538 #endif
1539 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), -1);
1540 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1541 }
1542 }
1543
SetAverageCharWidth(int width)1544 void ListBoxX::SetAverageCharWidth(int width) {
1545 aveCharWidth = width;
1546 }
1547
SetVisibleRows(int rows)1548 void ListBoxX::SetVisibleRows(int rows) {
1549 desiredVisibleRows = rows;
1550 }
1551
GetVisibleRows() const1552 int ListBoxX::GetVisibleRows() const {
1553 return desiredVisibleRows;
1554 }
1555
GetRowHeight()1556 int ListBoxX::GetRowHeight() {
1557 return treeViewGetRowHeight(GTK_TREE_VIEW(list));
1558 }
1559
GetDesiredRect()1560 PRectangle ListBoxX::GetDesiredRect() {
1561 // Before any size allocated pretend its 100 wide so not scrolled
1562 PRectangle rc(0, 0, 100, 100);
1563 if (wid) {
1564 int rows = Length();
1565 if ((rows == 0) || (rows > desiredVisibleRows))
1566 rows = desiredVisibleRows;
1567
1568 GtkRequisition req;
1569 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1570 // returns reasonable values.
1571 #if GTK_CHECK_VERSION(3,0,0)
1572 gtk_widget_get_preferred_size(GTK_WIDGET(frame), nullptr, &req);
1573 #else
1574 gtk_widget_size_request(GTK_WIDGET(frame), &req);
1575 #endif
1576 int height;
1577
1578 // First calculate height of the clist for our desired visible
1579 // row count otherwise it tries to expand to the total # of rows
1580 // Get cell height
1581 const int row_height = GetRowHeight();
1582 #if GTK_CHECK_VERSION(3,0,0)
1583 GtkStyleContext *styleContextFrame = gtk_widget_get_style_context(PWidget(frame));
1584 GtkStateFlags stateFlagsFrame = gtk_style_context_get_state(styleContextFrame);
1585 GtkBorder padding, border, border_border = { 0, 0, 0, 0 };
1586 gtk_style_context_get_padding(styleContextFrame, stateFlagsFrame, &padding);
1587 gtk_style_context_get_border(styleContextFrame, stateFlagsFrame, &border);
1588
1589 # if GTK_CHECK_VERSION(3,20,0)
1590 // on GTK 3.20 the frame border is in a sub-node "border".
1591 // Unfortunately we need to be built against 3.20 to be able to support this, as it requires
1592 // new API.
1593 GtkStyleContext *styleContextFrameBorder = gtk_style_context_new();
1594 GtkWidgetPath *widget_path = gtk_widget_path_copy(gtk_style_context_get_path(styleContextFrame));
1595 gtk_widget_path_append_type(widget_path, GTK_TYPE_BORDER); // dummy type
1596 gtk_widget_path_iter_set_object_name(widget_path, -1, "border");
1597 gtk_style_context_set_path(styleContextFrameBorder, widget_path);
1598 gtk_widget_path_free(widget_path);
1599 gtk_style_context_get_border(styleContextFrameBorder, stateFlagsFrame, &border_border);
1600 g_object_unref(styleContextFrameBorder);
1601 # else // < 3.20
1602 if (gtk_check_version(3, 20, 0) == nullptr) {
1603 // default to 1px all around as it's likely what it is, and so we don't miss 2px height
1604 // on GTK 3.20 when built against an earlier version.
1605 border_border.top = border_border.bottom = border_border.left = border_border.right = 1;
1606 }
1607 # endif
1608
1609 height = (rows * row_height
1610 + padding.top + padding.bottom
1611 + border.top + border.bottom
1612 + border_border.top + border_border.bottom
1613 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
1614 #else
1615 height = (rows * row_height
1616 + 2 * (PWidget(frame)->style->ythickness
1617 + GTK_CONTAINER(PWidget(list))->border_width));
1618 #endif
1619 rc.bottom = height;
1620
1621 int width = maxItemCharacters;
1622 if (width < 12)
1623 width = 12;
1624 rc.right = width * (aveCharWidth + aveCharWidth / 3);
1625 // Add horizontal padding and borders
1626 int horizontal_separator=0;
1627 gtk_widget_style_get(PWidget(list),
1628 "horizontal-separator", &horizontal_separator, nullptr);
1629 rc.right += horizontal_separator;
1630 #if GTK_CHECK_VERSION(3,0,0)
1631 rc.right += (padding.left + padding.right
1632 + border.left + border.right
1633 + border_border.left + border_border.right
1634 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
1635 #else
1636 rc.right += 2 * (PWidget(frame)->style->xthickness
1637 + GTK_CONTAINER(PWidget(list))->border_width);
1638 #endif
1639 if (Length() > rows) {
1640 // Add the width of the scrollbar
1641 GtkWidget *vscrollbar =
1642 gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(scroller));
1643 #if GTK_CHECK_VERSION(3,0,0)
1644 gtk_widget_get_preferred_size(vscrollbar, nullptr, &req);
1645 #else
1646 gtk_widget_size_request(vscrollbar, &req);
1647 #endif
1648 rc.right += req.width;
1649 }
1650 }
1651 return rc;
1652 }
1653
CaretFromEdge()1654 int ListBoxX::CaretFromEdge() {
1655 gint renderer_width, renderer_height;
1656 gtk_cell_renderer_get_fixed_size(pixbuf_renderer, &renderer_width,
1657 &renderer_height);
1658 return 4 + renderer_width;
1659 }
1660
Clear()1661 void ListBoxX::Clear() {
1662 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1663 gtk_list_store_clear(GTK_LIST_STORE(model));
1664 maxItemCharacters = 0;
1665 }
1666
init_pixmap(ListImage * list_image)1667 static void init_pixmap(ListImage *list_image) {
1668 if (list_image->rgba_data) {
1669 // Drop any existing pixmap/bitmap as data may have changed
1670 if (list_image->pixbuf)
1671 g_object_unref(list_image->pixbuf);
1672 list_image->pixbuf =
1673 gdk_pixbuf_new_from_data(list_image->rgba_data->Pixels(),
1674 GDK_COLORSPACE_RGB,
1675 TRUE,
1676 8,
1677 list_image->rgba_data->GetWidth(),
1678 list_image->rgba_data->GetHeight(),
1679 list_image->rgba_data->GetWidth() * 4,
1680 nullptr,
1681 nullptr);
1682 }
1683 }
1684
1685 #define SPACING 5
1686
Append(char * s,int type)1687 void ListBoxX::Append(char *s, int type) {
1688 ListImage *list_image = nullptr;
1689 if ((type >= 0) && pixhash) {
1690 list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash,
1691 GINT_TO_POINTER(type)));
1692 }
1693 GtkTreeIter iter {};
1694 GtkListStore *store =
1695 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
1696 gtk_list_store_append(GTK_LIST_STORE(store), &iter);
1697 if (list_image) {
1698 if (nullptr == list_image->pixbuf)
1699 init_pixmap(list_image);
1700 if (list_image->pixbuf) {
1701 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1702 PIXBUF_COLUMN, list_image->pixbuf,
1703 TEXT_COLUMN, s, -1);
1704
1705 const gint pixbuf_width = gdk_pixbuf_get_width(list_image->pixbuf);
1706 gint renderer_height, renderer_width;
1707 gtk_cell_renderer_get_fixed_size(pixbuf_renderer,
1708 &renderer_width, &renderer_height);
1709 if (pixbuf_width > renderer_width)
1710 gtk_cell_renderer_set_fixed_size(pixbuf_renderer,
1711 pixbuf_width, -1);
1712 } else {
1713 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1714 TEXT_COLUMN, s, -1);
1715 }
1716 } else {
1717 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1718 TEXT_COLUMN, s, -1);
1719 }
1720 const size_t len = strlen(s);
1721 if (maxItemCharacters < len)
1722 maxItemCharacters = len;
1723 }
1724
Length()1725 int ListBoxX::Length() {
1726 if (wid)
1727 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1728 (GTK_TREE_VIEW(list)), nullptr);
1729 return 0;
1730 }
1731
Select(int n)1732 void ListBoxX::Select(int n) {
1733 GtkTreeIter iter {};
1734 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1735 GtkTreeSelection *selection =
1736 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1737
1738 if (n < 0) {
1739 gtk_tree_selection_unselect_all(selection);
1740 return;
1741 }
1742
1743 const bool valid = gtk_tree_model_iter_nth_child(model, &iter, nullptr, n) != FALSE;
1744 if (valid) {
1745 gtk_tree_selection_select_iter(selection, &iter);
1746
1747 // Move the scrollbar to show the selection.
1748 const int total = Length();
1749 #if GTK_CHECK_VERSION(3,0,0)
1750 GtkAdjustment *adj =
1751 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list));
1752 #else
1753 GtkAdjustment *adj =
1754 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list));
1755 #endif
1756 gfloat value = (static_cast<gfloat>(n) / total) * (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_lower(adj))
1757 + gtk_adjustment_get_lower(adj) - gtk_adjustment_get_page_size(adj) / 2;
1758 // Get cell height
1759 const int row_height = GetRowHeight();
1760
1761 int rows = Length();
1762 if ((rows == 0) || (rows > desiredVisibleRows))
1763 rows = desiredVisibleRows;
1764 if (rows & 0x1) {
1765 // Odd rows to display -- We are now in the middle.
1766 // Align it so that we don't chop off rows.
1767 value += static_cast<gfloat>(row_height) / 2.0f;
1768 }
1769 // Clamp it.
1770 value = (value < 0)? 0 : value;
1771 value = (value > (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)))?
1772 (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)) : value;
1773
1774 // Set it.
1775 gtk_adjustment_set_value(adj, value);
1776 } else {
1777 gtk_tree_selection_unselect_all(selection);
1778 }
1779
1780 if (delegate) {
1781 ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
1782 delegate->ListNotify(&event);
1783 }
1784 }
1785
GetSelection()1786 int ListBoxX::GetSelection() {
1787 int index = -1;
1788 GtkTreeIter iter {};
1789 GtkTreeModel *model {};
1790 GtkTreeSelection *selection;
1791 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1792 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1793 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1794 const int *indices = gtk_tree_path_get_indices(path);
1795 // Don't free indices.
1796 if (indices)
1797 index = indices[0];
1798 gtk_tree_path_free(path);
1799 }
1800 return index;
1801 }
1802
Find(const char * prefix)1803 int ListBoxX::Find(const char *prefix) {
1804 GtkTreeIter iter {};
1805 GtkTreeModel *model =
1806 gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1807 bool valid = gtk_tree_model_get_iter_first(model, &iter) != FALSE;
1808 int i = 0;
1809 while (valid) {
1810 gchar *s = nullptr;
1811 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &s, -1);
1812 if (s && (0 == strncmp(prefix, s, strlen(prefix)))) {
1813 g_free(s);
1814 return i;
1815 }
1816 g_free(s);
1817 valid = gtk_tree_model_iter_next(model, &iter) != FALSE;
1818 i++;
1819 }
1820 return -1;
1821 }
1822
GetValue(int n,char * value,int len)1823 void ListBoxX::GetValue(int n, char *value, int len) {
1824 char *text = nullptr;
1825 GtkTreeIter iter {};
1826 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1827 const bool valid = gtk_tree_model_iter_nth_child(model, &iter, nullptr, n) != FALSE;
1828 if (valid) {
1829 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &text, -1);
1830 }
1831 if (text && len > 0) {
1832 g_strlcpy(value, text, len);
1833 } else {
1834 value[0] = '\0';
1835 }
1836 g_free(text);
1837 }
1838
1839 // g_return_if_fail causes unnecessary compiler warning in release compile.
1840 #ifdef _MSC_VER
1841 #pragma warning(disable: 4127)
1842 #endif
1843
RegisterRGBA(int type,RGBAImage * image)1844 void ListBoxX::RegisterRGBA(int type, RGBAImage *image) {
1845 images.Add(type, image);
1846
1847 if (!pixhash) {
1848 pixhash = g_hash_table_new(g_direct_hash, g_direct_equal);
1849 }
1850 ListImage *list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash,
1851 GINT_TO_POINTER(type)));
1852 if (list_image) {
1853 // Drop icon already registered
1854 if (list_image->pixbuf)
1855 g_object_unref(list_image->pixbuf);
1856 list_image->pixbuf = nullptr;
1857 list_image->rgba_data = image;
1858 } else {
1859 list_image = g_new0(ListImage, 1);
1860 list_image->rgba_data = image;
1861 g_hash_table_insert((GHashTable *) pixhash, GINT_TO_POINTER(type),
1862 (gpointer) list_image);
1863 }
1864 }
1865
RegisterImage(int type,const char * xpm_data)1866 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
1867 g_return_if_fail(xpm_data);
1868 XPM xpmImage(xpm_data);
1869 RegisterRGBA(type, new RGBAImage(xpmImage));
1870 }
1871
RegisterRGBAImage(int type,int width,int height,const unsigned char * pixelsImage)1872 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
1873 RegisterRGBA(type, new RGBAImage(width, height, 1.0, pixelsImage));
1874 }
1875
ClearRegisteredImages()1876 void ListBoxX::ClearRegisteredImages() {
1877 images.Clear();
1878 }
1879
SetDelegate(IListBoxDelegate * lbDelegate)1880 void ListBoxX::SetDelegate(IListBoxDelegate *lbDelegate) {
1881 delegate = lbDelegate;
1882 }
1883
SetList(const char * listText,char separator,char typesep)1884 void ListBoxX::SetList(const char *listText, char separator, char typesep) {
1885 Clear();
1886 const size_t count = strlen(listText) + 1;
1887 std::vector<char> words(listText, listText+count);
1888 char *startword = &words[0];
1889 char *numword = nullptr;
1890 int i = 0;
1891 for (; words[i]; i++) {
1892 if (words[i] == separator) {
1893 words[i] = '\0';
1894 if (numword)
1895 *numword = '\0';
1896 Append(startword, numword?atoi(numword + 1):-1);
1897 startword = &words[0] + i + 1;
1898 numword = nullptr;
1899 } else if (words[i] == typesep) {
1900 numword = &words[0] + i;
1901 }
1902 }
1903 if (startword) {
1904 if (numword)
1905 *numword = '\0';
1906 Append(startword, numword?atoi(numword + 1):-1);
1907 }
1908 }
1909
Menu()1910 Menu::Menu() noexcept : mid(nullptr) {}
1911
CreatePopUp()1912 void Menu::CreatePopUp() {
1913 Destroy();
1914 mid = gtk_menu_new();
1915 g_object_ref_sink(G_OBJECT(mid));
1916 }
1917
Destroy()1918 void Menu::Destroy() {
1919 if (mid)
1920 g_object_unref(G_OBJECT(mid));
1921 mid = nullptr;
1922 }
1923
1924 #if !GTK_CHECK_VERSION(3,22,0)
MenuPositionFunc(GtkMenu *,gint * x,gint * y,gboolean *,gpointer userData)1925 static void MenuPositionFunc(GtkMenu *, gint *x, gint *y, gboolean *, gpointer userData) noexcept {
1926 sptr_t intFromPointer = GPOINTER_TO_INT(userData);
1927 *x = intFromPointer & 0xffff;
1928 *y = intFromPointer >> 16;
1929 }
1930 #endif
1931
Show(Point pt,Window & w)1932 void Menu::Show(Point pt, Window &w) {
1933 GtkMenu *widget = static_cast<GtkMenu *>(mid);
1934 gtk_widget_show_all(GTK_WIDGET(widget));
1935 #if GTK_CHECK_VERSION(3,22,0)
1936 // Rely on GTK+ to do the right thing with positioning
1937 gtk_menu_popup_at_pointer(widget, nullptr);
1938 #else
1939 const GdkRectangle rcMonitor = MonitorRectangleForWidget(PWidget(w.GetID()));
1940 GtkRequisition requisition;
1941 #if GTK_CHECK_VERSION(3,0,0)
1942 gtk_widget_get_preferred_size(GTK_WIDGET(widget), nullptr, &requisition);
1943 #else
1944 gtk_widget_size_request(GTK_WIDGET(widget), &requisition);
1945 #endif
1946 if ((pt.x + requisition.width) > rcMonitor.x + rcMonitor.width) {
1947 pt.x = rcMonitor.x + rcMonitor.width - requisition.width;
1948 }
1949 if ((pt.y + requisition.height) > rcMonitor.y + rcMonitor.height) {
1950 pt.y = rcMonitor.y + rcMonitor.height - requisition.height;
1951 }
1952 gtk_menu_popup(widget, nullptr, nullptr, MenuPositionFunc,
1953 GINT_TO_POINTER((static_cast<int>(pt.y) << 16) | static_cast<int>(pt.x)), 0,
1954 gtk_get_current_event_time());
1955 #endif
1956 }
1957
1958 class DynamicLibraryImpl : public DynamicLibrary {
1959 protected:
1960 GModule *m;
1961 public:
DynamicLibraryImpl(const char * modulePath)1962 explicit DynamicLibraryImpl(const char *modulePath) noexcept {
1963 m = g_module_open(modulePath, G_MODULE_BIND_LAZY);
1964 }
1965 // Deleted so DynamicLibraryImpl objects can not be copied.
1966 DynamicLibraryImpl(const DynamicLibraryImpl&) = delete;
1967 DynamicLibraryImpl(DynamicLibraryImpl&&) = delete;
1968 DynamicLibraryImpl&operator=(const DynamicLibraryImpl&) = delete;
1969 DynamicLibraryImpl&operator=(DynamicLibraryImpl&&) = delete;
~DynamicLibraryImpl()1970 ~DynamicLibraryImpl() override {
1971 if (m != nullptr)
1972 g_module_close(m);
1973 }
1974
1975 // Use g_module_symbol to get a pointer to the relevant function.
FindFunction(const char * name)1976 Function FindFunction(const char *name) override {
1977 if (m != nullptr) {
1978 gpointer fn_address = nullptr;
1979 const gboolean status = g_module_symbol(m, name, &fn_address);
1980 if (status)
1981 return static_cast<Function>(fn_address);
1982 else
1983 return nullptr;
1984 } else {
1985 return nullptr;
1986 }
1987 }
1988
IsValid()1989 bool IsValid() override {
1990 return m != nullptr;
1991 }
1992 };
1993
Load(const char * modulePath)1994 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
1995 return static_cast<DynamicLibrary *>(new DynamicLibraryImpl(modulePath));
1996 }
1997
Chrome()1998 ColourDesired Platform::Chrome() {
1999 return ColourDesired(0xe0, 0xe0, 0xe0);
2000 }
2001
ChromeHighlight()2002 ColourDesired Platform::ChromeHighlight() {
2003 return ColourDesired(0xff, 0xff, 0xff);
2004 }
2005
DefaultFont()2006 const char *Platform::DefaultFont() {
2007 #ifdef G_OS_WIN32
2008 return "Lucida Console";
2009 #else
2010 return "!Sans";
2011 #endif
2012 }
2013
DefaultFontSize()2014 int Platform::DefaultFontSize() {
2015 #ifdef G_OS_WIN32
2016 return 10;
2017 #else
2018 return 12;
2019 #endif
2020 }
2021
DoubleClickTime()2022 unsigned int Platform::DoubleClickTime() {
2023 return 500; // Half a second
2024 }
2025
DebugDisplay(const char * s)2026 void Platform::DebugDisplay(const char *s) {
2027 fprintf(stderr, "%s", s);
2028 }
2029
2030 //#define TRACE
2031
2032 #ifdef TRACE
DebugPrintf(const char * format,...)2033 void Platform::DebugPrintf(const char *format, ...) {
2034 char buffer[2000];
2035 va_list pArguments;
2036 va_start(pArguments, format);
2037 vsprintf(buffer, format, pArguments);
2038 va_end(pArguments);
2039 Platform::DebugDisplay(buffer);
2040 }
2041 #else
DebugPrintf(const char *,...)2042 void Platform::DebugPrintf(const char *, ...) {}
2043
2044 #endif
2045
2046 // Not supported for GTK+
2047 static bool assertionPopUps = true;
2048
ShowAssertionPopUps(bool assertionPopUps_)2049 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
2050 const bool ret = assertionPopUps;
2051 assertionPopUps = assertionPopUps_;
2052 return ret;
2053 }
2054
Assert(const char * c,const char * file,int line)2055 void Platform::Assert(const char *c, const char *file, int line) {
2056 char buffer[2000];
2057 g_snprintf(buffer, sizeof(buffer), "Assertion [%s] failed at %s %d\r\n", c, file, line);
2058 Platform::DebugDisplay(buffer);
2059 abort();
2060 }
2061
Platform_Initialise()2062 void Platform_Initialise() {
2063 }
2064
Platform_Finalise()2065 void Platform_Finalise() {
2066 }
2067