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