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