Update Scintilla to 3.5.1 pre-release
[geany-mirror.git] / scintilla / gtk / PlatGTK.cxx
blobc1d7ac7aee21c78932174d284d1866a15a2e32d1
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;
607 void SurfaceImpl::PenColour(ColourDesired fore) {
608 if (context) {
609 ColourDesired cdFore(fore.AsLong());
610 cairo_set_source_rgb(context,
611 cdFore.GetRed() / 255.0,
612 cdFore.GetGreen() / 255.0,
613 cdFore.GetBlue() / 255.0);
617 int SurfaceImpl::LogPixelsY() {
618 return 72;
621 int SurfaceImpl::DeviceHeightFont(int points) {
622 int logPix = LogPixelsY();
623 return (points * logPix + logPix / 2) / 72;
626 void SurfaceImpl::MoveTo(int x_, int y_) {
627 x = x_;
628 y = y_;
631 static int Delta(int difference) {
632 if (difference < 0)
633 return -1;
634 else if (difference > 0)
635 return 1;
636 else
637 return 0;
640 void SurfaceImpl::LineTo(int x_, int y_) {
641 // cairo_line_to draws the end position, unlike Win32 or GDK with GDK_CAP_NOT_LAST.
642 // For simple cases, move back one pixel from end.
643 if (context) {
644 int xDiff = x_ - x;
645 int xDelta = Delta(xDiff);
646 int yDiff = y_ - y;
647 int yDelta = Delta(yDiff);
648 if ((xDiff == 0) || (yDiff == 0)) {
649 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
650 int xEnd = x_ - xDelta;
651 int left = Platform::Minimum(x, xEnd);
652 int width = abs(x - xEnd) + 1;
653 int yEnd = y_ - yDelta;
654 int top = Platform::Minimum(y, yEnd);
655 int height = abs(y - yEnd) + 1;
656 cairo_rectangle(context, left, top, width, height);
657 cairo_fill(context);
658 } else if ((abs(xDiff) == abs(yDiff))) {
659 // 45 degree slope
660 cairo_move_to(context, x + 0.5, y + 0.5);
661 cairo_line_to(context, x_ + 0.5 - xDelta, y_ + 0.5 - yDelta);
662 } else {
663 // Line has a different slope so difficult to avoid last pixel
664 cairo_move_to(context, x + 0.5, y + 0.5);
665 cairo_line_to(context, x_ + 0.5, y_ + 0.5);
667 cairo_stroke(context);
669 x = x_;
670 y = y_;
673 void SurfaceImpl::Polygon(Point *pts, int npts, ColourDesired fore,
674 ColourDesired back) {
675 PLATFORM_ASSERT(context);
676 PenColour(back);
677 cairo_move_to(context, pts[0].x + 0.5, pts[0].y + 0.5);
678 for (int i = 1; i < npts; i++) {
679 cairo_line_to(context, pts[i].x + 0.5, pts[i].y + 0.5);
681 cairo_close_path(context);
682 cairo_fill_preserve(context);
683 PenColour(fore);
684 cairo_stroke(context);
687 void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
688 if (context) {
689 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5,
690 rc.right - rc.left - 1, rc.bottom - rc.top - 1);
691 PenColour(back);
692 cairo_fill_preserve(context);
693 PenColour(fore);
694 cairo_stroke(context);
698 void SurfaceImpl::FillRectangle(PRectangle rc, ColourDesired back) {
699 PenColour(back);
700 if (context && (rc.left < maxCoordinate)) { // Protect against out of range
701 rc.left = lround(rc.left);
702 rc.right = lround(rc.right);
703 cairo_rectangle(context, rc.left, rc.top,
704 rc.right - rc.left, rc.bottom - rc.top);
705 cairo_fill(context);
709 void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
710 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfacePattern);
711 bool canDraw = surfi.psurf;
712 if (canDraw) {
713 PLATFORM_ASSERT(context);
714 // Tile pattern over rectangle
715 // Currently assumes 8x8 pattern
716 int widthPat = 8;
717 int heightPat = 8;
718 for (int xTile = rc.left; xTile < rc.right; xTile += widthPat) {
719 int widthx = (xTile + widthPat > rc.right) ? rc.right - xTile : widthPat;
720 for (int yTile = rc.top; yTile < rc.bottom; yTile += heightPat) {
721 int heighty = (yTile + heightPat > rc.bottom) ? rc.bottom - yTile : heightPat;
722 cairo_set_source_surface(context, surfi.psurf, xTile, yTile);
723 cairo_rectangle(context, xTile, yTile, widthx, heighty);
724 cairo_fill(context);
727 } else {
728 // Something is wrong so try to show anyway
729 // Shows up black because colour not allocated
730 FillRectangle(rc, ColourDesired(0));
734 void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
735 if (((rc.right - rc.left) > 4) && ((rc.bottom - rc.top) > 4)) {
736 // Approximate a round rect with some cut off corners
737 Point pts[] = {
738 Point(rc.left + 2, rc.top),
739 Point(rc.right - 2, rc.top),
740 Point(rc.right, rc.top + 2),
741 Point(rc.right, rc.bottom - 2),
742 Point(rc.right - 2, rc.bottom),
743 Point(rc.left + 2, rc.bottom),
744 Point(rc.left, rc.bottom - 2),
745 Point(rc.left, rc.top + 2),
747 Polygon(pts, ELEMENTS(pts), fore, back);
748 } else {
749 RectangleDraw(rc, fore, back);
753 static void PathRoundRectangle(cairo_t *context, double left, double top, double width, double height, int radius) {
754 double degrees = kPi / 180.0;
756 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 2, 0)
757 cairo_new_sub_path(context);
758 #else
759 // First arc is in the top-right corner and starts from a point on the top line
760 cairo_move_to(context, left + width - radius, top);
761 #endif
762 cairo_arc(context, left + width - radius, top + radius, radius, -90 * degrees, 0 * degrees);
763 cairo_arc(context, left + width - radius, top + height - radius, radius, 0 * degrees, 90 * degrees);
764 cairo_arc(context, left + radius, top + height - radius, radius, 90 * degrees, 180 * degrees);
765 cairo_arc(context, left + radius, top + radius, radius, 180 * degrees, 270 * degrees);
766 cairo_close_path(context);
769 void SurfaceImpl::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
770 ColourDesired outline, int alphaOutline, int flags) {
771 if (context && rc.Width() > 0) {
772 ColourDesired cdFill(fill.AsLong());
773 cairo_set_source_rgba(context,
774 cdFill.GetRed() / 255.0,
775 cdFill.GetGreen() / 255.0,
776 cdFill.GetBlue() / 255.0,
777 alphaFill / 255.0);
778 if (cornerSize > 0)
779 PathRoundRectangle(context, rc.left + 1.0, rc.top + 1.0, rc.right - rc.left - 2.0, rc.bottom - rc.top - 2.0, cornerSize);
780 else
781 cairo_rectangle(context, rc.left + 1.0, rc.top + 1.0, rc.right - rc.left - 2.0, rc.bottom - rc.top - 2.0);
782 cairo_fill(context);
784 ColourDesired cdOutline(outline.AsLong());
785 cairo_set_source_rgba(context,
786 cdOutline.GetRed() / 255.0,
787 cdOutline.GetGreen() / 255.0,
788 cdOutline.GetBlue() / 255.0,
789 alphaOutline / 255.0);
790 if (cornerSize > 0)
791 PathRoundRectangle(context, rc.left + 0.5, rc.top + 0.5, rc.right - rc.left - 1, rc.bottom - rc.top - 1, cornerSize);
792 else
793 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5, rc.right - rc.left - 1, rc.bottom - rc.top - 1);
794 cairo_stroke(context);
798 void SurfaceImpl::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
799 PLATFORM_ASSERT(context);
800 if (rc.Width() > width)
801 rc.left += (rc.Width() - width) / 2;
802 rc.right = rc.left + width;
803 if (rc.Height() > height)
804 rc.top += (rc.Height() - height) / 2;
805 rc.bottom = rc.top + height;
807 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
808 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
809 #else
810 int stride = width * 4;
811 #endif
812 int ucs = stride * height;
813 std::vector<unsigned char> image(ucs);
814 for (int iy=0; iy<height; iy++) {
815 for (int ix=0; ix<width; ix++) {
816 unsigned char *pixel = &image[0] + iy*stride + ix * 4;
817 unsigned char alpha = pixelsImage[3];
818 pixel[2] = (*pixelsImage++) * alpha / 255;
819 pixel[1] = (*pixelsImage++) * alpha / 255;
820 pixel[0] = (*pixelsImage++) * alpha / 255;
821 pixel[3] = *pixelsImage++;
825 cairo_surface_t *psurfImage = cairo_image_surface_create_for_data(&image[0], CAIRO_FORMAT_ARGB32, width, height, stride);
826 cairo_set_source_surface(context, psurfImage, rc.left, rc.top);
827 cairo_rectangle(context, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
828 cairo_fill(context);
830 cairo_surface_destroy(psurfImage);
833 void SurfaceImpl::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
834 PLATFORM_ASSERT(context);
835 PenColour(back);
836 cairo_arc(context, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2,
837 Platform::Minimum(rc.Width(), rc.Height()) / 2, 0, 2*kPi);
838 cairo_fill_preserve(context);
839 PenColour(fore);
840 cairo_stroke(context);
843 void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
844 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfaceSource);
845 bool canDraw = surfi.psurf;
846 if (canDraw) {
847 PLATFORM_ASSERT(context);
848 cairo_set_source_surface(context, surfi.psurf,
849 rc.left - from.x, rc.top - from.y);
850 cairo_rectangle(context, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
851 cairo_fill(context);
855 std::string UTF8FromLatin1(const char *s, int len) {
856 std::string utfForm(len*2 + 1, '\0');
857 size_t lenU = 0;
858 for (int i=0; i<len; i++) {
859 unsigned int uch = static_cast<unsigned char>(s[i]);
860 if (uch < 0x80) {
861 utfForm[lenU++] = uch;
862 } else {
863 utfForm[lenU++] = static_cast<char>(0xC0 | (uch >> 6));
864 utfForm[lenU++] = static_cast<char>(0x80 | (uch & 0x3f));
867 utfForm.resize(lenU);
868 return utfForm;
871 static std::string UTF8FromIconv(const Converter &conv, const char *s, int len) {
872 if (conv) {
873 std::string utfForm(len*3+1, '\0');
874 char *pin = const_cast<char *>(s);
875 size_t inLeft = len;
876 char *putf = &utfForm[0];
877 char *pout = putf;
878 size_t outLeft = len*3+1;
879 size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
880 if (conversions != ((size_t)(-1))) {
881 *pout = '\0';
882 utfForm.resize(pout - putf);
883 return utfForm;
886 return std::string();
889 // Work out how many bytes are in a character by trying to convert using iconv,
890 // returning the first length that succeeds.
891 static size_t MultiByteLenFromIconv(const Converter &conv, const char *s, size_t len) {
892 for (size_t lenMB=1; (lenMB<4) && (lenMB <= len); lenMB++) {
893 char wcForm[2];
894 char *pin = const_cast<char *>(s);
895 size_t inLeft = lenMB;
896 char *pout = wcForm;
897 size_t outLeft = 2;
898 size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
899 if (conversions != ((size_t)(-1))) {
900 return lenMB;
903 return 1;
906 void SurfaceImpl::DrawTextBase(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
907 ColourDesired fore) {
908 PenColour(fore);
909 if (context) {
910 XYPOSITION xText = rc.left;
911 if (PFont(font_)->pfd) {
912 std::string utfForm;
913 if (et == UTF8) {
914 pango_layout_set_text(layout, s, len);
915 } else {
916 SetConverter(PFont(font_)->characterSet);
917 utfForm = UTF8FromIconv(conv, s, len);
918 if (utfForm.empty()) { // iconv failed so treat as Latin1
919 utfForm = UTF8FromLatin1(s, len);
921 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
923 pango_layout_set_font_description(layout, PFont(font_)->pfd);
924 pango_cairo_update_layout(context, layout);
925 #ifdef PANGO_VERSION
926 PangoLayoutLine *pll = pango_layout_get_line_readonly(layout,0);
927 #else
928 PangoLayoutLine *pll = pango_layout_get_line(layout,0);
929 #endif
930 cairo_move_to(context, xText, ybase);
931 pango_cairo_show_layout_line(context, pll);
936 void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
937 ColourDesired fore, ColourDesired back) {
938 FillRectangle(rc, back);
939 DrawTextBase(rc, font_, ybase, s, len, fore);
942 // On GTK+, exactly same as DrawTextNoClip
943 void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
944 ColourDesired fore, ColourDesired back) {
945 FillRectangle(rc, back);
946 DrawTextBase(rc, font_, ybase, s, len, fore);
949 void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
950 ColourDesired fore) {
951 // Avoid drawing spaces in transparent mode
952 for (int i=0; i<len; i++) {
953 if (s[i] != ' ') {
954 DrawTextBase(rc, font_, ybase, s, len, fore);
955 return;
960 class ClusterIterator {
961 PangoLayoutIter *iter;
962 PangoRectangle pos;
963 int lenPositions;
964 public:
965 bool finished;
966 XYPOSITION positionStart;
967 XYPOSITION position;
968 XYPOSITION distance;
969 int curIndex;
970 ClusterIterator(PangoLayout *layout, int len) : lenPositions(len), finished(false),
971 positionStart(0), position(0), distance(0), curIndex(0) {
972 iter = pango_layout_get_iter(layout);
973 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
975 ~ClusterIterator() {
976 pango_layout_iter_free(iter);
979 void Next() {
980 positionStart = position;
981 if (pango_layout_iter_next_cluster(iter)) {
982 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
983 position = doubleFromPangoUnits(pos.x);
984 curIndex = pango_layout_iter_get_index(iter);
985 } else {
986 finished = true;
987 position = doubleFromPangoUnits(pos.x + pos.width);
988 curIndex = lenPositions;
990 distance = position - positionStart;
994 void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
995 if (font_.GetID()) {
996 const int lenPositions = len;
997 if (PFont(font_)->pfd) {
998 if (len == 1) {
999 int width = PFont(font_)->CharWidth(*s, et);
1000 if (width) {
1001 positions[0] = width;
1002 return;
1005 pango_layout_set_font_description(layout, PFont(font_)->pfd);
1006 if (et == UTF8) {
1007 // Simple and direct as UTF-8 is native Pango encoding
1008 int i = 0;
1009 pango_layout_set_text(layout, s, len);
1010 ClusterIterator iti(layout, lenPositions);
1011 while (!iti.finished) {
1012 iti.Next();
1013 int places = iti.curIndex - i;
1014 while (i < iti.curIndex) {
1015 // Evenly distribute space among bytes of this cluster.
1016 // Would be better to find number of characters and then
1017 // divide evenly between characters with each byte of a character
1018 // being at the same position.
1019 positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places;
1020 i++;
1023 PLATFORM_ASSERT(i == lenPositions);
1024 } else {
1025 int positionsCalculated = 0;
1026 if (et == dbcs) {
1027 SetConverter(PFont(font_)->characterSet);
1028 std::string utfForm = UTF8FromIconv(conv, s, len);
1029 if (!utfForm.empty()) {
1030 // Convert to UTF-8 so can ask Pango for widths, then
1031 // Loop through UTF-8 and DBCS forms, taking account of different
1032 // character byte lengths.
1033 Converter convMeasure("UCS-2", CharacterSetID(characterSet), false);
1034 pango_layout_set_text(layout, utfForm.c_str(), strlen(utfForm.c_str()));
1035 int i = 0;
1036 int clusterStart = 0;
1037 ClusterIterator iti(layout, strlen(utfForm.c_str()));
1038 while (!iti.finished) {
1039 iti.Next();
1040 int clusterEnd = iti.curIndex;
1041 int places = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
1042 int place = 1;
1043 while (clusterStart < clusterEnd) {
1044 size_t lenChar = MultiByteLenFromIconv(convMeasure, s+i, len-i);
1045 while (lenChar--) {
1046 positions[i++] = iti.position - (places - place) * iti.distance / places;
1047 positionsCalculated++;
1049 clusterStart += UTF8CharLength(static_cast<unsigned char>(utfForm.c_str()[clusterStart]));
1050 place++;
1053 PLATFORM_ASSERT(i == lenPositions);
1056 if (positionsCalculated < 1 ) {
1057 // Either 8-bit or DBCS conversion failed so treat as 8-bit.
1058 SetConverter(PFont(font_)->characterSet);
1059 const bool rtlCheck = PFont(font_)->characterSet == SC_CHARSET_HEBREW ||
1060 PFont(font_)->characterSet == SC_CHARSET_ARABIC;
1061 std::string utfForm = UTF8FromIconv(conv, s, len);
1062 if (utfForm.empty()) {
1063 utfForm = UTF8FromLatin1(s, len);
1065 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
1066 int i = 0;
1067 int clusterStart = 0;
1068 // Each 8-bit input character may take 1 or 2 bytes in UTF-8
1069 // and groups of up to 3 may be represented as ligatures.
1070 ClusterIterator iti(layout, utfForm.length());
1071 while (!iti.finished) {
1072 iti.Next();
1073 int clusterEnd = iti.curIndex;
1074 int ligatureLength = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
1075 if (rtlCheck && ((clusterEnd <= clusterStart) || (ligatureLength == 0) || (ligatureLength > 3))) {
1076 // Something has gone wrong: exit quickly but pretend all the characters are equally spaced:
1077 int widthLayout = 0;
1078 pango_layout_get_size(layout, &widthLayout, NULL);
1079 XYPOSITION widthTotal = doubleFromPangoUnits(widthLayout);
1080 for (int bytePos=0; bytePos<lenPositions; bytePos++) {
1081 positions[bytePos] = widthTotal / lenPositions * (bytePos + 1);
1083 return;
1085 PLATFORM_ASSERT(ligatureLength > 0 && ligatureLength <= 3);
1086 for (int charInLig=0; charInLig<ligatureLength; charInLig++) {
1087 positions[i++] = iti.position - (ligatureLength - 1 - charInLig) * iti.distance / ligatureLength;
1089 clusterStart = clusterEnd;
1091 while (i < lenPositions) {
1092 // If something failed, fill in rest of the positions
1093 positions[i++] = clusterStart;
1095 PLATFORM_ASSERT(i == lenPositions);
1098 if (len == 1) {
1099 PFont(font_)->SetCharWidth(*s, positions[0], et);
1101 return;
1103 } else {
1104 // No font so return an ascending range of values
1105 for (int i = 0; i < len; i++) {
1106 positions[i] = i + 1;
1111 XYPOSITION SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
1112 if (font_.GetID()) {
1113 if (PFont(font_)->pfd) {
1114 std::string utfForm;
1115 pango_layout_set_font_description(layout, PFont(font_)->pfd);
1116 PangoRectangle pos;
1117 if (et == UTF8) {
1118 pango_layout_set_text(layout, s, len);
1119 } else {
1120 SetConverter(PFont(font_)->characterSet);
1121 utfForm = UTF8FromIconv(conv, s, len);
1122 if (utfForm.empty()) { // iconv failed so treat as Latin1
1123 utfForm = UTF8FromLatin1(s, len);
1125 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
1127 #ifdef PANGO_VERSION
1128 PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout,0);
1129 #else
1130 PangoLayoutLine *pangoLine = pango_layout_get_line(layout,0);
1131 #endif
1132 pango_layout_line_get_extents(pangoLine, NULL, &pos);
1133 return doubleFromPangoUnits(pos.width);
1135 return 1;
1136 } else {
1137 return 1;
1141 XYPOSITION SurfaceImpl::WidthChar(Font &font_, char ch) {
1142 if (font_.GetID()) {
1143 if (PFont(font_)->pfd) {
1144 return WidthText(font_, &ch, 1);
1146 return 1;
1147 } else {
1148 return 1;
1152 // Ascent and descent determined by Pango font metrics.
1154 XYPOSITION SurfaceImpl::Ascent(Font &font_) {
1155 if (!(font_.GetID()))
1156 return 1;
1157 FontMutexLock();
1158 int ascent = PFont(font_)->ascent;
1159 if ((ascent == 0) && (PFont(font_)->pfd)) {
1160 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
1161 PFont(font_)->pfd, pango_context_get_language(pcontext));
1162 PFont(font_)->ascent =
1163 doubleFromPangoUnits(pango_font_metrics_get_ascent(metrics));
1164 pango_font_metrics_unref(metrics);
1165 ascent = PFont(font_)->ascent;
1167 if (ascent == 0) {
1168 ascent = 1;
1170 FontMutexUnlock();
1171 return ascent;
1174 XYPOSITION SurfaceImpl::Descent(Font &font_) {
1175 if (!(font_.GetID()))
1176 return 1;
1177 if (PFont(font_)->pfd) {
1178 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
1179 PFont(font_)->pfd, pango_context_get_language(pcontext));
1180 int descent = doubleFromPangoUnits(pango_font_metrics_get_descent(metrics));
1181 pango_font_metrics_unref(metrics);
1182 return descent;
1184 return 0;
1187 XYPOSITION SurfaceImpl::InternalLeading(Font &) {
1188 return 0;
1191 XYPOSITION SurfaceImpl::ExternalLeading(Font &) {
1192 return 0;
1195 XYPOSITION SurfaceImpl::Height(Font &font_) {
1196 return Ascent(font_) + Descent(font_);
1199 XYPOSITION SurfaceImpl::AverageCharWidth(Font &font_) {
1200 return WidthChar(font_, 'n');
1203 void SurfaceImpl::SetClip(PRectangle rc) {
1204 PLATFORM_ASSERT(context);
1205 cairo_rectangle(context, rc.left, rc.top, rc.right, rc.bottom);
1206 cairo_clip(context);
1209 void SurfaceImpl::FlushCachedState() {}
1211 void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
1212 if (unicodeMode_)
1213 et = UTF8;
1216 void SurfaceImpl::SetDBCSMode(int codePage) {
1217 if (codePage && (codePage != SC_CP_UTF8))
1218 et = dbcs;
1221 Surface *Surface::Allocate(int) {
1222 return new SurfaceImpl();
1225 Window::~Window() {}
1227 void Window::Destroy() {
1228 if (wid) {
1229 ListBox *listbox = dynamic_cast<ListBox*>(this);
1230 if (listbox) {
1231 gtk_widget_hide(GTK_WIDGET(wid));
1232 // clear up window content
1233 listbox->Clear();
1234 // resize the window to the smallest possible size for it to adapt
1235 // to future content
1236 gtk_window_resize(GTK_WINDOW(wid), 1, 1);
1237 } else {
1238 gtk_widget_destroy(GTK_WIDGET(wid));
1240 wid = 0;
1244 bool Window::HasFocus() {
1245 return IS_WIDGET_FOCUSSED(wid);
1248 PRectangle Window::GetPosition() {
1249 // Before any size allocated pretend its 1000 wide so not scrolled
1250 PRectangle rc(0, 0, 1000, 1000);
1251 if (wid) {
1252 GtkAllocation allocation;
1253 #if GTK_CHECK_VERSION(3,0,0)
1254 gtk_widget_get_allocation(PWidget(wid), &allocation);
1255 #else
1256 allocation = PWidget(wid)->allocation;
1257 #endif
1258 rc.left = allocation.x;
1259 rc.top = allocation.y;
1260 if (allocation.width > 20) {
1261 rc.right = rc.left + allocation.width;
1262 rc.bottom = rc.top + allocation.height;
1265 return rc;
1268 void Window::SetPosition(PRectangle rc) {
1269 GtkAllocation alloc;
1270 alloc.x = rc.left;
1271 alloc.y = rc.top;
1272 alloc.width = rc.Width();
1273 alloc.height = rc.Height();
1274 gtk_widget_size_allocate(PWidget(wid), &alloc);
1277 void Window::SetPositionRelative(PRectangle rc, Window relativeTo) {
1278 int ox = 0;
1279 int oy = 0;
1280 gdk_window_get_origin(WindowFromWidget(PWidget(relativeTo.wid)), &ox, &oy);
1281 ox += rc.left;
1282 if (ox < 0)
1283 ox = 0;
1284 oy += rc.top;
1285 if (oy < 0)
1286 oy = 0;
1288 /* do some corrections to fit into screen */
1289 int sizex = rc.right - rc.left;
1290 int sizey = rc.bottom - rc.top;
1291 int screenWidth = gdk_screen_width();
1292 int screenHeight = gdk_screen_height();
1293 if (sizex > screenWidth)
1294 ox = 0; /* the best we can do */
1295 else if (ox + sizex > screenWidth)
1296 ox = screenWidth - sizex;
1297 if (oy + sizey > screenHeight)
1298 oy = screenHeight - sizey;
1300 gtk_window_move(GTK_WINDOW(PWidget(wid)), ox, oy);
1302 gtk_window_resize(GTK_WINDOW(wid), sizex, sizey);
1305 PRectangle Window::GetClientPosition() {
1306 // On GTK+, the client position is the window position
1307 return GetPosition();
1310 void Window::Show(bool show) {
1311 if (show)
1312 gtk_widget_show(PWidget(wid));
1315 void Window::InvalidateAll() {
1316 if (wid) {
1317 gtk_widget_queue_draw(PWidget(wid));
1321 void Window::InvalidateRectangle(PRectangle rc) {
1322 if (wid) {
1323 gtk_widget_queue_draw_area(PWidget(wid),
1324 rc.left, rc.top,
1325 rc.right - rc.left, rc.bottom - rc.top);
1329 void Window::SetFont(Font &) {
1330 // Can not be done generically but only needed for ListBox
1333 void Window::SetCursor(Cursor curs) {
1334 // We don't set the cursor to same value numerous times under gtk because
1335 // it stores the cursor in the window once it's set
1336 if (curs == cursorLast)
1337 return;
1339 cursorLast = curs;
1340 GdkCursor *gdkCurs;
1341 switch (curs) {
1342 case cursorText:
1343 gdkCurs = gdk_cursor_new(GDK_XTERM);
1344 break;
1345 case cursorArrow:
1346 gdkCurs = gdk_cursor_new(GDK_LEFT_PTR);
1347 break;
1348 case cursorUp:
1349 gdkCurs = gdk_cursor_new(GDK_CENTER_PTR);
1350 break;
1351 case cursorWait:
1352 gdkCurs = gdk_cursor_new(GDK_WATCH);
1353 break;
1354 case cursorHand:
1355 gdkCurs = gdk_cursor_new(GDK_HAND2);
1356 break;
1357 case cursorReverseArrow:
1358 gdkCurs = gdk_cursor_new(GDK_RIGHT_PTR);
1359 break;
1360 default:
1361 gdkCurs = gdk_cursor_new(GDK_LEFT_PTR);
1362 cursorLast = cursorArrow;
1363 break;
1366 if (WindowFromWidget(PWidget(wid)))
1367 gdk_window_set_cursor(WindowFromWidget(PWidget(wid)), gdkCurs);
1368 #if GTK_CHECK_VERSION(3,0,0)
1369 g_object_unref(gdkCurs);
1370 #else
1371 gdk_cursor_unref(gdkCurs);
1372 #endif
1375 void Window::SetTitle(const char *s) {
1376 gtk_window_set_title(GTK_WINDOW(wid), s);
1379 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1380 gdk window coordinates */
1381 PRectangle Window::GetMonitorRect(Point pt) {
1382 gint x_offset, y_offset;
1384 gdk_window_get_origin(WindowFromWidget(PWidget(wid)), &x_offset, &y_offset);
1386 GdkScreen* screen;
1387 gint monitor_num;
1388 GdkRectangle rect;
1390 screen = gtk_widget_get_screen(PWidget(wid));
1391 monitor_num = gdk_screen_get_monitor_at_point(screen, pt.x + x_offset, pt.y + y_offset);
1392 gdk_screen_get_monitor_geometry(screen, monitor_num, &rect);
1393 rect.x -= x_offset;
1394 rect.y -= y_offset;
1395 return PRectangle(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
1398 typedef std::map<int, RGBAImage*> ImageMap;
1400 struct ListImage {
1401 const RGBAImage *rgba_data;
1402 GdkPixbuf *pixbuf;
1405 static void list_image_free(gpointer, gpointer value, gpointer) {
1406 ListImage *list_image = static_cast<ListImage *>(value);
1407 if (list_image->pixbuf)
1408 g_object_unref(list_image->pixbuf);
1409 g_free(list_image);
1412 ListBox::ListBox() {
1415 ListBox::~ListBox() {
1418 enum {
1419 PIXBUF_COLUMN,
1420 TEXT_COLUMN,
1421 N_COLUMNS
1424 class ListBoxX : public ListBox {
1425 WindowID widCached;
1426 WindowID frame;
1427 WindowID list;
1428 WindowID scroller;
1429 void *pixhash;
1430 GtkCellRenderer* pixbuf_renderer;
1431 RGBAImageSet images;
1432 int desiredVisibleRows;
1433 unsigned int maxItemCharacters;
1434 unsigned int aveCharWidth;
1435 public:
1436 CallBackAction doubleClickAction;
1437 void *doubleClickActionData;
1439 ListBoxX() : widCached(0), frame(0), list(0), scroller(0), pixhash(NULL), pixbuf_renderer(0),
1440 desiredVisibleRows(5), maxItemCharacters(0),
1441 aveCharWidth(1), doubleClickAction(NULL), doubleClickActionData(NULL) {
1443 virtual ~ListBoxX() {
1444 if (pixhash) {
1445 g_hash_table_foreach((GHashTable *) pixhash, list_image_free, NULL);
1446 g_hash_table_destroy((GHashTable *) pixhash);
1448 if (widCached) {
1449 gtk_widget_destroy(GTK_WIDGET(widCached));
1450 wid = widCached = 0;
1453 virtual void SetFont(Font &font);
1454 virtual void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_);
1455 virtual void SetAverageCharWidth(int width);
1456 virtual void SetVisibleRows(int rows);
1457 virtual int GetVisibleRows() const;
1458 int GetRowHeight();
1459 virtual PRectangle GetDesiredRect();
1460 virtual int CaretFromEdge();
1461 virtual void Clear();
1462 virtual void Append(char *s, int type = -1);
1463 virtual int Length();
1464 virtual void Select(int n);
1465 virtual int GetSelection();
1466 virtual int Find(const char *prefix);
1467 virtual void GetValue(int n, char *value, int len);
1468 void RegisterRGBA(int type, RGBAImage *image);
1469 virtual void RegisterImage(int type, const char *xpm_data);
1470 virtual void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage);
1471 virtual void ClearRegisteredImages();
1472 virtual void SetDoubleClickAction(CallBackAction action, void *data) {
1473 doubleClickAction = action;
1474 doubleClickActionData = data;
1476 virtual void SetList(const char *listText, char separator, char typesep);
1479 ListBox *ListBox::Allocate() {
1480 ListBoxX *lb = new ListBoxX();
1481 return lb;
1484 // SmallScroller, a GtkScrolledWindow that can shrink very small, as
1485 // gtk_widget_set_size_request() cannot shrink widgets on GTK3
1486 typedef GtkScrolledWindow SmallScroller;
1487 typedef GtkScrolledWindowClass SmallScrollerClass;
1489 G_DEFINE_TYPE(SmallScroller, small_scroller, GTK_TYPE_SCROLLED_WINDOW)
1491 #if GTK_CHECK_VERSION(3,0,0)
1492 static void small_scroller_get_preferred_height(GtkWidget *widget, gint *min, gint *nat) {
1493 GTK_WIDGET_CLASS(small_scroller_parent_class)->get_preferred_height(widget, min, nat);
1494 *min = 1;
1496 #else
1497 static void small_scroller_size_request(GtkWidget *widget, GtkRequisition *req) {
1498 GTK_WIDGET_CLASS(small_scroller_parent_class)->size_request(widget, req);
1499 req->height = 1;
1501 #endif
1503 static void small_scroller_class_init(SmallScrollerClass *klass) {
1504 #if GTK_CHECK_VERSION(3,0,0)
1505 GTK_WIDGET_CLASS(klass)->get_preferred_height = small_scroller_get_preferred_height;
1506 #else
1507 GTK_WIDGET_CLASS(klass)->size_request = small_scroller_size_request;
1508 #endif
1511 static void small_scroller_init(SmallScroller *){}
1513 static gboolean ButtonPress(GtkWidget *, GdkEventButton* ev, gpointer p) {
1514 try {
1515 ListBoxX* lb = reinterpret_cast<ListBoxX*>(p);
1516 if (ev->type == GDK_2BUTTON_PRESS && lb->doubleClickAction != NULL) {
1517 lb->doubleClickAction(lb->doubleClickActionData);
1518 return TRUE;
1521 } catch (...) {
1522 // No pointer back to Scintilla to save status
1524 return FALSE;
1527 /* Change the active color to the selected color so the listbox uses the color
1528 scheme that it would use if it had the focus. */
1529 static void StyleSet(GtkWidget *w, GtkStyle*, void*) {
1531 g_return_if_fail(w != NULL);
1533 /* Copy the selected color to active. Note that the modify calls will cause
1534 recursive calls to this function after the value is updated and w->style to
1535 be set to a new object */
1537 #if GTK_CHECK_VERSION(3,0,0)
1538 GtkStyleContext *styleContext = gtk_widget_get_style_context(w);
1539 if (styleContext == NULL)
1540 return;
1542 GdkRGBA colourForeSelected;
1543 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourForeSelected);
1544 GdkRGBA colourForeActive;
1545 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourForeActive);
1546 if (!gdk_rgba_equal(&colourForeSelected, &colourForeActive))
1547 gtk_widget_override_color(w, GTK_STATE_FLAG_ACTIVE, &colourForeSelected);
1549 styleContext = gtk_widget_get_style_context(w);
1550 if (styleContext == NULL)
1551 return;
1553 GdkRGBA colourBaseSelected;
1554 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourBaseSelected);
1555 GdkRGBA colourBaseActive;
1556 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourBaseActive);
1557 if (!gdk_rgba_equal(&colourBaseSelected, &colourBaseActive))
1558 gtk_widget_override_background_color(w, GTK_STATE_FLAG_ACTIVE, &colourBaseSelected);
1559 #else
1560 GtkStyle *style = gtk_widget_get_style(w);
1561 if (style == NULL)
1562 return;
1563 if (!gdk_color_equal(&style->base[GTK_STATE_SELECTED], &style->base[GTK_STATE_ACTIVE]))
1564 gtk_widget_modify_base(w, GTK_STATE_ACTIVE, &style->base[GTK_STATE_SELECTED]);
1565 style = gtk_widget_get_style(w);
1566 if (style == NULL)
1567 return;
1568 if (!gdk_color_equal(&style->text[GTK_STATE_SELECTED], &style->text[GTK_STATE_ACTIVE]))
1569 gtk_widget_modify_text(w, GTK_STATE_ACTIVE, &style->text[GTK_STATE_SELECTED]);
1570 #endif
1573 void ListBoxX::Create(Window &, int, Point, int, bool, int) {
1574 if (widCached != 0) {
1575 wid = widCached;
1576 return;
1579 wid = widCached = gtk_window_new(GTK_WINDOW_POPUP);
1581 frame = gtk_frame_new(NULL);
1582 gtk_widget_show(PWidget(frame));
1583 gtk_container_add(GTK_CONTAINER(GetID()), PWidget(frame));
1584 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1585 gtk_container_set_border_width(GTK_CONTAINER(frame), 0);
1587 scroller = g_object_new(small_scroller_get_type(), NULL);
1588 gtk_container_set_border_width(GTK_CONTAINER(scroller), 0);
1589 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
1590 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1591 gtk_container_add(GTK_CONTAINER(frame), PWidget(scroller));
1592 gtk_widget_show(PWidget(scroller));
1594 /* Tree and its model */
1595 GtkListStore *store =
1596 gtk_list_store_new(N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING);
1598 list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1599 g_signal_connect(G_OBJECT(list), "style-set", G_CALLBACK(StyleSet), NULL);
1601 GtkTreeSelection *selection =
1602 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1603 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1604 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
1605 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list), FALSE);
1607 /* Columns */
1608 GtkTreeViewColumn *column = gtk_tree_view_column_new();
1609 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1610 gtk_tree_view_column_set_title(column, "Autocomplete");
1612 pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
1613 gtk_cell_renderer_set_fixed_size(pixbuf_renderer, 0, -1);
1614 gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE);
1615 gtk_tree_view_column_add_attribute(column, pixbuf_renderer,
1616 "pixbuf", PIXBUF_COLUMN);
1618 GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
1619 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1620 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1621 gtk_tree_view_column_add_attribute(column, renderer,
1622 "text", TEXT_COLUMN);
1624 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
1625 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list), "fixed-height-mode"))
1626 g_object_set(G_OBJECT(list), "fixed-height-mode", TRUE, NULL);
1628 GtkWidget *widget = PWidget(list); // No code inside the G_OBJECT macro
1629 gtk_container_add(GTK_CONTAINER(PWidget(scroller)), widget);
1630 gtk_widget_show(widget);
1631 g_signal_connect(G_OBJECT(widget), "button_press_event",
1632 G_CALLBACK(ButtonPress), this);
1635 void ListBoxX::SetFont(Font &scint_font) {
1636 // Only do for Pango font as there have been crashes for GDK fonts
1637 if (Created() && PFont(scint_font)->pfd) {
1638 // Current font is Pango font
1639 #if GTK_CHECK_VERSION(3,0,0)
1640 gtk_widget_override_font(PWidget(list), PFont(scint_font)->pfd);
1641 #else
1642 gtk_widget_modify_font(PWidget(list), PFont(scint_font)->pfd);
1643 #endif
1647 void ListBoxX::SetAverageCharWidth(int width) {
1648 aveCharWidth = width;
1651 void ListBoxX::SetVisibleRows(int rows) {
1652 desiredVisibleRows = rows;
1655 int ListBoxX::GetVisibleRows() const {
1656 return desiredVisibleRows;
1659 int ListBoxX::GetRowHeight()
1661 #if GTK_CHECK_VERSION(3,0,0)
1662 // This version sometimes reports erroneous results on GTK2, but the GTK2
1663 // version is inaccurate for GTK 3.14.
1664 GdkRectangle rect;
1665 GtkTreePath *path = gtk_tree_path_new_first();
1666 gtk_tree_view_get_background_area(GTK_TREE_VIEW(list), path, NULL, &rect);
1667 return rect.height;
1668 #else
1669 int row_height=0;
1670 int vertical_separator=0;
1671 int expander_size=0;
1672 GtkTreeViewColumn *column = gtk_tree_view_get_column(GTK_TREE_VIEW(list), 0);
1673 gtk_tree_view_column_cell_get_size(column, NULL, NULL, NULL, NULL, &row_height);
1674 gtk_widget_style_get(PWidget(list),
1675 "vertical-separator", &vertical_separator,
1676 "expander-size", &expander_size, NULL);
1677 row_height += vertical_separator;
1678 row_height = Platform::Maximum(row_height, expander_size);
1679 return row_height;
1680 #endif
1683 PRectangle ListBoxX::GetDesiredRect() {
1684 // Before any size allocated pretend its 100 wide so not scrolled
1685 PRectangle rc(0, 0, 100, 100);
1686 if (wid) {
1687 int rows = Length();
1688 if ((rows == 0) || (rows > desiredVisibleRows))
1689 rows = desiredVisibleRows;
1691 GtkRequisition req;
1692 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1693 // returns reasonable values.
1694 #if GTK_CHECK_VERSION(3,0,0)
1695 gtk_widget_get_preferred_size(GTK_WIDGET(frame), NULL, &req);
1696 #else
1697 gtk_widget_size_request(GTK_WIDGET(frame), &req);
1698 #endif
1699 int height;
1701 // First calculate height of the clist for our desired visible
1702 // row count otherwise it tries to expand to the total # of rows
1703 // Get cell height
1704 int row_height = GetRowHeight();
1705 #if GTK_CHECK_VERSION(3,0,0)
1706 GtkStyleContext *styleContextFrame = gtk_widget_get_style_context(PWidget(frame));
1707 GtkBorder padding, border;
1708 gtk_style_context_get_padding(styleContextFrame, GTK_STATE_FLAG_NORMAL, &padding);
1709 gtk_style_context_get_border(styleContextFrame, GTK_STATE_FLAG_NORMAL, &border);
1710 height = (rows * row_height
1711 + padding.top + padding.bottom
1712 + border.top + border.bottom
1713 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
1714 #else
1715 height = (rows * row_height
1716 + 2 * (PWidget(frame)->style->ythickness
1717 + GTK_CONTAINER(PWidget(list))->border_width));
1718 #endif
1719 rc.bottom = height;
1721 int width = maxItemCharacters;
1722 if (width < 12)
1723 width = 12;
1724 rc.right = width * (aveCharWidth + aveCharWidth / 3);
1725 // Add horizontal padding and borders
1726 int horizontal_separator=0;
1727 gtk_widget_style_get(PWidget(list),
1728 "horizontal-separator", &horizontal_separator, NULL);
1729 rc.right += horizontal_separator;
1730 #if GTK_CHECK_VERSION(3,0,0)
1731 rc.right += (padding.left + padding.right
1732 + border.left + border.right
1733 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
1734 #else
1735 rc.right += 2 * (PWidget(frame)->style->xthickness
1736 + GTK_CONTAINER(PWidget(list))->border_width);
1737 #endif
1738 if (Length() > rows) {
1739 // Add the width of the scrollbar
1740 GtkWidget *vscrollbar =
1741 gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(scroller));
1742 #if GTK_CHECK_VERSION(3,0,0)
1743 gtk_widget_get_preferred_size(vscrollbar, NULL, &req);
1744 #else
1745 gtk_widget_size_request(vscrollbar, &req);
1746 #endif
1747 rc.right += req.width;
1750 return rc;
1753 int ListBoxX::CaretFromEdge() {
1754 gint renderer_width, renderer_height;
1755 gtk_cell_renderer_get_fixed_size(pixbuf_renderer, &renderer_width,
1756 &renderer_height);
1757 return 4 + renderer_width;
1760 void ListBoxX::Clear() {
1761 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1762 gtk_list_store_clear(GTK_LIST_STORE(model));
1763 maxItemCharacters = 0;
1766 static void init_pixmap(ListImage *list_image) {
1767 if (list_image->rgba_data) {
1768 // Drop any existing pixmap/bitmap as data may have changed
1769 if (list_image->pixbuf)
1770 g_object_unref(list_image->pixbuf);
1771 list_image->pixbuf =
1772 gdk_pixbuf_new_from_data(list_image->rgba_data->Pixels(),
1773 GDK_COLORSPACE_RGB,
1774 TRUE,
1776 list_image->rgba_data->GetWidth(),
1777 list_image->rgba_data->GetHeight(),
1778 list_image->rgba_data->GetWidth() * 4,
1779 NULL,
1780 NULL);
1784 #define SPACING 5
1786 void ListBoxX::Append(char *s, int type) {
1787 ListImage *list_image = NULL;
1788 if ((type >= 0) && pixhash) {
1789 list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash
1790 , (gconstpointer) GINT_TO_POINTER(type)));
1792 GtkTreeIter iter;
1793 GtkListStore *store =
1794 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
1795 gtk_list_store_append(GTK_LIST_STORE(store), &iter);
1796 if (list_image) {
1797 if (NULL == list_image->pixbuf)
1798 init_pixmap(list_image);
1799 if (list_image->pixbuf) {
1800 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1801 PIXBUF_COLUMN, list_image->pixbuf,
1802 TEXT_COLUMN, s, -1);
1804 gint pixbuf_width = gdk_pixbuf_get_width(list_image->pixbuf);
1805 gint renderer_height, renderer_width;
1806 gtk_cell_renderer_get_fixed_size(pixbuf_renderer,
1807 &renderer_width, &renderer_height);
1808 if (pixbuf_width > renderer_width)
1809 gtk_cell_renderer_set_fixed_size(pixbuf_renderer,
1810 pixbuf_width, -1);
1811 } else {
1812 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1813 TEXT_COLUMN, s, -1);
1815 } else {
1816 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1817 TEXT_COLUMN, s, -1);
1819 size_t len = strlen(s);
1820 if (maxItemCharacters < len)
1821 maxItemCharacters = len;
1824 int ListBoxX::Length() {
1825 if (wid)
1826 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1827 (GTK_TREE_VIEW(list)), NULL);
1828 return 0;
1831 void ListBoxX::Select(int n) {
1832 GtkTreeIter iter;
1833 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1834 GtkTreeSelection *selection =
1835 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1837 if (n < 0) {
1838 gtk_tree_selection_unselect_all(selection);
1839 return;
1842 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
1843 if (valid) {
1844 gtk_tree_selection_select_iter(selection, &iter);
1846 // Move the scrollbar to show the selection.
1847 int total = Length();
1848 #if GTK_CHECK_VERSION(3,0,0)
1849 GtkAdjustment *adj =
1850 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list));
1851 gfloat value = ((gfloat)n / total) * (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_lower(adj))
1852 + gtk_adjustment_get_lower(adj) - gtk_adjustment_get_page_size(adj) / 2;
1853 #else
1854 GtkAdjustment *adj =
1855 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list));
1856 gfloat value = ((gfloat)n / total) * (adj->upper - adj->lower)
1857 + adj->lower - adj->page_size / 2;
1858 #endif
1859 // Get cell height
1860 int row_height = GetRowHeight();
1862 int rows = Length();
1863 if ((rows == 0) || (rows > desiredVisibleRows))
1864 rows = desiredVisibleRows;
1865 if (rows & 0x1) {
1866 // Odd rows to display -- We are now in the middle.
1867 // Align it so that we don't chop off rows.
1868 value += (gfloat)row_height / 2.0;
1870 // Clamp it.
1871 value = (value < 0)? 0 : value;
1872 #if GTK_CHECK_VERSION(3,0,0)
1873 value = (value > (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)))?
1874 (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)) : value;
1875 #else
1876 value = (value > (adj->upper - adj->page_size))?
1877 (adj->upper - adj->page_size) : value;
1878 #endif
1880 // Set it.
1881 gtk_adjustment_set_value(adj, value);
1882 } else {
1883 gtk_tree_selection_unselect_all(selection);
1887 int ListBoxX::GetSelection() {
1888 int index = -1;
1889 GtkTreeIter iter;
1890 GtkTreeModel *model;
1891 GtkTreeSelection *selection;
1892 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1893 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1894 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1895 int *indices = gtk_tree_path_get_indices(path);
1896 // Don't free indices.
1897 if (indices)
1898 index = indices[0];
1899 gtk_tree_path_free(path);
1901 return index;
1904 int ListBoxX::Find(const char *prefix) {
1905 GtkTreeIter iter;
1906 GtkTreeModel *model =
1907 gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1908 bool valid = gtk_tree_model_get_iter_first(model, &iter) != FALSE;
1909 int i = 0;
1910 while(valid) {
1911 gchar *s;
1912 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &s, -1);
1913 if (s && (0 == strncmp(prefix, s, strlen(prefix)))) {
1914 g_free(s);
1915 return i;
1917 g_free(s);
1918 valid = gtk_tree_model_iter_next(model, &iter) != FALSE;
1919 i++;
1921 return -1;
1924 void ListBoxX::GetValue(int n, char *value, int len) {
1925 char *text = NULL;
1926 GtkTreeIter iter;
1927 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1928 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
1929 if (valid) {
1930 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &text, -1);
1932 if (text && len > 0) {
1933 g_strlcpy(value, text, len);
1934 } else {
1935 value[0] = '\0';
1937 g_free(text);
1940 // g_return_if_fail causes unnecessary compiler warning in release compile.
1941 #ifdef _MSC_VER
1942 #pragma warning(disable: 4127)
1943 #endif
1945 void ListBoxX::RegisterRGBA(int type, RGBAImage *image) {
1946 images.Add(type, image);
1948 if (!pixhash) {
1949 pixhash = g_hash_table_new(g_direct_hash, g_direct_equal);
1951 ListImage *list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash,
1952 (gconstpointer) GINT_TO_POINTER(type)));
1953 if (list_image) {
1954 // Drop icon already registered
1955 if (list_image->pixbuf)
1956 g_object_unref(list_image->pixbuf);
1957 list_image->pixbuf = NULL;
1958 list_image->rgba_data = image;
1959 } else {
1960 list_image = g_new0(ListImage, 1);
1961 list_image->rgba_data = image;
1962 g_hash_table_insert((GHashTable *) pixhash, GINT_TO_POINTER(type),
1963 (gpointer) list_image);
1967 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
1968 g_return_if_fail(xpm_data);
1969 XPM xpmImage(xpm_data);
1970 RegisterRGBA(type, new RGBAImage(xpmImage));
1973 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
1974 RegisterRGBA(type, new RGBAImage(width, height, 1.0, pixelsImage));
1977 void ListBoxX::ClearRegisteredImages() {
1978 images.Clear();
1981 void ListBoxX::SetList(const char *listText, char separator, char typesep) {
1982 Clear();
1983 int count = strlen(listText) + 1;
1984 std::vector<char> words(listText, listText+count);
1985 char *startword = &words[0];
1986 char *numword = NULL;
1987 int i = 0;
1988 for (; words[i]; i++) {
1989 if (words[i] == separator) {
1990 words[i] = '\0';
1991 if (numword)
1992 *numword = '\0';
1993 Append(startword, numword?atoi(numword + 1):-1);
1994 startword = &words[0] + i + 1;
1995 numword = NULL;
1996 } else if (words[i] == typesep) {
1997 numword = &words[0] + i;
2000 if (startword) {
2001 if (numword)
2002 *numword = '\0';
2003 Append(startword, numword?atoi(numword + 1):-1);
2007 Menu::Menu() : mid(0) {}
2009 void Menu::CreatePopUp() {
2010 Destroy();
2011 mid = gtk_menu_new();
2012 #if GLIB_CHECK_VERSION(2,10,0)
2013 g_object_ref_sink(G_OBJECT(mid));
2014 #else
2015 g_object_ref(G_OBJECT(mid));
2016 gtk_object_sink(GTK_OBJECT(G_OBJECT(mid)));
2017 #endif
2020 void Menu::Destroy() {
2021 if (mid)
2022 g_object_unref(G_OBJECT(mid));
2023 mid = 0;
2026 static void MenuPositionFunc(GtkMenu *, gint *x, gint *y, gboolean *, gpointer userData) {
2027 sptr_t intFromPointer = reinterpret_cast<sptr_t>(userData);
2028 *x = intFromPointer & 0xffff;
2029 *y = intFromPointer >> 16;
2032 void Menu::Show(Point pt, Window &) {
2033 int screenHeight = gdk_screen_height();
2034 int screenWidth = gdk_screen_width();
2035 GtkMenu *widget = reinterpret_cast<GtkMenu *>(mid);
2036 gtk_widget_show_all(GTK_WIDGET(widget));
2037 GtkRequisition requisition;
2038 #if GTK_CHECK_VERSION(3,0,0)
2039 gtk_widget_get_preferred_size(GTK_WIDGET(widget), NULL, &requisition);
2040 #else
2041 gtk_widget_size_request(GTK_WIDGET(widget), &requisition);
2042 #endif
2043 if ((pt.x + requisition.width) > screenWidth) {
2044 pt.x = screenWidth - requisition.width;
2046 if ((pt.y + requisition.height) > screenHeight) {
2047 pt.y = screenHeight - requisition.height;
2049 gtk_menu_popup(widget, NULL, NULL, MenuPositionFunc,
2050 reinterpret_cast<void *>((static_cast<int>(pt.y) << 16) | static_cast<int>(pt.x)), 0,
2051 gtk_get_current_event_time());
2054 ElapsedTime::ElapsedTime() {
2055 GTimeVal curTime;
2056 g_get_current_time(&curTime);
2057 bigBit = curTime.tv_sec;
2058 littleBit = curTime.tv_usec;
2061 class DynamicLibraryImpl : public DynamicLibrary {
2062 protected:
2063 GModule* m;
2064 public:
2065 explicit DynamicLibraryImpl(const char *modulePath) {
2066 m = g_module_open(modulePath, G_MODULE_BIND_LAZY);
2069 virtual ~DynamicLibraryImpl() {
2070 if (m != NULL)
2071 g_module_close(m);
2074 // Use g_module_symbol to get a pointer to the relevant function.
2075 virtual Function FindFunction(const char *name) {
2076 if (m != NULL) {
2077 gpointer fn_address = NULL;
2078 gboolean status = g_module_symbol(m, name, &fn_address);
2079 if (status)
2080 return static_cast<Function>(fn_address);
2081 else
2082 return NULL;
2083 } else {
2084 return NULL;
2088 virtual bool IsValid() {
2089 return m != NULL;
2093 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
2094 return static_cast<DynamicLibrary *>( new DynamicLibraryImpl(modulePath) );
2097 double ElapsedTime::Duration(bool reset) {
2098 GTimeVal curTime;
2099 g_get_current_time(&curTime);
2100 long endBigBit = curTime.tv_sec;
2101 long endLittleBit = curTime.tv_usec;
2102 double result = 1000000.0 * (endBigBit - bigBit);
2103 result += endLittleBit - littleBit;
2104 result /= 1000000.0;
2105 if (reset) {
2106 bigBit = endBigBit;
2107 littleBit = endLittleBit;
2109 return result;
2112 ColourDesired Platform::Chrome() {
2113 return ColourDesired(0xe0, 0xe0, 0xe0);
2116 ColourDesired Platform::ChromeHighlight() {
2117 return ColourDesired(0xff, 0xff, 0xff);
2120 const char *Platform::DefaultFont() {
2121 #ifdef G_OS_WIN32
2122 return "Lucida Console";
2123 #else
2124 return "!Sans";
2125 #endif
2128 int Platform::DefaultFontSize() {
2129 #ifdef G_OS_WIN32
2130 return 10;
2131 #else
2132 return 12;
2133 #endif
2136 unsigned int Platform::DoubleClickTime() {
2137 return 500; // Half a second
2140 bool Platform::MouseButtonBounce() {
2141 return true;
2144 void Platform::DebugDisplay(const char *s) {
2145 fprintf(stderr, "%s", s);
2148 bool Platform::IsKeyDown(int) {
2149 // TODO: discover state of keys in GTK+/X
2150 return false;
2153 long Platform::SendScintilla(
2154 WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
2155 return scintilla_send_message(SCINTILLA(w), msg, wParam, lParam);
2158 long Platform::SendScintillaPointer(
2159 WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
2160 return scintilla_send_message(SCINTILLA(w), msg, wParam,
2161 reinterpret_cast<sptr_t>(lParam));
2164 bool Platform::IsDBCSLeadByte(int codePage, char ch) {
2165 // Byte ranges found in Wikipedia articles with relevant search strings in each case
2166 unsigned char uch = static_cast<unsigned char>(ch);
2167 switch (codePage) {
2168 case 932:
2169 // Shift_jis
2170 return ((uch >= 0x81) && (uch <= 0x9F)) ||
2171 ((uch >= 0xE0) && (uch <= 0xFC));
2172 // Lead bytes F0 to FC may be a Microsoft addition.
2173 case 936:
2174 // GBK
2175 return (uch >= 0x81) && (uch <= 0xFE);
2176 case 950:
2177 // Big5
2178 return (uch >= 0x81) && (uch <= 0xFE);
2179 // Korean EUC-KR may be code page 949.
2181 return false;
2184 int Platform::DBCSCharLength(int codePage, const char *s) {
2185 if (codePage == 932 || codePage == 936 || codePage == 950) {
2186 return IsDBCSLeadByte(codePage, s[0]) ? 2 : 1;
2187 } else {
2188 int bytes = mblen(s, MB_CUR_MAX);
2189 if (bytes >= 1)
2190 return bytes;
2191 else
2192 return 1;
2196 int Platform::DBCSCharMaxLength() {
2197 return MB_CUR_MAX;
2198 //return 2;
2201 // These are utility functions not really tied to a platform
2203 int Platform::Minimum(int a, int b) {
2204 if (a < b)
2205 return a;
2206 else
2207 return b;
2210 int Platform::Maximum(int a, int b) {
2211 if (a > b)
2212 return a;
2213 else
2214 return b;
2217 //#define TRACE
2219 #ifdef TRACE
2220 void Platform::DebugPrintf(const char *format, ...) {
2221 char buffer[2000];
2222 va_list pArguments;
2223 va_start(pArguments, format);
2224 vsprintf(buffer, format, pArguments);
2225 va_end(pArguments);
2226 Platform::DebugDisplay(buffer);
2228 #else
2229 void Platform::DebugPrintf(const char *, ...) {}
2231 #endif
2233 // Not supported for GTK+
2234 static bool assertionPopUps = true;
2236 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
2237 bool ret = assertionPopUps;
2238 assertionPopUps = assertionPopUps_;
2239 return ret;
2242 void Platform::Assert(const char *c, const char *file, int line) {
2243 char buffer[2000];
2244 g_snprintf(buffer, sizeof(buffer), "Assertion [%s] failed at %s %d\r\n", c, file, line);
2245 Platform::DebugDisplay(buffer);
2246 abort();
2249 int Platform::Clamp(int val, int minVal, int maxVal) {
2250 if (val > maxVal)
2251 val = maxVal;
2252 if (val < minVal)
2253 val = minVal;
2254 return val;
2257 void Platform_Initialise() {
2258 FontMutexAllocate();
2261 void Platform_Finalise() {
2262 FontCached::ReleaseAll();
2263 FontMutexFree();