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