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