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