GTK: Fix auto-completion popup sizing code for GTK 3.20
[geany-mirror.git] / scintilla / gtk / PlatGTK.cxx
blob11aff4313ad100593eb1232583674be94310e93b
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 <stddef.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <stdio.h>
10 #include <math.h>
12 #include <string>
13 #include <vector>
14 #include <map>
15 #include <sstream>
17 #include <glib.h>
18 #include <gmodule.h>
19 #include <gdk/gdk.h>
20 #include <gtk/gtk.h>
21 #include <gdk/gdkkeysyms.h>
23 #include "Platform.h"
25 #include "Scintilla.h"
26 #include "ScintillaWidget.h"
27 #include "StringCopy.h"
28 #include "XPM.h"
29 #include "UniConversion.h"
31 #if defined(__clang__)
32 // Clang 3.0 incorrectly displays sentinel warnings. Fixed by clang 3.1.
33 #pragma GCC diagnostic ignored "-Wsentinel"
34 #endif
36 #include "Converter.h"
38 static const double kPi = 3.14159265358979323846;
40 // The Pango version guard for pango_units_from_double and pango_units_to_double
41 // is more complex than simply implementing these here.
43 static int pangoUnitsFromDouble(double d) {
44 return static_cast<int>(d * PANGO_SCALE + 0.5);
47 static double doubleFromPangoUnits(int pu) {
48 return static_cast<double>(pu) / PANGO_SCALE;
51 static cairo_surface_t *CreateSimilarSurface(GdkWindow *window, cairo_content_t content, int width, int height) {
52 #if GTK_CHECK_VERSION(2,22,0)
53 return gdk_window_create_similar_surface(window, content, width, height);
54 #else
55 cairo_surface_t *window_surface, *surface;
57 g_return_val_if_fail(GDK_IS_WINDOW(window), NULL);
59 window_surface = GDK_DRAWABLE_GET_CLASS(window)->ref_cairo_surface(window);
61 surface = cairo_surface_create_similar(window_surface, content, width, height);
63 cairo_surface_destroy(window_surface);
65 return surface;
66 #endif
69 static GdkWindow *WindowFromWidget(GtkWidget *w) {
70 return gtk_widget_get_window(w);
73 #ifdef _MSC_VER
74 // Ignore unreferenced local functions in GTK+ headers
75 #pragma warning(disable: 4505)
76 #endif
78 #ifdef SCI_NAMESPACE
79 using namespace Scintilla;
80 #endif
82 enum encodingType { singleByte, UTF8, dbcs};
84 // Holds a PangoFontDescription*.
85 class FontHandle {
86 public:
87 PangoFontDescription *pfd;
88 int characterSet;
89 FontHandle() : pfd(0), characterSet(-1) {
91 FontHandle(PangoFontDescription *pfd_, int characterSet_) {
92 pfd = pfd_;
93 characterSet = characterSet_;
95 ~FontHandle() {
96 if (pfd)
97 pango_font_description_free(pfd);
98 pfd = 0;
100 static FontHandle *CreateNewFont(const FontParameters &fp);
103 FontHandle *FontHandle::CreateNewFont(const FontParameters &fp) {
104 PangoFontDescription *pfd = pango_font_description_new();
105 if (pfd) {
106 pango_font_description_set_family(pfd,
107 (fp.faceName[0] == '!') ? fp.faceName+1 : fp.faceName);
108 pango_font_description_set_size(pfd, pangoUnitsFromDouble(fp.size));
109 pango_font_description_set_weight(pfd, static_cast<PangoWeight>(fp.weight));
110 pango_font_description_set_style(pfd, fp.italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
111 return new FontHandle(pfd,fp.characterSet);
114 return NULL;
117 // X has a 16 bit coordinate space, so stop drawing here to avoid wrapping
118 static const int maxCoordinate = 32000;
120 static FontHandle *PFont(Font &f) {
121 return static_cast<FontHandle *>(f.GetID());
124 static GtkWidget *PWidget(WindowID wid) {
125 return static_cast<GtkWidget *>(wid);
128 Point Point::FromLong(long lpoint) {
129 return Point(
130 Platform::LowShortFromLong(lpoint),
131 Platform::HighShortFromLong(lpoint));
134 Font::Font() : fid(0) {}
136 Font::~Font() {}
138 void Font::Create(const FontParameters &fp) {
139 Release();
140 fid = FontHandle::CreateNewFont(fp);
143 void Font::Release() {
144 if (fid)
145 delete static_cast<FontHandle *>(fid);
146 fid = 0;
149 // Required on OS X
150 #ifdef SCI_NAMESPACE
151 namespace Scintilla {
152 #endif
154 // SurfaceID is a cairo_t*
155 class SurfaceImpl : public Surface {
156 encodingType et;
157 cairo_t *context;
158 cairo_surface_t *psurf;
159 int x;
160 int y;
161 bool inited;
162 bool createdGC;
163 PangoContext *pcontext;
164 PangoLayout *layout;
165 Converter conv;
166 int characterSet;
167 void SetConverter(int characterSet_);
168 public:
169 SurfaceImpl();
170 virtual ~SurfaceImpl();
172 void Init(WindowID wid);
173 void Init(SurfaceID sid, WindowID wid);
174 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
176 void Release();
177 bool Initialised();
178 void PenColour(ColourDesired fore);
179 int LogPixelsY();
180 int DeviceHeightFont(int points);
181 void MoveTo(int x_, int y_);
182 void LineTo(int x_, int y_);
183 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back);
184 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back);
185 void FillRectangle(PRectangle rc, ColourDesired back);
186 void FillRectangle(PRectangle rc, Surface &surfacePattern);
187 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back);
188 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
189 ColourDesired outline, int alphaOutline, int flags);
190 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage);
191 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back);
192 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
194 void DrawTextBase(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
195 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
196 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
197 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
198 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions);
199 XYPOSITION WidthText(Font &font_, const char *s, int len);
200 XYPOSITION WidthChar(Font &font_, char ch);
201 XYPOSITION Ascent(Font &font_);
202 XYPOSITION Descent(Font &font_);
203 XYPOSITION InternalLeading(Font &font_);
204 XYPOSITION ExternalLeading(Font &font_);
205 XYPOSITION Height(Font &font_);
206 XYPOSITION AverageCharWidth(Font &font_);
208 void SetClip(PRectangle rc);
209 void FlushCachedState();
211 void SetUnicodeMode(bool unicodeMode_);
212 void SetDBCSMode(int codePage);
214 #ifdef SCI_NAMESPACE
216 #endif
218 const char *CharacterSetID(int characterSet) {
219 switch (characterSet) {
220 case SC_CHARSET_ANSI:
221 return "";
222 case SC_CHARSET_DEFAULT:
223 return "ISO-8859-1";
224 case SC_CHARSET_BALTIC:
225 return "ISO-8859-13";
226 case SC_CHARSET_CHINESEBIG5:
227 return "BIG-5";
228 case SC_CHARSET_EASTEUROPE:
229 return "ISO-8859-2";
230 case SC_CHARSET_GB2312:
231 return "CP936";
232 case SC_CHARSET_GREEK:
233 return "ISO-8859-7";
234 case SC_CHARSET_HANGUL:
235 return "CP949";
236 case SC_CHARSET_MAC:
237 return "MACINTOSH";
238 case SC_CHARSET_OEM:
239 return "ASCII";
240 case SC_CHARSET_RUSSIAN:
241 return "KOI8-R";
242 case SC_CHARSET_OEM866:
243 return "CP866";
244 case SC_CHARSET_CYRILLIC:
245 return "CP1251";
246 case SC_CHARSET_SHIFTJIS:
247 return "SHIFT-JIS";
248 case SC_CHARSET_SYMBOL:
249 return "";
250 case SC_CHARSET_TURKISH:
251 return "ISO-8859-9";
252 case SC_CHARSET_JOHAB:
253 return "CP1361";
254 case SC_CHARSET_HEBREW:
255 return "ISO-8859-8";
256 case SC_CHARSET_ARABIC:
257 return "ISO-8859-6";
258 case SC_CHARSET_VIETNAMESE:
259 return "";
260 case SC_CHARSET_THAI:
261 return "ISO-8859-11";
262 case SC_CHARSET_8859_15:
263 return "ISO-8859-15";
264 default:
265 return "";
269 void SurfaceImpl::SetConverter(int characterSet_) {
270 if (characterSet != characterSet_) {
271 characterSet = characterSet_;
272 conv.Open("UTF-8", CharacterSetID(characterSet), false);
276 SurfaceImpl::SurfaceImpl() : et(singleByte),
277 context(0),
278 psurf(0),
279 x(0), y(0), inited(false), createdGC(false)
280 , pcontext(0), layout(0), characterSet(-1) {
283 SurfaceImpl::~SurfaceImpl() {
284 Release();
287 void SurfaceImpl::Release() {
288 et = singleByte;
289 if (createdGC) {
290 createdGC = false;
291 cairo_destroy(context);
293 context = 0;
294 if (psurf)
295 cairo_surface_destroy(psurf);
296 psurf = 0;
297 if (layout)
298 g_object_unref(layout);
299 layout = 0;
300 if (pcontext)
301 g_object_unref(pcontext);
302 pcontext = 0;
303 conv.Close();
304 characterSet = -1;
305 x = 0;
306 y = 0;
307 inited = false;
308 createdGC = false;
311 bool SurfaceImpl::Initialised() {
312 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 8, 0)
313 if (inited && context) {
314 if (cairo_status(context) == CAIRO_STATUS_SUCCESS) {
315 // Even when status is success, the target surface may have been
316 // finished whch may cause an assertion to fail crashing the application.
317 // The cairo_surface_has_show_text_glyphs call checks the finished flag
318 // and when set, sets the status to CAIRO_STATUS_SURFACE_FINISHED
319 // which leads to warning messages instead of crashes.
320 // Performing the check in this method as it is called rarely and has no
321 // other side effects.
322 cairo_surface_t *psurfContext = cairo_get_target(context);
323 if (psurfContext) {
324 cairo_surface_has_show_text_glyphs(psurfContext);
327 return cairo_status(context) == CAIRO_STATUS_SUCCESS;
329 #endif
330 return inited;
333 void SurfaceImpl::Init(WindowID wid) {
334 Release();
335 PLATFORM_ASSERT(wid);
336 // if we are only created from a window ID, we can't perform drawing
337 psurf = 0;
338 context = 0;
339 createdGC = false;
340 pcontext = gtk_widget_create_pango_context(PWidget(wid));
341 PLATFORM_ASSERT(pcontext);
342 layout = pango_layout_new(pcontext);
343 PLATFORM_ASSERT(layout);
344 inited = true;
347 void SurfaceImpl::Init(SurfaceID sid, WindowID wid) {
348 PLATFORM_ASSERT(sid);
349 Release();
350 PLATFORM_ASSERT(wid);
351 context = cairo_reference(static_cast<cairo_t *>(sid));
352 pcontext = gtk_widget_create_pango_context(PWidget(wid));
353 // update the Pango context in case sid isn't the widget's surface
354 pango_cairo_update_context(context, pcontext);
355 layout = pango_layout_new(pcontext);
356 cairo_set_line_width(context, 1);
357 createdGC = true;
358 inited = true;
361 void SurfaceImpl::InitPixMap(int width, int height, Surface *surface_, WindowID wid) {
362 PLATFORM_ASSERT(surface_);
363 Release();
364 SurfaceImpl *surfImpl = static_cast<SurfaceImpl *>(surface_);
365 PLATFORM_ASSERT(wid);
366 context = cairo_reference(surfImpl->context);
367 pcontext = gtk_widget_create_pango_context(PWidget(wid));
368 // update the Pango context in case surface_ isn't the widget's surface
369 pango_cairo_update_context(context, pcontext);
370 PLATFORM_ASSERT(pcontext);
371 layout = pango_layout_new(pcontext);
372 PLATFORM_ASSERT(layout);
373 if (height > 0 && width > 0)
374 psurf = CreateSimilarSurface(
375 WindowFromWidget(PWidget(wid)),
376 CAIRO_CONTENT_COLOR_ALPHA, width, height);
377 cairo_destroy(context);
378 context = cairo_create(psurf);
379 cairo_rectangle(context, 0, 0, width, height);
380 cairo_set_source_rgb(context, 1.0, 0, 0);
381 cairo_fill(context);
382 // This produces sharp drawing more similar to GDK:
383 //cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
384 cairo_set_line_width(context, 1);
385 createdGC = true;
386 inited = true;
387 et = surfImpl->et;
390 void SurfaceImpl::PenColour(ColourDesired fore) {
391 if (context) {
392 ColourDesired cdFore(fore.AsLong());
393 cairo_set_source_rgb(context,
394 cdFore.GetRed() / 255.0,
395 cdFore.GetGreen() / 255.0,
396 cdFore.GetBlue() / 255.0);
400 int SurfaceImpl::LogPixelsY() {
401 return 72;
404 int SurfaceImpl::DeviceHeightFont(int points) {
405 int logPix = LogPixelsY();
406 return (points * logPix + logPix / 2) / 72;
409 void SurfaceImpl::MoveTo(int x_, int y_) {
410 x = x_;
411 y = y_;
414 static int Delta(int difference) {
415 if (difference < 0)
416 return -1;
417 else if (difference > 0)
418 return 1;
419 else
420 return 0;
423 void SurfaceImpl::LineTo(int x_, int y_) {
424 // cairo_line_to draws the end position, unlike Win32 or GDK with GDK_CAP_NOT_LAST.
425 // For simple cases, move back one pixel from end.
426 if (context) {
427 int xDiff = x_ - x;
428 int xDelta = Delta(xDiff);
429 int yDiff = y_ - y;
430 int yDelta = Delta(yDiff);
431 if ((xDiff == 0) || (yDiff == 0)) {
432 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
433 int xEnd = x_ - xDelta;
434 int left = Platform::Minimum(x, xEnd);
435 int width = abs(x - xEnd) + 1;
436 int yEnd = y_ - yDelta;
437 int top = Platform::Minimum(y, yEnd);
438 int height = abs(y - yEnd) + 1;
439 cairo_rectangle(context, left, top, width, height);
440 cairo_fill(context);
441 } else if ((abs(xDiff) == abs(yDiff))) {
442 // 45 degree slope
443 cairo_move_to(context, x + 0.5, y + 0.5);
444 cairo_line_to(context, x_ + 0.5 - xDelta, y_ + 0.5 - yDelta);
445 } else {
446 // Line has a different slope so difficult to avoid last pixel
447 cairo_move_to(context, x + 0.5, y + 0.5);
448 cairo_line_to(context, x_ + 0.5, y_ + 0.5);
450 cairo_stroke(context);
452 x = x_;
453 y = y_;
456 void SurfaceImpl::Polygon(Point *pts, int npts, ColourDesired fore,
457 ColourDesired back) {
458 PLATFORM_ASSERT(context);
459 PenColour(back);
460 cairo_move_to(context, pts[0].x + 0.5, pts[0].y + 0.5);
461 for (int i = 1; i < npts; i++) {
462 cairo_line_to(context, pts[i].x + 0.5, pts[i].y + 0.5);
464 cairo_close_path(context);
465 cairo_fill_preserve(context);
466 PenColour(fore);
467 cairo_stroke(context);
470 void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
471 if (context) {
472 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5,
473 rc.right - rc.left - 1, rc.bottom - rc.top - 1);
474 PenColour(back);
475 cairo_fill_preserve(context);
476 PenColour(fore);
477 cairo_stroke(context);
481 void SurfaceImpl::FillRectangle(PRectangle rc, ColourDesired back) {
482 PenColour(back);
483 if (context && (rc.left < maxCoordinate)) { // Protect against out of range
484 rc.left = lround(rc.left);
485 rc.right = lround(rc.right);
486 cairo_rectangle(context, rc.left, rc.top,
487 rc.right - rc.left, rc.bottom - rc.top);
488 cairo_fill(context);
492 void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
493 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfacePattern);
494 bool canDraw = surfi.psurf != NULL;
495 if (canDraw) {
496 PLATFORM_ASSERT(context);
497 // Tile pattern over rectangle
498 // Currently assumes 8x8 pattern
499 int widthPat = 8;
500 int heightPat = 8;
501 for (int xTile = rc.left; xTile < rc.right; xTile += widthPat) {
502 int widthx = (xTile + widthPat > rc.right) ? rc.right - xTile : widthPat;
503 for (int yTile = rc.top; yTile < rc.bottom; yTile += heightPat) {
504 int heighty = (yTile + heightPat > rc.bottom) ? rc.bottom - yTile : heightPat;
505 cairo_set_source_surface(context, surfi.psurf, xTile, yTile);
506 cairo_rectangle(context, xTile, yTile, widthx, heighty);
507 cairo_fill(context);
510 } else {
511 // Something is wrong so try to show anyway
512 // Shows up black because colour not allocated
513 FillRectangle(rc, ColourDesired(0));
517 void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
518 if (((rc.right - rc.left) > 4) && ((rc.bottom - rc.top) > 4)) {
519 // Approximate a round rect with some cut off corners
520 Point pts[] = {
521 Point(rc.left + 2, rc.top),
522 Point(rc.right - 2, rc.top),
523 Point(rc.right, rc.top + 2),
524 Point(rc.right, rc.bottom - 2),
525 Point(rc.right - 2, rc.bottom),
526 Point(rc.left + 2, rc.bottom),
527 Point(rc.left, rc.bottom - 2),
528 Point(rc.left, rc.top + 2),
530 Polygon(pts, ELEMENTS(pts), fore, back);
531 } else {
532 RectangleDraw(rc, fore, back);
536 static void PathRoundRectangle(cairo_t *context, double left, double top, double width, double height, int radius) {
537 double degrees = kPi / 180.0;
539 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 2, 0)
540 cairo_new_sub_path(context);
541 #else
542 // First arc is in the top-right corner and starts from a point on the top line
543 cairo_move_to(context, left + width - radius, top);
544 #endif
545 cairo_arc(context, left + width - radius, top + radius, radius, -90 * degrees, 0 * degrees);
546 cairo_arc(context, left + width - radius, top + height - radius, radius, 0 * degrees, 90 * degrees);
547 cairo_arc(context, left + radius, top + height - radius, radius, 90 * degrees, 180 * degrees);
548 cairo_arc(context, left + radius, top + radius, radius, 180 * degrees, 270 * degrees);
549 cairo_close_path(context);
552 void SurfaceImpl::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
553 ColourDesired outline, int alphaOutline, int flags) {
554 if (context && rc.Width() > 0) {
555 ColourDesired cdFill(fill.AsLong());
556 cairo_set_source_rgba(context,
557 cdFill.GetRed() / 255.0,
558 cdFill.GetGreen() / 255.0,
559 cdFill.GetBlue() / 255.0,
560 alphaFill / 255.0);
561 if (cornerSize > 0)
562 PathRoundRectangle(context, rc.left + 1.0, rc.top + 1.0, rc.right - rc.left - 2.0, rc.bottom - rc.top - 2.0, cornerSize);
563 else
564 cairo_rectangle(context, rc.left + 1.0, rc.top + 1.0, rc.right - rc.left - 2.0, rc.bottom - rc.top - 2.0);
565 cairo_fill(context);
567 ColourDesired cdOutline(outline.AsLong());
568 cairo_set_source_rgba(context,
569 cdOutline.GetRed() / 255.0,
570 cdOutline.GetGreen() / 255.0,
571 cdOutline.GetBlue() / 255.0,
572 alphaOutline / 255.0);
573 if (cornerSize > 0)
574 PathRoundRectangle(context, rc.left + 0.5, rc.top + 0.5, rc.right - rc.left - 1, rc.bottom - rc.top - 1, cornerSize);
575 else
576 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5, rc.right - rc.left - 1, rc.bottom - rc.top - 1);
577 cairo_stroke(context);
581 void SurfaceImpl::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
582 PLATFORM_ASSERT(context);
583 if (rc.Width() > width)
584 rc.left += (rc.Width() - width) / 2;
585 rc.right = rc.left + width;
586 if (rc.Height() > height)
587 rc.top += (rc.Height() - height) / 2;
588 rc.bottom = rc.top + height;
590 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
591 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
592 #else
593 int stride = width * 4;
594 #endif
595 int ucs = stride * height;
596 std::vector<unsigned char> image(ucs);
597 for (int iy=0; iy<height; iy++) {
598 for (int ix=0; ix<width; ix++) {
599 unsigned char *pixel = &image[0] + iy*stride + ix * 4;
600 unsigned char alpha = pixelsImage[3];
601 pixel[2] = (*pixelsImage++) * alpha / 255;
602 pixel[1] = (*pixelsImage++) * alpha / 255;
603 pixel[0] = (*pixelsImage++) * alpha / 255;
604 pixel[3] = *pixelsImage++;
608 cairo_surface_t *psurfImage = cairo_image_surface_create_for_data(&image[0], CAIRO_FORMAT_ARGB32, width, height, stride);
609 cairo_set_source_surface(context, psurfImage, rc.left, rc.top);
610 cairo_rectangle(context, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
611 cairo_fill(context);
613 cairo_surface_destroy(psurfImage);
616 void SurfaceImpl::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
617 PLATFORM_ASSERT(context);
618 PenColour(back);
619 cairo_arc(context, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2,
620 Platform::Minimum(rc.Width(), rc.Height()) / 2, 0, 2*kPi);
621 cairo_fill_preserve(context);
622 PenColour(fore);
623 cairo_stroke(context);
626 void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
627 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfaceSource);
628 bool canDraw = surfi.psurf != NULL;
629 if (canDraw) {
630 PLATFORM_ASSERT(context);
631 cairo_set_source_surface(context, surfi.psurf,
632 rc.left - from.x, rc.top - from.y);
633 cairo_rectangle(context, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
634 cairo_fill(context);
638 std::string UTF8FromLatin1(const char *s, int len) {
639 std::string utfForm(len*2 + 1, '\0');
640 size_t lenU = 0;
641 for (int i=0; i<len; i++) {
642 unsigned int uch = static_cast<unsigned char>(s[i]);
643 if (uch < 0x80) {
644 utfForm[lenU++] = uch;
645 } else {
646 utfForm[lenU++] = static_cast<char>(0xC0 | (uch >> 6));
647 utfForm[lenU++] = static_cast<char>(0x80 | (uch & 0x3f));
650 utfForm.resize(lenU);
651 return utfForm;
654 static std::string UTF8FromIconv(const Converter &conv, const char *s, int len) {
655 if (conv) {
656 std::string utfForm(len*3+1, '\0');
657 char *pin = const_cast<char *>(s);
658 size_t inLeft = len;
659 char *putf = &utfForm[0];
660 char *pout = putf;
661 size_t outLeft = len*3+1;
662 size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
663 if (conversions != ((size_t)(-1))) {
664 *pout = '\0';
665 utfForm.resize(pout - putf);
666 return utfForm;
669 return std::string();
672 // Work out how many bytes are in a character by trying to convert using iconv,
673 // returning the first length that succeeds.
674 static size_t MultiByteLenFromIconv(const Converter &conv, const char *s, size_t len) {
675 for (size_t lenMB=1; (lenMB<4) && (lenMB <= len); lenMB++) {
676 char wcForm[2];
677 char *pin = const_cast<char *>(s);
678 size_t inLeft = lenMB;
679 char *pout = wcForm;
680 size_t outLeft = 2;
681 size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
682 if (conversions != ((size_t)(-1))) {
683 return lenMB;
686 return 1;
689 void SurfaceImpl::DrawTextBase(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
690 ColourDesired fore) {
691 PenColour(fore);
692 if (context) {
693 XYPOSITION xText = rc.left;
694 if (PFont(font_)->pfd) {
695 std::string utfForm;
696 if (et == UTF8) {
697 pango_layout_set_text(layout, s, len);
698 } else {
699 SetConverter(PFont(font_)->characterSet);
700 utfForm = UTF8FromIconv(conv, s, len);
701 if (utfForm.empty()) { // iconv failed so treat as Latin1
702 utfForm = UTF8FromLatin1(s, len);
704 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
706 pango_layout_set_font_description(layout, PFont(font_)->pfd);
707 pango_cairo_update_layout(context, layout);
708 #ifdef PANGO_VERSION
709 PangoLayoutLine *pll = pango_layout_get_line_readonly(layout,0);
710 #else
711 PangoLayoutLine *pll = pango_layout_get_line(layout,0);
712 #endif
713 cairo_move_to(context, xText, ybase);
714 pango_cairo_show_layout_line(context, pll);
719 void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
720 ColourDesired fore, ColourDesired back) {
721 FillRectangle(rc, back);
722 DrawTextBase(rc, font_, ybase, s, len, fore);
725 // On GTK+, exactly same as DrawTextNoClip
726 void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
727 ColourDesired fore, ColourDesired back) {
728 FillRectangle(rc, back);
729 DrawTextBase(rc, font_, ybase, s, len, fore);
732 void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
733 ColourDesired fore) {
734 // Avoid drawing spaces in transparent mode
735 for (int i=0; i<len; i++) {
736 if (s[i] != ' ') {
737 DrawTextBase(rc, font_, ybase, s, len, fore);
738 return;
743 class ClusterIterator {
744 PangoLayoutIter *iter;
745 PangoRectangle pos;
746 int lenPositions;
747 public:
748 bool finished;
749 XYPOSITION positionStart;
750 XYPOSITION position;
751 XYPOSITION distance;
752 int curIndex;
753 ClusterIterator(PangoLayout *layout, int len) : lenPositions(len), finished(false),
754 positionStart(0), position(0), distance(0), curIndex(0) {
755 iter = pango_layout_get_iter(layout);
756 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
758 ~ClusterIterator() {
759 pango_layout_iter_free(iter);
762 void Next() {
763 positionStart = position;
764 if (pango_layout_iter_next_cluster(iter)) {
765 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
766 position = doubleFromPangoUnits(pos.x);
767 curIndex = pango_layout_iter_get_index(iter);
768 } else {
769 finished = true;
770 position = doubleFromPangoUnits(pos.x + pos.width);
771 curIndex = lenPositions;
773 distance = position - positionStart;
777 void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
778 if (font_.GetID()) {
779 const int lenPositions = len;
780 if (PFont(font_)->pfd) {
781 pango_layout_set_font_description(layout, PFont(font_)->pfd);
782 if (et == UTF8) {
783 // Simple and direct as UTF-8 is native Pango encoding
784 int i = 0;
785 pango_layout_set_text(layout, s, len);
786 ClusterIterator iti(layout, lenPositions);
787 while (!iti.finished) {
788 iti.Next();
789 int places = iti.curIndex - i;
790 while (i < iti.curIndex) {
791 // Evenly distribute space among bytes of this cluster.
792 // Would be better to find number of characters and then
793 // divide evenly between characters with each byte of a character
794 // being at the same position.
795 positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places;
796 i++;
799 PLATFORM_ASSERT(i == lenPositions);
800 } else {
801 int positionsCalculated = 0;
802 if (et == dbcs) {
803 SetConverter(PFont(font_)->characterSet);
804 std::string utfForm = UTF8FromIconv(conv, s, len);
805 if (!utfForm.empty()) {
806 // Convert to UTF-8 so can ask Pango for widths, then
807 // Loop through UTF-8 and DBCS forms, taking account of different
808 // character byte lengths.
809 Converter convMeasure("UCS-2", CharacterSetID(characterSet), false);
810 pango_layout_set_text(layout, utfForm.c_str(), strlen(utfForm.c_str()));
811 int i = 0;
812 int clusterStart = 0;
813 ClusterIterator iti(layout, strlen(utfForm.c_str()));
814 while (!iti.finished) {
815 iti.Next();
816 int clusterEnd = iti.curIndex;
817 int places = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
818 int place = 1;
819 while (clusterStart < clusterEnd) {
820 size_t lenChar = MultiByteLenFromIconv(convMeasure, s+i, len-i);
821 while (lenChar--) {
822 positions[i++] = iti.position - (places - place) * iti.distance / places;
823 positionsCalculated++;
825 clusterStart += UTF8CharLength(static_cast<unsigned char>(utfForm.c_str()[clusterStart]));
826 place++;
829 PLATFORM_ASSERT(i == lenPositions);
832 if (positionsCalculated < 1 ) {
833 // Either 8-bit or DBCS conversion failed so treat as 8-bit.
834 SetConverter(PFont(font_)->characterSet);
835 const bool rtlCheck = PFont(font_)->characterSet == SC_CHARSET_HEBREW ||
836 PFont(font_)->characterSet == SC_CHARSET_ARABIC;
837 std::string utfForm = UTF8FromIconv(conv, s, len);
838 if (utfForm.empty()) {
839 utfForm = UTF8FromLatin1(s, len);
841 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
842 int i = 0;
843 int clusterStart = 0;
844 // Each 8-bit input character may take 1 or 2 bytes in UTF-8
845 // and groups of up to 3 may be represented as ligatures.
846 ClusterIterator iti(layout, utfForm.length());
847 while (!iti.finished) {
848 iti.Next();
849 int clusterEnd = iti.curIndex;
850 int ligatureLength = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
851 if (rtlCheck && ((clusterEnd <= clusterStart) || (ligatureLength == 0) || (ligatureLength > 3))) {
852 // Something has gone wrong: exit quickly but pretend all the characters are equally spaced:
853 int widthLayout = 0;
854 pango_layout_get_size(layout, &widthLayout, NULL);
855 XYPOSITION widthTotal = doubleFromPangoUnits(widthLayout);
856 for (int bytePos=0; bytePos<lenPositions; bytePos++) {
857 positions[bytePos] = widthTotal / lenPositions * (bytePos + 1);
859 return;
861 PLATFORM_ASSERT(ligatureLength > 0 && ligatureLength <= 3);
862 for (int charInLig=0; charInLig<ligatureLength; charInLig++) {
863 positions[i++] = iti.position - (ligatureLength - 1 - charInLig) * iti.distance / ligatureLength;
865 clusterStart = clusterEnd;
867 while (i < lenPositions) {
868 // If something failed, fill in rest of the positions
869 positions[i++] = clusterStart;
871 PLATFORM_ASSERT(i == lenPositions);
875 } else {
876 // No font so return an ascending range of values
877 for (int i = 0; i < len; i++) {
878 positions[i] = i + 1;
883 XYPOSITION SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
884 if (font_.GetID()) {
885 if (PFont(font_)->pfd) {
886 std::string utfForm;
887 pango_layout_set_font_description(layout, PFont(font_)->pfd);
888 PangoRectangle pos;
889 if (et == UTF8) {
890 pango_layout_set_text(layout, s, len);
891 } else {
892 SetConverter(PFont(font_)->characterSet);
893 utfForm = UTF8FromIconv(conv, s, len);
894 if (utfForm.empty()) { // iconv failed so treat as Latin1
895 utfForm = UTF8FromLatin1(s, len);
897 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
899 #ifdef PANGO_VERSION
900 PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout,0);
901 #else
902 PangoLayoutLine *pangoLine = pango_layout_get_line(layout,0);
903 #endif
904 pango_layout_line_get_extents(pangoLine, NULL, &pos);
905 return doubleFromPangoUnits(pos.width);
907 return 1;
908 } else {
909 return 1;
913 XYPOSITION SurfaceImpl::WidthChar(Font &font_, char ch) {
914 if (font_.GetID()) {
915 if (PFont(font_)->pfd) {
916 return WidthText(font_, &ch, 1);
918 return 1;
919 } else {
920 return 1;
924 // Ascent and descent determined by Pango font metrics.
926 XYPOSITION SurfaceImpl::Ascent(Font &font_) {
927 if (!(font_.GetID()))
928 return 1;
929 int ascent = 0;
930 if (PFont(font_)->pfd) {
931 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
932 PFont(font_)->pfd, pango_context_get_language(pcontext));
933 ascent =
934 doubleFromPangoUnits(pango_font_metrics_get_ascent(metrics));
935 pango_font_metrics_unref(metrics);
937 if (ascent == 0) {
938 ascent = 1;
940 return ascent;
943 XYPOSITION SurfaceImpl::Descent(Font &font_) {
944 if (!(font_.GetID()))
945 return 1;
946 if (PFont(font_)->pfd) {
947 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
948 PFont(font_)->pfd, pango_context_get_language(pcontext));
949 int descent = doubleFromPangoUnits(pango_font_metrics_get_descent(metrics));
950 pango_font_metrics_unref(metrics);
951 return descent;
953 return 0;
956 XYPOSITION SurfaceImpl::InternalLeading(Font &) {
957 return 0;
960 XYPOSITION SurfaceImpl::ExternalLeading(Font &) {
961 return 0;
964 XYPOSITION SurfaceImpl::Height(Font &font_) {
965 return Ascent(font_) + Descent(font_);
968 XYPOSITION SurfaceImpl::AverageCharWidth(Font &font_) {
969 return WidthChar(font_, 'n');
972 void SurfaceImpl::SetClip(PRectangle rc) {
973 PLATFORM_ASSERT(context);
974 cairo_rectangle(context, rc.left, rc.top, rc.right, rc.bottom);
975 cairo_clip(context);
978 void SurfaceImpl::FlushCachedState() {}
980 void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
981 if (unicodeMode_)
982 et = UTF8;
985 void SurfaceImpl::SetDBCSMode(int codePage) {
986 if (codePage && (codePage != SC_CP_UTF8))
987 et = dbcs;
990 Surface *Surface::Allocate(int) {
991 return new SurfaceImpl();
994 Window::~Window() {}
996 void Window::Destroy() {
997 if (wid) {
998 ListBox *listbox = dynamic_cast<ListBox*>(this);
999 if (listbox) {
1000 gtk_widget_hide(GTK_WIDGET(wid));
1001 // clear up window content
1002 listbox->Clear();
1003 // resize the window to the smallest possible size for it to adapt
1004 // to future content
1005 gtk_window_resize(GTK_WINDOW(wid), 1, 1);
1006 } else {
1007 gtk_widget_destroy(GTK_WIDGET(wid));
1009 wid = 0;
1013 bool Window::HasFocus() {
1014 return gtk_widget_has_focus(GTK_WIDGET(wid));
1017 PRectangle Window::GetPosition() {
1018 // Before any size allocated pretend its 1000 wide so not scrolled
1019 PRectangle rc(0, 0, 1000, 1000);
1020 if (wid) {
1021 GtkAllocation allocation;
1022 gtk_widget_get_allocation(PWidget(wid), &allocation);
1023 rc.left = allocation.x;
1024 rc.top = allocation.y;
1025 if (allocation.width > 20) {
1026 rc.right = rc.left + allocation.width;
1027 rc.bottom = rc.top + allocation.height;
1030 return rc;
1033 void Window::SetPosition(PRectangle rc) {
1034 GtkAllocation alloc;
1035 alloc.x = rc.left;
1036 alloc.y = rc.top;
1037 alloc.width = rc.Width();
1038 alloc.height = rc.Height();
1039 gtk_widget_size_allocate(PWidget(wid), &alloc);
1042 void Window::SetPositionRelative(PRectangle rc, Window relativeTo) {
1043 int ox = 0;
1044 int oy = 0;
1045 gdk_window_get_origin(WindowFromWidget(PWidget(relativeTo.wid)), &ox, &oy);
1046 ox += rc.left;
1047 if (ox < 0)
1048 ox = 0;
1049 oy += rc.top;
1050 if (oy < 0)
1051 oy = 0;
1053 /* do some corrections to fit into screen */
1054 int sizex = rc.right - rc.left;
1055 int sizey = rc.bottom - rc.top;
1056 int screenWidth = gdk_screen_width();
1057 int screenHeight = gdk_screen_height();
1058 if (sizex > screenWidth)
1059 ox = 0; /* the best we can do */
1060 else if (ox + sizex > screenWidth)
1061 ox = screenWidth - sizex;
1062 if (oy + sizey > screenHeight)
1063 oy = screenHeight - sizey;
1065 gtk_window_move(GTK_WINDOW(PWidget(wid)), ox, oy);
1067 gtk_window_resize(GTK_WINDOW(wid), sizex, sizey);
1070 PRectangle Window::GetClientPosition() {
1071 // On GTK+, the client position is the window position
1072 return GetPosition();
1075 void Window::Show(bool show) {
1076 if (show)
1077 gtk_widget_show(PWidget(wid));
1080 void Window::InvalidateAll() {
1081 if (wid) {
1082 gtk_widget_queue_draw(PWidget(wid));
1086 void Window::InvalidateRectangle(PRectangle rc) {
1087 if (wid) {
1088 gtk_widget_queue_draw_area(PWidget(wid),
1089 rc.left, rc.top,
1090 rc.right - rc.left, rc.bottom - rc.top);
1094 void Window::SetFont(Font &) {
1095 // Can not be done generically but only needed for ListBox
1098 void Window::SetCursor(Cursor curs) {
1099 // We don't set the cursor to same value numerous times under gtk because
1100 // it stores the cursor in the window once it's set
1101 if (curs == cursorLast)
1102 return;
1104 cursorLast = curs;
1105 GdkDisplay *pdisplay = gtk_widget_get_display(PWidget(wid));
1107 GdkCursor *gdkCurs;
1108 switch (curs) {
1109 case cursorText:
1110 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_XTERM);
1111 break;
1112 case cursorArrow:
1113 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
1114 break;
1115 case cursorUp:
1116 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_CENTER_PTR);
1117 break;
1118 case cursorWait:
1119 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_WATCH);
1120 break;
1121 case cursorHand:
1122 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_HAND2);
1123 break;
1124 case cursorReverseArrow:
1125 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_RIGHT_PTR);
1126 break;
1127 default:
1128 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
1129 cursorLast = cursorArrow;
1130 break;
1133 if (WindowFromWidget(PWidget(wid)))
1134 gdk_window_set_cursor(WindowFromWidget(PWidget(wid)), gdkCurs);
1135 #if GTK_CHECK_VERSION(3,0,0)
1136 g_object_unref(gdkCurs);
1137 #else
1138 gdk_cursor_unref(gdkCurs);
1139 #endif
1142 void Window::SetTitle(const char *s) {
1143 gtk_window_set_title(GTK_WINDOW(wid), s);
1146 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1147 gdk window coordinates */
1148 PRectangle Window::GetMonitorRect(Point pt) {
1149 gint x_offset, y_offset;
1151 gdk_window_get_origin(WindowFromWidget(PWidget(wid)), &x_offset, &y_offset);
1153 GdkScreen* screen;
1154 gint monitor_num;
1155 GdkRectangle rect;
1157 screen = gtk_widget_get_screen(PWidget(wid));
1158 monitor_num = gdk_screen_get_monitor_at_point(screen, pt.x + x_offset, pt.y + y_offset);
1159 gdk_screen_get_monitor_geometry(screen, monitor_num, &rect);
1160 rect.x -= x_offset;
1161 rect.y -= y_offset;
1162 return PRectangle(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
1165 typedef std::map<int, RGBAImage*> ImageMap;
1167 struct ListImage {
1168 const RGBAImage *rgba_data;
1169 GdkPixbuf *pixbuf;
1172 static void list_image_free(gpointer, gpointer value, gpointer) {
1173 ListImage *list_image = static_cast<ListImage *>(value);
1174 if (list_image->pixbuf)
1175 g_object_unref(list_image->pixbuf);
1176 g_free(list_image);
1179 ListBox::ListBox() {
1182 ListBox::~ListBox() {
1185 enum {
1186 PIXBUF_COLUMN,
1187 TEXT_COLUMN,
1188 N_COLUMNS
1191 class ListBoxX : public ListBox {
1192 WindowID widCached;
1193 WindowID frame;
1194 WindowID list;
1195 WindowID scroller;
1196 void *pixhash;
1197 GtkCellRenderer* pixbuf_renderer;
1198 GtkCellRenderer* renderer;
1199 RGBAImageSet images;
1200 int desiredVisibleRows;
1201 unsigned int maxItemCharacters;
1202 unsigned int aveCharWidth;
1203 #if GTK_CHECK_VERSION(3,0,0)
1204 GtkCssProvider *cssProvider;
1205 #endif
1206 public:
1207 CallBackAction doubleClickAction;
1208 void *doubleClickActionData;
1210 ListBoxX() : widCached(0), frame(0), list(0), scroller(0), pixhash(NULL), pixbuf_renderer(0),
1211 renderer(0),
1212 desiredVisibleRows(5), maxItemCharacters(0),
1213 aveCharWidth(1),
1214 #if GTK_CHECK_VERSION(3,0,0)
1215 cssProvider(NULL),
1216 #endif
1217 doubleClickAction(NULL), doubleClickActionData(NULL) {
1219 virtual ~ListBoxX() {
1220 if (pixhash) {
1221 g_hash_table_foreach((GHashTable *) pixhash, list_image_free, NULL);
1222 g_hash_table_destroy((GHashTable *) pixhash);
1224 if (widCached) {
1225 gtk_widget_destroy(GTK_WIDGET(widCached));
1226 wid = widCached = 0;
1228 #if GTK_CHECK_VERSION(3,0,0)
1229 if (cssProvider) {
1230 g_object_unref(cssProvider);
1231 cssProvider = NULL;
1233 #endif
1235 virtual void SetFont(Font &font);
1236 virtual void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_);
1237 virtual void SetAverageCharWidth(int width);
1238 virtual void SetVisibleRows(int rows);
1239 virtual int GetVisibleRows() const;
1240 int GetRowHeight();
1241 virtual PRectangle GetDesiredRect();
1242 virtual int CaretFromEdge();
1243 virtual void Clear();
1244 virtual void Append(char *s, int type = -1);
1245 virtual int Length();
1246 virtual void Select(int n);
1247 virtual int GetSelection();
1248 virtual int Find(const char *prefix);
1249 virtual void GetValue(int n, char *value, int len);
1250 void RegisterRGBA(int type, RGBAImage *image);
1251 virtual void RegisterImage(int type, const char *xpm_data);
1252 virtual void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage);
1253 virtual void ClearRegisteredImages();
1254 virtual void SetDoubleClickAction(CallBackAction action, void *data) {
1255 doubleClickAction = action;
1256 doubleClickActionData = data;
1258 virtual void SetList(const char *listText, char separator, char typesep);
1261 ListBox *ListBox::Allocate() {
1262 ListBoxX *lb = new ListBoxX();
1263 return lb;
1266 static int treeViewGetRowHeight(GtkTreeView *view)
1268 #if GTK_CHECK_VERSION(3,0,0)
1269 // This version sometimes reports erroneous results on GTK2, but the GTK2
1270 // version is inaccurate for GTK 3.14.
1271 GdkRectangle rect;
1272 GtkTreePath *path = gtk_tree_path_new_first();
1273 gtk_tree_view_get_background_area(view, path, NULL, &rect);
1274 gtk_tree_path_free(path);
1275 return rect.height;
1276 #else
1277 int row_height=0;
1278 int vertical_separator=0;
1279 int expander_size=0;
1280 GtkTreeViewColumn *column = gtk_tree_view_get_column(view, 0);
1281 gtk_tree_view_column_cell_get_size(column, NULL, NULL, NULL, NULL, &row_height);
1282 gtk_widget_style_get(GTK_WIDGET(view),
1283 "vertical-separator", &vertical_separator,
1284 "expander-size", &expander_size, NULL);
1285 row_height += vertical_separator;
1286 row_height = Platform::Maximum(row_height, expander_size);
1287 return row_height;
1288 #endif
1291 // SmallScroller, a GtkScrolledWindow that can shrink very small, as
1292 // gtk_widget_set_size_request() cannot shrink widgets on GTK3
1293 typedef struct {
1294 GtkScrolledWindow parent;
1295 /* Workaround ABI issue with Windows GTK2 bundle and GCC > 3.
1296 See http://lists.geany.org/pipermail/devel/2015-April/thread.html#9379
1298 GtkScrolledWindow contains a bitfield, and GCC 3.4 and 4.8 don't agree
1299 on the size of the structure (regardless of -mms-bitfields):
1300 - GCC 3.4 has sizeof(GtkScrolledWindow)=88
1301 - GCC 4.8 has sizeof(GtkScrolledWindow)=84
1302 As Windows GTK2 bundle is built with GCC 3, it requires types derived
1303 from GtkScrolledWindow to be at least 88 bytes, which means we need to
1304 add some fake padding to fill in the extra 4 bytes.
1305 There is however no other issue with the layout difference as we never
1306 access any GtkScrolledWindow fields ourselves. */
1307 int padding;
1308 } SmallScroller;
1309 typedef GtkScrolledWindowClass SmallScrollerClass;
1311 G_DEFINE_TYPE(SmallScroller, small_scroller, GTK_TYPE_SCROLLED_WINDOW)
1313 #if GTK_CHECK_VERSION(3,0,0)
1314 static void small_scroller_get_preferred_height(GtkWidget *widget, gint *min, gint *nat) {
1315 GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
1316 if (GTK_IS_TREE_VIEW(child)) {
1317 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(child));
1318 int n_rows = gtk_tree_model_iter_n_children(model, NULL);
1319 int row_height = treeViewGetRowHeight(GTK_TREE_VIEW(child));
1321 *min = MAX(1, row_height);
1322 *nat = MAX(*min, n_rows * row_height);
1323 } else {
1324 GTK_WIDGET_CLASS(small_scroller_parent_class)->get_preferred_height(widget, min, nat);
1325 if (*min > 1)
1326 *min = 1;
1329 #else
1330 static void small_scroller_size_request(GtkWidget *widget, GtkRequisition *req) {
1331 GTK_WIDGET_CLASS(small_scroller_parent_class)->size_request(widget, req);
1332 req->height = 1;
1334 #endif
1336 static void small_scroller_class_init(SmallScrollerClass *klass) {
1337 #if GTK_CHECK_VERSION(3,0,0)
1338 GTK_WIDGET_CLASS(klass)->get_preferred_height = small_scroller_get_preferred_height;
1339 #else
1340 GTK_WIDGET_CLASS(klass)->size_request = small_scroller_size_request;
1341 #endif
1344 static void small_scroller_init(SmallScroller *){}
1346 static gboolean ButtonPress(GtkWidget *, GdkEventButton* ev, gpointer p) {
1347 try {
1348 ListBoxX* lb = static_cast<ListBoxX*>(p);
1349 if (ev->type == GDK_2BUTTON_PRESS && lb->doubleClickAction != NULL) {
1350 lb->doubleClickAction(lb->doubleClickActionData);
1351 return TRUE;
1354 } catch (...) {
1355 // No pointer back to Scintilla to save status
1357 return FALSE;
1360 /* Change the active color to the selected color so the listbox uses the color
1361 scheme that it would use if it had the focus. */
1362 static void StyleSet(GtkWidget *w, GtkStyle*, void*) {
1364 g_return_if_fail(w != NULL);
1366 /* Copy the selected color to active. Note that the modify calls will cause
1367 recursive calls to this function after the value is updated and w->style to
1368 be set to a new object */
1370 #if GTK_CHECK_VERSION(3,16,0)
1371 // On recent releases of GTK+, it does not appear necessary to set the list box colours.
1372 // This may be because of common themes and may be needed with other themes.
1373 // The *override* calls are deprecated now, so only call them for older versions of GTK+.
1374 #elif GTK_CHECK_VERSION(3,0,0)
1375 GtkStyleContext *styleContext = gtk_widget_get_style_context(w);
1376 if (styleContext == NULL)
1377 return;
1379 GdkRGBA colourForeSelected;
1380 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourForeSelected);
1381 GdkRGBA colourForeActive;
1382 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourForeActive);
1383 if (!gdk_rgba_equal(&colourForeSelected, &colourForeActive))
1384 gtk_widget_override_color(w, GTK_STATE_FLAG_ACTIVE, &colourForeSelected);
1386 styleContext = gtk_widget_get_style_context(w);
1387 if (styleContext == NULL)
1388 return;
1390 GdkRGBA colourBaseSelected;
1391 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourBaseSelected);
1392 GdkRGBA colourBaseActive;
1393 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourBaseActive);
1394 if (!gdk_rgba_equal(&colourBaseSelected, &colourBaseActive))
1395 gtk_widget_override_background_color(w, GTK_STATE_FLAG_ACTIVE, &colourBaseSelected);
1396 #else
1397 GtkStyle *style = gtk_widget_get_style(w);
1398 if (style == NULL)
1399 return;
1400 if (!gdk_color_equal(&style->base[GTK_STATE_SELECTED], &style->base[GTK_STATE_ACTIVE]))
1401 gtk_widget_modify_base(w, GTK_STATE_ACTIVE, &style->base[GTK_STATE_SELECTED]);
1402 style = gtk_widget_get_style(w);
1403 if (style == NULL)
1404 return;
1405 if (!gdk_color_equal(&style->text[GTK_STATE_SELECTED], &style->text[GTK_STATE_ACTIVE]))
1406 gtk_widget_modify_text(w, GTK_STATE_ACTIVE, &style->text[GTK_STATE_SELECTED]);
1407 #endif
1410 void ListBoxX::Create(Window &, int, Point, int, bool, int) {
1411 if (widCached != 0) {
1412 wid = widCached;
1413 return;
1416 #if GTK_CHECK_VERSION(3,0,0)
1417 if (!cssProvider) {
1418 cssProvider = gtk_css_provider_new();
1420 #endif
1422 wid = widCached = gtk_window_new(GTK_WINDOW_POPUP);
1424 frame = gtk_frame_new(NULL);
1425 gtk_widget_show(PWidget(frame));
1426 gtk_container_add(GTK_CONTAINER(GetID()), PWidget(frame));
1427 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1428 gtk_container_set_border_width(GTK_CONTAINER(frame), 0);
1430 scroller = g_object_new(small_scroller_get_type(), NULL);
1431 gtk_container_set_border_width(GTK_CONTAINER(scroller), 0);
1432 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
1433 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1434 gtk_container_add(GTK_CONTAINER(frame), PWidget(scroller));
1435 gtk_widget_show(PWidget(scroller));
1437 /* Tree and its model */
1438 GtkListStore *store =
1439 gtk_list_store_new(N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING);
1441 list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1442 g_signal_connect(G_OBJECT(list), "style-set", G_CALLBACK(StyleSet), NULL);
1444 #if GTK_CHECK_VERSION(3,0,0)
1445 GtkStyleContext *styleContext = gtk_widget_get_style_context(GTK_WIDGET(list));
1446 if (styleContext) {
1447 gtk_style_context_add_provider(styleContext, GTK_STYLE_PROVIDER(cssProvider),
1448 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1450 #endif
1452 GtkTreeSelection *selection =
1453 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1454 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1455 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
1456 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list), FALSE);
1458 /* Columns */
1459 GtkTreeViewColumn *column = gtk_tree_view_column_new();
1460 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1461 gtk_tree_view_column_set_title(column, "Autocomplete");
1463 pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
1464 gtk_cell_renderer_set_fixed_size(pixbuf_renderer, 0, -1);
1465 gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE);
1466 gtk_tree_view_column_add_attribute(column, pixbuf_renderer,
1467 "pixbuf", PIXBUF_COLUMN);
1469 renderer = gtk_cell_renderer_text_new();
1470 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1471 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1472 gtk_tree_view_column_add_attribute(column, renderer,
1473 "text", TEXT_COLUMN);
1475 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
1476 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list), "fixed-height-mode"))
1477 g_object_set(G_OBJECT(list), "fixed-height-mode", TRUE, NULL);
1479 GtkWidget *widget = PWidget(list); // No code inside the G_OBJECT macro
1480 gtk_container_add(GTK_CONTAINER(PWidget(scroller)), widget);
1481 gtk_widget_show(widget);
1482 g_signal_connect(G_OBJECT(widget), "button_press_event",
1483 G_CALLBACK(ButtonPress), this);
1486 void ListBoxX::SetFont(Font &scint_font) {
1487 // Only do for Pango font as there have been crashes for GDK fonts
1488 if (Created() && PFont(scint_font)->pfd) {
1489 // Current font is Pango font
1490 #if GTK_CHECK_VERSION(3,0,0)
1491 if (cssProvider) {
1492 PangoFontDescription *pfd = PFont(scint_font)->pfd;
1493 std::ostringstream ssFontSetting;
1494 ssFontSetting << "GtkTreeView, treeview { ";
1495 ssFontSetting << "font-family: " << pango_font_description_get_family(pfd) << "; ";
1496 ssFontSetting << "font-size:";
1497 ssFontSetting << static_cast<double>(pango_font_description_get_size(pfd)) / PANGO_SCALE;
1498 ssFontSetting << "px; ";
1499 ssFontSetting << "font-weight:"<< pango_font_description_get_weight(pfd) << "; ";
1500 ssFontSetting << "}";
1501 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(cssProvider),
1502 ssFontSetting.str().c_str(), -1, NULL);
1504 #else
1505 gtk_widget_modify_font(PWidget(list), PFont(scint_font)->pfd);
1506 #endif
1507 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), -1);
1508 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1512 void ListBoxX::SetAverageCharWidth(int width) {
1513 aveCharWidth = width;
1516 void ListBoxX::SetVisibleRows(int rows) {
1517 desiredVisibleRows = rows;
1520 int ListBoxX::GetVisibleRows() const {
1521 return desiredVisibleRows;
1524 int ListBoxX::GetRowHeight()
1526 return treeViewGetRowHeight(GTK_TREE_VIEW(list));
1529 PRectangle ListBoxX::GetDesiredRect() {
1530 // Before any size allocated pretend its 100 wide so not scrolled
1531 PRectangle rc(0, 0, 100, 100);
1532 if (wid) {
1533 int rows = Length();
1534 if ((rows == 0) || (rows > desiredVisibleRows))
1535 rows = desiredVisibleRows;
1537 GtkRequisition req;
1538 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1539 // returns reasonable values.
1540 #if GTK_CHECK_VERSION(3,0,0)
1541 gtk_widget_get_preferred_size(GTK_WIDGET(frame), NULL, &req);
1542 #else
1543 gtk_widget_size_request(GTK_WIDGET(frame), &req);
1544 #endif
1545 int height;
1547 // First calculate height of the clist for our desired visible
1548 // row count otherwise it tries to expand to the total # of rows
1549 // Get cell height
1550 int row_height = GetRowHeight();
1551 #if GTK_CHECK_VERSION(3,0,0)
1552 GtkStyleContext *styleContextFrame = gtk_widget_get_style_context(PWidget(frame));
1553 GtkBorder padding, border, border_border = { 0, 0, 0, 0 };
1554 gtk_style_context_get_padding(styleContextFrame, GTK_STATE_FLAG_NORMAL, &padding);
1555 gtk_style_context_get_border(styleContextFrame, GTK_STATE_FLAG_NORMAL, &border);
1557 # if GTK_CHECK_VERSION(3,20,0)
1558 // on GTK 3.20 the frame border is in a sub-node "border".
1559 // Unfortunately we need to be built against 3.20 to be able to support this, as it requires
1560 // new API.
1561 GtkStyleContext *styleContextFrameBorder = gtk_style_context_new();
1562 GtkWidgetPath *widget_path = gtk_widget_path_copy(gtk_style_context_get_path(styleContextFrame));
1563 gtk_widget_path_append_type(widget_path, GTK_TYPE_BORDER); // dummy type
1564 gtk_widget_path_iter_set_object_name(widget_path, -1, "border");
1565 gtk_style_context_set_path(styleContextFrameBorder, widget_path);
1566 gtk_widget_path_free(widget_path);
1567 gtk_style_context_get_border(styleContextFrameBorder, GTK_STATE_FLAG_NORMAL, &border_border);
1568 g_object_unref(styleContextFrameBorder);
1569 # else // < 3.20
1570 if (gtk_check_version(3, 20, 0) == NULL) {
1571 // default to 1px all around as it's likely what it is, and so we don't miss 2px height
1572 // on GTK 3.20 when built against an earlier version.
1573 border_border.top = border_border.bottom = border_border.left = border_border.right = 1;
1575 # endif
1577 height = (rows * row_height
1578 + padding.top + padding.bottom
1579 + border.top + border.bottom
1580 + border_border.top + border_border.bottom
1581 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
1582 #else
1583 height = (rows * row_height
1584 + 2 * (PWidget(frame)->style->ythickness
1585 + GTK_CONTAINER(PWidget(list))->border_width));
1586 #endif
1587 rc.bottom = height;
1589 int width = maxItemCharacters;
1590 if (width < 12)
1591 width = 12;
1592 rc.right = width * (aveCharWidth + aveCharWidth / 3);
1593 // Add horizontal padding and borders
1594 int horizontal_separator=0;
1595 gtk_widget_style_get(PWidget(list),
1596 "horizontal-separator", &horizontal_separator, NULL);
1597 rc.right += horizontal_separator;
1598 #if GTK_CHECK_VERSION(3,0,0)
1599 rc.right += (padding.left + padding.right
1600 + border.left + border.right
1601 + border_border.left + border_border.right
1602 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
1603 #else
1604 rc.right += 2 * (PWidget(frame)->style->xthickness
1605 + GTK_CONTAINER(PWidget(list))->border_width);
1606 #endif
1607 if (Length() > rows) {
1608 // Add the width of the scrollbar
1609 GtkWidget *vscrollbar =
1610 gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(scroller));
1611 #if GTK_CHECK_VERSION(3,0,0)
1612 gtk_widget_get_preferred_size(vscrollbar, NULL, &req);
1613 #else
1614 gtk_widget_size_request(vscrollbar, &req);
1615 #endif
1616 rc.right += req.width;
1619 return rc;
1622 int ListBoxX::CaretFromEdge() {
1623 gint renderer_width, renderer_height;
1624 gtk_cell_renderer_get_fixed_size(pixbuf_renderer, &renderer_width,
1625 &renderer_height);
1626 return 4 + renderer_width;
1629 void ListBoxX::Clear() {
1630 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1631 gtk_list_store_clear(GTK_LIST_STORE(model));
1632 maxItemCharacters = 0;
1635 static void init_pixmap(ListImage *list_image) {
1636 if (list_image->rgba_data) {
1637 // Drop any existing pixmap/bitmap as data may have changed
1638 if (list_image->pixbuf)
1639 g_object_unref(list_image->pixbuf);
1640 list_image->pixbuf =
1641 gdk_pixbuf_new_from_data(list_image->rgba_data->Pixels(),
1642 GDK_COLORSPACE_RGB,
1643 TRUE,
1645 list_image->rgba_data->GetWidth(),
1646 list_image->rgba_data->GetHeight(),
1647 list_image->rgba_data->GetWidth() * 4,
1648 NULL,
1649 NULL);
1653 #define SPACING 5
1655 void ListBoxX::Append(char *s, int type) {
1656 ListImage *list_image = NULL;
1657 if ((type >= 0) && pixhash) {
1658 list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash
1659 , (gconstpointer) GINT_TO_POINTER(type)));
1661 GtkTreeIter iter;
1662 GtkListStore *store =
1663 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
1664 gtk_list_store_append(GTK_LIST_STORE(store), &iter);
1665 if (list_image) {
1666 if (NULL == list_image->pixbuf)
1667 init_pixmap(list_image);
1668 if (list_image->pixbuf) {
1669 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1670 PIXBUF_COLUMN, list_image->pixbuf,
1671 TEXT_COLUMN, s, -1);
1673 gint pixbuf_width = gdk_pixbuf_get_width(list_image->pixbuf);
1674 gint renderer_height, renderer_width;
1675 gtk_cell_renderer_get_fixed_size(pixbuf_renderer,
1676 &renderer_width, &renderer_height);
1677 if (pixbuf_width > renderer_width)
1678 gtk_cell_renderer_set_fixed_size(pixbuf_renderer,
1679 pixbuf_width, -1);
1680 } else {
1681 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1682 TEXT_COLUMN, s, -1);
1684 } else {
1685 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1686 TEXT_COLUMN, s, -1);
1688 size_t len = strlen(s);
1689 if (maxItemCharacters < len)
1690 maxItemCharacters = len;
1693 int ListBoxX::Length() {
1694 if (wid)
1695 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1696 (GTK_TREE_VIEW(list)), NULL);
1697 return 0;
1700 void ListBoxX::Select(int n) {
1701 GtkTreeIter iter;
1702 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1703 GtkTreeSelection *selection =
1704 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1706 if (n < 0) {
1707 gtk_tree_selection_unselect_all(selection);
1708 return;
1711 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
1712 if (valid) {
1713 gtk_tree_selection_select_iter(selection, &iter);
1715 // Move the scrollbar to show the selection.
1716 int total = Length();
1717 #if GTK_CHECK_VERSION(3,0,0)
1718 GtkAdjustment *adj =
1719 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list));
1720 #else
1721 GtkAdjustment *adj =
1722 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list));
1723 #endif
1724 gfloat value = ((gfloat)n / total) * (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_lower(adj))
1725 + gtk_adjustment_get_lower(adj) - gtk_adjustment_get_page_size(adj) / 2;
1726 // Get cell height
1727 int row_height = GetRowHeight();
1729 int rows = Length();
1730 if ((rows == 0) || (rows > desiredVisibleRows))
1731 rows = desiredVisibleRows;
1732 if (rows & 0x1) {
1733 // Odd rows to display -- We are now in the middle.
1734 // Align it so that we don't chop off rows.
1735 value += (gfloat)row_height / 2.0;
1737 // Clamp it.
1738 value = (value < 0)? 0 : value;
1739 value = (value > (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)))?
1740 (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)) : value;
1742 // Set it.
1743 gtk_adjustment_set_value(adj, value);
1744 } else {
1745 gtk_tree_selection_unselect_all(selection);
1749 int ListBoxX::GetSelection() {
1750 int index = -1;
1751 GtkTreeIter iter;
1752 GtkTreeModel *model;
1753 GtkTreeSelection *selection;
1754 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1755 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1756 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1757 int *indices = gtk_tree_path_get_indices(path);
1758 // Don't free indices.
1759 if (indices)
1760 index = indices[0];
1761 gtk_tree_path_free(path);
1763 return index;
1766 int ListBoxX::Find(const char *prefix) {
1767 GtkTreeIter iter;
1768 GtkTreeModel *model =
1769 gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1770 bool valid = gtk_tree_model_get_iter_first(model, &iter) != FALSE;
1771 int i = 0;
1772 while(valid) {
1773 gchar *s;
1774 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &s, -1);
1775 if (s && (0 == strncmp(prefix, s, strlen(prefix)))) {
1776 g_free(s);
1777 return i;
1779 g_free(s);
1780 valid = gtk_tree_model_iter_next(model, &iter) != FALSE;
1781 i++;
1783 return -1;
1786 void ListBoxX::GetValue(int n, char *value, int len) {
1787 char *text = NULL;
1788 GtkTreeIter iter;
1789 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1790 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
1791 if (valid) {
1792 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &text, -1);
1794 if (text && len > 0) {
1795 g_strlcpy(value, text, len);
1796 } else {
1797 value[0] = '\0';
1799 g_free(text);
1802 // g_return_if_fail causes unnecessary compiler warning in release compile.
1803 #ifdef _MSC_VER
1804 #pragma warning(disable: 4127)
1805 #endif
1807 void ListBoxX::RegisterRGBA(int type, RGBAImage *image) {
1808 images.Add(type, image);
1810 if (!pixhash) {
1811 pixhash = g_hash_table_new(g_direct_hash, g_direct_equal);
1813 ListImage *list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash,
1814 (gconstpointer) GINT_TO_POINTER(type)));
1815 if (list_image) {
1816 // Drop icon already registered
1817 if (list_image->pixbuf)
1818 g_object_unref(list_image->pixbuf);
1819 list_image->pixbuf = NULL;
1820 list_image->rgba_data = image;
1821 } else {
1822 list_image = g_new0(ListImage, 1);
1823 list_image->rgba_data = image;
1824 g_hash_table_insert((GHashTable *) pixhash, GINT_TO_POINTER(type),
1825 (gpointer) list_image);
1829 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
1830 g_return_if_fail(xpm_data);
1831 XPM xpmImage(xpm_data);
1832 RegisterRGBA(type, new RGBAImage(xpmImage));
1835 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
1836 RegisterRGBA(type, new RGBAImage(width, height, 1.0, pixelsImage));
1839 void ListBoxX::ClearRegisteredImages() {
1840 images.Clear();
1843 void ListBoxX::SetList(const char *listText, char separator, char typesep) {
1844 Clear();
1845 int count = strlen(listText) + 1;
1846 std::vector<char> words(listText, listText+count);
1847 char *startword = &words[0];
1848 char *numword = NULL;
1849 int i = 0;
1850 for (; words[i]; i++) {
1851 if (words[i] == separator) {
1852 words[i] = '\0';
1853 if (numword)
1854 *numword = '\0';
1855 Append(startword, numword?atoi(numword + 1):-1);
1856 startword = &words[0] + i + 1;
1857 numword = NULL;
1858 } else if (words[i] == typesep) {
1859 numword = &words[0] + i;
1862 if (startword) {
1863 if (numword)
1864 *numword = '\0';
1865 Append(startword, numword?atoi(numword + 1):-1);
1869 Menu::Menu() : mid(0) {}
1871 void Menu::CreatePopUp() {
1872 Destroy();
1873 mid = gtk_menu_new();
1874 g_object_ref_sink(G_OBJECT(mid));
1877 void Menu::Destroy() {
1878 if (mid)
1879 g_object_unref(G_OBJECT(mid));
1880 mid = 0;
1883 static void MenuPositionFunc(GtkMenu *, gint *x, gint *y, gboolean *, gpointer userData) {
1884 sptr_t intFromPointer = GPOINTER_TO_INT(userData);
1885 *x = intFromPointer & 0xffff;
1886 *y = intFromPointer >> 16;
1889 void Menu::Show(Point pt, Window &) {
1890 int screenHeight = gdk_screen_height();
1891 int screenWidth = gdk_screen_width();
1892 GtkMenu *widget = static_cast<GtkMenu *>(mid);
1893 gtk_widget_show_all(GTK_WIDGET(widget));
1894 GtkRequisition requisition;
1895 #if GTK_CHECK_VERSION(3,0,0)
1896 gtk_widget_get_preferred_size(GTK_WIDGET(widget), NULL, &requisition);
1897 #else
1898 gtk_widget_size_request(GTK_WIDGET(widget), &requisition);
1899 #endif
1900 if ((pt.x + requisition.width) > screenWidth) {
1901 pt.x = screenWidth - requisition.width;
1903 if ((pt.y + requisition.height) > screenHeight) {
1904 pt.y = screenHeight - requisition.height;
1906 gtk_menu_popup(widget, NULL, NULL, MenuPositionFunc,
1907 GINT_TO_POINTER((static_cast<int>(pt.y) << 16) | static_cast<int>(pt.x)), 0,
1908 gtk_get_current_event_time());
1911 ElapsedTime::ElapsedTime() {
1912 GTimeVal curTime;
1913 g_get_current_time(&curTime);
1914 bigBit = curTime.tv_sec;
1915 littleBit = curTime.tv_usec;
1918 class DynamicLibraryImpl : public DynamicLibrary {
1919 protected:
1920 GModule* m;
1921 public:
1922 explicit DynamicLibraryImpl(const char *modulePath) {
1923 m = g_module_open(modulePath, G_MODULE_BIND_LAZY);
1926 virtual ~DynamicLibraryImpl() {
1927 if (m != NULL)
1928 g_module_close(m);
1931 // Use g_module_symbol to get a pointer to the relevant function.
1932 virtual Function FindFunction(const char *name) {
1933 if (m != NULL) {
1934 gpointer fn_address = NULL;
1935 gboolean status = g_module_symbol(m, name, &fn_address);
1936 if (status)
1937 return static_cast<Function>(fn_address);
1938 else
1939 return NULL;
1940 } else {
1941 return NULL;
1945 virtual bool IsValid() {
1946 return m != NULL;
1950 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
1951 return static_cast<DynamicLibrary *>( new DynamicLibraryImpl(modulePath) );
1954 double ElapsedTime::Duration(bool reset) {
1955 GTimeVal curTime;
1956 g_get_current_time(&curTime);
1957 long endBigBit = curTime.tv_sec;
1958 long endLittleBit = curTime.tv_usec;
1959 double result = 1000000.0 * (endBigBit - bigBit);
1960 result += endLittleBit - littleBit;
1961 result /= 1000000.0;
1962 if (reset) {
1963 bigBit = endBigBit;
1964 littleBit = endLittleBit;
1966 return result;
1969 ColourDesired Platform::Chrome() {
1970 return ColourDesired(0xe0, 0xe0, 0xe0);
1973 ColourDesired Platform::ChromeHighlight() {
1974 return ColourDesired(0xff, 0xff, 0xff);
1977 const char *Platform::DefaultFont() {
1978 #ifdef G_OS_WIN32
1979 return "Lucida Console";
1980 #else
1981 return "!Sans";
1982 #endif
1985 int Platform::DefaultFontSize() {
1986 #ifdef G_OS_WIN32
1987 return 10;
1988 #else
1989 return 12;
1990 #endif
1993 unsigned int Platform::DoubleClickTime() {
1994 return 500; // Half a second
1997 bool Platform::MouseButtonBounce() {
1998 return true;
2001 void Platform::DebugDisplay(const char *s) {
2002 fprintf(stderr, "%s", s);
2005 bool Platform::IsKeyDown(int) {
2006 // TODO: discover state of keys in GTK+/X
2007 return false;
2010 long Platform::SendScintilla(
2011 WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
2012 return scintilla_send_message(SCINTILLA(w), msg, wParam, lParam);
2015 long Platform::SendScintillaPointer(
2016 WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
2017 return scintilla_send_message(SCINTILLA(w), msg, wParam,
2018 reinterpret_cast<sptr_t>(lParam));
2021 bool Platform::IsDBCSLeadByte(int codePage, char ch) {
2022 // Byte ranges found in Wikipedia articles with relevant search strings in each case
2023 unsigned char uch = static_cast<unsigned char>(ch);
2024 switch (codePage) {
2025 case 932:
2026 // Shift_jis
2027 return ((uch >= 0x81) && (uch <= 0x9F)) ||
2028 ((uch >= 0xE0) && (uch <= 0xFC));
2029 // Lead bytes F0 to FC may be a Microsoft addition.
2030 case 936:
2031 // GBK
2032 return (uch >= 0x81) && (uch <= 0xFE);
2033 case 950:
2034 // Big5
2035 return (uch >= 0x81) && (uch <= 0xFE);
2036 // Korean EUC-KR may be code page 949.
2038 return false;
2041 int Platform::DBCSCharLength(int codePage, const char *s) {
2042 if (codePage == 932 || codePage == 936 || codePage == 950) {
2043 return IsDBCSLeadByte(codePage, s[0]) ? 2 : 1;
2044 } else {
2045 int bytes = mblen(s, MB_CUR_MAX);
2046 if (bytes >= 1)
2047 return bytes;
2048 else
2049 return 1;
2053 int Platform::DBCSCharMaxLength() {
2054 return MB_CUR_MAX;
2055 //return 2;
2058 // These are utility functions not really tied to a platform
2060 int Platform::Minimum(int a, int b) {
2061 if (a < b)
2062 return a;
2063 else
2064 return b;
2067 int Platform::Maximum(int a, int b) {
2068 if (a > b)
2069 return a;
2070 else
2071 return b;
2074 //#define TRACE
2076 #ifdef TRACE
2077 void Platform::DebugPrintf(const char *format, ...) {
2078 char buffer[2000];
2079 va_list pArguments;
2080 va_start(pArguments, format);
2081 vsprintf(buffer, format, pArguments);
2082 va_end(pArguments);
2083 Platform::DebugDisplay(buffer);
2085 #else
2086 void Platform::DebugPrintf(const char *, ...) {}
2088 #endif
2090 // Not supported for GTK+
2091 static bool assertionPopUps = true;
2093 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
2094 bool ret = assertionPopUps;
2095 assertionPopUps = assertionPopUps_;
2096 return ret;
2099 void Platform::Assert(const char *c, const char *file, int line) {
2100 char buffer[2000];
2101 g_snprintf(buffer, sizeof(buffer), "Assertion [%s] failed at %s %d\r\n", c, file, line);
2102 Platform::DebugDisplay(buffer);
2103 abort();
2106 int Platform::Clamp(int val, int minVal, int maxVal) {
2107 if (val > maxVal)
2108 val = maxVal;
2109 if (val < minVal)
2110 val = minVal;
2111 return val;
2114 void Platform_Initialise() {
2117 void Platform_Finalise() {