Update Scintilla to version 3.4.1
[geany-mirror.git] / scintilla / gtk / PlatGTK.cxx
blobc1e5566e5184f60ae40f8d0bf3e7b380d8482bc4
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 return inited;
533 void SurfaceImpl::Init(WindowID wid) {
534 Release();
535 PLATFORM_ASSERT(wid);
536 #if GTK_CHECK_VERSION(3,0,0)
537 GdkWindow *drawable_ = gtk_widget_get_window(PWidget(wid));
538 #else
539 GdkDrawable *drawable_ = GDK_DRAWABLE(PWidget(wid)->window);
540 #endif
541 if (drawable_) {
542 context = gdk_cairo_create(drawable_);
543 PLATFORM_ASSERT(context);
544 } else {
545 // Shouldn't happen with valid window but may when calls made before
546 // window completely allocated and mapped.
547 psurf = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 1, 1);
548 context = cairo_create(psurf);
550 createdGC = true;
551 pcontext = gtk_widget_create_pango_context(PWidget(wid));
552 PLATFORM_ASSERT(pcontext);
553 layout = pango_layout_new(pcontext);
554 PLATFORM_ASSERT(layout);
555 inited = true;
558 void SurfaceImpl::Init(SurfaceID sid, WindowID wid) {
559 PLATFORM_ASSERT(sid);
560 Release();
561 PLATFORM_ASSERT(wid);
562 context = cairo_reference(reinterpret_cast<cairo_t *>(sid));
563 pcontext = gtk_widget_create_pango_context(PWidget(wid));
564 // update the Pango context in case sid isn't the widget's surface
565 pango_cairo_update_context(context, pcontext);
566 layout = pango_layout_new(pcontext);
567 cairo_set_line_width(context, 1);
568 createdGC = true;
569 inited = true;
572 void SurfaceImpl::InitPixMap(int width, int height, Surface *surface_, WindowID wid) {
573 PLATFORM_ASSERT(surface_);
574 Release();
575 SurfaceImpl *surfImpl = static_cast<SurfaceImpl *>(surface_);
576 PLATFORM_ASSERT(wid);
577 context = cairo_reference(surfImpl->context);
578 pcontext = gtk_widget_create_pango_context(PWidget(wid));
579 // update the Pango context in case surface_ isn't the widget's surface
580 pango_cairo_update_context(context, pcontext);
581 PLATFORM_ASSERT(pcontext);
582 layout = pango_layout_new(pcontext);
583 PLATFORM_ASSERT(layout);
584 if (height > 0 && width > 0)
585 psurf = CreateSimilarSurface(
586 WindowFromWidget(PWidget(wid)),
587 CAIRO_CONTENT_COLOR_ALPHA, width, height);
588 cairo_destroy(context);
589 context = cairo_create(psurf);
590 cairo_rectangle(context, 0, 0, width, height);
591 cairo_set_source_rgb(context, 1.0, 0, 0);
592 cairo_fill(context);
593 // This produces sharp drawing more similar to GDK:
594 //cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
595 cairo_set_line_width(context, 1);
596 createdGC = true;
597 inited = true;
600 void SurfaceImpl::PenColour(ColourDesired fore) {
601 if (context) {
602 ColourDesired cdFore(fore.AsLong());
603 cairo_set_source_rgb(context,
604 cdFore.GetRed() / 255.0,
605 cdFore.GetGreen() / 255.0,
606 cdFore.GetBlue() / 255.0);
610 int SurfaceImpl::LogPixelsY() {
611 return 72;
614 int SurfaceImpl::DeviceHeightFont(int points) {
615 int logPix = LogPixelsY();
616 return (points * logPix + logPix / 2) / 72;
619 void SurfaceImpl::MoveTo(int x_, int y_) {
620 x = x_;
621 y = y_;
624 static int Delta(int difference) {
625 if (difference < 0)
626 return -1;
627 else if (difference > 0)
628 return 1;
629 else
630 return 0;
633 void SurfaceImpl::LineTo(int x_, int y_) {
634 // cairo_line_to draws the end position, unlike Win32 or GDK with GDK_CAP_NOT_LAST.
635 // For simple cases, move back one pixel from end.
636 if (context) {
637 int xDiff = x_ - x;
638 int xDelta = Delta(xDiff);
639 int yDiff = y_ - y;
640 int yDelta = Delta(yDiff);
641 if ((xDiff == 0) || (yDiff == 0)) {
642 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
643 int xEnd = x_ - xDelta;
644 int left = Platform::Minimum(x, xEnd);
645 int width = abs(x - xEnd) + 1;
646 int yEnd = y_ - yDelta;
647 int top = Platform::Minimum(y, yEnd);
648 int height = abs(y - yEnd) + 1;
649 cairo_rectangle(context, left, top, width, height);
650 cairo_fill(context);
651 } else if ((abs(xDiff) == abs(yDiff))) {
652 // 45 degree slope
653 cairo_move_to(context, x + 0.5, y + 0.5);
654 cairo_line_to(context, x_ + 0.5 - xDelta, y_ + 0.5 - yDelta);
655 } else {
656 // Line has a different slope so difficult to avoid last pixel
657 cairo_move_to(context, x + 0.5, y + 0.5);
658 cairo_line_to(context, x_ + 0.5, y_ + 0.5);
660 cairo_stroke(context);
662 x = x_;
663 y = y_;
666 void SurfaceImpl::Polygon(Point *pts, int npts, ColourDesired fore,
667 ColourDesired back) {
668 PenColour(back);
669 cairo_move_to(context, pts[0].x + 0.5, pts[0].y + 0.5);
670 for (int i = 1; i < npts; i++) {
671 cairo_line_to(context, pts[i].x + 0.5, pts[i].y + 0.5);
673 cairo_close_path(context);
674 cairo_fill_preserve(context);
675 PenColour(fore);
676 cairo_stroke(context);
679 void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
680 if (context) {
681 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5,
682 rc.right - rc.left - 1, rc.bottom - rc.top - 1);
683 PenColour(back);
684 cairo_fill_preserve(context);
685 PenColour(fore);
686 cairo_stroke(context);
690 void SurfaceImpl::FillRectangle(PRectangle rc, ColourDesired back) {
691 PenColour(back);
692 if (context && (rc.left < maxCoordinate)) { // Protect against out of range
693 rc.left = lround(rc.left);
694 rc.right = lround(rc.right);
695 cairo_rectangle(context, rc.left, rc.top,
696 rc.right - rc.left, rc.bottom - rc.top);
697 cairo_fill(context);
701 void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
702 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfacePattern);
703 bool canDraw = surfi.psurf;
704 if (canDraw) {
705 // Tile pattern over rectangle
706 // Currently assumes 8x8 pattern
707 int widthPat = 8;
708 int heightPat = 8;
709 for (int xTile = rc.left; xTile < rc.right; xTile += widthPat) {
710 int widthx = (xTile + widthPat > rc.right) ? rc.right - xTile : widthPat;
711 for (int yTile = rc.top; yTile < rc.bottom; yTile += heightPat) {
712 int heighty = (yTile + heightPat > rc.bottom) ? rc.bottom - yTile : heightPat;
713 cairo_set_source_surface(context, surfi.psurf, xTile, yTile);
714 cairo_rectangle(context, xTile, yTile, widthx, heighty);
715 cairo_fill(context);
718 } else {
719 // Something is wrong so try to show anyway
720 // Shows up black because colour not allocated
721 FillRectangle(rc, ColourDesired(0));
725 void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
726 if (((rc.right - rc.left) > 4) && ((rc.bottom - rc.top) > 4)) {
727 // Approximate a round rect with some cut off corners
728 Point pts[] = {
729 Point(rc.left + 2, rc.top),
730 Point(rc.right - 2, rc.top),
731 Point(rc.right, rc.top + 2),
732 Point(rc.right, rc.bottom - 2),
733 Point(rc.right - 2, rc.bottom),
734 Point(rc.left + 2, rc.bottom),
735 Point(rc.left, rc.bottom - 2),
736 Point(rc.left, rc.top + 2),
738 Polygon(pts, ELEMENTS(pts), fore, back);
739 } else {
740 RectangleDraw(rc, fore, back);
744 static void PathRoundRectangle(cairo_t *context, double left, double top, double width, double height, int radius) {
745 double degrees = kPi / 180.0;
747 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 2, 0)
748 cairo_new_sub_path(context);
749 #else
750 // First arc is in the top-right corner and starts from a point on the top line
751 cairo_move_to(context, left + width - radius, top);
752 #endif
753 cairo_arc(context, left + width - radius, top + radius, radius, -90 * degrees, 0 * degrees);
754 cairo_arc(context, left + width - radius, top + height - radius, radius, 0 * degrees, 90 * degrees);
755 cairo_arc(context, left + radius, top + height - radius, radius, 90 * degrees, 180 * degrees);
756 cairo_arc(context, left + radius, top + radius, radius, 180 * degrees, 270 * degrees);
757 cairo_close_path(context);
760 void SurfaceImpl::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
761 ColourDesired outline, int alphaOutline, int flags) {
762 if (context && rc.Width() > 0) {
763 ColourDesired cdFill(fill.AsLong());
764 cairo_set_source_rgba(context,
765 cdFill.GetRed() / 255.0,
766 cdFill.GetGreen() / 255.0,
767 cdFill.GetBlue() / 255.0,
768 alphaFill / 255.0);
769 if (cornerSize > 0)
770 PathRoundRectangle(context, rc.left + 1.0, rc.top + 1.0, rc.right - rc.left - 2.0, rc.bottom - rc.top - 2.0, cornerSize);
771 else
772 cairo_rectangle(context, rc.left + 1.0, rc.top + 1.0, rc.right - rc.left - 2.0, rc.bottom - rc.top - 2.0);
773 cairo_fill(context);
775 ColourDesired cdOutline(outline.AsLong());
776 cairo_set_source_rgba(context,
777 cdOutline.GetRed() / 255.0,
778 cdOutline.GetGreen() / 255.0,
779 cdOutline.GetBlue() / 255.0,
780 alphaOutline / 255.0);
781 if (cornerSize > 0)
782 PathRoundRectangle(context, rc.left + 0.5, rc.top + 0.5, rc.right - rc.left - 1, rc.bottom - rc.top - 1, cornerSize);
783 else
784 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5, rc.right - rc.left - 1, rc.bottom - rc.top - 1);
785 cairo_stroke(context);
789 void SurfaceImpl::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
790 if (rc.Width() > width)
791 rc.left += (rc.Width() - width) / 2;
792 rc.right = rc.left + width;
793 if (rc.Height() > height)
794 rc.top += (rc.Height() - height) / 2;
795 rc.bottom = rc.top + height;
797 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
798 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
799 #else
800 int stride = width * 4;
801 #endif
802 int ucs = stride * height;
803 std::vector<unsigned char> image(ucs);
804 for (int y=0; y<height; y++) {
805 for (int x=0; x<width; x++) {
806 unsigned char *pixel = &image[0] + y*stride + x * 4;
807 unsigned char alpha = pixelsImage[3];
808 pixel[2] = (*pixelsImage++) * alpha / 255;
809 pixel[1] = (*pixelsImage++) * alpha / 255;
810 pixel[0] = (*pixelsImage++) * alpha / 255;
811 pixel[3] = *pixelsImage++;
815 cairo_surface_t *psurf = cairo_image_surface_create_for_data(&image[0], CAIRO_FORMAT_ARGB32, width, height, stride);
816 cairo_set_source_surface(context, psurf, rc.left, rc.top);
817 cairo_rectangle(context, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
818 cairo_fill(context);
820 cairo_surface_destroy(psurf);
823 void SurfaceImpl::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
824 PenColour(back);
825 cairo_arc(context, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2,
826 Platform::Minimum(rc.Width(), rc.Height()) / 2, 0, 2*kPi);
827 cairo_fill_preserve(context);
828 PenColour(fore);
829 cairo_stroke(context);
832 void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
833 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfaceSource);
834 bool canDraw = surfi.psurf;
835 if (canDraw) {
836 cairo_set_source_surface(context, surfi.psurf,
837 rc.left - from.x, rc.top - from.y);
838 cairo_rectangle(context, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
839 cairo_fill(context);
843 std::string UTF8FromLatin1(const char *s, int len) {
844 std::string utfForm(len*2 + 1, '\0');
845 size_t lenU = 0;
846 for (int i=0; i<len; i++) {
847 unsigned int uch = static_cast<unsigned char>(s[i]);
848 if (uch < 0x80) {
849 utfForm[lenU++] = uch;
850 } else {
851 utfForm[lenU++] = static_cast<char>(0xC0 | (uch >> 6));
852 utfForm[lenU++] = static_cast<char>(0x80 | (uch & 0x3f));
855 utfForm.resize(lenU);
856 return utfForm;
859 static std::string UTF8FromIconv(const Converter &conv, const char *s, int len) {
860 if (conv) {
861 std::string utfForm(len*3+1, '\0');
862 char *pin = const_cast<char *>(s);
863 size_t inLeft = len;
864 char *putf = &utfForm[0];
865 char *pout = putf;
866 size_t outLeft = len*3+1;
867 size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
868 if (conversions != ((size_t)(-1))) {
869 *pout = '\0';
870 utfForm.resize(pout - putf);
871 return utfForm;
874 return std::string();
877 // Work out how many bytes are in a character by trying to convert using iconv,
878 // returning the first length that succeeds.
879 static size_t MultiByteLenFromIconv(const Converter &conv, const char *s, size_t len) {
880 for (size_t lenMB=1; (lenMB<4) && (lenMB <= len); lenMB++) {
881 char wcForm[2];
882 char *pin = const_cast<char *>(s);
883 size_t inLeft = lenMB;
884 char *pout = wcForm;
885 size_t outLeft = 2;
886 size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
887 if (conversions != ((size_t)(-1))) {
888 return lenMB;
891 return 1;
894 void SurfaceImpl::DrawTextBase(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
895 ColourDesired fore) {
896 PenColour(fore);
897 if (context) {
898 XYPOSITION xText = rc.left;
899 if (PFont(font_)->pfd) {
900 std::string utfForm;
901 if (et == UTF8) {
902 pango_layout_set_text(layout, s, len);
903 } else {
904 SetConverter(PFont(font_)->characterSet);
905 utfForm = UTF8FromIconv(conv, s, len);
906 if (utfForm.empty()) { // iconv failed so treat as Latin1
907 utfForm = UTF8FromLatin1(s, len);
909 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
911 pango_layout_set_font_description(layout, PFont(font_)->pfd);
912 pango_cairo_update_layout(context, layout);
913 #ifdef PANGO_VERSION
914 PangoLayoutLine *pll = pango_layout_get_line_readonly(layout,0);
915 #else
916 PangoLayoutLine *pll = pango_layout_get_line(layout,0);
917 #endif
918 cairo_move_to(context, xText, ybase);
919 pango_cairo_show_layout_line(context, pll);
924 void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
925 ColourDesired fore, ColourDesired back) {
926 FillRectangle(rc, back);
927 DrawTextBase(rc, font_, ybase, s, len, fore);
930 // On GTK+, exactly same as DrawTextNoClip
931 void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
932 ColourDesired fore, ColourDesired back) {
933 FillRectangle(rc, back);
934 DrawTextBase(rc, font_, ybase, s, len, fore);
937 void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
938 ColourDesired fore) {
939 // Avoid drawing spaces in transparent mode
940 for (int i=0; i<len; i++) {
941 if (s[i] != ' ') {
942 DrawTextBase(rc, font_, ybase, s, len, fore);
943 return;
948 class ClusterIterator {
949 PangoLayoutIter *iter;
950 PangoRectangle pos;
951 int lenPositions;
952 public:
953 bool finished;
954 XYPOSITION positionStart;
955 XYPOSITION position;
956 XYPOSITION distance;
957 int curIndex;
958 ClusterIterator(PangoLayout *layout, int len) : lenPositions(len), finished(false),
959 positionStart(0), position(0), distance(0), curIndex(0) {
960 iter = pango_layout_get_iter(layout);
961 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
963 ~ClusterIterator() {
964 pango_layout_iter_free(iter);
967 void Next() {
968 positionStart = position;
969 if (pango_layout_iter_next_cluster(iter)) {
970 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
971 position = doubleFromPangoUnits(pos.x);
972 curIndex = pango_layout_iter_get_index(iter);
973 } else {
974 finished = true;
975 position = doubleFromPangoUnits(pos.x + pos.width);
976 curIndex = lenPositions;
978 distance = position - positionStart;
982 void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
983 if (font_.GetID()) {
984 const int lenPositions = len;
985 if (PFont(font_)->pfd) {
986 if (len == 1) {
987 int width = PFont(font_)->CharWidth(*s, et);
988 if (width) {
989 positions[0] = width;
990 return;
993 pango_layout_set_font_description(layout, PFont(font_)->pfd);
994 if (et == UTF8) {
995 // Simple and direct as UTF-8 is native Pango encoding
996 int i = 0;
997 pango_layout_set_text(layout, s, len);
998 ClusterIterator iti(layout, lenPositions);
999 while (!iti.finished) {
1000 iti.Next();
1001 int places = iti.curIndex - i;
1002 while (i < iti.curIndex) {
1003 // Evenly distribute space among bytes of this cluster.
1004 // Would be better to find number of characters and then
1005 // divide evenly between characters with each byte of a character
1006 // being at the same position.
1007 positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places;
1008 i++;
1011 PLATFORM_ASSERT(i == lenPositions);
1012 } else {
1013 int positionsCalculated = 0;
1014 if (et == dbcs) {
1015 SetConverter(PFont(font_)->characterSet);
1016 std::string utfForm = UTF8FromIconv(conv, s, len);
1017 if (!utfForm.empty()) {
1018 // Convert to UTF-8 so can ask Pango for widths, then
1019 // Loop through UTF-8 and DBCS forms, taking account of different
1020 // character byte lengths.
1021 Converter convMeasure("UCS-2", CharacterSetID(characterSet), false);
1022 pango_layout_set_text(layout, utfForm.c_str(), strlen(utfForm.c_str()));
1023 int i = 0;
1024 int clusterStart = 0;
1025 ClusterIterator iti(layout, strlen(utfForm.c_str()));
1026 while (!iti.finished) {
1027 iti.Next();
1028 int clusterEnd = iti.curIndex;
1029 int places = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
1030 int place = 1;
1031 while (clusterStart < clusterEnd) {
1032 size_t lenChar = MultiByteLenFromIconv(convMeasure, s+i, len-i);
1033 while (lenChar--) {
1034 positions[i++] = iti.position - (places - place) * iti.distance / places;
1035 positionsCalculated++;
1037 clusterStart += UTF8CharLength(static_cast<unsigned char>(utfForm.c_str()[clusterStart]));
1038 place++;
1041 PLATFORM_ASSERT(i == lenPositions);
1044 if (positionsCalculated < 1 ) {
1045 // Either 8-bit or DBCS conversion failed so treat as 8-bit.
1046 SetConverter(PFont(font_)->characterSet);
1047 const bool rtlCheck = PFont(font_)->characterSet == SC_CHARSET_HEBREW ||
1048 PFont(font_)->characterSet == SC_CHARSET_ARABIC;
1049 std::string utfForm = UTF8FromIconv(conv, s, len);
1050 if (utfForm.empty()) {
1051 utfForm = UTF8FromLatin1(s, len);
1053 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
1054 int i = 0;
1055 int clusterStart = 0;
1056 // Each 8-bit input character may take 1 or 2 bytes in UTF-8
1057 // and groups of up to 3 may be represented as ligatures.
1058 ClusterIterator iti(layout, utfForm.length());
1059 while (!iti.finished) {
1060 iti.Next();
1061 int clusterEnd = iti.curIndex;
1062 int ligatureLength = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
1063 if (rtlCheck && ((clusterEnd <= clusterStart) || (ligatureLength == 0) || (ligatureLength > 3))) {
1064 // Something has gone wrong: exit quickly but pretend all the characters are equally spaced:
1065 int widthLayout = 0;
1066 pango_layout_get_size(layout, &widthLayout, NULL);
1067 XYPOSITION widthTotal = doubleFromPangoUnits(widthLayout);
1068 for (int bytePos=0; bytePos<lenPositions; bytePos++) {
1069 positions[bytePos] = widthTotal / lenPositions * (bytePos + 1);
1071 return;
1073 PLATFORM_ASSERT(ligatureLength > 0 && ligatureLength <= 3);
1074 for (int charInLig=0; charInLig<ligatureLength; charInLig++) {
1075 positions[i++] = iti.position - (ligatureLength - 1 - charInLig) * iti.distance / ligatureLength;
1077 clusterStart = clusterEnd;
1079 PLATFORM_ASSERT(i == lenPositions);
1082 if (len == 1) {
1083 PFont(font_)->SetCharWidth(*s, positions[0], et);
1085 return;
1087 } else {
1088 // No font so return an ascending range of values
1089 for (int i = 0; i < len; i++) {
1090 positions[i] = i + 1;
1095 XYPOSITION SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
1096 if (font_.GetID()) {
1097 if (PFont(font_)->pfd) {
1098 std::string utfForm;
1099 pango_layout_set_font_description(layout, PFont(font_)->pfd);
1100 PangoRectangle pos;
1101 if (et == UTF8) {
1102 pango_layout_set_text(layout, s, len);
1103 } else {
1104 SetConverter(PFont(font_)->characterSet);
1105 utfForm = UTF8FromIconv(conv, s, len);
1106 if (utfForm.empty()) { // iconv failed so treat as Latin1
1107 utfForm = UTF8FromLatin1(s, len);
1109 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
1111 #ifdef PANGO_VERSION
1112 PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout,0);
1113 #else
1114 PangoLayoutLine *pangoLine = pango_layout_get_line(layout,0);
1115 #endif
1116 pango_layout_line_get_extents(pangoLine, NULL, &pos);
1117 return doubleFromPangoUnits(pos.width);
1119 return 1;
1120 } else {
1121 return 1;
1125 XYPOSITION SurfaceImpl::WidthChar(Font &font_, char ch) {
1126 if (font_.GetID()) {
1127 if (PFont(font_)->pfd) {
1128 return WidthText(font_, &ch, 1);
1130 return 1;
1131 } else {
1132 return 1;
1136 // Ascent and descent determined by Pango font metrics.
1138 XYPOSITION SurfaceImpl::Ascent(Font &font_) {
1139 if (!(font_.GetID()))
1140 return 1;
1141 FontMutexLock();
1142 int ascent = PFont(font_)->ascent;
1143 if ((ascent == 0) && (PFont(font_)->pfd)) {
1144 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
1145 PFont(font_)->pfd, pango_context_get_language(pcontext));
1146 PFont(font_)->ascent =
1147 doubleFromPangoUnits(pango_font_metrics_get_ascent(metrics));
1148 pango_font_metrics_unref(metrics);
1149 ascent = PFont(font_)->ascent;
1151 if (ascent == 0) {
1152 ascent = 1;
1154 FontMutexUnlock();
1155 return ascent;
1158 XYPOSITION SurfaceImpl::Descent(Font &font_) {
1159 if (!(font_.GetID()))
1160 return 1;
1161 if (PFont(font_)->pfd) {
1162 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
1163 PFont(font_)->pfd, pango_context_get_language(pcontext));
1164 int descent = doubleFromPangoUnits(pango_font_metrics_get_descent(metrics));
1165 pango_font_metrics_unref(metrics);
1166 return descent;
1168 return 0;
1171 XYPOSITION SurfaceImpl::InternalLeading(Font &) {
1172 return 0;
1175 XYPOSITION SurfaceImpl::ExternalLeading(Font &) {
1176 return 0;
1179 XYPOSITION SurfaceImpl::Height(Font &font_) {
1180 return Ascent(font_) + Descent(font_);
1183 XYPOSITION SurfaceImpl::AverageCharWidth(Font &font_) {
1184 return WidthChar(font_, 'n');
1187 void SurfaceImpl::SetClip(PRectangle rc) {
1188 cairo_rectangle(context, rc.left, rc.top, rc.right, rc.bottom);
1189 cairo_clip(context);
1192 void SurfaceImpl::FlushCachedState() {}
1194 void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
1195 if (unicodeMode_)
1196 et = UTF8;
1199 void SurfaceImpl::SetDBCSMode(int codePage) {
1200 if (codePage && (codePage != SC_CP_UTF8))
1201 et = dbcs;
1204 Surface *Surface::Allocate(int) {
1205 return new SurfaceImpl();
1208 Window::~Window() {}
1210 void Window::Destroy() {
1211 if (wid)
1212 gtk_widget_destroy(GTK_WIDGET(wid));
1213 wid = 0;
1216 bool Window::HasFocus() {
1217 return IS_WIDGET_FOCUSSED(wid);
1220 PRectangle Window::GetPosition() {
1221 // Before any size allocated pretend its 1000 wide so not scrolled
1222 PRectangle rc(0, 0, 1000, 1000);
1223 if (wid) {
1224 GtkAllocation allocation;
1225 #if GTK_CHECK_VERSION(3,0,0)
1226 gtk_widget_get_allocation(PWidget(wid), &allocation);
1227 #else
1228 allocation = PWidget(wid)->allocation;
1229 #endif
1230 rc.left = allocation.x;
1231 rc.top = allocation.y;
1232 if (allocation.width > 20) {
1233 rc.right = rc.left + allocation.width;
1234 rc.bottom = rc.top + allocation.height;
1237 return rc;
1240 void Window::SetPosition(PRectangle rc) {
1241 GtkAllocation alloc;
1242 alloc.x = rc.left;
1243 alloc.y = rc.top;
1244 alloc.width = rc.Width();
1245 alloc.height = rc.Height();
1246 gtk_widget_size_allocate(PWidget(wid), &alloc);
1249 void Window::SetPositionRelative(PRectangle rc, Window relativeTo) {
1250 int ox = 0;
1251 int oy = 0;
1252 gdk_window_get_origin(WindowFromWidget(PWidget(relativeTo.wid)), &ox, &oy);
1253 ox += rc.left;
1254 if (ox < 0)
1255 ox = 0;
1256 oy += rc.top;
1257 if (oy < 0)
1258 oy = 0;
1260 /* do some corrections to fit into screen */
1261 int sizex = rc.right - rc.left;
1262 int sizey = rc.bottom - rc.top;
1263 int screenWidth = gdk_screen_width();
1264 int screenHeight = gdk_screen_height();
1265 if (sizex > screenWidth)
1266 ox = 0; /* the best we can do */
1267 else if (ox + sizex > screenWidth)
1268 ox = screenWidth - sizex;
1269 if (oy + sizey > screenHeight)
1270 oy = screenHeight - sizey;
1272 gtk_window_move(GTK_WINDOW(PWidget(wid)), ox, oy);
1274 gtk_widget_set_size_request(PWidget(wid), sizex, sizey);
1277 PRectangle Window::GetClientPosition() {
1278 // On GTK+, the client position is the window position
1279 return GetPosition();
1282 void Window::Show(bool show) {
1283 if (show)
1284 gtk_widget_show(PWidget(wid));
1287 void Window::InvalidateAll() {
1288 if (wid) {
1289 gtk_widget_queue_draw(PWidget(wid));
1293 void Window::InvalidateRectangle(PRectangle rc) {
1294 if (wid) {
1295 gtk_widget_queue_draw_area(PWidget(wid),
1296 rc.left, rc.top,
1297 rc.right - rc.left, rc.bottom - rc.top);
1301 void Window::SetFont(Font &) {
1302 // Can not be done generically but only needed for ListBox
1305 void Window::SetCursor(Cursor curs) {
1306 // We don't set the cursor to same value numerous times under gtk because
1307 // it stores the cursor in the window once it's set
1308 if (curs == cursorLast)
1309 return;
1311 cursorLast = curs;
1312 GdkCursor *gdkCurs;
1313 switch (curs) {
1314 case cursorText:
1315 gdkCurs = gdk_cursor_new(GDK_XTERM);
1316 break;
1317 case cursorArrow:
1318 gdkCurs = gdk_cursor_new(GDK_LEFT_PTR);
1319 break;
1320 case cursorUp:
1321 gdkCurs = gdk_cursor_new(GDK_CENTER_PTR);
1322 break;
1323 case cursorWait:
1324 gdkCurs = gdk_cursor_new(GDK_WATCH);
1325 break;
1326 case cursorHand:
1327 gdkCurs = gdk_cursor_new(GDK_HAND2);
1328 break;
1329 case cursorReverseArrow:
1330 gdkCurs = gdk_cursor_new(GDK_RIGHT_PTR);
1331 break;
1332 default:
1333 gdkCurs = gdk_cursor_new(GDK_LEFT_PTR);
1334 cursorLast = cursorArrow;
1335 break;
1338 if (WindowFromWidget(PWidget(wid)))
1339 gdk_window_set_cursor(WindowFromWidget(PWidget(wid)), gdkCurs);
1340 #if GTK_CHECK_VERSION(3,0,0)
1341 g_object_unref(gdkCurs);
1342 #else
1343 gdk_cursor_unref(gdkCurs);
1344 #endif
1347 void Window::SetTitle(const char *s) {
1348 gtk_window_set_title(GTK_WINDOW(wid), s);
1351 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1352 gdk window coordinates */
1353 PRectangle Window::GetMonitorRect(Point pt) {
1354 gint x_offset, y_offset;
1356 gdk_window_get_origin(WindowFromWidget(PWidget(wid)), &x_offset, &y_offset);
1358 GdkScreen* screen;
1359 gint monitor_num;
1360 GdkRectangle rect;
1362 screen = gtk_widget_get_screen(PWidget(wid));
1363 monitor_num = gdk_screen_get_monitor_at_point(screen, pt.x + x_offset, pt.y + y_offset);
1364 gdk_screen_get_monitor_geometry(screen, monitor_num, &rect);
1365 rect.x -= x_offset;
1366 rect.y -= y_offset;
1367 return PRectangle(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
1370 typedef std::map<int, RGBAImage*> ImageMap;
1372 struct ListImage {
1373 const RGBAImage *rgba_data;
1374 GdkPixbuf *pixbuf;
1377 static void list_image_free(gpointer, gpointer value, gpointer) {
1378 ListImage *list_image = static_cast<ListImage *>(value);
1379 if (list_image->pixbuf)
1380 g_object_unref(list_image->pixbuf);
1381 g_free(list_image);
1384 ListBox::ListBox() {
1387 ListBox::~ListBox() {
1390 enum {
1391 PIXBUF_COLUMN,
1392 TEXT_COLUMN,
1393 N_COLUMNS
1396 class ListBoxX : public ListBox {
1397 WindowID list;
1398 WindowID scroller;
1399 void *pixhash;
1400 GtkCellRenderer* pixbuf_renderer;
1401 RGBAImageSet images;
1402 int desiredVisibleRows;
1403 unsigned int maxItemCharacters;
1404 unsigned int aveCharWidth;
1405 public:
1406 CallBackAction doubleClickAction;
1407 void *doubleClickActionData;
1409 ListBoxX() : list(0), scroller(0), pixhash(NULL), pixbuf_renderer(0),
1410 desiredVisibleRows(5), maxItemCharacters(0),
1411 aveCharWidth(1), doubleClickAction(NULL), doubleClickActionData(NULL) {
1413 virtual ~ListBoxX() {
1414 if (pixhash) {
1415 g_hash_table_foreach((GHashTable *) pixhash, list_image_free, NULL);
1416 g_hash_table_destroy((GHashTable *) pixhash);
1419 virtual void SetFont(Font &font);
1420 virtual void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_);
1421 virtual void SetAverageCharWidth(int width);
1422 virtual void SetVisibleRows(int rows);
1423 virtual int GetVisibleRows() const;
1424 virtual PRectangle GetDesiredRect();
1425 virtual int CaretFromEdge();
1426 virtual void Clear();
1427 virtual void Append(char *s, int type = -1);
1428 virtual int Length();
1429 virtual void Select(int n);
1430 virtual int GetSelection();
1431 virtual int Find(const char *prefix);
1432 virtual void GetValue(int n, char *value, int len);
1433 void RegisterRGBA(int type, RGBAImage *image);
1434 virtual void RegisterImage(int type, const char *xpm_data);
1435 virtual void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage);
1436 virtual void ClearRegisteredImages();
1437 virtual void SetDoubleClickAction(CallBackAction action, void *data) {
1438 doubleClickAction = action;
1439 doubleClickActionData = data;
1441 virtual void SetList(const char *listText, char separator, char typesep);
1444 ListBox *ListBox::Allocate() {
1445 ListBoxX *lb = new ListBoxX();
1446 return lb;
1449 static gboolean ButtonPress(GtkWidget *, GdkEventButton* ev, gpointer p) {
1450 try {
1451 ListBoxX* lb = reinterpret_cast<ListBoxX*>(p);
1452 if (ev->type == GDK_2BUTTON_PRESS && lb->doubleClickAction != NULL) {
1453 lb->doubleClickAction(lb->doubleClickActionData);
1454 return TRUE;
1457 } catch (...) {
1458 // No pointer back to Scintilla to save status
1460 return FALSE;
1463 /* Change the active color to the selected color so the listbox uses the color
1464 scheme that it would use if it had the focus. */
1465 static void StyleSet(GtkWidget *w, GtkStyle*, void*) {
1467 g_return_if_fail(w != NULL);
1469 /* Copy the selected color to active. Note that the modify calls will cause
1470 recursive calls to this function after the value is updated and w->style to
1471 be set to a new object */
1473 #if GTK_CHECK_VERSION(3,0,0)
1474 GtkStyleContext *styleContext = gtk_widget_get_style_context(w);
1475 if (styleContext == NULL)
1476 return;
1478 GdkRGBA colourForeSelected;
1479 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourForeSelected);
1480 GdkRGBA colourForeActive;
1481 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourForeActive);
1482 if (!gdk_rgba_equal(&colourForeSelected, &colourForeActive))
1483 gtk_widget_override_color(w, GTK_STATE_FLAG_ACTIVE, &colourForeSelected);
1485 styleContext = gtk_widget_get_style_context(w);
1486 if (styleContext == NULL)
1487 return;
1489 GdkRGBA colourBaseSelected;
1490 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourBaseSelected);
1491 GdkRGBA colourBaseActive;
1492 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourBaseActive);
1493 if (!gdk_rgba_equal(&colourBaseSelected, &colourBaseActive))
1494 gtk_widget_override_background_color(w, GTK_STATE_FLAG_ACTIVE, &colourBaseSelected);
1495 #else
1496 GtkStyle *style = gtk_widget_get_style(w);
1497 if (style == NULL)
1498 return;
1499 if (!gdk_color_equal(&style->base[GTK_STATE_SELECTED], &style->base[GTK_STATE_ACTIVE]))
1500 gtk_widget_modify_base(w, GTK_STATE_ACTIVE, &style->base[GTK_STATE_SELECTED]);
1501 style = gtk_widget_get_style(w);
1502 if (style == NULL)
1503 return;
1504 if (!gdk_color_equal(&style->text[GTK_STATE_SELECTED], &style->text[GTK_STATE_ACTIVE]))
1505 gtk_widget_modify_text(w, GTK_STATE_ACTIVE, &style->text[GTK_STATE_SELECTED]);
1506 #endif
1509 void ListBoxX::Create(Window &, int, Point, int, bool, int) {
1510 wid = gtk_window_new(GTK_WINDOW_POPUP);
1512 GtkWidget *frame = gtk_frame_new(NULL);
1513 gtk_widget_show(frame);
1514 gtk_container_add(GTK_CONTAINER(GetID()), frame);
1515 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1516 gtk_container_set_border_width(GTK_CONTAINER(frame), 0);
1518 scroller = gtk_scrolled_window_new(NULL, NULL);
1519 gtk_container_set_border_width(GTK_CONTAINER(scroller), 0);
1520 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
1521 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1522 gtk_container_add(GTK_CONTAINER(frame), PWidget(scroller));
1523 gtk_widget_show(PWidget(scroller));
1525 /* Tree and its model */
1526 GtkListStore *store =
1527 gtk_list_store_new(N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING);
1529 list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1530 g_signal_connect(G_OBJECT(list), "style-set", G_CALLBACK(StyleSet), NULL);
1532 GtkTreeSelection *selection =
1533 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1534 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1535 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
1536 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list), FALSE);
1538 /* Columns */
1539 GtkTreeViewColumn *column = gtk_tree_view_column_new();
1540 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1541 gtk_tree_view_column_set_title(column, "Autocomplete");
1543 pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
1544 gtk_cell_renderer_set_fixed_size(pixbuf_renderer, 0, -1);
1545 gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE);
1546 gtk_tree_view_column_add_attribute(column, pixbuf_renderer,
1547 "pixbuf", PIXBUF_COLUMN);
1549 GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
1550 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1551 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1552 gtk_tree_view_column_add_attribute(column, renderer,
1553 "text", TEXT_COLUMN);
1555 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
1556 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list), "fixed-height-mode"))
1557 g_object_set(G_OBJECT(list), "fixed-height-mode", TRUE, NULL);
1559 GtkWidget *wid = PWidget(list); // No code inside the G_OBJECT macro
1560 gtk_container_add(GTK_CONTAINER(PWidget(scroller)), wid);
1561 gtk_widget_show(wid);
1562 g_signal_connect(G_OBJECT(wid), "button_press_event",
1563 G_CALLBACK(ButtonPress), this);
1564 gtk_widget_realize(PWidget(wid));
1567 void ListBoxX::SetFont(Font &scint_font) {
1568 // Only do for Pango font as there have been crashes for GDK fonts
1569 if (Created() && PFont(scint_font)->pfd) {
1570 // Current font is Pango font
1571 #if GTK_CHECK_VERSION(3,0,0)
1572 gtk_widget_override_font(PWidget(list), PFont(scint_font)->pfd);
1573 #else
1574 gtk_widget_modify_font(PWidget(list), PFont(scint_font)->pfd);
1575 #endif
1579 void ListBoxX::SetAverageCharWidth(int width) {
1580 aveCharWidth = width;
1583 void ListBoxX::SetVisibleRows(int rows) {
1584 desiredVisibleRows = rows;
1587 int ListBoxX::GetVisibleRows() const {
1588 return desiredVisibleRows;
1591 PRectangle ListBoxX::GetDesiredRect() {
1592 // Before any size allocated pretend its 100 wide so not scrolled
1593 PRectangle rc(0, 0, 100, 100);
1594 if (wid) {
1595 int rows = Length();
1596 if ((rows == 0) || (rows > desiredVisibleRows))
1597 rows = desiredVisibleRows;
1599 GtkRequisition req;
1600 #if GTK_CHECK_VERSION(3,0,0)
1601 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1602 // returns reasonable values.
1603 gtk_widget_get_preferred_size(GTK_WIDGET(scroller), NULL, &req);
1604 #endif
1605 int height;
1607 // First calculate height of the clist for our desired visible
1608 // row count otherwise it tries to expand to the total # of rows
1609 // Get cell height
1610 int row_width=0;
1611 int row_height=0;
1612 GtkTreeViewColumn * column =
1613 gtk_tree_view_get_column(GTK_TREE_VIEW(list), 0);
1614 gtk_tree_view_column_cell_get_size(column, NULL,
1615 NULL, NULL, &row_width, &row_height);
1616 #if GTK_CHECK_VERSION(3,0,0)
1617 GtkStyleContext *styleContextList = gtk_widget_get_style_context(PWidget(list));
1618 GtkBorder padding;
1619 gtk_style_context_get_padding(styleContextList, GTK_STATE_FLAG_NORMAL, &padding);
1620 height = (rows * row_height
1621 + padding.top + padding.bottom
1622 + 2 * (gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))) + 1));
1623 #else
1624 int ythickness = PWidget(list)->style->ythickness;
1625 height = (rows * row_height
1626 + 2 * (ythickness
1627 + GTK_CONTAINER(PWidget(list))->border_width + 1));
1628 #endif
1629 gtk_widget_set_size_request(GTK_WIDGET(PWidget(list)), -1, height);
1631 // Get the size of the scroller because we set usize on the window
1632 #if GTK_CHECK_VERSION(3,0,0)
1633 gtk_widget_get_preferred_size(GTK_WIDGET(scroller), NULL, &req);
1634 #else
1635 gtk_widget_size_request(GTK_WIDGET(scroller), &req);
1636 #endif
1637 rc.right = req.width;
1638 rc.bottom = Platform::Maximum(height, req.height);
1640 gtk_widget_set_size_request(GTK_WIDGET(list), -1, -1);
1641 int width = maxItemCharacters;
1642 if (width < 12)
1643 width = 12;
1644 rc.right = width * (aveCharWidth + aveCharWidth / 3);
1645 if (Length() > rows)
1646 rc.right = rc.right + 16;
1648 return rc;
1651 int ListBoxX::CaretFromEdge() {
1652 gint renderer_width, renderer_height;
1653 gtk_cell_renderer_get_fixed_size(pixbuf_renderer, &renderer_width,
1654 &renderer_height);
1655 return 4 + renderer_width;
1658 void ListBoxX::Clear() {
1659 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1660 gtk_list_store_clear(GTK_LIST_STORE(model));
1661 maxItemCharacters = 0;
1664 static void init_pixmap(ListImage *list_image) {
1665 if (list_image->rgba_data) {
1666 // Drop any existing pixmap/bitmap as data may have changed
1667 if (list_image->pixbuf)
1668 g_object_unref(list_image->pixbuf);
1669 list_image->pixbuf =
1670 gdk_pixbuf_new_from_data(list_image->rgba_data->Pixels(),
1671 GDK_COLORSPACE_RGB,
1672 TRUE,
1674 list_image->rgba_data->GetWidth(),
1675 list_image->rgba_data->GetHeight(),
1676 list_image->rgba_data->GetWidth() * 4,
1677 NULL,
1678 NULL);
1682 #define SPACING 5
1684 void ListBoxX::Append(char *s, int type) {
1685 ListImage *list_image = NULL;
1686 if ((type >= 0) && pixhash) {
1687 list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash
1688 , (gconstpointer) GINT_TO_POINTER(type)));
1690 GtkTreeIter iter;
1691 GtkListStore *store =
1692 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
1693 gtk_list_store_append(GTK_LIST_STORE(store), &iter);
1694 if (list_image) {
1695 if (NULL == list_image->pixbuf)
1696 init_pixmap(list_image);
1697 if (list_image->pixbuf) {
1698 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1699 PIXBUF_COLUMN, list_image->pixbuf,
1700 TEXT_COLUMN, s, -1);
1702 gint pixbuf_width = gdk_pixbuf_get_width(list_image->pixbuf);
1703 gint renderer_height, renderer_width;
1704 gtk_cell_renderer_get_fixed_size(pixbuf_renderer,
1705 &renderer_width, &renderer_height);
1706 if (pixbuf_width > renderer_width)
1707 gtk_cell_renderer_set_fixed_size(pixbuf_renderer,
1708 pixbuf_width, -1);
1709 } else {
1710 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1711 TEXT_COLUMN, s, -1);
1713 } else {
1714 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1715 TEXT_COLUMN, s, -1);
1717 size_t len = strlen(s);
1718 if (maxItemCharacters < len)
1719 maxItemCharacters = len;
1722 int ListBoxX::Length() {
1723 if (wid)
1724 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1725 (GTK_TREE_VIEW(list)), NULL);
1726 return 0;
1729 void ListBoxX::Select(int n) {
1730 GtkTreeIter iter;
1731 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1732 GtkTreeSelection *selection =
1733 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1735 if (n < 0) {
1736 gtk_tree_selection_unselect_all(selection);
1737 return;
1740 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
1741 if (valid) {
1742 gtk_tree_selection_select_iter(selection, &iter);
1744 // Move the scrollbar to show the selection.
1745 int total = Length();
1746 #if GTK_CHECK_VERSION(3,0,0)
1747 GtkAdjustment *adj =
1748 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list));
1749 gfloat value = ((gfloat)n / total) * (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_lower(adj))
1750 + gtk_adjustment_get_lower(adj) - gtk_adjustment_get_page_size(adj) / 2;
1751 #else
1752 GtkAdjustment *adj =
1753 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list));
1754 gfloat value = ((gfloat)n / total) * (adj->upper - adj->lower)
1755 + adj->lower - adj->page_size / 2;
1756 #endif
1757 // Get cell height
1758 int row_width;
1759 int row_height;
1760 GtkTreeViewColumn * column =
1761 gtk_tree_view_get_column(GTK_TREE_VIEW(list), 0);
1762 gtk_tree_view_column_cell_get_size(column, NULL, NULL,
1763 NULL, &row_width, &row_height);
1765 int rows = Length();
1766 if ((rows == 0) || (rows > desiredVisibleRows))
1767 rows = desiredVisibleRows;
1768 if (rows & 0x1) {
1769 // Odd rows to display -- We are now in the middle.
1770 // Align it so that we don't chop off rows.
1771 value += (gfloat)row_height / 2.0;
1773 // Clamp it.
1774 value = (value < 0)? 0 : value;
1775 #if GTK_CHECK_VERSION(3,0,0)
1776 value = (value > (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)))?
1777 (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)) : value;
1778 #else
1779 value = (value > (adj->upper - adj->page_size))?
1780 (adj->upper - adj->page_size) : value;
1781 #endif
1783 // Set it.
1784 gtk_adjustment_set_value(adj, value);
1785 } else {
1786 gtk_tree_selection_unselect_all(selection);
1790 int ListBoxX::GetSelection() {
1791 GtkTreeIter iter;
1792 GtkTreeModel *model;
1793 GtkTreeSelection *selection;
1794 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1795 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1796 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1797 int *indices = gtk_tree_path_get_indices(path);
1798 // Don't free indices.
1799 if (indices)
1800 return indices[0];
1802 return -1;
1805 int ListBoxX::Find(const char *prefix) {
1806 GtkTreeIter iter;
1807 GtkTreeModel *model =
1808 gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1809 bool valid = gtk_tree_model_get_iter_first(model, &iter) != FALSE;
1810 int i = 0;
1811 while(valid) {
1812 gchar *s;
1813 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &s, -1);
1814 if (s && (0 == strncmp(prefix, s, strlen(prefix)))) {
1815 g_free(s);
1816 return i;
1818 g_free(s);
1819 valid = gtk_tree_model_iter_next(model, &iter) != FALSE;
1820 i++;
1822 return -1;
1825 void ListBoxX::GetValue(int n, char *value, int len) {
1826 char *text = NULL;
1827 GtkTreeIter iter;
1828 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1829 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
1830 if (valid) {
1831 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &text, -1);
1833 if (text && len > 0) {
1834 g_strlcpy(value, text, len);
1835 } else {
1836 value[0] = '\0';
1838 g_free(text);
1841 // g_return_if_fail causes unnecessary compiler warning in release compile.
1842 #ifdef _MSC_VER
1843 #pragma warning(disable: 4127)
1844 #endif
1846 void ListBoxX::RegisterRGBA(int type, RGBAImage *image) {
1847 images.Add(type, image);
1849 if (!pixhash) {
1850 pixhash = g_hash_table_new(g_direct_hash, g_direct_equal);
1852 ListImage *list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash,
1853 (gconstpointer) GINT_TO_POINTER(type)));
1854 if (list_image) {
1855 // Drop icon already registered
1856 if (list_image->pixbuf)
1857 g_object_unref(list_image->pixbuf);
1858 list_image->pixbuf = NULL;
1859 list_image->rgba_data = image;
1860 } else {
1861 list_image = g_new0(ListImage, 1);
1862 list_image->rgba_data = image;
1863 g_hash_table_insert((GHashTable *) pixhash, GINT_TO_POINTER(type),
1864 (gpointer) list_image);
1868 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
1869 g_return_if_fail(xpm_data);
1870 XPM xpmImage(xpm_data);
1871 RegisterRGBA(type, new RGBAImage(xpmImage));
1874 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
1875 RegisterRGBA(type, new RGBAImage(width, height, 1.0, pixelsImage));
1878 void ListBoxX::ClearRegisteredImages() {
1879 images.Clear();
1882 void ListBoxX::SetList(const char *listText, char separator, char typesep) {
1883 Clear();
1884 int count = strlen(listText) + 1;
1885 std::vector<char> words(listText, listText+count);
1886 char *startword = &words[0];
1887 char *numword = NULL;
1888 int i = 0;
1889 for (; words[i]; i++) {
1890 if (words[i] == separator) {
1891 words[i] = '\0';
1892 if (numword)
1893 *numword = '\0';
1894 Append(startword, numword?atoi(numword + 1):-1);
1895 startword = &words[0] + i + 1;
1896 numword = NULL;
1897 } else if (words[i] == typesep) {
1898 numword = &words[0] + i;
1901 if (startword) {
1902 if (numword)
1903 *numword = '\0';
1904 Append(startword, numword?atoi(numword + 1):-1);
1908 Menu::Menu() : mid(0) {}
1910 void Menu::CreatePopUp() {
1911 Destroy();
1912 mid = gtk_menu_new();
1913 #if GLIB_CHECK_VERSION(2,10,0)
1914 g_object_ref_sink(G_OBJECT(mid));
1915 #else
1916 g_object_ref(G_OBJECT(mid));
1917 gtk_object_sink(GTK_OBJECT(G_OBJECT(mid)));
1918 #endif
1921 void Menu::Destroy() {
1922 if (mid)
1923 g_object_unref(G_OBJECT(mid));
1924 mid = 0;
1927 static void MenuPositionFunc(GtkMenu *, gint *x, gint *y, gboolean *, gpointer userData) {
1928 sptr_t intFromPointer = reinterpret_cast<sptr_t>(userData);
1929 *x = intFromPointer & 0xffff;
1930 *y = intFromPointer >> 16;
1933 void Menu::Show(Point pt, Window &) {
1934 int screenHeight = gdk_screen_height();
1935 int screenWidth = gdk_screen_width();
1936 GtkMenu *widget = reinterpret_cast<GtkMenu *>(mid);
1937 gtk_widget_show_all(GTK_WIDGET(widget));
1938 GtkRequisition requisition;
1939 #if GTK_CHECK_VERSION(3,0,0)
1940 gtk_widget_get_preferred_size(GTK_WIDGET(widget), NULL, &requisition);
1941 #else
1942 gtk_widget_size_request(GTK_WIDGET(widget), &requisition);
1943 #endif
1944 if ((pt.x + requisition.width) > screenWidth) {
1945 pt.x = screenWidth - requisition.width;
1947 if ((pt.y + requisition.height) > screenHeight) {
1948 pt.y = screenHeight - requisition.height;
1950 gtk_menu_popup(widget, NULL, NULL, MenuPositionFunc,
1951 reinterpret_cast<void *>((static_cast<int>(pt.y) << 16) | static_cast<int>(pt.x)), 0,
1952 gtk_get_current_event_time());
1955 ElapsedTime::ElapsedTime() {
1956 GTimeVal curTime;
1957 g_get_current_time(&curTime);
1958 bigBit = curTime.tv_sec;
1959 littleBit = curTime.tv_usec;
1962 class DynamicLibraryImpl : public DynamicLibrary {
1963 protected:
1964 GModule* m;
1965 public:
1966 explicit DynamicLibraryImpl(const char *modulePath) {
1967 m = g_module_open(modulePath, G_MODULE_BIND_LAZY);
1970 virtual ~DynamicLibraryImpl() {
1971 if (m != NULL)
1972 g_module_close(m);
1975 // Use g_module_symbol to get a pointer to the relevant function.
1976 virtual Function FindFunction(const char *name) {
1977 if (m != NULL) {
1978 gpointer fn_address = NULL;
1979 gboolean status = g_module_symbol(m, name, &fn_address);
1980 if (status)
1981 return static_cast<Function>(fn_address);
1982 else
1983 return NULL;
1984 } else {
1985 return NULL;
1989 virtual bool IsValid() {
1990 return m != NULL;
1994 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
1995 return static_cast<DynamicLibrary *>( new DynamicLibraryImpl(modulePath) );
1998 double ElapsedTime::Duration(bool reset) {
1999 GTimeVal curTime;
2000 g_get_current_time(&curTime);
2001 long endBigBit = curTime.tv_sec;
2002 long endLittleBit = curTime.tv_usec;
2003 double result = 1000000.0 * (endBigBit - bigBit);
2004 result += endLittleBit - littleBit;
2005 result /= 1000000.0;
2006 if (reset) {
2007 bigBit = endBigBit;
2008 littleBit = endLittleBit;
2010 return result;
2013 ColourDesired Platform::Chrome() {
2014 return ColourDesired(0xe0, 0xe0, 0xe0);
2017 ColourDesired Platform::ChromeHighlight() {
2018 return ColourDesired(0xff, 0xff, 0xff);
2021 const char *Platform::DefaultFont() {
2022 #ifdef G_OS_WIN32
2023 return "Lucida Console";
2024 #else
2025 return "!Sans";
2026 #endif
2029 int Platform::DefaultFontSize() {
2030 #ifdef G_OS_WIN32
2031 return 10;
2032 #else
2033 return 12;
2034 #endif
2037 unsigned int Platform::DoubleClickTime() {
2038 return 500; // Half a second
2041 bool Platform::MouseButtonBounce() {
2042 return true;
2045 void Platform::DebugDisplay(const char *s) {
2046 fprintf(stderr, "%s", s);
2049 bool Platform::IsKeyDown(int) {
2050 // TODO: discover state of keys in GTK+/X
2051 return false;
2054 long Platform::SendScintilla(
2055 WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
2056 return scintilla_send_message(SCINTILLA(w), msg, wParam, lParam);
2059 long Platform::SendScintillaPointer(
2060 WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
2061 return scintilla_send_message(SCINTILLA(w), msg, wParam,
2062 reinterpret_cast<sptr_t>(lParam));
2065 bool Platform::IsDBCSLeadByte(int codePage, char ch) {
2066 // Byte ranges found in Wikipedia articles with relevant search strings in each case
2067 unsigned char uch = static_cast<unsigned char>(ch);
2068 switch (codePage) {
2069 case 932:
2070 // Shift_jis
2071 return ((uch >= 0x81) && (uch <= 0x9F)) ||
2072 ((uch >= 0xE0) && (uch <= 0xFC));
2073 // Lead bytes F0 to FC may be a Microsoft addition.
2074 case 936:
2075 // GBK
2076 return (uch >= 0x81) && (uch <= 0xFE);
2077 case 950:
2078 // Big5
2079 return (uch >= 0x81) && (uch <= 0xFE);
2080 // Korean EUC-KR may be code page 949.
2082 return false;
2085 int Platform::DBCSCharLength(int codePage, const char *s) {
2086 if (codePage == 932 || codePage == 936 || codePage == 950) {
2087 return IsDBCSLeadByte(codePage, s[0]) ? 2 : 1;
2088 } else {
2089 int bytes = mblen(s, MB_CUR_MAX);
2090 if (bytes >= 1)
2091 return bytes;
2092 else
2093 return 1;
2097 int Platform::DBCSCharMaxLength() {
2098 return MB_CUR_MAX;
2099 //return 2;
2102 // These are utility functions not really tied to a platform
2104 int Platform::Minimum(int a, int b) {
2105 if (a < b)
2106 return a;
2107 else
2108 return b;
2111 int Platform::Maximum(int a, int b) {
2112 if (a > b)
2113 return a;
2114 else
2115 return b;
2118 //#define TRACE
2120 #ifdef TRACE
2121 void Platform::DebugPrintf(const char *format, ...) {
2122 char buffer[2000];
2123 va_list pArguments;
2124 va_start(pArguments, format);
2125 vsprintf(buffer, format, pArguments);
2126 va_end(pArguments);
2127 Platform::DebugDisplay(buffer);
2129 #else
2130 void Platform::DebugPrintf(const char *, ...) {}
2132 #endif
2134 // Not supported for GTK+
2135 static bool assertionPopUps = true;
2137 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
2138 bool ret = assertionPopUps;
2139 assertionPopUps = assertionPopUps_;
2140 return ret;
2143 void Platform::Assert(const char *c, const char *file, int line) {
2144 char buffer[2000];
2145 g_snprintf(buffer, sizeof(buffer), "Assertion [%s] failed at %s %d\r\n", c, file, line);
2146 Platform::DebugDisplay(buffer);
2147 abort();
2150 int Platform::Clamp(int val, int minVal, int maxVal) {
2151 if (val > maxVal)
2152 val = maxVal;
2153 if (val < minVal)
2154 val = minVal;
2155 return val;
2158 void Platform_Initialise() {
2159 FontMutexAllocate();
2162 void Platform_Finalise() {
2163 FontCached::ReleaseAll();
2164 FontMutexFree();