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