Adjust 'fall through' comments to be recognized by GCC
[geany-mirror.git] / scintilla / gtk / PlatGTK.cxx
blobee001cde4784e8f822f9495a92329532dda7768c
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.
6 #include <cstddef>
7 #include <cstdlib>
8 #include <cstring>
9 #include <cstdio>
10 #include <cmath>
12 #include <string>
13 #include <vector>
14 #include <map>
15 #include <memory>
16 #include <sstream>
18 #include <glib.h>
19 #include <gmodule.h>
20 #include <gdk/gdk.h>
21 #include <gtk/gtk.h>
22 #include <gdk/gdkkeysyms.h>
24 #include "Platform.h"
26 #include "Scintilla.h"
27 #include "ScintillaWidget.h"
28 #include "StringCopy.h"
29 #include "XPM.h"
30 #include "UniConversion.h"
32 #include "Converter.h"
34 static const double kPi = 3.14159265358979323846;
36 // The Pango version guard for pango_units_from_double and pango_units_to_double
37 // is more complex than simply implementing these here.
39 static int pangoUnitsFromDouble(double d) {
40 return static_cast<int>(d * PANGO_SCALE + 0.5);
43 static double doubleFromPangoUnits(int pu) {
44 return static_cast<double>(pu) / PANGO_SCALE;
47 static cairo_surface_t *CreateSimilarSurface(GdkWindow *window, cairo_content_t content, int width, int height) {
48 #if GTK_CHECK_VERSION(2,22,0)
49 return gdk_window_create_similar_surface(window, content, width, height);
50 #else
51 cairo_surface_t *window_surface, *surface;
53 g_return_val_if_fail(GDK_IS_WINDOW(window), NULL);
55 window_surface = GDK_DRAWABLE_GET_CLASS(window)->ref_cairo_surface(window);
57 surface = cairo_surface_create_similar(window_surface, content, width, height);
59 cairo_surface_destroy(window_surface);
61 return surface;
62 #endif
65 static GdkWindow *WindowFromWidget(GtkWidget *w) {
66 return gtk_widget_get_window(w);
69 #ifdef _MSC_VER
70 // Ignore unreferenced local functions in GTK+ headers
71 #pragma warning(disable: 4505)
72 #endif
74 #ifdef SCI_NAMESPACE
75 using namespace Scintilla;
76 #endif
78 enum encodingType { singleByte, UTF8, dbcs};
80 // Holds a PangoFontDescription*.
81 class FontHandle {
82 public:
83 PangoFontDescription *pfd;
84 int characterSet;
85 FontHandle() : pfd(0), characterSet(-1) {
87 FontHandle(PangoFontDescription *pfd_, int characterSet_) {
88 pfd = pfd_;
89 characterSet = characterSet_;
91 ~FontHandle() {
92 if (pfd)
93 pango_font_description_free(pfd);
94 pfd = 0;
96 static FontHandle *CreateNewFont(const FontParameters &fp);
99 FontHandle *FontHandle::CreateNewFont(const FontParameters &fp) {
100 PangoFontDescription *pfd = pango_font_description_new();
101 if (pfd) {
102 pango_font_description_set_family(pfd,
103 (fp.faceName[0] == '!') ? fp.faceName+1 : fp.faceName);
104 pango_font_description_set_size(pfd, pangoUnitsFromDouble(fp.size));
105 pango_font_description_set_weight(pfd, static_cast<PangoWeight>(fp.weight));
106 pango_font_description_set_style(pfd, fp.italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
107 return new FontHandle(pfd,fp.characterSet);
110 return NULL;
113 // X has a 16 bit coordinate space, so stop drawing here to avoid wrapping
114 static const int maxCoordinate = 32000;
116 static FontHandle *PFont(Font &f) {
117 return static_cast<FontHandle *>(f.GetID());
120 static GtkWidget *PWidget(WindowID wid) {
121 return static_cast<GtkWidget *>(wid);
124 Point Point::FromLong(long lpoint) {
125 return Point(
126 Platform::LowShortFromLong(lpoint),
127 Platform::HighShortFromLong(lpoint));
130 Font::Font() : fid(0) {}
132 Font::~Font() {}
134 void Font::Create(const FontParameters &fp) {
135 Release();
136 fid = FontHandle::CreateNewFont(fp);
139 void Font::Release() {
140 if (fid)
141 delete static_cast<FontHandle *>(fid);
142 fid = 0;
145 // Required on OS X
146 #ifdef SCI_NAMESPACE
147 namespace Scintilla {
148 #endif
150 // SurfaceID is a cairo_t*
151 class SurfaceImpl : public Surface {
152 encodingType et;
153 cairo_t *context;
154 cairo_surface_t *psurf;
155 int x;
156 int y;
157 bool inited;
158 bool createdGC;
159 PangoContext *pcontext;
160 PangoLayout *layout;
161 Converter conv;
162 int characterSet;
163 void SetConverter(int characterSet_);
164 public:
165 SurfaceImpl();
166 ~SurfaceImpl() override;
168 void Init(WindowID wid) override;
169 void Init(SurfaceID sid, WindowID wid) override;
170 void InitPixMap(int width, int height, Surface *surface_, WindowID wid) override;
172 void Clear();
173 void Release() override;
174 bool Initialised() override;
175 void PenColour(ColourDesired fore) override;
176 int LogPixelsY() override;
177 int DeviceHeightFont(int points) override;
178 void MoveTo(int x_, int y_) override;
179 void LineTo(int x_, int y_) override;
180 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back) override;
181 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) override;
182 void FillRectangle(PRectangle rc, ColourDesired back) override;
183 void FillRectangle(PRectangle rc, Surface &surfacePattern) override;
184 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) override;
185 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
186 ColourDesired outline, int alphaOutline, int flags) override;
187 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override;
188 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override;
189 void Copy(PRectangle rc, Point from, Surface &surfaceSource) override;
191 void DrawTextBase(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
192 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back) override;
193 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back) override;
194 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore) override;
195 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) override;
196 XYPOSITION WidthText(Font &font_, const char *s, int len) override;
197 XYPOSITION WidthChar(Font &font_, char ch) override;
198 XYPOSITION Ascent(Font &font_) override;
199 XYPOSITION Descent(Font &font_) override;
200 XYPOSITION InternalLeading(Font &font_) override;
201 XYPOSITION ExternalLeading(Font &font_) override;
202 XYPOSITION Height(Font &font_) override;
203 XYPOSITION AverageCharWidth(Font &font_) override;
205 void SetClip(PRectangle rc) override;
206 void FlushCachedState() override;
208 void SetUnicodeMode(bool unicodeMode_) override;
209 void SetDBCSMode(int codePage) override;
211 #ifdef SCI_NAMESPACE
213 #endif
215 const char *CharacterSetID(int characterSet) {
216 switch (characterSet) {
217 case SC_CHARSET_ANSI:
218 return "";
219 case SC_CHARSET_DEFAULT:
220 return "ISO-8859-1";
221 case SC_CHARSET_BALTIC:
222 return "ISO-8859-13";
223 case SC_CHARSET_CHINESEBIG5:
224 return "BIG-5";
225 case SC_CHARSET_EASTEUROPE:
226 return "ISO-8859-2";
227 case SC_CHARSET_GB2312:
228 return "CP936";
229 case SC_CHARSET_GREEK:
230 return "ISO-8859-7";
231 case SC_CHARSET_HANGUL:
232 return "CP949";
233 case SC_CHARSET_MAC:
234 return "MACINTOSH";
235 case SC_CHARSET_OEM:
236 return "ASCII";
237 case SC_CHARSET_RUSSIAN:
238 return "KOI8-R";
239 case SC_CHARSET_OEM866:
240 return "CP866";
241 case SC_CHARSET_CYRILLIC:
242 return "CP1251";
243 case SC_CHARSET_SHIFTJIS:
244 return "SHIFT-JIS";
245 case SC_CHARSET_SYMBOL:
246 return "";
247 case SC_CHARSET_TURKISH:
248 return "ISO-8859-9";
249 case SC_CHARSET_JOHAB:
250 return "CP1361";
251 case SC_CHARSET_HEBREW:
252 return "ISO-8859-8";
253 case SC_CHARSET_ARABIC:
254 return "ISO-8859-6";
255 case SC_CHARSET_VIETNAMESE:
256 return "";
257 case SC_CHARSET_THAI:
258 return "ISO-8859-11";
259 case SC_CHARSET_8859_15:
260 return "ISO-8859-15";
261 default:
262 return "";
266 void SurfaceImpl::SetConverter(int characterSet_) {
267 if (characterSet != characterSet_) {
268 characterSet = characterSet_;
269 conv.Open("UTF-8", CharacterSetID(characterSet), false);
273 SurfaceImpl::SurfaceImpl() : et(singleByte),
274 context(0),
275 psurf(0),
276 x(0), y(0), inited(false), createdGC(false)
277 , pcontext(0), layout(0), characterSet(-1) {
280 SurfaceImpl::~SurfaceImpl() {
281 Clear();
284 void SurfaceImpl::Clear() {
285 et = singleByte;
286 if (createdGC) {
287 createdGC = false;
288 cairo_destroy(context);
290 context = 0;
291 if (psurf)
292 cairo_surface_destroy(psurf);
293 psurf = 0;
294 if (layout)
295 g_object_unref(layout);
296 layout = 0;
297 if (pcontext)
298 g_object_unref(pcontext);
299 pcontext = 0;
300 conv.Close();
301 characterSet = -1;
302 x = 0;
303 y = 0;
304 inited = false;
305 createdGC = false;
308 void SurfaceImpl::Release() {
309 Clear();
312 bool SurfaceImpl::Initialised() {
313 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 8, 0)
314 if (inited && context) {
315 if (cairo_status(context) == CAIRO_STATUS_SUCCESS) {
316 // Even when status is success, the target surface may have been
317 // finished whch may cause an assertion to fail crashing the application.
318 // The cairo_surface_has_show_text_glyphs call checks the finished flag
319 // and when set, sets the status to CAIRO_STATUS_SURFACE_FINISHED
320 // which leads to warning messages instead of crashes.
321 // Performing the check in this method as it is called rarely and has no
322 // other side effects.
323 cairo_surface_t *psurfContext = cairo_get_target(context);
324 if (psurfContext) {
325 cairo_surface_has_show_text_glyphs(psurfContext);
328 return cairo_status(context) == CAIRO_STATUS_SUCCESS;
330 #endif
331 return inited;
334 void SurfaceImpl::Init(WindowID wid) {
335 Release();
336 PLATFORM_ASSERT(wid);
337 // if we are only created from a window ID, we can't perform drawing
338 psurf = 0;
339 context = 0;
340 createdGC = false;
341 pcontext = gtk_widget_create_pango_context(PWidget(wid));
342 PLATFORM_ASSERT(pcontext);
343 layout = pango_layout_new(pcontext);
344 PLATFORM_ASSERT(layout);
345 inited = true;
348 void SurfaceImpl::Init(SurfaceID sid, WindowID wid) {
349 PLATFORM_ASSERT(sid);
350 Release();
351 PLATFORM_ASSERT(wid);
352 context = cairo_reference(static_cast<cairo_t *>(sid));
353 pcontext = gtk_widget_create_pango_context(PWidget(wid));
354 // update the Pango context in case sid isn't the widget's surface
355 pango_cairo_update_context(context, pcontext);
356 layout = pango_layout_new(pcontext);
357 cairo_set_line_width(context, 1);
358 createdGC = true;
359 inited = true;
362 void SurfaceImpl::InitPixMap(int width, int height, Surface *surface_, WindowID wid) {
363 PLATFORM_ASSERT(surface_);
364 Release();
365 SurfaceImpl *surfImpl = static_cast<SurfaceImpl *>(surface_);
366 PLATFORM_ASSERT(wid);
367 context = cairo_reference(surfImpl->context);
368 pcontext = gtk_widget_create_pango_context(PWidget(wid));
369 // update the Pango context in case surface_ isn't the widget's surface
370 pango_cairo_update_context(context, pcontext);
371 PLATFORM_ASSERT(pcontext);
372 layout = pango_layout_new(pcontext);
373 PLATFORM_ASSERT(layout);
374 if (height > 0 && width > 0)
375 psurf = CreateSimilarSurface(
376 WindowFromWidget(PWidget(wid)),
377 CAIRO_CONTENT_COLOR_ALPHA, width, height);
378 cairo_destroy(context);
379 context = cairo_create(psurf);
380 cairo_rectangle(context, 0, 0, width, height);
381 cairo_set_source_rgb(context, 1.0, 0, 0);
382 cairo_fill(context);
383 // This produces sharp drawing more similar to GDK:
384 //cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
385 cairo_set_line_width(context, 1);
386 createdGC = true;
387 inited = true;
388 et = surfImpl->et;
391 void SurfaceImpl::PenColour(ColourDesired fore) {
392 if (context) {
393 ColourDesired cdFore(fore.AsLong());
394 cairo_set_source_rgb(context,
395 cdFore.GetRed() / 255.0,
396 cdFore.GetGreen() / 255.0,
397 cdFore.GetBlue() / 255.0);
401 int SurfaceImpl::LogPixelsY() {
402 return 72;
405 int SurfaceImpl::DeviceHeightFont(int points) {
406 int logPix = LogPixelsY();
407 return (points * logPix + logPix / 2) / 72;
410 void SurfaceImpl::MoveTo(int x_, int y_) {
411 x = x_;
412 y = y_;
415 static int Delta(int difference) {
416 if (difference < 0)
417 return -1;
418 else if (difference > 0)
419 return 1;
420 else
421 return 0;
424 void SurfaceImpl::LineTo(int x_, int y_) {
425 // cairo_line_to draws the end position, unlike Win32 or GDK with GDK_CAP_NOT_LAST.
426 // For simple cases, move back one pixel from end.
427 if (context) {
428 int xDiff = x_ - x;
429 int xDelta = Delta(xDiff);
430 int yDiff = y_ - y;
431 int yDelta = Delta(yDiff);
432 if ((xDiff == 0) || (yDiff == 0)) {
433 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
434 int xEnd = x_ - xDelta;
435 int left = Platform::Minimum(x, xEnd);
436 int width = abs(x - xEnd) + 1;
437 int yEnd = y_ - yDelta;
438 int top = Platform::Minimum(y, yEnd);
439 int height = abs(y - yEnd) + 1;
440 cairo_rectangle(context, left, top, width, height);
441 cairo_fill(context);
442 } else if ((abs(xDiff) == abs(yDiff))) {
443 // 45 degree slope
444 cairo_move_to(context, x + 0.5, y + 0.5);
445 cairo_line_to(context, x_ + 0.5 - xDelta, y_ + 0.5 - yDelta);
446 } else {
447 // Line has a different slope so difficult to avoid last pixel
448 cairo_move_to(context, x + 0.5, y + 0.5);
449 cairo_line_to(context, x_ + 0.5, y_ + 0.5);
451 cairo_stroke(context);
453 x = x_;
454 y = y_;
457 void SurfaceImpl::Polygon(Point *pts, int npts, ColourDesired fore,
458 ColourDesired back) {
459 PLATFORM_ASSERT(context);
460 PenColour(back);
461 cairo_move_to(context, pts[0].x + 0.5, pts[0].y + 0.5);
462 for (int i = 1; i < npts; i++) {
463 cairo_line_to(context, pts[i].x + 0.5, pts[i].y + 0.5);
465 cairo_close_path(context);
466 cairo_fill_preserve(context);
467 PenColour(fore);
468 cairo_stroke(context);
471 void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
472 if (context) {
473 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5,
474 rc.right - rc.left - 1, rc.bottom - rc.top - 1);
475 PenColour(back);
476 cairo_fill_preserve(context);
477 PenColour(fore);
478 cairo_stroke(context);
482 void SurfaceImpl::FillRectangle(PRectangle rc, ColourDesired back) {
483 PenColour(back);
484 if (context && (rc.left < maxCoordinate)) { // Protect against out of range
485 rc.left = lround(rc.left);
486 rc.right = lround(rc.right);
487 cairo_rectangle(context, rc.left, rc.top,
488 rc.right - rc.left, rc.bottom - rc.top);
489 cairo_fill(context);
493 void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
494 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfacePattern);
495 bool canDraw = surfi.psurf != NULL;
496 if (canDraw) {
497 PLATFORM_ASSERT(context);
498 // Tile pattern over rectangle
499 // Currently assumes 8x8 pattern
500 int widthPat = 8;
501 int heightPat = 8;
502 for (int xTile = rc.left; xTile < rc.right; xTile += widthPat) {
503 int widthx = (xTile + widthPat > rc.right) ? rc.right - xTile : widthPat;
504 for (int yTile = rc.top; yTile < rc.bottom; yTile += heightPat) {
505 int heighty = (yTile + heightPat > rc.bottom) ? rc.bottom - yTile : heightPat;
506 cairo_set_source_surface(context, surfi.psurf, xTile, yTile);
507 cairo_rectangle(context, xTile, yTile, widthx, heighty);
508 cairo_fill(context);
511 } else {
512 // Something is wrong so try to show anyway
513 // Shows up black because colour not allocated
514 FillRectangle(rc, ColourDesired(0));
518 void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
519 if (((rc.right - rc.left) > 4) && ((rc.bottom - rc.top) > 4)) {
520 // Approximate a round rect with some cut off corners
521 Point pts[] = {
522 Point(rc.left + 2, rc.top),
523 Point(rc.right - 2, rc.top),
524 Point(rc.right, rc.top + 2),
525 Point(rc.right, rc.bottom - 2),
526 Point(rc.right - 2, rc.bottom),
527 Point(rc.left + 2, rc.bottom),
528 Point(rc.left, rc.bottom - 2),
529 Point(rc.left, rc.top + 2),
531 Polygon(pts, ELEMENTS(pts), fore, back);
532 } else {
533 RectangleDraw(rc, fore, back);
537 static void PathRoundRectangle(cairo_t *context, double left, double top, double width, double height, int radius) {
538 double degrees = kPi / 180.0;
540 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 2, 0)
541 cairo_new_sub_path(context);
542 #else
543 // First arc is in the top-right corner and starts from a point on the top line
544 cairo_move_to(context, left + width - radius, top);
545 #endif
546 cairo_arc(context, left + width - radius, top + radius, radius, -90 * degrees, 0 * degrees);
547 cairo_arc(context, left + width - radius, top + height - radius, radius, 0 * degrees, 90 * degrees);
548 cairo_arc(context, left + radius, top + height - radius, radius, 90 * degrees, 180 * degrees);
549 cairo_arc(context, left + radius, top + radius, radius, 180 * degrees, 270 * degrees);
550 cairo_close_path(context);
553 void SurfaceImpl::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
554 ColourDesired outline, int alphaOutline, int /*flags*/) {
555 if (context && rc.Width() > 0) {
556 ColourDesired cdFill(fill.AsLong());
557 cairo_set_source_rgba(context,
558 cdFill.GetRed() / 255.0,
559 cdFill.GetGreen() / 255.0,
560 cdFill.GetBlue() / 255.0,
561 alphaFill / 255.0);
562 if (cornerSize > 0)
563 PathRoundRectangle(context, rc.left + 1.0, rc.top + 1.0, rc.right - rc.left - 2.0, rc.bottom - rc.top - 2.0, cornerSize);
564 else
565 cairo_rectangle(context, rc.left + 1.0, rc.top + 1.0, rc.right - rc.left - 2.0, rc.bottom - rc.top - 2.0);
566 cairo_fill(context);
568 ColourDesired cdOutline(outline.AsLong());
569 cairo_set_source_rgba(context,
570 cdOutline.GetRed() / 255.0,
571 cdOutline.GetGreen() / 255.0,
572 cdOutline.GetBlue() / 255.0,
573 alphaOutline / 255.0);
574 if (cornerSize > 0)
575 PathRoundRectangle(context, rc.left + 0.5, rc.top + 0.5, rc.right - rc.left - 1, rc.bottom - rc.top - 1, cornerSize);
576 else
577 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5, rc.right - rc.left - 1, rc.bottom - rc.top - 1);
578 cairo_stroke(context);
582 void SurfaceImpl::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
583 PLATFORM_ASSERT(context);
584 if (rc.Width() > width)
585 rc.left += (rc.Width() - width) / 2;
586 rc.right = rc.left + width;
587 if (rc.Height() > height)
588 rc.top += (rc.Height() - height) / 2;
589 rc.bottom = rc.top + height;
591 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
592 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
593 #else
594 int stride = width * 4;
595 #endif
596 int ucs = stride * height;
597 std::vector<unsigned char> image(ucs);
598 for (int iy=0; iy<height; iy++) {
599 for (int ix=0; ix<width; ix++) {
600 unsigned char *pixel = &image[0] + iy*stride + ix * 4;
601 unsigned char alpha = pixelsImage[3];
602 pixel[2] = (*pixelsImage++) * alpha / 255;
603 pixel[1] = (*pixelsImage++) * alpha / 255;
604 pixel[0] = (*pixelsImage++) * alpha / 255;
605 pixel[3] = *pixelsImage++;
609 cairo_surface_t *psurfImage = cairo_image_surface_create_for_data(&image[0], CAIRO_FORMAT_ARGB32, width, height, stride);
610 cairo_set_source_surface(context, psurfImage, rc.left, rc.top);
611 cairo_rectangle(context, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
612 cairo_fill(context);
614 cairo_surface_destroy(psurfImage);
617 void SurfaceImpl::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
618 PLATFORM_ASSERT(context);
619 PenColour(back);
620 cairo_arc(context, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2,
621 Platform::Minimum(rc.Width(), rc.Height()) / 2, 0, 2*kPi);
622 cairo_fill_preserve(context);
623 PenColour(fore);
624 cairo_stroke(context);
627 void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
628 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfaceSource);
629 bool canDraw = surfi.psurf != NULL;
630 if (canDraw) {
631 PLATFORM_ASSERT(context);
632 cairo_set_source_surface(context, surfi.psurf,
633 rc.left - from.x, rc.top - from.y);
634 cairo_rectangle(context, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
635 cairo_fill(context);
639 std::string UTF8FromLatin1(const char *s, int len) {
640 std::string utfForm(len*2 + 1, '\0');
641 size_t lenU = 0;
642 for (int i=0; i<len; i++) {
643 unsigned int uch = static_cast<unsigned char>(s[i]);
644 if (uch < 0x80) {
645 utfForm[lenU++] = uch;
646 } else {
647 utfForm[lenU++] = static_cast<char>(0xC0 | (uch >> 6));
648 utfForm[lenU++] = static_cast<char>(0x80 | (uch & 0x3f));
651 utfForm.resize(lenU);
652 return utfForm;
655 static std::string UTF8FromIconv(const Converter &conv, const char *s, int len) {
656 if (conv) {
657 std::string utfForm(len*3+1, '\0');
658 char *pin = const_cast<char *>(s);
659 gsize inLeft = len;
660 char *putf = &utfForm[0];
661 char *pout = putf;
662 gsize outLeft = len*3+1;
663 gsize conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
664 if (conversions != sizeFailure) {
665 *pout = '\0';
666 utfForm.resize(pout - putf);
667 return utfForm;
670 return std::string();
673 // Work out how many bytes are in a character by trying to convert using iconv,
674 // returning the first length that succeeds.
675 static size_t MultiByteLenFromIconv(const Converter &conv, const char *s, size_t len) {
676 for (size_t lenMB=1; (lenMB<4) && (lenMB <= len); lenMB++) {
677 char wcForm[2];
678 char *pin = const_cast<char *>(s);
679 gsize inLeft = lenMB;
680 char *pout = wcForm;
681 gsize outLeft = 2;
682 gsize conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
683 if (conversions != sizeFailure) {
684 return lenMB;
687 return 1;
690 void SurfaceImpl::DrawTextBase(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
691 ColourDesired fore) {
692 PenColour(fore);
693 if (context) {
694 XYPOSITION xText = rc.left;
695 if (PFont(font_)->pfd) {
696 std::string utfForm;
697 if (et == UTF8) {
698 pango_layout_set_text(layout, s, len);
699 } else {
700 SetConverter(PFont(font_)->characterSet);
701 utfForm = UTF8FromIconv(conv, s, len);
702 if (utfForm.empty()) { // iconv failed so treat as Latin1
703 utfForm = UTF8FromLatin1(s, len);
705 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
707 pango_layout_set_font_description(layout, PFont(font_)->pfd);
708 pango_cairo_update_layout(context, layout);
709 #ifdef PANGO_VERSION
710 PangoLayoutLine *pll = pango_layout_get_line_readonly(layout,0);
711 #else
712 PangoLayoutLine *pll = pango_layout_get_line(layout,0);
713 #endif
714 cairo_move_to(context, xText, ybase);
715 pango_cairo_show_layout_line(context, pll);
720 void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
721 ColourDesired fore, ColourDesired back) {
722 FillRectangle(rc, back);
723 DrawTextBase(rc, font_, ybase, s, len, fore);
726 // On GTK+, exactly same as DrawTextNoClip
727 void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
728 ColourDesired fore, ColourDesired back) {
729 FillRectangle(rc, back);
730 DrawTextBase(rc, font_, ybase, s, len, fore);
733 void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
734 ColourDesired fore) {
735 // Avoid drawing spaces in transparent mode
736 for (int i=0; i<len; i++) {
737 if (s[i] != ' ') {
738 DrawTextBase(rc, font_, ybase, s, len, fore);
739 return;
744 class ClusterIterator {
745 PangoLayoutIter *iter;
746 PangoRectangle pos;
747 int lenPositions;
748 public:
749 bool finished;
750 XYPOSITION positionStart;
751 XYPOSITION position;
752 XYPOSITION distance;
753 int curIndex;
754 ClusterIterator(PangoLayout *layout, int len) : lenPositions(len), finished(false),
755 positionStart(0), position(0), distance(0), curIndex(0) {
756 iter = pango_layout_get_iter(layout);
757 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
759 ~ClusterIterator() {
760 pango_layout_iter_free(iter);
763 void Next() {
764 positionStart = position;
765 if (pango_layout_iter_next_cluster(iter)) {
766 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
767 position = doubleFromPangoUnits(pos.x);
768 curIndex = pango_layout_iter_get_index(iter);
769 } else {
770 finished = true;
771 position = doubleFromPangoUnits(pos.x + pos.width);
772 curIndex = lenPositions;
774 distance = position - positionStart;
778 void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
779 if (font_.GetID()) {
780 const int lenPositions = len;
781 if (PFont(font_)->pfd) {
782 pango_layout_set_font_description(layout, PFont(font_)->pfd);
783 if (et == UTF8) {
784 // Simple and direct as UTF-8 is native Pango encoding
785 int i = 0;
786 pango_layout_set_text(layout, s, len);
787 ClusterIterator iti(layout, lenPositions);
788 while (!iti.finished) {
789 iti.Next();
790 int places = iti.curIndex - i;
791 while (i < iti.curIndex) {
792 // Evenly distribute space among bytes of this cluster.
793 // Would be better to find number of characters and then
794 // divide evenly between characters with each byte of a character
795 // being at the same position.
796 positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places;
797 i++;
800 PLATFORM_ASSERT(i == lenPositions);
801 } else {
802 int positionsCalculated = 0;
803 if (et == dbcs) {
804 SetConverter(PFont(font_)->characterSet);
805 std::string utfForm = UTF8FromIconv(conv, s, len);
806 if (!utfForm.empty()) {
807 // Convert to UTF-8 so can ask Pango for widths, then
808 // Loop through UTF-8 and DBCS forms, taking account of different
809 // character byte lengths.
810 Converter convMeasure("UCS-2", CharacterSetID(characterSet), false);
811 pango_layout_set_text(layout, utfForm.c_str(), strlen(utfForm.c_str()));
812 int i = 0;
813 int clusterStart = 0;
814 ClusterIterator iti(layout, strlen(utfForm.c_str()));
815 while (!iti.finished) {
816 iti.Next();
817 int clusterEnd = iti.curIndex;
818 int places = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
819 int place = 1;
820 while (clusterStart < clusterEnd) {
821 size_t lenChar = MultiByteLenFromIconv(convMeasure, s+i, len-i);
822 while (lenChar--) {
823 positions[i++] = iti.position - (places - place) * iti.distance / places;
824 positionsCalculated++;
826 clusterStart += UTF8CharLength(static_cast<unsigned char>(utfForm.c_str()[clusterStart]));
827 place++;
830 PLATFORM_ASSERT(i == lenPositions);
833 if (positionsCalculated < 1 ) {
834 // Either 8-bit or DBCS conversion failed so treat as 8-bit.
835 SetConverter(PFont(font_)->characterSet);
836 const bool rtlCheck = PFont(font_)->characterSet == SC_CHARSET_HEBREW ||
837 PFont(font_)->characterSet == SC_CHARSET_ARABIC;
838 std::string utfForm = UTF8FromIconv(conv, s, len);
839 if (utfForm.empty()) {
840 utfForm = UTF8FromLatin1(s, len);
842 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
843 int i = 0;
844 int clusterStart = 0;
845 // Each 8-bit input character may take 1 or 2 bytes in UTF-8
846 // and groups of up to 3 may be represented as ligatures.
847 ClusterIterator iti(layout, utfForm.length());
848 while (!iti.finished) {
849 iti.Next();
850 int clusterEnd = iti.curIndex;
851 int ligatureLength = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
852 if (rtlCheck && ((clusterEnd <= clusterStart) || (ligatureLength == 0) || (ligatureLength > 3))) {
853 // Something has gone wrong: exit quickly but pretend all the characters are equally spaced:
854 int widthLayout = 0;
855 pango_layout_get_size(layout, &widthLayout, NULL);
856 XYPOSITION widthTotal = doubleFromPangoUnits(widthLayout);
857 for (int bytePos=0; bytePos<lenPositions; bytePos++) {
858 positions[bytePos] = widthTotal / lenPositions * (bytePos + 1);
860 return;
862 PLATFORM_ASSERT(ligatureLength > 0 && ligatureLength <= 3);
863 for (int charInLig=0; charInLig<ligatureLength; charInLig++) {
864 positions[i++] = iti.position - (ligatureLength - 1 - charInLig) * iti.distance / ligatureLength;
866 clusterStart = clusterEnd;
868 while (i < lenPositions) {
869 // If something failed, fill in rest of the positions
870 positions[i++] = clusterStart;
872 PLATFORM_ASSERT(i == lenPositions);
876 } else {
877 // No font so return an ascending range of values
878 for (int i = 0; i < len; i++) {
879 positions[i] = i + 1;
884 XYPOSITION SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
885 if (font_.GetID()) {
886 if (PFont(font_)->pfd) {
887 std::string utfForm;
888 pango_layout_set_font_description(layout, PFont(font_)->pfd);
889 PangoRectangle pos;
890 if (et == UTF8) {
891 pango_layout_set_text(layout, s, len);
892 } else {
893 SetConverter(PFont(font_)->characterSet);
894 utfForm = UTF8FromIconv(conv, s, len);
895 if (utfForm.empty()) { // iconv failed so treat as Latin1
896 utfForm = UTF8FromLatin1(s, len);
898 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
900 #ifdef PANGO_VERSION
901 PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout,0);
902 #else
903 PangoLayoutLine *pangoLine = pango_layout_get_line(layout,0);
904 #endif
905 pango_layout_line_get_extents(pangoLine, NULL, &pos);
906 return doubleFromPangoUnits(pos.width);
908 return 1;
909 } else {
910 return 1;
914 XYPOSITION SurfaceImpl::WidthChar(Font &font_, char ch) {
915 if (font_.GetID()) {
916 if (PFont(font_)->pfd) {
917 return WidthText(font_, &ch, 1);
919 return 1;
920 } else {
921 return 1;
925 // Ascent and descent determined by Pango font metrics.
927 XYPOSITION SurfaceImpl::Ascent(Font &font_) {
928 if (!(font_.GetID()))
929 return 1;
930 int ascent = 0;
931 if (PFont(font_)->pfd) {
932 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
933 PFont(font_)->pfd, pango_context_get_language(pcontext));
934 ascent =
935 doubleFromPangoUnits(pango_font_metrics_get_ascent(metrics));
936 pango_font_metrics_unref(metrics);
938 if (ascent == 0) {
939 ascent = 1;
941 return ascent;
944 XYPOSITION SurfaceImpl::Descent(Font &font_) {
945 if (!(font_.GetID()))
946 return 1;
947 if (PFont(font_)->pfd) {
948 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
949 PFont(font_)->pfd, pango_context_get_language(pcontext));
950 int descent = doubleFromPangoUnits(pango_font_metrics_get_descent(metrics));
951 pango_font_metrics_unref(metrics);
952 return descent;
954 return 0;
957 XYPOSITION SurfaceImpl::InternalLeading(Font &) {
958 return 0;
961 XYPOSITION SurfaceImpl::ExternalLeading(Font &) {
962 return 0;
965 XYPOSITION SurfaceImpl::Height(Font &font_) {
966 return Ascent(font_) + Descent(font_);
969 XYPOSITION SurfaceImpl::AverageCharWidth(Font &font_) {
970 return WidthChar(font_, 'n');
973 void SurfaceImpl::SetClip(PRectangle rc) {
974 PLATFORM_ASSERT(context);
975 cairo_rectangle(context, rc.left, rc.top, rc.right, rc.bottom);
976 cairo_clip(context);
979 void SurfaceImpl::FlushCachedState() {}
981 void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
982 if (unicodeMode_)
983 et = UTF8;
986 void SurfaceImpl::SetDBCSMode(int codePage) {
987 if (codePage && (codePage != SC_CP_UTF8))
988 et = dbcs;
991 Surface *Surface::Allocate(int) {
992 return new SurfaceImpl();
995 Window::~Window() {}
997 void Window::Destroy() {
998 if (wid) {
999 ListBox *listbox = dynamic_cast<ListBox*>(this);
1000 if (listbox) {
1001 gtk_widget_hide(GTK_WIDGET(wid));
1002 // clear up window content
1003 listbox->Clear();
1004 // resize the window to the smallest possible size for it to adapt
1005 // to future content
1006 gtk_window_resize(GTK_WINDOW(wid), 1, 1);
1007 } else {
1008 gtk_widget_destroy(GTK_WIDGET(wid));
1010 wid = 0;
1014 bool Window::HasFocus() {
1015 return gtk_widget_has_focus(GTK_WIDGET(wid));
1018 PRectangle Window::GetPosition() {
1019 // Before any size allocated pretend its 1000 wide so not scrolled
1020 PRectangle rc(0, 0, 1000, 1000);
1021 if (wid) {
1022 GtkAllocation allocation;
1023 gtk_widget_get_allocation(PWidget(wid), &allocation);
1024 rc.left = allocation.x;
1025 rc.top = allocation.y;
1026 if (allocation.width > 20) {
1027 rc.right = rc.left + allocation.width;
1028 rc.bottom = rc.top + allocation.height;
1031 return rc;
1034 void Window::SetPosition(PRectangle rc) {
1035 GtkAllocation alloc;
1036 alloc.x = rc.left;
1037 alloc.y = rc.top;
1038 alloc.width = rc.Width();
1039 alloc.height = rc.Height();
1040 gtk_widget_size_allocate(PWidget(wid), &alloc);
1043 namespace {
1045 GdkRectangle MonitorRectangleForWidget(GtkWidget *wid) {
1046 GdkWindow *wnd = WindowFromWidget(wid);
1047 GdkRectangle rcScreen = GdkRectangle();
1048 #if GTK_CHECK_VERSION(3,22,0)
1049 GdkDisplay *pdisplay = gtk_widget_get_display(wid);
1050 GdkMonitor *monitor = gdk_display_get_monitor_at_window(pdisplay, wnd);
1051 gdk_monitor_get_geometry(monitor, &rcScreen);
1052 #else
1053 GdkScreen* screen = gtk_widget_get_screen(wid);
1054 gint monitor_num = gdk_screen_get_monitor_at_window(screen, wnd);
1055 gdk_screen_get_monitor_geometry(screen, monitor_num, &rcScreen);
1056 #endif
1057 return rcScreen;
1062 void Window::SetPositionRelative(PRectangle rc, Window relativeTo) {
1063 int ox = 0;
1064 int oy = 0;
1065 GdkWindow *wndRelativeTo = WindowFromWidget(PWidget(relativeTo.wid));
1066 gdk_window_get_origin(wndRelativeTo, &ox, &oy);
1067 ox += rc.left;
1068 oy += rc.top;
1070 GdkRectangle rcMonitor = MonitorRectangleForWidget(PWidget(relativeTo.wid));
1072 /* do some corrections to fit into screen */
1073 int sizex = rc.right - rc.left;
1074 int sizey = rc.bottom - rc.top;
1075 if (sizex > rcMonitor.width || ox < rcMonitor.x)
1076 ox = rcMonitor.x; /* the best we can do */
1077 else if (ox + sizex > rcMonitor.x + rcMonitor.width)
1078 ox = rcMonitor.x + rcMonitor.width - sizex;
1079 if (sizey > rcMonitor.height || oy < rcMonitor.y)
1080 oy = rcMonitor.y;
1081 else if (oy + sizey > rcMonitor.y + rcMonitor.height)
1082 oy = rcMonitor.y + rcMonitor.height - sizey;
1084 gtk_window_move(GTK_WINDOW(PWidget(wid)), ox, oy);
1086 gtk_window_resize(GTK_WINDOW(wid), sizex, sizey);
1089 PRectangle Window::GetClientPosition() {
1090 // On GTK+, the client position is the window position
1091 return GetPosition();
1094 void Window::Show(bool show) {
1095 if (show)
1096 gtk_widget_show(PWidget(wid));
1099 void Window::InvalidateAll() {
1100 if (wid) {
1101 gtk_widget_queue_draw(PWidget(wid));
1105 void Window::InvalidateRectangle(PRectangle rc) {
1106 if (wid) {
1107 gtk_widget_queue_draw_area(PWidget(wid),
1108 rc.left, rc.top,
1109 rc.right - rc.left, rc.bottom - rc.top);
1113 void Window::SetFont(Font &) {
1114 // Can not be done generically but only needed for ListBox
1117 void Window::SetCursor(Cursor curs) {
1118 // We don't set the cursor to same value numerous times under gtk because
1119 // it stores the cursor in the window once it's set
1120 if (curs == cursorLast)
1121 return;
1123 cursorLast = curs;
1124 GdkDisplay *pdisplay = gtk_widget_get_display(PWidget(wid));
1126 GdkCursor *gdkCurs;
1127 switch (curs) {
1128 case cursorText:
1129 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_XTERM);
1130 break;
1131 case cursorArrow:
1132 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
1133 break;
1134 case cursorUp:
1135 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_CENTER_PTR);
1136 break;
1137 case cursorWait:
1138 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_WATCH);
1139 break;
1140 case cursorHand:
1141 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_HAND2);
1142 break;
1143 case cursorReverseArrow:
1144 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_RIGHT_PTR);
1145 break;
1146 default:
1147 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
1148 cursorLast = cursorArrow;
1149 break;
1152 if (WindowFromWidget(PWidget(wid)))
1153 gdk_window_set_cursor(WindowFromWidget(PWidget(wid)), gdkCurs);
1154 #if GTK_CHECK_VERSION(3,0,0)
1155 g_object_unref(gdkCurs);
1156 #else
1157 gdk_cursor_unref(gdkCurs);
1158 #endif
1161 void Window::SetTitle(const char *s) {
1162 gtk_window_set_title(GTK_WINDOW(wid), s);
1165 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1166 gdk window coordinates */
1167 PRectangle Window::GetMonitorRect(Point pt) {
1168 gint x_offset, y_offset;
1170 gdk_window_get_origin(WindowFromWidget(PWidget(wid)), &x_offset, &y_offset);
1172 GdkRectangle rect;
1174 #if GTK_CHECK_VERSION(3,22,0)
1175 GdkDisplay *pdisplay = gtk_widget_get_display(PWidget(wid));
1176 GdkMonitor *monitor = gdk_display_get_monitor_at_point(pdisplay,
1177 pt.x + x_offset, pt.y + y_offset);
1178 gdk_monitor_get_geometry(monitor, &rect);
1179 #else
1180 GdkScreen* screen = gtk_widget_get_screen(PWidget(wid));
1181 gint monitor_num = gdk_screen_get_monitor_at_point(screen,
1182 pt.x + x_offset, pt.y + y_offset);
1183 gdk_screen_get_monitor_geometry(screen, monitor_num, &rect);
1184 #endif
1185 rect.x -= x_offset;
1186 rect.y -= y_offset;
1187 return PRectangle(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
1190 typedef std::map<int, RGBAImage*> ImageMap;
1192 struct ListImage {
1193 const RGBAImage *rgba_data;
1194 GdkPixbuf *pixbuf;
1197 static void list_image_free(gpointer, gpointer value, gpointer) {
1198 ListImage *list_image = static_cast<ListImage *>(value);
1199 if (list_image->pixbuf)
1200 g_object_unref(list_image->pixbuf);
1201 g_free(list_image);
1204 ListBox::ListBox() {
1207 ListBox::~ListBox() {
1210 enum {
1211 PIXBUF_COLUMN,
1212 TEXT_COLUMN,
1213 N_COLUMNS
1216 class ListBoxX : public ListBox {
1217 WindowID widCached;
1218 WindowID frame;
1219 WindowID list;
1220 WindowID scroller;
1221 void *pixhash;
1222 GtkCellRenderer* pixbuf_renderer;
1223 GtkCellRenderer* renderer;
1224 RGBAImageSet images;
1225 int desiredVisibleRows;
1226 unsigned int maxItemCharacters;
1227 unsigned int aveCharWidth;
1228 #if GTK_CHECK_VERSION(3,0,0)
1229 GtkCssProvider *cssProvider;
1230 #endif
1231 public:
1232 CallBackAction doubleClickAction;
1233 void *doubleClickActionData;
1235 ListBoxX() : widCached(0), frame(0), list(0), scroller(0), pixhash(NULL), pixbuf_renderer(0),
1236 renderer(0),
1237 desiredVisibleRows(5), maxItemCharacters(0),
1238 aveCharWidth(1),
1239 #if GTK_CHECK_VERSION(3,0,0)
1240 cssProvider(NULL),
1241 #endif
1242 doubleClickAction(NULL), doubleClickActionData(NULL) {
1244 ~ListBoxX() override {
1245 if (pixhash) {
1246 g_hash_table_foreach((GHashTable *) pixhash, list_image_free, NULL);
1247 g_hash_table_destroy((GHashTable *) pixhash);
1249 if (widCached) {
1250 gtk_widget_destroy(GTK_WIDGET(widCached));
1251 wid = widCached = 0;
1253 #if GTK_CHECK_VERSION(3,0,0)
1254 if (cssProvider) {
1255 g_object_unref(cssProvider);
1256 cssProvider = NULL;
1258 #endif
1260 void SetFont(Font &font) override;
1261 void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_) override;
1262 void SetAverageCharWidth(int width) override;
1263 void SetVisibleRows(int rows) override;
1264 int GetVisibleRows() const override;
1265 int GetRowHeight();
1266 PRectangle GetDesiredRect() override;
1267 int CaretFromEdge() override;
1268 void Clear() override;
1269 void Append(char *s, int type = -1) override;
1270 int Length() override;
1271 void Select(int n) override;
1272 int GetSelection() override;
1273 int Find(const char *prefix) override;
1274 void GetValue(int n, char *value, int len) override;
1275 void RegisterRGBA(int type, RGBAImage *image);
1276 void RegisterImage(int type, const char *xpm_data) override;
1277 void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) override;
1278 void ClearRegisteredImages() override;
1279 void SetDoubleClickAction(CallBackAction action, void *data) override {
1280 doubleClickAction = action;
1281 doubleClickActionData = data;
1283 void SetList(const char *listText, char separator, char typesep) override;
1286 ListBox *ListBox::Allocate() {
1287 ListBoxX *lb = new ListBoxX();
1288 return lb;
1291 static int treeViewGetRowHeight(GtkTreeView *view) {
1292 #if GTK_CHECK_VERSION(3,0,0)
1293 // This version sometimes reports erroneous results on GTK2, but the GTK2
1294 // version is inaccurate for GTK 3.14.
1295 GdkRectangle rect;
1296 GtkTreePath *path = gtk_tree_path_new_first();
1297 gtk_tree_view_get_background_area(view, path, NULL, &rect);
1298 gtk_tree_path_free(path);
1299 return rect.height;
1300 #else
1301 int row_height=0;
1302 int vertical_separator=0;
1303 int expander_size=0;
1304 GtkTreeViewColumn *column = gtk_tree_view_get_column(view, 0);
1305 gtk_tree_view_column_cell_get_size(column, NULL, NULL, NULL, NULL, &row_height);
1306 gtk_widget_style_get(GTK_WIDGET(view),
1307 "vertical-separator", &vertical_separator,
1308 "expander-size", &expander_size, NULL);
1309 row_height += vertical_separator;
1310 row_height = Platform::Maximum(row_height, expander_size);
1311 return row_height;
1312 #endif
1315 // SmallScroller, a GtkScrolledWindow that can shrink very small, as
1316 // gtk_widget_set_size_request() cannot shrink widgets on GTK3
1317 typedef struct {
1318 GtkScrolledWindow parent;
1319 /* Workaround ABI issue with Windows GTK2 bundle and GCC > 3.
1320 See http://lists.geany.org/pipermail/devel/2015-April/thread.html#9379
1322 GtkScrolledWindow contains a bitfield, and GCC 3.4 and 4.8 don't agree
1323 on the size of the structure (regardless of -mms-bitfields):
1324 - GCC 3.4 has sizeof(GtkScrolledWindow)=88
1325 - GCC 4.8 has sizeof(GtkScrolledWindow)=84
1326 As Windows GTK2 bundle is built with GCC 3, it requires types derived
1327 from GtkScrolledWindow to be at least 88 bytes, which means we need to
1328 add some fake padding to fill in the extra 4 bytes.
1329 There is however no other issue with the layout difference as we never
1330 access any GtkScrolledWindow fields ourselves. */
1331 int padding;
1332 } SmallScroller;
1333 typedef GtkScrolledWindowClass SmallScrollerClass;
1335 G_DEFINE_TYPE(SmallScroller, small_scroller, GTK_TYPE_SCROLLED_WINDOW)
1337 #if GTK_CHECK_VERSION(3,0,0)
1338 static void small_scroller_get_preferred_height(GtkWidget *widget, gint *min, gint *nat) {
1339 GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
1340 if (GTK_IS_TREE_VIEW(child)) {
1341 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(child));
1342 int n_rows = gtk_tree_model_iter_n_children(model, NULL);
1343 int row_height = treeViewGetRowHeight(GTK_TREE_VIEW(child));
1345 *min = MAX(1, row_height);
1346 *nat = MAX(*min, n_rows * row_height);
1347 } else {
1348 GTK_WIDGET_CLASS(small_scroller_parent_class)->get_preferred_height(widget, min, nat);
1349 if (*min > 1)
1350 *min = 1;
1353 #else
1354 static void small_scroller_size_request(GtkWidget *widget, GtkRequisition *req) {
1355 GTK_WIDGET_CLASS(small_scroller_parent_class)->size_request(widget, req);
1356 req->height = 1;
1358 #endif
1360 static void small_scroller_class_init(SmallScrollerClass *klass) {
1361 #if GTK_CHECK_VERSION(3,0,0)
1362 GTK_WIDGET_CLASS(klass)->get_preferred_height = small_scroller_get_preferred_height;
1363 #else
1364 GTK_WIDGET_CLASS(klass)->size_request = small_scroller_size_request;
1365 #endif
1368 static void small_scroller_init(SmallScroller *){}
1370 static gboolean ButtonPress(GtkWidget *, GdkEventButton* ev, gpointer p) {
1371 try {
1372 ListBoxX* lb = static_cast<ListBoxX*>(p);
1373 if (ev->type == GDK_2BUTTON_PRESS && lb->doubleClickAction != NULL) {
1374 lb->doubleClickAction(lb->doubleClickActionData);
1375 return TRUE;
1378 } catch (...) {
1379 // No pointer back to Scintilla to save status
1381 return FALSE;
1384 /* Change the active color to the selected color so the listbox uses the color
1385 scheme that it would use if it had the focus. */
1386 static void StyleSet(GtkWidget *w, GtkStyle*, void*) {
1388 g_return_if_fail(w != NULL);
1390 /* Copy the selected color to active. Note that the modify calls will cause
1391 recursive calls to this function after the value is updated and w->style to
1392 be set to a new object */
1394 #if GTK_CHECK_VERSION(3,16,0)
1395 // On recent releases of GTK+, it does not appear necessary to set the list box colours.
1396 // This may be because of common themes and may be needed with other themes.
1397 // The *override* calls are deprecated now, so only call them for older versions of GTK+.
1398 #elif GTK_CHECK_VERSION(3,0,0)
1399 GtkStyleContext *styleContext = gtk_widget_get_style_context(w);
1400 if (styleContext == NULL)
1401 return;
1403 GdkRGBA colourForeSelected;
1404 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourForeSelected);
1405 GdkRGBA colourForeActive;
1406 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourForeActive);
1407 if (!gdk_rgba_equal(&colourForeSelected, &colourForeActive))
1408 gtk_widget_override_color(w, GTK_STATE_FLAG_ACTIVE, &colourForeSelected);
1410 styleContext = gtk_widget_get_style_context(w);
1411 if (styleContext == NULL)
1412 return;
1414 GdkRGBA colourBaseSelected;
1415 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourBaseSelected);
1416 GdkRGBA colourBaseActive;
1417 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourBaseActive);
1418 if (!gdk_rgba_equal(&colourBaseSelected, &colourBaseActive))
1419 gtk_widget_override_background_color(w, GTK_STATE_FLAG_ACTIVE, &colourBaseSelected);
1420 #else
1421 GtkStyle *style = gtk_widget_get_style(w);
1422 if (style == NULL)
1423 return;
1424 if (!gdk_color_equal(&style->base[GTK_STATE_SELECTED], &style->base[GTK_STATE_ACTIVE]))
1425 gtk_widget_modify_base(w, GTK_STATE_ACTIVE, &style->base[GTK_STATE_SELECTED]);
1426 style = gtk_widget_get_style(w);
1427 if (style == NULL)
1428 return;
1429 if (!gdk_color_equal(&style->text[GTK_STATE_SELECTED], &style->text[GTK_STATE_ACTIVE]))
1430 gtk_widget_modify_text(w, GTK_STATE_ACTIVE, &style->text[GTK_STATE_SELECTED]);
1431 #endif
1434 void ListBoxX::Create(Window &parent, int, Point, int, bool, int) {
1435 if (widCached != 0) {
1436 wid = widCached;
1437 return;
1440 #if GTK_CHECK_VERSION(3,0,0)
1441 if (!cssProvider) {
1442 cssProvider = gtk_css_provider_new();
1444 #endif
1446 wid = widCached = gtk_window_new(GTK_WINDOW_POPUP);
1448 frame = gtk_frame_new(NULL);
1449 gtk_widget_show(PWidget(frame));
1450 gtk_container_add(GTK_CONTAINER(GetID()), PWidget(frame));
1451 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1452 gtk_container_set_border_width(GTK_CONTAINER(frame), 0);
1454 scroller = g_object_new(small_scroller_get_type(), NULL);
1455 gtk_container_set_border_width(GTK_CONTAINER(scroller), 0);
1456 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
1457 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1458 gtk_container_add(GTK_CONTAINER(frame), PWidget(scroller));
1459 gtk_widget_show(PWidget(scroller));
1461 /* Tree and its model */
1462 GtkListStore *store =
1463 gtk_list_store_new(N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING);
1465 list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1466 g_signal_connect(G_OBJECT(list), "style-set", G_CALLBACK(StyleSet), NULL);
1468 #if GTK_CHECK_VERSION(3,0,0)
1469 GtkStyleContext *styleContext = gtk_widget_get_style_context(GTK_WIDGET(list));
1470 if (styleContext) {
1471 gtk_style_context_add_provider(styleContext, GTK_STYLE_PROVIDER(cssProvider),
1472 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1474 #endif
1476 GtkTreeSelection *selection =
1477 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1478 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1479 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
1480 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list), FALSE);
1482 /* Columns */
1483 GtkTreeViewColumn *column = gtk_tree_view_column_new();
1484 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1485 gtk_tree_view_column_set_title(column, "Autocomplete");
1487 pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
1488 gtk_cell_renderer_set_fixed_size(pixbuf_renderer, 0, -1);
1489 gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE);
1490 gtk_tree_view_column_add_attribute(column, pixbuf_renderer,
1491 "pixbuf", PIXBUF_COLUMN);
1493 renderer = gtk_cell_renderer_text_new();
1494 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1495 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1496 gtk_tree_view_column_add_attribute(column, renderer,
1497 "text", TEXT_COLUMN);
1499 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
1500 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list), "fixed-height-mode"))
1501 g_object_set(G_OBJECT(list), "fixed-height-mode", TRUE, NULL);
1503 GtkWidget *widget = PWidget(list); // No code inside the G_OBJECT macro
1504 gtk_container_add(GTK_CONTAINER(PWidget(scroller)), widget);
1505 gtk_widget_show(widget);
1506 g_signal_connect(G_OBJECT(widget), "button_press_event",
1507 G_CALLBACK(ButtonPress), this);
1509 GtkWidget *top = gtk_widget_get_toplevel(static_cast<GtkWidget *>(parent.GetID()));
1510 gtk_window_set_transient_for(GTK_WINDOW(static_cast<GtkWidget *>(wid)),
1511 GTK_WINDOW(top));
1514 void ListBoxX::SetFont(Font &font) {
1515 // Only do for Pango font as there have been crashes for GDK fonts
1516 if (Created() && PFont(font)->pfd) {
1517 // Current font is Pango font
1518 #if GTK_CHECK_VERSION(3,0,0)
1519 if (cssProvider) {
1520 PangoFontDescription *pfd = PFont(font)->pfd;
1521 std::ostringstream ssFontSetting;
1522 ssFontSetting << "GtkTreeView, treeview { ";
1523 ssFontSetting << "font-family: " << pango_font_description_get_family(pfd) << "; ";
1524 ssFontSetting << "font-size:";
1525 ssFontSetting << static_cast<double>(pango_font_description_get_size(pfd)) / PANGO_SCALE;
1526 // On GTK < 3.21.0 the units are incorrectly parsed, so a font size in points
1527 // need to use the "px" unit. Normally we only get fonts in points here, so
1528 // don't bother to handle the case the font is actually in pixels on < 3.21.0.
1529 if (gtk_check_version(3, 21, 0) != NULL || // on < 3.21.0
1530 pango_font_description_get_size_is_absolute(pfd)) {
1531 ssFontSetting << "px; ";
1532 } else {
1533 ssFontSetting << "pt; ";
1535 ssFontSetting << "font-weight:"<< pango_font_description_get_weight(pfd) << "; ";
1536 ssFontSetting << "}";
1537 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(cssProvider),
1538 ssFontSetting.str().c_str(), -1, NULL);
1540 #else
1541 gtk_widget_modify_font(PWidget(list), PFont(font)->pfd);
1542 #endif
1543 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), -1);
1544 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1548 void ListBoxX::SetAverageCharWidth(int width) {
1549 aveCharWidth = width;
1552 void ListBoxX::SetVisibleRows(int rows) {
1553 desiredVisibleRows = rows;
1556 int ListBoxX::GetVisibleRows() const {
1557 return desiredVisibleRows;
1560 int ListBoxX::GetRowHeight() {
1561 return treeViewGetRowHeight(GTK_TREE_VIEW(list));
1564 PRectangle ListBoxX::GetDesiredRect() {
1565 // Before any size allocated pretend its 100 wide so not scrolled
1566 PRectangle rc(0, 0, 100, 100);
1567 if (wid) {
1568 int rows = Length();
1569 if ((rows == 0) || (rows > desiredVisibleRows))
1570 rows = desiredVisibleRows;
1572 GtkRequisition req;
1573 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1574 // returns reasonable values.
1575 #if GTK_CHECK_VERSION(3,0,0)
1576 gtk_widget_get_preferred_size(GTK_WIDGET(frame), NULL, &req);
1577 #else
1578 gtk_widget_size_request(GTK_WIDGET(frame), &req);
1579 #endif
1580 int height;
1582 // First calculate height of the clist for our desired visible
1583 // row count otherwise it tries to expand to the total # of rows
1584 // Get cell height
1585 int row_height = GetRowHeight();
1586 #if GTK_CHECK_VERSION(3,0,0)
1587 GtkStyleContext *styleContextFrame = gtk_widget_get_style_context(PWidget(frame));
1588 GtkStateFlags stateFlagsFrame = gtk_style_context_get_state(styleContextFrame);
1589 GtkBorder padding, border, border_border = { 0, 0, 0, 0 };
1590 gtk_style_context_get_padding(styleContextFrame, stateFlagsFrame, &padding);
1591 gtk_style_context_get_border(styleContextFrame, stateFlagsFrame, &border);
1593 # if GTK_CHECK_VERSION(3,20,0)
1594 // on GTK 3.20 the frame border is in a sub-node "border".
1595 // Unfortunately we need to be built against 3.20 to be able to support this, as it requires
1596 // new API.
1597 GtkStyleContext *styleContextFrameBorder = gtk_style_context_new();
1598 GtkWidgetPath *widget_path = gtk_widget_path_copy(gtk_style_context_get_path(styleContextFrame));
1599 gtk_widget_path_append_type(widget_path, GTK_TYPE_BORDER); // dummy type
1600 gtk_widget_path_iter_set_object_name(widget_path, -1, "border");
1601 gtk_style_context_set_path(styleContextFrameBorder, widget_path);
1602 gtk_widget_path_free(widget_path);
1603 gtk_style_context_get_border(styleContextFrameBorder, stateFlagsFrame, &border_border);
1604 g_object_unref(styleContextFrameBorder);
1605 # else // < 3.20
1606 if (gtk_check_version(3, 20, 0) == NULL) {
1607 // default to 1px all around as it's likely what it is, and so we don't miss 2px height
1608 // on GTK 3.20 when built against an earlier version.
1609 border_border.top = border_border.bottom = border_border.left = border_border.right = 1;
1611 # endif
1613 height = (rows * row_height
1614 + padding.top + padding.bottom
1615 + border.top + border.bottom
1616 + border_border.top + border_border.bottom
1617 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
1618 #else
1619 height = (rows * row_height
1620 + 2 * (PWidget(frame)->style->ythickness
1621 + GTK_CONTAINER(PWidget(list))->border_width));
1622 #endif
1623 rc.bottom = height;
1625 int width = maxItemCharacters;
1626 if (width < 12)
1627 width = 12;
1628 rc.right = width * (aveCharWidth + aveCharWidth / 3);
1629 // Add horizontal padding and borders
1630 int horizontal_separator=0;
1631 gtk_widget_style_get(PWidget(list),
1632 "horizontal-separator", &horizontal_separator, NULL);
1633 rc.right += horizontal_separator;
1634 #if GTK_CHECK_VERSION(3,0,0)
1635 rc.right += (padding.left + padding.right
1636 + border.left + border.right
1637 + border_border.left + border_border.right
1638 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
1639 #else
1640 rc.right += 2 * (PWidget(frame)->style->xthickness
1641 + GTK_CONTAINER(PWidget(list))->border_width);
1642 #endif
1643 if (Length() > rows) {
1644 // Add the width of the scrollbar
1645 GtkWidget *vscrollbar =
1646 gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(scroller));
1647 #if GTK_CHECK_VERSION(3,0,0)
1648 gtk_widget_get_preferred_size(vscrollbar, NULL, &req);
1649 #else
1650 gtk_widget_size_request(vscrollbar, &req);
1651 #endif
1652 rc.right += req.width;
1655 return rc;
1658 int ListBoxX::CaretFromEdge() {
1659 gint renderer_width, renderer_height;
1660 gtk_cell_renderer_get_fixed_size(pixbuf_renderer, &renderer_width,
1661 &renderer_height);
1662 return 4 + renderer_width;
1665 void ListBoxX::Clear() {
1666 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1667 gtk_list_store_clear(GTK_LIST_STORE(model));
1668 maxItemCharacters = 0;
1671 static void init_pixmap(ListImage *list_image) {
1672 if (list_image->rgba_data) {
1673 // Drop any existing pixmap/bitmap as data may have changed
1674 if (list_image->pixbuf)
1675 g_object_unref(list_image->pixbuf);
1676 list_image->pixbuf =
1677 gdk_pixbuf_new_from_data(list_image->rgba_data->Pixels(),
1678 GDK_COLORSPACE_RGB,
1679 TRUE,
1681 list_image->rgba_data->GetWidth(),
1682 list_image->rgba_data->GetHeight(),
1683 list_image->rgba_data->GetWidth() * 4,
1684 NULL,
1685 NULL);
1689 #define SPACING 5
1691 void ListBoxX::Append(char *s, int type) {
1692 ListImage *list_image = NULL;
1693 if ((type >= 0) && pixhash) {
1694 list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash
1695 , (gconstpointer) GINT_TO_POINTER(type)));
1697 GtkTreeIter iter;
1698 GtkListStore *store =
1699 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
1700 gtk_list_store_append(GTK_LIST_STORE(store), &iter);
1701 if (list_image) {
1702 if (NULL == list_image->pixbuf)
1703 init_pixmap(list_image);
1704 if (list_image->pixbuf) {
1705 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1706 PIXBUF_COLUMN, list_image->pixbuf,
1707 TEXT_COLUMN, s, -1);
1709 gint pixbuf_width = gdk_pixbuf_get_width(list_image->pixbuf);
1710 gint renderer_height, renderer_width;
1711 gtk_cell_renderer_get_fixed_size(pixbuf_renderer,
1712 &renderer_width, &renderer_height);
1713 if (pixbuf_width > renderer_width)
1714 gtk_cell_renderer_set_fixed_size(pixbuf_renderer,
1715 pixbuf_width, -1);
1716 } else {
1717 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1718 TEXT_COLUMN, s, -1);
1720 } else {
1721 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1722 TEXT_COLUMN, s, -1);
1724 size_t len = strlen(s);
1725 if (maxItemCharacters < len)
1726 maxItemCharacters = len;
1729 int ListBoxX::Length() {
1730 if (wid)
1731 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1732 (GTK_TREE_VIEW(list)), NULL);
1733 return 0;
1736 void ListBoxX::Select(int n) {
1737 GtkTreeIter iter;
1738 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1739 GtkTreeSelection *selection =
1740 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1742 if (n < 0) {
1743 gtk_tree_selection_unselect_all(selection);
1744 return;
1747 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
1748 if (valid) {
1749 gtk_tree_selection_select_iter(selection, &iter);
1751 // Move the scrollbar to show the selection.
1752 int total = Length();
1753 #if GTK_CHECK_VERSION(3,0,0)
1754 GtkAdjustment *adj =
1755 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list));
1756 #else
1757 GtkAdjustment *adj =
1758 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list));
1759 #endif
1760 gfloat value = ((gfloat)n / total) * (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_lower(adj))
1761 + gtk_adjustment_get_lower(adj) - gtk_adjustment_get_page_size(adj) / 2;
1762 // Get cell height
1763 int row_height = GetRowHeight();
1765 int rows = Length();
1766 if ((rows == 0) || (rows > desiredVisibleRows))
1767 rows = desiredVisibleRows;
1768 if (rows & 0x1) {
1769 // Odd rows to display -- We are now in the middle.
1770 // Align it so that we don't chop off rows.
1771 value += (gfloat)row_height / 2.0;
1773 // Clamp it.
1774 value = (value < 0)? 0 : value;
1775 value = (value > (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)))?
1776 (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)) : value;
1778 // Set it.
1779 gtk_adjustment_set_value(adj, value);
1780 } else {
1781 gtk_tree_selection_unselect_all(selection);
1785 int ListBoxX::GetSelection() {
1786 int index = -1;
1787 GtkTreeIter iter;
1788 GtkTreeModel *model;
1789 GtkTreeSelection *selection;
1790 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1791 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1792 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1793 int *indices = gtk_tree_path_get_indices(path);
1794 // Don't free indices.
1795 if (indices)
1796 index = indices[0];
1797 gtk_tree_path_free(path);
1799 return index;
1802 int ListBoxX::Find(const char *prefix) {
1803 GtkTreeIter iter;
1804 GtkTreeModel *model =
1805 gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1806 bool valid = gtk_tree_model_get_iter_first(model, &iter) != FALSE;
1807 int i = 0;
1808 while(valid) {
1809 gchar *s;
1810 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &s, -1);
1811 if (s && (0 == strncmp(prefix, s, strlen(prefix)))) {
1812 g_free(s);
1813 return i;
1815 g_free(s);
1816 valid = gtk_tree_model_iter_next(model, &iter) != FALSE;
1817 i++;
1819 return -1;
1822 void ListBoxX::GetValue(int n, char *value, int len) {
1823 char *text = NULL;
1824 GtkTreeIter iter;
1825 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1826 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
1827 if (valid) {
1828 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &text, -1);
1830 if (text && len > 0) {
1831 g_strlcpy(value, text, len);
1832 } else {
1833 value[0] = '\0';
1835 g_free(text);
1838 // g_return_if_fail causes unnecessary compiler warning in release compile.
1839 #ifdef _MSC_VER
1840 #pragma warning(disable: 4127)
1841 #endif
1843 void ListBoxX::RegisterRGBA(int type, RGBAImage *image) {
1844 images.Add(type, image);
1846 if (!pixhash) {
1847 pixhash = g_hash_table_new(g_direct_hash, g_direct_equal);
1849 ListImage *list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash,
1850 (gconstpointer) GINT_TO_POINTER(type)));
1851 if (list_image) {
1852 // Drop icon already registered
1853 if (list_image->pixbuf)
1854 g_object_unref(list_image->pixbuf);
1855 list_image->pixbuf = NULL;
1856 list_image->rgba_data = image;
1857 } else {
1858 list_image = g_new0(ListImage, 1);
1859 list_image->rgba_data = image;
1860 g_hash_table_insert((GHashTable *) pixhash, GINT_TO_POINTER(type),
1861 (gpointer) list_image);
1865 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
1866 g_return_if_fail(xpm_data);
1867 XPM xpmImage(xpm_data);
1868 RegisterRGBA(type, new RGBAImage(xpmImage));
1871 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
1872 RegisterRGBA(type, new RGBAImage(width, height, 1.0, pixelsImage));
1875 void ListBoxX::ClearRegisteredImages() {
1876 images.Clear();
1879 void ListBoxX::SetList(const char *listText, char separator, char typesep) {
1880 Clear();
1881 int count = strlen(listText) + 1;
1882 std::vector<char> words(listText, listText+count);
1883 char *startword = &words[0];
1884 char *numword = NULL;
1885 int i = 0;
1886 for (; words[i]; i++) {
1887 if (words[i] == separator) {
1888 words[i] = '\0';
1889 if (numword)
1890 *numword = '\0';
1891 Append(startword, numword?atoi(numword + 1):-1);
1892 startword = &words[0] + i + 1;
1893 numword = NULL;
1894 } else if (words[i] == typesep) {
1895 numword = &words[0] + i;
1898 if (startword) {
1899 if (numword)
1900 *numword = '\0';
1901 Append(startword, numword?atoi(numword + 1):-1);
1905 Menu::Menu() : mid(0) {}
1907 void Menu::CreatePopUp() {
1908 Destroy();
1909 mid = gtk_menu_new();
1910 g_object_ref_sink(G_OBJECT(mid));
1913 void Menu::Destroy() {
1914 if (mid)
1915 g_object_unref(G_OBJECT(mid));
1916 mid = 0;
1919 #if !GTK_CHECK_VERSION(3,22,0)
1920 static void MenuPositionFunc(GtkMenu *, gint *x, gint *y, gboolean *, gpointer userData) {
1921 sptr_t intFromPointer = GPOINTER_TO_INT(userData);
1922 *x = intFromPointer & 0xffff;
1923 *y = intFromPointer >> 16;
1925 #endif
1927 void Menu::Show(Point pt, Window &w) {
1928 GtkMenu *widget = static_cast<GtkMenu *>(mid);
1929 gtk_widget_show_all(GTK_WIDGET(widget));
1930 #if GTK_CHECK_VERSION(3,22,0)
1931 // Rely on GTK+ to do the right thing with positioning
1932 gtk_menu_popup_at_pointer(widget, NULL);
1933 #else
1934 GdkRectangle rcMonitor = MonitorRectangleForWidget(PWidget(w.GetID()));
1935 GtkRequisition requisition;
1936 #if GTK_CHECK_VERSION(3,0,0)
1937 gtk_widget_get_preferred_size(GTK_WIDGET(widget), NULL, &requisition);
1938 #else
1939 gtk_widget_size_request(GTK_WIDGET(widget), &requisition);
1940 #endif
1941 if ((pt.x + requisition.width) > rcMonitor.x + rcMonitor.width) {
1942 pt.x = rcMonitor.x + rcMonitor.width - requisition.width;
1944 if ((pt.y + requisition.height) > rcMonitor.y + rcMonitor.height) {
1945 pt.y = rcMonitor.y + rcMonitor.height - requisition.height;
1947 gtk_menu_popup(widget, NULL, NULL, MenuPositionFunc,
1948 GINT_TO_POINTER((static_cast<int>(pt.y) << 16) | static_cast<int>(pt.x)), 0,
1949 gtk_get_current_event_time());
1950 #endif
1953 ElapsedTime::ElapsedTime() {
1954 GTimeVal curTime;
1955 g_get_current_time(&curTime);
1956 bigBit = curTime.tv_sec;
1957 littleBit = curTime.tv_usec;
1960 class DynamicLibraryImpl : public DynamicLibrary {
1961 protected:
1962 GModule* m;
1963 public:
1964 explicit DynamicLibraryImpl(const char *modulePath) {
1965 m = g_module_open(modulePath, G_MODULE_BIND_LAZY);
1968 ~DynamicLibraryImpl() override {
1969 if (m != NULL)
1970 g_module_close(m);
1973 // Use g_module_symbol to get a pointer to the relevant function.
1974 Function FindFunction(const char *name) override {
1975 if (m != NULL) {
1976 gpointer fn_address = NULL;
1977 gboolean status = g_module_symbol(m, name, &fn_address);
1978 if (status)
1979 return static_cast<Function>(fn_address);
1980 else
1981 return NULL;
1982 } else {
1983 return NULL;
1987 bool IsValid() override {
1988 return m != NULL;
1992 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
1993 return static_cast<DynamicLibrary *>( new DynamicLibraryImpl(modulePath) );
1996 double ElapsedTime::Duration(bool reset) {
1997 GTimeVal curTime;
1998 g_get_current_time(&curTime);
1999 long endBigBit = curTime.tv_sec;
2000 long endLittleBit = curTime.tv_usec;
2001 double result = 1000000.0 * (endBigBit - bigBit);
2002 result += endLittleBit - littleBit;
2003 result /= 1000000.0;
2004 if (reset) {
2005 bigBit = endBigBit;
2006 littleBit = endLittleBit;
2008 return result;
2011 ColourDesired Platform::Chrome() {
2012 return ColourDesired(0xe0, 0xe0, 0xe0);
2015 ColourDesired Platform::ChromeHighlight() {
2016 return ColourDesired(0xff, 0xff, 0xff);
2019 const char *Platform::DefaultFont() {
2020 #ifdef G_OS_WIN32
2021 return "Lucida Console";
2022 #else
2023 return "!Sans";
2024 #endif
2027 int Platform::DefaultFontSize() {
2028 #ifdef G_OS_WIN32
2029 return 10;
2030 #else
2031 return 12;
2032 #endif
2035 unsigned int Platform::DoubleClickTime() {
2036 return 500; // Half a second
2039 bool Platform::MouseButtonBounce() {
2040 return true;
2043 void Platform::DebugDisplay(const char *s) {
2044 fprintf(stderr, "%s", s);
2047 bool Platform::IsKeyDown(int) {
2048 // TODO: discover state of keys in GTK+/X
2049 return false;
2052 long Platform::SendScintilla(
2053 WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
2054 return scintilla_send_message(SCINTILLA(w), msg, wParam, lParam);
2057 long Platform::SendScintillaPointer(
2058 WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
2059 return scintilla_send_message(SCINTILLA(w), msg, wParam,
2060 reinterpret_cast<sptr_t>(lParam));
2063 bool Platform::IsDBCSLeadByte(int codePage, char ch) {
2064 // Byte ranges found in Wikipedia articles with relevant search strings in each case
2065 unsigned char uch = static_cast<unsigned char>(ch);
2066 switch (codePage) {
2067 case 932:
2068 // Shift_jis
2069 return ((uch >= 0x81) && (uch <= 0x9F)) ||
2070 ((uch >= 0xE0) && (uch <= 0xFC));
2071 // Lead bytes F0 to FC may be a Microsoft addition.
2072 case 936:
2073 // GBK
2074 return (uch >= 0x81) && (uch <= 0xFE);
2075 case 950:
2076 // Big5
2077 return (uch >= 0x81) && (uch <= 0xFE);
2078 // Korean EUC-KR may be code page 949.
2080 return false;
2083 int Platform::DBCSCharLength(int codePage, const char *s) {
2084 if (codePage == 932 || codePage == 936 || codePage == 950) {
2085 return IsDBCSLeadByte(codePage, s[0]) ? 2 : 1;
2086 } else {
2087 int bytes = mblen(s, MB_CUR_MAX);
2088 if (bytes >= 1)
2089 return bytes;
2090 else
2091 return 1;
2095 int Platform::DBCSCharMaxLength() {
2096 return MB_CUR_MAX;
2097 //return 2;
2100 // These are utility functions not really tied to a platform
2102 int Platform::Minimum(int a, int b) {
2103 if (a < b)
2104 return a;
2105 else
2106 return b;
2109 int Platform::Maximum(int a, int b) {
2110 if (a > b)
2111 return a;
2112 else
2113 return b;
2116 //#define TRACE
2118 #ifdef TRACE
2119 void Platform::DebugPrintf(const char *format, ...) {
2120 char buffer[2000];
2121 va_list pArguments;
2122 va_start(pArguments, format);
2123 vsprintf(buffer, format, pArguments);
2124 va_end(pArguments);
2125 Platform::DebugDisplay(buffer);
2127 #else
2128 void Platform::DebugPrintf(const char *, ...) {}
2130 #endif
2132 // Not supported for GTK+
2133 static bool assertionPopUps = true;
2135 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
2136 bool ret = assertionPopUps;
2137 assertionPopUps = assertionPopUps_;
2138 return ret;
2141 void Platform::Assert(const char *c, const char *file, int line) {
2142 char buffer[2000];
2143 g_snprintf(buffer, sizeof(buffer), "Assertion [%s] failed at %s %d\r\n", c, file, line);
2144 Platform::DebugDisplay(buffer);
2145 abort();
2148 int Platform::Clamp(int val, int minVal, int maxVal) {
2149 if (val > maxVal)
2150 val = maxVal;
2151 if (val < minVal)
2152 val = minVal;
2153 return val;
2156 void Platform_Initialise() {
2159 void Platform_Finalise() {