Update Scintilla to version 3.6.3
[geany-mirror.git] / scintilla / gtk / PlatGTK.cxx
blob063c42b3aa0d9e9e92b4ff6863b6a6bc22577626
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>
15 #include <sstream>
17 #include <glib.h>
18 #include <gmodule.h>
19 #include <gdk/gdk.h>
20 #include <gtk/gtk.h>
21 #include <gdk/gdkkeysyms.h>
23 #include "Platform.h"
25 #include "Scintilla.h"
26 #include "ScintillaWidget.h"
27 #include "StringCopy.h"
28 #include "XPM.h"
29 #include "UniConversion.h"
31 #if defined(__clang__)
32 // Clang 3.0 incorrectly displays sentinel warnings. Fixed by clang 3.1.
33 #pragma GCC diagnostic ignored "-Wsentinel"
34 #endif
36 /* GLIB must be compiled with thread support, otherwise we
37 will bail on trying to use locks, and that could lead to
38 problems for someone. `glib-config --libs gthread` needs
39 to be used to get the glib libraries for linking, otherwise
40 g_thread_init will fail */
41 #define USE_LOCK defined(G_THREADS_ENABLED) && !defined(G_THREADS_IMPL_NONE)
43 #include "Converter.h"
45 static const double kPi = 3.14159265358979323846;
47 // The Pango version guard for pango_units_from_double and pango_units_to_double
48 // is more complex than simply implementing these here.
50 static int pangoUnitsFromDouble(double d) {
51 return static_cast<int>(d * PANGO_SCALE + 0.5);
54 static double doubleFromPangoUnits(int pu) {
55 return static_cast<double>(pu) / PANGO_SCALE;
58 static cairo_surface_t *CreateSimilarSurface(GdkWindow *window, cairo_content_t content, int width, int height) {
59 #if GTK_CHECK_VERSION(2,22,0)
60 return gdk_window_create_similar_surface(window, content, width, height);
61 #else
62 cairo_surface_t *window_surface, *surface;
64 g_return_val_if_fail(GDK_IS_WINDOW(window), NULL);
66 window_surface = GDK_DRAWABLE_GET_CLASS(window)->ref_cairo_surface(window);
68 surface = cairo_surface_create_similar(window_surface, content, width, height);
70 cairo_surface_destroy(window_surface);
72 return surface;
73 #endif
76 static GdkWindow *WindowFromWidget(GtkWidget *w) {
77 return gtk_widget_get_window(w);
80 #ifdef _MSC_VER
81 // Ignore unreferenced local functions in GTK+ headers
82 #pragma warning(disable: 4505)
83 #endif
85 #ifdef SCI_NAMESPACE
86 using namespace Scintilla;
87 #endif
89 enum encodingType { singleByte, UTF8, dbcs};
91 struct LOGFONT {
92 int size;
93 int weight;
94 bool italic;
95 int characterSet;
96 char faceName[300];
99 #if USE_LOCK
100 static GMutex *fontMutex = NULL;
102 static void InitializeGLIBThreads() {
103 #if !GLIB_CHECK_VERSION(2,31,0)
104 if (!g_thread_supported()) {
105 g_thread_init(NULL);
107 #endif
109 #endif
111 static void FontMutexAllocate() {
112 #if USE_LOCK
113 if (!fontMutex) {
114 InitializeGLIBThreads();
115 #if GLIB_CHECK_VERSION(2,31,0)
116 fontMutex = g_new(GMutex, 1);
117 g_mutex_init(fontMutex);
118 #else
119 fontMutex = g_mutex_new();
120 #endif
122 #endif
125 static void FontMutexFree() {
126 #if USE_LOCK
127 if (fontMutex) {
128 #if GLIB_CHECK_VERSION(2,31,0)
129 g_mutex_clear(fontMutex);
130 g_free(fontMutex);
131 #else
132 g_mutex_free(fontMutex);
133 #endif
134 fontMutex = NULL;
136 #endif
139 static void FontMutexLock() {
140 #if USE_LOCK
141 g_mutex_lock(fontMutex);
142 #endif
145 static void FontMutexUnlock() {
146 #if USE_LOCK
147 if (fontMutex) {
148 g_mutex_unlock(fontMutex);
150 #endif
153 // Holds a PangoFontDescription*.
154 class FontHandle {
155 XYPOSITION width[128];
156 encodingType et;
157 public:
158 int ascent;
159 PangoFontDescription *pfd;
160 int characterSet;
161 FontHandle() : et(singleByte), ascent(0), pfd(0), characterSet(-1) {
162 ResetWidths(et);
164 FontHandle(PangoFontDescription *pfd_, int characterSet_) {
165 et = singleByte;
166 ascent = 0;
167 pfd = pfd_;
168 characterSet = characterSet_;
169 ResetWidths(et);
171 ~FontHandle() {
172 if (pfd)
173 pango_font_description_free(pfd);
174 pfd = 0;
176 void ResetWidths(encodingType et_) {
177 et = et_;
178 for (int i=0; i<=127; i++) {
179 width[i] = 0;
182 XYPOSITION CharWidth(unsigned char ch, encodingType et_) const {
183 XYPOSITION w = 0;
184 FontMutexLock();
185 if ((ch <= 127) && (et == et_)) {
186 w = width[ch];
188 FontMutexUnlock();
189 return w;
191 void SetCharWidth(unsigned char ch, XYPOSITION w, encodingType et_) {
192 if (ch <= 127) {
193 FontMutexLock();
194 if (et != et_) {
195 ResetWidths(et_);
197 width[ch] = w;
198 FontMutexUnlock();
203 // X has a 16 bit coordinate space, so stop drawing here to avoid wrapping
204 static const int maxCoordinate = 32000;
206 static FontHandle *PFont(Font &f) {
207 return static_cast<FontHandle *>(f.GetID());
210 static GtkWidget *PWidget(WindowID wid) {
211 return static_cast<GtkWidget *>(wid);
214 Point Point::FromLong(long lpoint) {
215 return Point(
216 Platform::LowShortFromLong(lpoint),
217 Platform::HighShortFromLong(lpoint));
220 static void SetLogFont(LOGFONT &lf, const char *faceName, int characterSet, float size, int weight, bool italic) {
221 lf = LOGFONT();
222 lf.size = size;
223 lf.weight = weight;
224 lf.italic = italic;
225 lf.characterSet = characterSet;
226 StringCopy(lf.faceName, faceName);
230 * Create a hash from the parameters for a font to allow easy checking for identity.
231 * If one font is the same as another, its hash will be the same, but if the hash is the
232 * same then they may still be different.
234 static int HashFont(const FontParameters &fp) {
235 return
236 static_cast<int>(fp.size+0.5) ^
237 (fp.characterSet << 10) ^
238 ((fp.weight / 100) << 12) ^
239 (fp.italic ? 0x20000000 : 0) ^
240 fp.faceName[0];
243 class FontCached : Font {
244 FontCached *next;
245 int usage;
246 LOGFONT lf;
247 int hash;
248 explicit FontCached(const FontParameters &fp);
249 ~FontCached() {}
250 bool SameAs(const FontParameters &fp);
251 virtual void Release();
252 static FontID CreateNewFont(const FontParameters &fp);
253 static FontCached *first;
254 public:
255 static FontID FindOrCreate(const FontParameters &fp);
256 static void ReleaseId(FontID fid_);
257 static void ReleaseAll();
260 FontCached *FontCached::first = 0;
262 FontCached::FontCached(const FontParameters &fp) :
263 next(0), usage(0), hash(0) {
264 ::SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic);
265 hash = HashFont(fp);
266 fid = CreateNewFont(fp);
267 usage = 1;
270 bool FontCached::SameAs(const FontParameters &fp) {
271 return
272 lf.size == fp.size &&
273 lf.weight == fp.weight &&
274 lf.italic == fp.italic &&
275 lf.characterSet == fp.characterSet &&
276 0 == strcmp(lf.faceName, fp.faceName);
279 void FontCached::Release() {
280 if (fid)
281 delete PFont(*this);
282 fid = 0;
285 FontID FontCached::FindOrCreate(const FontParameters &fp) {
286 FontID ret = 0;
287 FontMutexLock();
288 int hashFind = HashFont(fp);
289 for (FontCached *cur = first; cur; cur = cur->next) {
290 if ((cur->hash == hashFind) &&
291 cur->SameAs(fp)) {
292 cur->usage++;
293 ret = cur->fid;
296 if (ret == 0) {
297 FontCached *fc = new FontCached(fp);
298 fc->next = first;
299 first = fc;
300 ret = fc->fid;
302 FontMutexUnlock();
303 return ret;
306 void FontCached::ReleaseId(FontID fid_) {
307 FontMutexLock();
308 FontCached **pcur = &first;
309 for (FontCached *cur = first; cur; cur = cur->next) {
310 if (cur->fid == fid_) {
311 cur->usage--;
312 if (cur->usage == 0) {
313 *pcur = cur->next;
314 cur->Release();
315 cur->next = 0;
316 delete cur;
318 break;
320 pcur = &cur->next;
322 FontMutexUnlock();
325 void FontCached::ReleaseAll() {
326 while (first) {
327 ReleaseId(first->GetID());
331 FontID FontCached::CreateNewFont(const FontParameters &fp) {
332 PangoFontDescription *pfd = pango_font_description_new();
333 if (pfd) {
334 pango_font_description_set_family(pfd,
335 (fp.faceName[0] == '!') ? fp.faceName+1 : fp.faceName);
336 pango_font_description_set_size(pfd, pangoUnitsFromDouble(fp.size));
337 pango_font_description_set_weight(pfd, static_cast<PangoWeight>(fp.weight));
338 pango_font_description_set_style(pfd, fp.italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
339 return new FontHandle(pfd, fp.characterSet);
342 return new FontHandle();
345 Font::Font() : fid(0) {}
347 Font::~Font() {}
349 void Font::Create(const FontParameters &fp) {
350 Release();
351 fid = FontCached::FindOrCreate(fp);
354 void Font::Release() {
355 if (fid)
356 FontCached::ReleaseId(fid);
357 fid = 0;
360 // Required on OS X
361 #ifdef SCI_NAMESPACE
362 namespace Scintilla {
363 #endif
365 // SurfaceID is a cairo_t*
366 class SurfaceImpl : public Surface {
367 encodingType et;
368 cairo_t *context;
369 cairo_surface_t *psurf;
370 int x;
371 int y;
372 bool inited;
373 bool createdGC;
374 PangoContext *pcontext;
375 PangoLayout *layout;
376 Converter conv;
377 int characterSet;
378 void SetConverter(int characterSet_);
379 public:
380 SurfaceImpl();
381 virtual ~SurfaceImpl();
383 void Init(WindowID wid);
384 void Init(SurfaceID sid, WindowID wid);
385 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
387 void Release();
388 bool Initialised();
389 void PenColour(ColourDesired fore);
390 int LogPixelsY();
391 int DeviceHeightFont(int points);
392 void MoveTo(int x_, int y_);
393 void LineTo(int x_, int y_);
394 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back);
395 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back);
396 void FillRectangle(PRectangle rc, ColourDesired back);
397 void FillRectangle(PRectangle rc, Surface &surfacePattern);
398 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back);
399 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
400 ColourDesired outline, int alphaOutline, int flags);
401 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage);
402 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back);
403 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
405 void DrawTextBase(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
406 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
407 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
408 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
409 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions);
410 XYPOSITION WidthText(Font &font_, const char *s, int len);
411 XYPOSITION WidthChar(Font &font_, char ch);
412 XYPOSITION Ascent(Font &font_);
413 XYPOSITION Descent(Font &font_);
414 XYPOSITION InternalLeading(Font &font_);
415 XYPOSITION ExternalLeading(Font &font_);
416 XYPOSITION Height(Font &font_);
417 XYPOSITION AverageCharWidth(Font &font_);
419 void SetClip(PRectangle rc);
420 void FlushCachedState();
422 void SetUnicodeMode(bool unicodeMode_);
423 void SetDBCSMode(int codePage);
425 #ifdef SCI_NAMESPACE
427 #endif
429 const char *CharacterSetID(int characterSet) {
430 switch (characterSet) {
431 case SC_CHARSET_ANSI:
432 return "";
433 case SC_CHARSET_DEFAULT:
434 return "ISO-8859-1";
435 case SC_CHARSET_BALTIC:
436 return "ISO-8859-13";
437 case SC_CHARSET_CHINESEBIG5:
438 return "BIG-5";
439 case SC_CHARSET_EASTEUROPE:
440 return "ISO-8859-2";
441 case SC_CHARSET_GB2312:
442 return "CP936";
443 case SC_CHARSET_GREEK:
444 return "ISO-8859-7";
445 case SC_CHARSET_HANGUL:
446 return "CP949";
447 case SC_CHARSET_MAC:
448 return "MACINTOSH";
449 case SC_CHARSET_OEM:
450 return "ASCII";
451 case SC_CHARSET_RUSSIAN:
452 return "KOI8-R";
453 case SC_CHARSET_OEM866:
454 return "CP866";
455 case SC_CHARSET_CYRILLIC:
456 return "CP1251";
457 case SC_CHARSET_SHIFTJIS:
458 return "SHIFT-JIS";
459 case SC_CHARSET_SYMBOL:
460 return "";
461 case SC_CHARSET_TURKISH:
462 return "ISO-8859-9";
463 case SC_CHARSET_JOHAB:
464 return "CP1361";
465 case SC_CHARSET_HEBREW:
466 return "ISO-8859-8";
467 case SC_CHARSET_ARABIC:
468 return "ISO-8859-6";
469 case SC_CHARSET_VIETNAMESE:
470 return "";
471 case SC_CHARSET_THAI:
472 return "ISO-8859-11";
473 case SC_CHARSET_8859_15:
474 return "ISO-8859-15";
475 default:
476 return "";
480 void SurfaceImpl::SetConverter(int characterSet_) {
481 if (characterSet != characterSet_) {
482 characterSet = characterSet_;
483 conv.Open("UTF-8", CharacterSetID(characterSet), false);
487 SurfaceImpl::SurfaceImpl() : et(singleByte),
488 context(0),
489 psurf(0),
490 x(0), y(0), inited(false), createdGC(false)
491 , pcontext(0), layout(0), characterSet(-1) {
494 SurfaceImpl::~SurfaceImpl() {
495 Release();
498 void SurfaceImpl::Release() {
499 et = singleByte;
500 if (createdGC) {
501 createdGC = false;
502 cairo_destroy(context);
504 context = 0;
505 if (psurf)
506 cairo_surface_destroy(psurf);
507 psurf = 0;
508 if (layout)
509 g_object_unref(layout);
510 layout = 0;
511 if (pcontext)
512 g_object_unref(pcontext);
513 pcontext = 0;
514 conv.Close();
515 characterSet = -1;
516 x = 0;
517 y = 0;
518 inited = false;
519 createdGC = false;
522 bool SurfaceImpl::Initialised() {
523 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 8, 0)
524 if (inited && context) {
525 if (cairo_status(context) == CAIRO_STATUS_SUCCESS) {
526 // Even when status is success, the target surface may have been
527 // finished whch may cause an assertion to fail crashing the application.
528 // The cairo_surface_has_show_text_glyphs call checks the finished flag
529 // and when set, sets the status to CAIRO_STATUS_SURFACE_FINISHED
530 // which leads to warning messages instead of crashes.
531 // Performing the check in this method as it is called rarely and has no
532 // other side effects.
533 cairo_surface_t *psurfContext = cairo_get_target(context);
534 if (psurfContext) {
535 cairo_surface_has_show_text_glyphs(psurfContext);
538 return cairo_status(context) == CAIRO_STATUS_SUCCESS;
540 #endif
541 return inited;
544 void SurfaceImpl::Init(WindowID wid) {
545 Release();
546 PLATFORM_ASSERT(wid);
547 // if we are only created from a window ID, we can't perform drawing
548 psurf = 0;
549 context = 0;
550 createdGC = false;
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(static_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;
598 et = surfImpl->et;
601 void SurfaceImpl::PenColour(ColourDesired fore) {
602 if (context) {
603 ColourDesired cdFore(fore.AsLong());
604 cairo_set_source_rgb(context,
605 cdFore.GetRed() / 255.0,
606 cdFore.GetGreen() / 255.0,
607 cdFore.GetBlue() / 255.0);
611 int SurfaceImpl::LogPixelsY() {
612 return 72;
615 int SurfaceImpl::DeviceHeightFont(int points) {
616 int logPix = LogPixelsY();
617 return (points * logPix + logPix / 2) / 72;
620 void SurfaceImpl::MoveTo(int x_, int y_) {
621 x = x_;
622 y = y_;
625 static int Delta(int difference) {
626 if (difference < 0)
627 return -1;
628 else if (difference > 0)
629 return 1;
630 else
631 return 0;
634 void SurfaceImpl::LineTo(int x_, int y_) {
635 // cairo_line_to draws the end position, unlike Win32 or GDK with GDK_CAP_NOT_LAST.
636 // For simple cases, move back one pixel from end.
637 if (context) {
638 int xDiff = x_ - x;
639 int xDelta = Delta(xDiff);
640 int yDiff = y_ - y;
641 int yDelta = Delta(yDiff);
642 if ((xDiff == 0) || (yDiff == 0)) {
643 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
644 int xEnd = x_ - xDelta;
645 int left = Platform::Minimum(x, xEnd);
646 int width = abs(x - xEnd) + 1;
647 int yEnd = y_ - yDelta;
648 int top = Platform::Minimum(y, yEnd);
649 int height = abs(y - yEnd) + 1;
650 cairo_rectangle(context, left, top, width, height);
651 cairo_fill(context);
652 } else if ((abs(xDiff) == abs(yDiff))) {
653 // 45 degree slope
654 cairo_move_to(context, x + 0.5, y + 0.5);
655 cairo_line_to(context, x_ + 0.5 - xDelta, y_ + 0.5 - yDelta);
656 } else {
657 // Line has a different slope so difficult to avoid last pixel
658 cairo_move_to(context, x + 0.5, y + 0.5);
659 cairo_line_to(context, x_ + 0.5, y_ + 0.5);
661 cairo_stroke(context);
663 x = x_;
664 y = y_;
667 void SurfaceImpl::Polygon(Point *pts, int npts, ColourDesired fore,
668 ColourDesired back) {
669 PLATFORM_ASSERT(context);
670 PenColour(back);
671 cairo_move_to(context, pts[0].x + 0.5, pts[0].y + 0.5);
672 for (int i = 1; i < npts; i++) {
673 cairo_line_to(context, pts[i].x + 0.5, pts[i].y + 0.5);
675 cairo_close_path(context);
676 cairo_fill_preserve(context);
677 PenColour(fore);
678 cairo_stroke(context);
681 void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
682 if (context) {
683 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5,
684 rc.right - rc.left - 1, rc.bottom - rc.top - 1);
685 PenColour(back);
686 cairo_fill_preserve(context);
687 PenColour(fore);
688 cairo_stroke(context);
692 void SurfaceImpl::FillRectangle(PRectangle rc, ColourDesired back) {
693 PenColour(back);
694 if (context && (rc.left < maxCoordinate)) { // Protect against out of range
695 rc.left = lround(rc.left);
696 rc.right = lround(rc.right);
697 cairo_rectangle(context, rc.left, rc.top,
698 rc.right - rc.left, rc.bottom - rc.top);
699 cairo_fill(context);
703 void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
704 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfacePattern);
705 bool canDraw = surfi.psurf != NULL;
706 if (canDraw) {
707 PLATFORM_ASSERT(context);
708 // Tile pattern over rectangle
709 // Currently assumes 8x8 pattern
710 int widthPat = 8;
711 int heightPat = 8;
712 for (int xTile = rc.left; xTile < rc.right; xTile += widthPat) {
713 int widthx = (xTile + widthPat > rc.right) ? rc.right - xTile : widthPat;
714 for (int yTile = rc.top; yTile < rc.bottom; yTile += heightPat) {
715 int heighty = (yTile + heightPat > rc.bottom) ? rc.bottom - yTile : heightPat;
716 cairo_set_source_surface(context, surfi.psurf, xTile, yTile);
717 cairo_rectangle(context, xTile, yTile, widthx, heighty);
718 cairo_fill(context);
721 } else {
722 // Something is wrong so try to show anyway
723 // Shows up black because colour not allocated
724 FillRectangle(rc, ColourDesired(0));
728 void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
729 if (((rc.right - rc.left) > 4) && ((rc.bottom - rc.top) > 4)) {
730 // Approximate a round rect with some cut off corners
731 Point pts[] = {
732 Point(rc.left + 2, rc.top),
733 Point(rc.right - 2, rc.top),
734 Point(rc.right, rc.top + 2),
735 Point(rc.right, rc.bottom - 2),
736 Point(rc.right - 2, rc.bottom),
737 Point(rc.left + 2, rc.bottom),
738 Point(rc.left, rc.bottom - 2),
739 Point(rc.left, rc.top + 2),
741 Polygon(pts, ELEMENTS(pts), fore, back);
742 } else {
743 RectangleDraw(rc, fore, back);
747 static void PathRoundRectangle(cairo_t *context, double left, double top, double width, double height, int radius) {
748 double degrees = kPi / 180.0;
750 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 2, 0)
751 cairo_new_sub_path(context);
752 #else
753 // First arc is in the top-right corner and starts from a point on the top line
754 cairo_move_to(context, left + width - radius, top);
755 #endif
756 cairo_arc(context, left + width - radius, top + radius, radius, -90 * degrees, 0 * degrees);
757 cairo_arc(context, left + width - radius, top + height - radius, radius, 0 * degrees, 90 * degrees);
758 cairo_arc(context, left + radius, top + height - radius, radius, 90 * degrees, 180 * degrees);
759 cairo_arc(context, left + radius, top + radius, radius, 180 * degrees, 270 * degrees);
760 cairo_close_path(context);
763 void SurfaceImpl::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
764 ColourDesired outline, int alphaOutline, int flags) {
765 if (context && rc.Width() > 0) {
766 ColourDesired cdFill(fill.AsLong());
767 cairo_set_source_rgba(context,
768 cdFill.GetRed() / 255.0,
769 cdFill.GetGreen() / 255.0,
770 cdFill.GetBlue() / 255.0,
771 alphaFill / 255.0);
772 if (cornerSize > 0)
773 PathRoundRectangle(context, rc.left + 1.0, rc.top + 1.0, rc.right - rc.left - 2.0, rc.bottom - rc.top - 2.0, cornerSize);
774 else
775 cairo_rectangle(context, rc.left + 1.0, rc.top + 1.0, rc.right - rc.left - 2.0, rc.bottom - rc.top - 2.0);
776 cairo_fill(context);
778 ColourDesired cdOutline(outline.AsLong());
779 cairo_set_source_rgba(context,
780 cdOutline.GetRed() / 255.0,
781 cdOutline.GetGreen() / 255.0,
782 cdOutline.GetBlue() / 255.0,
783 alphaOutline / 255.0);
784 if (cornerSize > 0)
785 PathRoundRectangle(context, rc.left + 0.5, rc.top + 0.5, rc.right - rc.left - 1, rc.bottom - rc.top - 1, cornerSize);
786 else
787 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5, rc.right - rc.left - 1, rc.bottom - rc.top - 1);
788 cairo_stroke(context);
792 void SurfaceImpl::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
793 PLATFORM_ASSERT(context);
794 if (rc.Width() > width)
795 rc.left += (rc.Width() - width) / 2;
796 rc.right = rc.left + width;
797 if (rc.Height() > height)
798 rc.top += (rc.Height() - height) / 2;
799 rc.bottom = rc.top + height;
801 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
802 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
803 #else
804 int stride = width * 4;
805 #endif
806 int ucs = stride * height;
807 std::vector<unsigned char> image(ucs);
808 for (int iy=0; iy<height; iy++) {
809 for (int ix=0; ix<width; ix++) {
810 unsigned char *pixel = &image[0] + iy*stride + ix * 4;
811 unsigned char alpha = pixelsImage[3];
812 pixel[2] = (*pixelsImage++) * alpha / 255;
813 pixel[1] = (*pixelsImage++) * alpha / 255;
814 pixel[0] = (*pixelsImage++) * alpha / 255;
815 pixel[3] = *pixelsImage++;
819 cairo_surface_t *psurfImage = cairo_image_surface_create_for_data(&image[0], CAIRO_FORMAT_ARGB32, width, height, stride);
820 cairo_set_source_surface(context, psurfImage, rc.left, rc.top);
821 cairo_rectangle(context, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
822 cairo_fill(context);
824 cairo_surface_destroy(psurfImage);
827 void SurfaceImpl::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
828 PLATFORM_ASSERT(context);
829 PenColour(back);
830 cairo_arc(context, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2,
831 Platform::Minimum(rc.Width(), rc.Height()) / 2, 0, 2*kPi);
832 cairo_fill_preserve(context);
833 PenColour(fore);
834 cairo_stroke(context);
837 void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
838 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfaceSource);
839 bool canDraw = surfi.psurf != NULL;
840 if (canDraw) {
841 PLATFORM_ASSERT(context);
842 cairo_set_source_surface(context, surfi.psurf,
843 rc.left - from.x, rc.top - from.y);
844 cairo_rectangle(context, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
845 cairo_fill(context);
849 std::string UTF8FromLatin1(const char *s, int len) {
850 std::string utfForm(len*2 + 1, '\0');
851 size_t lenU = 0;
852 for (int i=0; i<len; i++) {
853 unsigned int uch = static_cast<unsigned char>(s[i]);
854 if (uch < 0x80) {
855 utfForm[lenU++] = uch;
856 } else {
857 utfForm[lenU++] = static_cast<char>(0xC0 | (uch >> 6));
858 utfForm[lenU++] = static_cast<char>(0x80 | (uch & 0x3f));
861 utfForm.resize(lenU);
862 return utfForm;
865 static std::string UTF8FromIconv(const Converter &conv, const char *s, int len) {
866 if (conv) {
867 std::string utfForm(len*3+1, '\0');
868 char *pin = const_cast<char *>(s);
869 size_t inLeft = len;
870 char *putf = &utfForm[0];
871 char *pout = putf;
872 size_t outLeft = len*3+1;
873 size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
874 if (conversions != ((size_t)(-1))) {
875 *pout = '\0';
876 utfForm.resize(pout - putf);
877 return utfForm;
880 return std::string();
883 // Work out how many bytes are in a character by trying to convert using iconv,
884 // returning the first length that succeeds.
885 static size_t MultiByteLenFromIconv(const Converter &conv, const char *s, size_t len) {
886 for (size_t lenMB=1; (lenMB<4) && (lenMB <= len); lenMB++) {
887 char wcForm[2];
888 char *pin = const_cast<char *>(s);
889 size_t inLeft = lenMB;
890 char *pout = wcForm;
891 size_t outLeft = 2;
892 size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
893 if (conversions != ((size_t)(-1))) {
894 return lenMB;
897 return 1;
900 void SurfaceImpl::DrawTextBase(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
901 ColourDesired fore) {
902 PenColour(fore);
903 if (context) {
904 XYPOSITION xText = rc.left;
905 if (PFont(font_)->pfd) {
906 std::string utfForm;
907 if (et == UTF8) {
908 pango_layout_set_text(layout, s, len);
909 } else {
910 SetConverter(PFont(font_)->characterSet);
911 utfForm = UTF8FromIconv(conv, s, len);
912 if (utfForm.empty()) { // iconv failed so treat as Latin1
913 utfForm = UTF8FromLatin1(s, len);
915 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
917 pango_layout_set_font_description(layout, PFont(font_)->pfd);
918 pango_cairo_update_layout(context, layout);
919 #ifdef PANGO_VERSION
920 PangoLayoutLine *pll = pango_layout_get_line_readonly(layout,0);
921 #else
922 PangoLayoutLine *pll = pango_layout_get_line(layout,0);
923 #endif
924 cairo_move_to(context, xText, ybase);
925 pango_cairo_show_layout_line(context, pll);
930 void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
931 ColourDesired fore, ColourDesired back) {
932 FillRectangle(rc, back);
933 DrawTextBase(rc, font_, ybase, s, len, fore);
936 // On GTK+, exactly same as DrawTextNoClip
937 void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
938 ColourDesired fore, ColourDesired back) {
939 FillRectangle(rc, back);
940 DrawTextBase(rc, font_, ybase, s, len, fore);
943 void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
944 ColourDesired fore) {
945 // Avoid drawing spaces in transparent mode
946 for (int i=0; i<len; i++) {
947 if (s[i] != ' ') {
948 DrawTextBase(rc, font_, ybase, s, len, fore);
949 return;
954 class ClusterIterator {
955 PangoLayoutIter *iter;
956 PangoRectangle pos;
957 int lenPositions;
958 public:
959 bool finished;
960 XYPOSITION positionStart;
961 XYPOSITION position;
962 XYPOSITION distance;
963 int curIndex;
964 ClusterIterator(PangoLayout *layout, int len) : lenPositions(len), finished(false),
965 positionStart(0), position(0), distance(0), curIndex(0) {
966 iter = pango_layout_get_iter(layout);
967 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
969 ~ClusterIterator() {
970 pango_layout_iter_free(iter);
973 void Next() {
974 positionStart = position;
975 if (pango_layout_iter_next_cluster(iter)) {
976 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
977 position = doubleFromPangoUnits(pos.x);
978 curIndex = pango_layout_iter_get_index(iter);
979 } else {
980 finished = true;
981 position = doubleFromPangoUnits(pos.x + pos.width);
982 curIndex = lenPositions;
984 distance = position - positionStart;
988 void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
989 if (font_.GetID()) {
990 const int lenPositions = len;
991 if (PFont(font_)->pfd) {
992 if (len == 1) {
993 int width = PFont(font_)->CharWidth(*s, et);
994 if (width) {
995 positions[0] = width;
996 return;
999 pango_layout_set_font_description(layout, PFont(font_)->pfd);
1000 if (et == UTF8) {
1001 // Simple and direct as UTF-8 is native Pango encoding
1002 int i = 0;
1003 pango_layout_set_text(layout, s, len);
1004 ClusterIterator iti(layout, lenPositions);
1005 while (!iti.finished) {
1006 iti.Next();
1007 int places = iti.curIndex - i;
1008 while (i < iti.curIndex) {
1009 // Evenly distribute space among bytes of this cluster.
1010 // Would be better to find number of characters and then
1011 // divide evenly between characters with each byte of a character
1012 // being at the same position.
1013 positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places;
1014 i++;
1017 PLATFORM_ASSERT(i == lenPositions);
1018 } else {
1019 int positionsCalculated = 0;
1020 if (et == dbcs) {
1021 SetConverter(PFont(font_)->characterSet);
1022 std::string utfForm = UTF8FromIconv(conv, s, len);
1023 if (!utfForm.empty()) {
1024 // Convert to UTF-8 so can ask Pango for widths, then
1025 // Loop through UTF-8 and DBCS forms, taking account of different
1026 // character byte lengths.
1027 Converter convMeasure("UCS-2", CharacterSetID(characterSet), false);
1028 pango_layout_set_text(layout, utfForm.c_str(), strlen(utfForm.c_str()));
1029 int i = 0;
1030 int clusterStart = 0;
1031 ClusterIterator iti(layout, strlen(utfForm.c_str()));
1032 while (!iti.finished) {
1033 iti.Next();
1034 int clusterEnd = iti.curIndex;
1035 int places = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
1036 int place = 1;
1037 while (clusterStart < clusterEnd) {
1038 size_t lenChar = MultiByteLenFromIconv(convMeasure, s+i, len-i);
1039 while (lenChar--) {
1040 positions[i++] = iti.position - (places - place) * iti.distance / places;
1041 positionsCalculated++;
1043 clusterStart += UTF8CharLength(static_cast<unsigned char>(utfForm.c_str()[clusterStart]));
1044 place++;
1047 PLATFORM_ASSERT(i == lenPositions);
1050 if (positionsCalculated < 1 ) {
1051 // Either 8-bit or DBCS conversion failed so treat as 8-bit.
1052 SetConverter(PFont(font_)->characterSet);
1053 const bool rtlCheck = PFont(font_)->characterSet == SC_CHARSET_HEBREW ||
1054 PFont(font_)->characterSet == SC_CHARSET_ARABIC;
1055 std::string utfForm = UTF8FromIconv(conv, s, len);
1056 if (utfForm.empty()) {
1057 utfForm = UTF8FromLatin1(s, len);
1059 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
1060 int i = 0;
1061 int clusterStart = 0;
1062 // Each 8-bit input character may take 1 or 2 bytes in UTF-8
1063 // and groups of up to 3 may be represented as ligatures.
1064 ClusterIterator iti(layout, utfForm.length());
1065 while (!iti.finished) {
1066 iti.Next();
1067 int clusterEnd = iti.curIndex;
1068 int ligatureLength = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
1069 if (rtlCheck && ((clusterEnd <= clusterStart) || (ligatureLength == 0) || (ligatureLength > 3))) {
1070 // Something has gone wrong: exit quickly but pretend all the characters are equally spaced:
1071 int widthLayout = 0;
1072 pango_layout_get_size(layout, &widthLayout, NULL);
1073 XYPOSITION widthTotal = doubleFromPangoUnits(widthLayout);
1074 for (int bytePos=0; bytePos<lenPositions; bytePos++) {
1075 positions[bytePos] = widthTotal / lenPositions * (bytePos + 1);
1077 return;
1079 PLATFORM_ASSERT(ligatureLength > 0 && ligatureLength <= 3);
1080 for (int charInLig=0; charInLig<ligatureLength; charInLig++) {
1081 positions[i++] = iti.position - (ligatureLength - 1 - charInLig) * iti.distance / ligatureLength;
1083 clusterStart = clusterEnd;
1085 while (i < lenPositions) {
1086 // If something failed, fill in rest of the positions
1087 positions[i++] = clusterStart;
1089 PLATFORM_ASSERT(i == lenPositions);
1092 if (len == 1) {
1093 PFont(font_)->SetCharWidth(*s, positions[0], et);
1095 return;
1097 } else {
1098 // No font so return an ascending range of values
1099 for (int i = 0; i < len; i++) {
1100 positions[i] = i + 1;
1105 XYPOSITION SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
1106 if (font_.GetID()) {
1107 if (PFont(font_)->pfd) {
1108 std::string utfForm;
1109 pango_layout_set_font_description(layout, PFont(font_)->pfd);
1110 PangoRectangle pos;
1111 if (et == UTF8) {
1112 pango_layout_set_text(layout, s, len);
1113 } else {
1114 SetConverter(PFont(font_)->characterSet);
1115 utfForm = UTF8FromIconv(conv, s, len);
1116 if (utfForm.empty()) { // iconv failed so treat as Latin1
1117 utfForm = UTF8FromLatin1(s, len);
1119 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
1121 #ifdef PANGO_VERSION
1122 PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout,0);
1123 #else
1124 PangoLayoutLine *pangoLine = pango_layout_get_line(layout,0);
1125 #endif
1126 pango_layout_line_get_extents(pangoLine, NULL, &pos);
1127 return doubleFromPangoUnits(pos.width);
1129 return 1;
1130 } else {
1131 return 1;
1135 XYPOSITION SurfaceImpl::WidthChar(Font &font_, char ch) {
1136 if (font_.GetID()) {
1137 if (PFont(font_)->pfd) {
1138 return WidthText(font_, &ch, 1);
1140 return 1;
1141 } else {
1142 return 1;
1146 // Ascent and descent determined by Pango font metrics.
1148 XYPOSITION SurfaceImpl::Ascent(Font &font_) {
1149 if (!(font_.GetID()))
1150 return 1;
1151 FontMutexLock();
1152 int ascent = PFont(font_)->ascent;
1153 if ((ascent == 0) && (PFont(font_)->pfd)) {
1154 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
1155 PFont(font_)->pfd, pango_context_get_language(pcontext));
1156 PFont(font_)->ascent =
1157 doubleFromPangoUnits(pango_font_metrics_get_ascent(metrics));
1158 pango_font_metrics_unref(metrics);
1159 ascent = PFont(font_)->ascent;
1161 if (ascent == 0) {
1162 ascent = 1;
1164 FontMutexUnlock();
1165 return ascent;
1168 XYPOSITION SurfaceImpl::Descent(Font &font_) {
1169 if (!(font_.GetID()))
1170 return 1;
1171 if (PFont(font_)->pfd) {
1172 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
1173 PFont(font_)->pfd, pango_context_get_language(pcontext));
1174 int descent = doubleFromPangoUnits(pango_font_metrics_get_descent(metrics));
1175 pango_font_metrics_unref(metrics);
1176 return descent;
1178 return 0;
1181 XYPOSITION SurfaceImpl::InternalLeading(Font &) {
1182 return 0;
1185 XYPOSITION SurfaceImpl::ExternalLeading(Font &) {
1186 return 0;
1189 XYPOSITION SurfaceImpl::Height(Font &font_) {
1190 return Ascent(font_) + Descent(font_);
1193 XYPOSITION SurfaceImpl::AverageCharWidth(Font &font_) {
1194 return WidthChar(font_, 'n');
1197 void SurfaceImpl::SetClip(PRectangle rc) {
1198 PLATFORM_ASSERT(context);
1199 cairo_rectangle(context, rc.left, rc.top, rc.right, rc.bottom);
1200 cairo_clip(context);
1203 void SurfaceImpl::FlushCachedState() {}
1205 void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
1206 if (unicodeMode_)
1207 et = UTF8;
1210 void SurfaceImpl::SetDBCSMode(int codePage) {
1211 if (codePage && (codePage != SC_CP_UTF8))
1212 et = dbcs;
1215 Surface *Surface::Allocate(int) {
1216 return new SurfaceImpl();
1219 Window::~Window() {}
1221 void Window::Destroy() {
1222 if (wid) {
1223 ListBox *listbox = dynamic_cast<ListBox*>(this);
1224 if (listbox) {
1225 gtk_widget_hide(GTK_WIDGET(wid));
1226 // clear up window content
1227 listbox->Clear();
1228 // resize the window to the smallest possible size for it to adapt
1229 // to future content
1230 gtk_window_resize(GTK_WINDOW(wid), 1, 1);
1231 } else {
1232 gtk_widget_destroy(GTK_WIDGET(wid));
1234 wid = 0;
1238 bool Window::HasFocus() {
1239 return gtk_widget_has_focus(GTK_WIDGET(wid));
1242 PRectangle Window::GetPosition() {
1243 // Before any size allocated pretend its 1000 wide so not scrolled
1244 PRectangle rc(0, 0, 1000, 1000);
1245 if (wid) {
1246 GtkAllocation allocation;
1247 gtk_widget_get_allocation(PWidget(wid), &allocation);
1248 rc.left = allocation.x;
1249 rc.top = allocation.y;
1250 if (allocation.width > 20) {
1251 rc.right = rc.left + allocation.width;
1252 rc.bottom = rc.top + allocation.height;
1255 return rc;
1258 void Window::SetPosition(PRectangle rc) {
1259 GtkAllocation alloc;
1260 alloc.x = rc.left;
1261 alloc.y = rc.top;
1262 alloc.width = rc.Width();
1263 alloc.height = rc.Height();
1264 gtk_widget_size_allocate(PWidget(wid), &alloc);
1267 void Window::SetPositionRelative(PRectangle rc, Window relativeTo) {
1268 int ox = 0;
1269 int oy = 0;
1270 gdk_window_get_origin(WindowFromWidget(PWidget(relativeTo.wid)), &ox, &oy);
1271 ox += rc.left;
1272 if (ox < 0)
1273 ox = 0;
1274 oy += rc.top;
1275 if (oy < 0)
1276 oy = 0;
1278 /* do some corrections to fit into screen */
1279 int sizex = rc.right - rc.left;
1280 int sizey = rc.bottom - rc.top;
1281 int screenWidth = gdk_screen_width();
1282 int screenHeight = gdk_screen_height();
1283 if (sizex > screenWidth)
1284 ox = 0; /* the best we can do */
1285 else if (ox + sizex > screenWidth)
1286 ox = screenWidth - sizex;
1287 if (oy + sizey > screenHeight)
1288 oy = screenHeight - sizey;
1290 gtk_window_move(GTK_WINDOW(PWidget(wid)), ox, oy);
1292 gtk_window_resize(GTK_WINDOW(wid), sizex, sizey);
1295 PRectangle Window::GetClientPosition() {
1296 // On GTK+, the client position is the window position
1297 return GetPosition();
1300 void Window::Show(bool show) {
1301 if (show)
1302 gtk_widget_show(PWidget(wid));
1305 void Window::InvalidateAll() {
1306 if (wid) {
1307 gtk_widget_queue_draw(PWidget(wid));
1311 void Window::InvalidateRectangle(PRectangle rc) {
1312 if (wid) {
1313 gtk_widget_queue_draw_area(PWidget(wid),
1314 rc.left, rc.top,
1315 rc.right - rc.left, rc.bottom - rc.top);
1319 void Window::SetFont(Font &) {
1320 // Can not be done generically but only needed for ListBox
1323 void Window::SetCursor(Cursor curs) {
1324 // We don't set the cursor to same value numerous times under gtk because
1325 // it stores the cursor in the window once it's set
1326 if (curs == cursorLast)
1327 return;
1329 cursorLast = curs;
1330 GdkDisplay *pdisplay = gtk_widget_get_display(PWidget(wid));
1332 GdkCursor *gdkCurs;
1333 switch (curs) {
1334 case cursorText:
1335 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_XTERM);
1336 break;
1337 case cursorArrow:
1338 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
1339 break;
1340 case cursorUp:
1341 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_CENTER_PTR);
1342 break;
1343 case cursorWait:
1344 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_WATCH);
1345 break;
1346 case cursorHand:
1347 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_HAND2);
1348 break;
1349 case cursorReverseArrow:
1350 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_RIGHT_PTR);
1351 break;
1352 default:
1353 gdkCurs = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
1354 cursorLast = cursorArrow;
1355 break;
1358 if (WindowFromWidget(PWidget(wid)))
1359 gdk_window_set_cursor(WindowFromWidget(PWidget(wid)), gdkCurs);
1360 #if GTK_CHECK_VERSION(3,0,0)
1361 g_object_unref(gdkCurs);
1362 #else
1363 gdk_cursor_unref(gdkCurs);
1364 #endif
1367 void Window::SetTitle(const char *s) {
1368 gtk_window_set_title(GTK_WINDOW(wid), s);
1371 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1372 gdk window coordinates */
1373 PRectangle Window::GetMonitorRect(Point pt) {
1374 gint x_offset, y_offset;
1376 gdk_window_get_origin(WindowFromWidget(PWidget(wid)), &x_offset, &y_offset);
1378 GdkScreen* screen;
1379 gint monitor_num;
1380 GdkRectangle rect;
1382 screen = gtk_widget_get_screen(PWidget(wid));
1383 monitor_num = gdk_screen_get_monitor_at_point(screen, pt.x + x_offset, pt.y + y_offset);
1384 gdk_screen_get_monitor_geometry(screen, monitor_num, &rect);
1385 rect.x -= x_offset;
1386 rect.y -= y_offset;
1387 return PRectangle(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
1390 typedef std::map<int, RGBAImage*> ImageMap;
1392 struct ListImage {
1393 const RGBAImage *rgba_data;
1394 GdkPixbuf *pixbuf;
1397 static void list_image_free(gpointer, gpointer value, gpointer) {
1398 ListImage *list_image = static_cast<ListImage *>(value);
1399 if (list_image->pixbuf)
1400 g_object_unref(list_image->pixbuf);
1401 g_free(list_image);
1404 ListBox::ListBox() {
1407 ListBox::~ListBox() {
1410 enum {
1411 PIXBUF_COLUMN,
1412 TEXT_COLUMN,
1413 N_COLUMNS
1416 class ListBoxX : public ListBox {
1417 WindowID widCached;
1418 WindowID frame;
1419 WindowID list;
1420 WindowID scroller;
1421 void *pixhash;
1422 GtkCellRenderer* pixbuf_renderer;
1423 GtkCellRenderer* renderer;
1424 RGBAImageSet images;
1425 int desiredVisibleRows;
1426 unsigned int maxItemCharacters;
1427 unsigned int aveCharWidth;
1428 #if GTK_CHECK_VERSION(3,0,0)
1429 GtkCssProvider *cssProvider;
1430 #endif
1431 public:
1432 CallBackAction doubleClickAction;
1433 void *doubleClickActionData;
1435 ListBoxX() : widCached(0), frame(0), list(0), scroller(0), pixhash(NULL), pixbuf_renderer(0),
1436 renderer(0),
1437 desiredVisibleRows(5), maxItemCharacters(0),
1438 aveCharWidth(1),
1439 #if GTK_CHECK_VERSION(3,0,0)
1440 cssProvider(NULL),
1441 #endif
1442 doubleClickAction(NULL), doubleClickActionData(NULL) {
1444 virtual ~ListBoxX() {
1445 if (pixhash) {
1446 g_hash_table_foreach((GHashTable *) pixhash, list_image_free, NULL);
1447 g_hash_table_destroy((GHashTable *) pixhash);
1449 if (widCached) {
1450 gtk_widget_destroy(GTK_WIDGET(widCached));
1451 wid = widCached = 0;
1453 #if GTK_CHECK_VERSION(3,0,0)
1454 if (cssProvider) {
1455 g_object_unref(cssProvider);
1456 cssProvider = NULL;
1458 #endif
1460 virtual void SetFont(Font &font);
1461 virtual void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_);
1462 virtual void SetAverageCharWidth(int width);
1463 virtual void SetVisibleRows(int rows);
1464 virtual int GetVisibleRows() const;
1465 int GetRowHeight();
1466 virtual PRectangle GetDesiredRect();
1467 virtual int CaretFromEdge();
1468 virtual void Clear();
1469 virtual void Append(char *s, int type = -1);
1470 virtual int Length();
1471 virtual void Select(int n);
1472 virtual int GetSelection();
1473 virtual int Find(const char *prefix);
1474 virtual void GetValue(int n, char *value, int len);
1475 void RegisterRGBA(int type, RGBAImage *image);
1476 virtual void RegisterImage(int type, const char *xpm_data);
1477 virtual void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage);
1478 virtual void ClearRegisteredImages();
1479 virtual void SetDoubleClickAction(CallBackAction action, void *data) {
1480 doubleClickAction = action;
1481 doubleClickActionData = data;
1483 virtual void SetList(const char *listText, char separator, char typesep);
1486 ListBox *ListBox::Allocate() {
1487 ListBoxX *lb = new ListBoxX();
1488 return lb;
1491 // SmallScroller, a GtkScrolledWindow that can shrink very small, as
1492 // gtk_widget_set_size_request() cannot shrink widgets on GTK3
1493 typedef struct {
1494 GtkScrolledWindow parent;
1495 /* Workaround ABI issue with Windows GTK2 bundle and GCC > 3.
1496 See http://lists.geany.org/pipermail/devel/2015-April/thread.html#9379
1498 GtkScrolledWindow contains a bitfield, and GCC 3.4 and 4.8 don't agree
1499 on the size of the structure (regardless of -mms-bitfields):
1500 - GCC 3.4 has sizeof(GtkScrolledWindow)=88
1501 - GCC 4.8 has sizeof(GtkScrolledWindow)=84
1502 As Windows GTK2 bundle is built with GCC 3, it requires types derived
1503 from GtkScrolledWindow to be at least 88 bytes, which means we need to
1504 add some fake padding to fill in the extra 4 bytes.
1505 There is however no other issue with the layout difference as we never
1506 access any GtkScrolledWindow fields ourselves. */
1507 int padding;
1508 } SmallScroller;
1509 typedef GtkScrolledWindowClass SmallScrollerClass;
1511 G_DEFINE_TYPE(SmallScroller, small_scroller, GTK_TYPE_SCROLLED_WINDOW)
1513 #if GTK_CHECK_VERSION(3,0,0)
1514 static void small_scroller_get_preferred_height(GtkWidget *widget, gint *min, gint *nat) {
1515 GTK_WIDGET_CLASS(small_scroller_parent_class)->get_preferred_height(widget, min, nat);
1516 if (*min > 1)
1517 *min = 1;
1519 #else
1520 static void small_scroller_size_request(GtkWidget *widget, GtkRequisition *req) {
1521 GTK_WIDGET_CLASS(small_scroller_parent_class)->size_request(widget, req);
1522 req->height = 1;
1524 #endif
1526 static void small_scroller_class_init(SmallScrollerClass *klass) {
1527 #if GTK_CHECK_VERSION(3,0,0)
1528 GTK_WIDGET_CLASS(klass)->get_preferred_height = small_scroller_get_preferred_height;
1529 #else
1530 GTK_WIDGET_CLASS(klass)->size_request = small_scroller_size_request;
1531 #endif
1534 static void small_scroller_init(SmallScroller *){}
1536 static gboolean ButtonPress(GtkWidget *, GdkEventButton* ev, gpointer p) {
1537 try {
1538 ListBoxX* lb = static_cast<ListBoxX*>(p);
1539 if (ev->type == GDK_2BUTTON_PRESS && lb->doubleClickAction != NULL) {
1540 lb->doubleClickAction(lb->doubleClickActionData);
1541 return TRUE;
1544 } catch (...) {
1545 // No pointer back to Scintilla to save status
1547 return FALSE;
1550 /* Change the active color to the selected color so the listbox uses the color
1551 scheme that it would use if it had the focus. */
1552 static void StyleSet(GtkWidget *w, GtkStyle*, void*) {
1554 g_return_if_fail(w != NULL);
1556 /* Copy the selected color to active. Note that the modify calls will cause
1557 recursive calls to this function after the value is updated and w->style to
1558 be set to a new object */
1560 #if GTK_CHECK_VERSION(3,16,0)
1561 // On recent releases of GTK+, it does not appear necessary to set the list box colours.
1562 // This may be because of common themes and may be needed with other themes.
1563 // The *override* calls are deprecated now, so only call them for older versions of GTK+.
1564 #elif GTK_CHECK_VERSION(3,0,0)
1565 GtkStyleContext *styleContext = gtk_widget_get_style_context(w);
1566 if (styleContext == NULL)
1567 return;
1569 GdkRGBA colourForeSelected;
1570 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourForeSelected);
1571 GdkRGBA colourForeActive;
1572 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourForeActive);
1573 if (!gdk_rgba_equal(&colourForeSelected, &colourForeActive))
1574 gtk_widget_override_color(w, GTK_STATE_FLAG_ACTIVE, &colourForeSelected);
1576 styleContext = gtk_widget_get_style_context(w);
1577 if (styleContext == NULL)
1578 return;
1580 GdkRGBA colourBaseSelected;
1581 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourBaseSelected);
1582 GdkRGBA colourBaseActive;
1583 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourBaseActive);
1584 if (!gdk_rgba_equal(&colourBaseSelected, &colourBaseActive))
1585 gtk_widget_override_background_color(w, GTK_STATE_FLAG_ACTIVE, &colourBaseSelected);
1586 #else
1587 GtkStyle *style = gtk_widget_get_style(w);
1588 if (style == NULL)
1589 return;
1590 if (!gdk_color_equal(&style->base[GTK_STATE_SELECTED], &style->base[GTK_STATE_ACTIVE]))
1591 gtk_widget_modify_base(w, GTK_STATE_ACTIVE, &style->base[GTK_STATE_SELECTED]);
1592 style = gtk_widget_get_style(w);
1593 if (style == NULL)
1594 return;
1595 if (!gdk_color_equal(&style->text[GTK_STATE_SELECTED], &style->text[GTK_STATE_ACTIVE]))
1596 gtk_widget_modify_text(w, GTK_STATE_ACTIVE, &style->text[GTK_STATE_SELECTED]);
1597 #endif
1600 void ListBoxX::Create(Window &, int, Point, int, bool, int) {
1601 if (widCached != 0) {
1602 wid = widCached;
1603 return;
1606 #if GTK_CHECK_VERSION(3,0,0)
1607 if (!cssProvider) {
1608 cssProvider = gtk_css_provider_new();
1610 #endif
1612 wid = widCached = gtk_window_new(GTK_WINDOW_POPUP);
1614 frame = gtk_frame_new(NULL);
1615 gtk_widget_show(PWidget(frame));
1616 gtk_container_add(GTK_CONTAINER(GetID()), PWidget(frame));
1617 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1618 gtk_container_set_border_width(GTK_CONTAINER(frame), 0);
1620 scroller = g_object_new(small_scroller_get_type(), NULL);
1621 gtk_container_set_border_width(GTK_CONTAINER(scroller), 0);
1622 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
1623 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1624 gtk_container_add(GTK_CONTAINER(frame), PWidget(scroller));
1625 gtk_widget_show(PWidget(scroller));
1627 /* Tree and its model */
1628 GtkListStore *store =
1629 gtk_list_store_new(N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING);
1631 list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1632 g_signal_connect(G_OBJECT(list), "style-set", G_CALLBACK(StyleSet), NULL);
1634 #if GTK_CHECK_VERSION(3,0,0)
1635 GtkStyleContext *styleContext = gtk_widget_get_style_context(GTK_WIDGET(list));
1636 if (styleContext) {
1637 gtk_style_context_add_provider(styleContext, GTK_STYLE_PROVIDER(cssProvider),
1638 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1640 #endif
1642 GtkTreeSelection *selection =
1643 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1644 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1645 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
1646 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list), FALSE);
1648 /* Columns */
1649 GtkTreeViewColumn *column = gtk_tree_view_column_new();
1650 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1651 gtk_tree_view_column_set_title(column, "Autocomplete");
1653 pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
1654 gtk_cell_renderer_set_fixed_size(pixbuf_renderer, 0, -1);
1655 gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE);
1656 gtk_tree_view_column_add_attribute(column, pixbuf_renderer,
1657 "pixbuf", PIXBUF_COLUMN);
1659 renderer = gtk_cell_renderer_text_new();
1660 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1661 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1662 gtk_tree_view_column_add_attribute(column, renderer,
1663 "text", TEXT_COLUMN);
1665 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
1666 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list), "fixed-height-mode"))
1667 g_object_set(G_OBJECT(list), "fixed-height-mode", TRUE, NULL);
1669 GtkWidget *widget = PWidget(list); // No code inside the G_OBJECT macro
1670 gtk_container_add(GTK_CONTAINER(PWidget(scroller)), widget);
1671 gtk_widget_show(widget);
1672 g_signal_connect(G_OBJECT(widget), "button_press_event",
1673 G_CALLBACK(ButtonPress), this);
1676 void ListBoxX::SetFont(Font &scint_font) {
1677 // Only do for Pango font as there have been crashes for GDK fonts
1678 if (Created() && PFont(scint_font)->pfd) {
1679 // Current font is Pango font
1680 #if GTK_CHECK_VERSION(3,0,0)
1681 if (cssProvider) {
1682 PangoFontDescription *pfd = PFont(scint_font)->pfd;
1683 std::ostringstream ssFontSetting;
1684 ssFontSetting << "GtkTreeView { ";
1685 ssFontSetting << "font-family: " << pango_font_description_get_family(pfd) << "; ";
1686 ssFontSetting << "font-size:";
1687 ssFontSetting << static_cast<double>(pango_font_description_get_size(pfd)) / PANGO_SCALE;
1688 ssFontSetting << "px; ";
1689 ssFontSetting << "font-weight:"<< pango_font_description_get_weight(pfd) << "; ";
1690 ssFontSetting << "}";
1691 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(cssProvider),
1692 ssFontSetting.str().c_str(), -1, NULL);
1694 #else
1695 gtk_widget_modify_font(PWidget(list), PFont(scint_font)->pfd);
1696 #endif
1697 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), -1);
1698 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1702 void ListBoxX::SetAverageCharWidth(int width) {
1703 aveCharWidth = width;
1706 void ListBoxX::SetVisibleRows(int rows) {
1707 desiredVisibleRows = rows;
1710 int ListBoxX::GetVisibleRows() const {
1711 return desiredVisibleRows;
1714 int ListBoxX::GetRowHeight()
1716 #if GTK_CHECK_VERSION(3,0,0)
1717 // This version sometimes reports erroneous results on GTK2, but the GTK2
1718 // version is inaccurate for GTK 3.14.
1719 GdkRectangle rect;
1720 GtkTreePath *path = gtk_tree_path_new_first();
1721 gtk_tree_view_get_background_area(GTK_TREE_VIEW(list), path, NULL, &rect);
1722 return rect.height;
1723 #else
1724 int row_height=0;
1725 int vertical_separator=0;
1726 int expander_size=0;
1727 GtkTreeViewColumn *column = gtk_tree_view_get_column(GTK_TREE_VIEW(list), 0);
1728 gtk_tree_view_column_cell_get_size(column, NULL, NULL, NULL, NULL, &row_height);
1729 gtk_widget_style_get(PWidget(list),
1730 "vertical-separator", &vertical_separator,
1731 "expander-size", &expander_size, NULL);
1732 row_height += vertical_separator;
1733 row_height = Platform::Maximum(row_height, expander_size);
1734 return row_height;
1735 #endif
1738 PRectangle ListBoxX::GetDesiredRect() {
1739 // Before any size allocated pretend its 100 wide so not scrolled
1740 PRectangle rc(0, 0, 100, 100);
1741 if (wid) {
1742 int rows = Length();
1743 if ((rows == 0) || (rows > desiredVisibleRows))
1744 rows = desiredVisibleRows;
1746 GtkRequisition req;
1747 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1748 // returns reasonable values.
1749 #if GTK_CHECK_VERSION(3,0,0)
1750 gtk_widget_get_preferred_size(GTK_WIDGET(frame), NULL, &req);
1751 #else
1752 gtk_widget_size_request(GTK_WIDGET(frame), &req);
1753 #endif
1754 int height;
1756 // First calculate height of the clist for our desired visible
1757 // row count otherwise it tries to expand to the total # of rows
1758 // Get cell height
1759 int row_height = GetRowHeight();
1760 #if GTK_CHECK_VERSION(3,0,0)
1761 GtkStyleContext *styleContextFrame = gtk_widget_get_style_context(PWidget(frame));
1762 GtkBorder padding, border;
1763 gtk_style_context_get_padding(styleContextFrame, GTK_STATE_FLAG_NORMAL, &padding);
1764 gtk_style_context_get_border(styleContextFrame, GTK_STATE_FLAG_NORMAL, &border);
1765 height = (rows * row_height
1766 + padding.top + padding.bottom
1767 + border.top + border.bottom
1768 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
1769 #else
1770 height = (rows * row_height
1771 + 2 * (PWidget(frame)->style->ythickness
1772 + GTK_CONTAINER(PWidget(list))->border_width));
1773 #endif
1774 rc.bottom = height;
1776 int width = maxItemCharacters;
1777 if (width < 12)
1778 width = 12;
1779 rc.right = width * (aveCharWidth + aveCharWidth / 3);
1780 // Add horizontal padding and borders
1781 int horizontal_separator=0;
1782 gtk_widget_style_get(PWidget(list),
1783 "horizontal-separator", &horizontal_separator, NULL);
1784 rc.right += horizontal_separator;
1785 #if GTK_CHECK_VERSION(3,0,0)
1786 rc.right += (padding.left + padding.right
1787 + border.left + border.right
1788 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))));
1789 #else
1790 rc.right += 2 * (PWidget(frame)->style->xthickness
1791 + GTK_CONTAINER(PWidget(list))->border_width);
1792 #endif
1793 if (Length() > rows) {
1794 // Add the width of the scrollbar
1795 GtkWidget *vscrollbar =
1796 gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(scroller));
1797 #if GTK_CHECK_VERSION(3,0,0)
1798 gtk_widget_get_preferred_size(vscrollbar, NULL, &req);
1799 #else
1800 gtk_widget_size_request(vscrollbar, &req);
1801 #endif
1802 rc.right += req.width;
1805 return rc;
1808 int ListBoxX::CaretFromEdge() {
1809 gint renderer_width, renderer_height;
1810 gtk_cell_renderer_get_fixed_size(pixbuf_renderer, &renderer_width,
1811 &renderer_height);
1812 return 4 + renderer_width;
1815 void ListBoxX::Clear() {
1816 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1817 gtk_list_store_clear(GTK_LIST_STORE(model));
1818 maxItemCharacters = 0;
1821 static void init_pixmap(ListImage *list_image) {
1822 if (list_image->rgba_data) {
1823 // Drop any existing pixmap/bitmap as data may have changed
1824 if (list_image->pixbuf)
1825 g_object_unref(list_image->pixbuf);
1826 list_image->pixbuf =
1827 gdk_pixbuf_new_from_data(list_image->rgba_data->Pixels(),
1828 GDK_COLORSPACE_RGB,
1829 TRUE,
1831 list_image->rgba_data->GetWidth(),
1832 list_image->rgba_data->GetHeight(),
1833 list_image->rgba_data->GetWidth() * 4,
1834 NULL,
1835 NULL);
1839 #define SPACING 5
1841 void ListBoxX::Append(char *s, int type) {
1842 ListImage *list_image = NULL;
1843 if ((type >= 0) && pixhash) {
1844 list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash
1845 , (gconstpointer) GINT_TO_POINTER(type)));
1847 GtkTreeIter iter;
1848 GtkListStore *store =
1849 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
1850 gtk_list_store_append(GTK_LIST_STORE(store), &iter);
1851 if (list_image) {
1852 if (NULL == list_image->pixbuf)
1853 init_pixmap(list_image);
1854 if (list_image->pixbuf) {
1855 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1856 PIXBUF_COLUMN, list_image->pixbuf,
1857 TEXT_COLUMN, s, -1);
1859 gint pixbuf_width = gdk_pixbuf_get_width(list_image->pixbuf);
1860 gint renderer_height, renderer_width;
1861 gtk_cell_renderer_get_fixed_size(pixbuf_renderer,
1862 &renderer_width, &renderer_height);
1863 if (pixbuf_width > renderer_width)
1864 gtk_cell_renderer_set_fixed_size(pixbuf_renderer,
1865 pixbuf_width, -1);
1866 } else {
1867 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1868 TEXT_COLUMN, s, -1);
1870 } else {
1871 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1872 TEXT_COLUMN, s, -1);
1874 size_t len = strlen(s);
1875 if (maxItemCharacters < len)
1876 maxItemCharacters = len;
1879 int ListBoxX::Length() {
1880 if (wid)
1881 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1882 (GTK_TREE_VIEW(list)), NULL);
1883 return 0;
1886 void ListBoxX::Select(int n) {
1887 GtkTreeIter iter;
1888 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1889 GtkTreeSelection *selection =
1890 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1892 if (n < 0) {
1893 gtk_tree_selection_unselect_all(selection);
1894 return;
1897 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
1898 if (valid) {
1899 gtk_tree_selection_select_iter(selection, &iter);
1901 // Move the scrollbar to show the selection.
1902 int total = Length();
1903 #if GTK_CHECK_VERSION(3,0,0)
1904 GtkAdjustment *adj =
1905 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list));
1906 #else
1907 GtkAdjustment *adj =
1908 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list));
1909 #endif
1910 gfloat value = ((gfloat)n / total) * (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_lower(adj))
1911 + gtk_adjustment_get_lower(adj) - gtk_adjustment_get_page_size(adj) / 2;
1912 // Get cell height
1913 int row_height = GetRowHeight();
1915 int rows = Length();
1916 if ((rows == 0) || (rows > desiredVisibleRows))
1917 rows = desiredVisibleRows;
1918 if (rows & 0x1) {
1919 // Odd rows to display -- We are now in the middle.
1920 // Align it so that we don't chop off rows.
1921 value += (gfloat)row_height / 2.0;
1923 // Clamp it.
1924 value = (value < 0)? 0 : value;
1925 value = (value > (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)))?
1926 (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)) : value;
1928 // Set it.
1929 gtk_adjustment_set_value(adj, value);
1930 } else {
1931 gtk_tree_selection_unselect_all(selection);
1935 int ListBoxX::GetSelection() {
1936 int index = -1;
1937 GtkTreeIter iter;
1938 GtkTreeModel *model;
1939 GtkTreeSelection *selection;
1940 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1941 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1942 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1943 int *indices = gtk_tree_path_get_indices(path);
1944 // Don't free indices.
1945 if (indices)
1946 index = indices[0];
1947 gtk_tree_path_free(path);
1949 return index;
1952 int ListBoxX::Find(const char *prefix) {
1953 GtkTreeIter iter;
1954 GtkTreeModel *model =
1955 gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1956 bool valid = gtk_tree_model_get_iter_first(model, &iter) != FALSE;
1957 int i = 0;
1958 while(valid) {
1959 gchar *s;
1960 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &s, -1);
1961 if (s && (0 == strncmp(prefix, s, strlen(prefix)))) {
1962 g_free(s);
1963 return i;
1965 g_free(s);
1966 valid = gtk_tree_model_iter_next(model, &iter) != FALSE;
1967 i++;
1969 return -1;
1972 void ListBoxX::GetValue(int n, char *value, int len) {
1973 char *text = NULL;
1974 GtkTreeIter iter;
1975 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1976 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
1977 if (valid) {
1978 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &text, -1);
1980 if (text && len > 0) {
1981 g_strlcpy(value, text, len);
1982 } else {
1983 value[0] = '\0';
1985 g_free(text);
1988 // g_return_if_fail causes unnecessary compiler warning in release compile.
1989 #ifdef _MSC_VER
1990 #pragma warning(disable: 4127)
1991 #endif
1993 void ListBoxX::RegisterRGBA(int type, RGBAImage *image) {
1994 images.Add(type, image);
1996 if (!pixhash) {
1997 pixhash = g_hash_table_new(g_direct_hash, g_direct_equal);
1999 ListImage *list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash,
2000 (gconstpointer) GINT_TO_POINTER(type)));
2001 if (list_image) {
2002 // Drop icon already registered
2003 if (list_image->pixbuf)
2004 g_object_unref(list_image->pixbuf);
2005 list_image->pixbuf = NULL;
2006 list_image->rgba_data = image;
2007 } else {
2008 list_image = g_new0(ListImage, 1);
2009 list_image->rgba_data = image;
2010 g_hash_table_insert((GHashTable *) pixhash, GINT_TO_POINTER(type),
2011 (gpointer) list_image);
2015 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
2016 g_return_if_fail(xpm_data);
2017 XPM xpmImage(xpm_data);
2018 RegisterRGBA(type, new RGBAImage(xpmImage));
2021 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
2022 RegisterRGBA(type, new RGBAImage(width, height, 1.0, pixelsImage));
2025 void ListBoxX::ClearRegisteredImages() {
2026 images.Clear();
2029 void ListBoxX::SetList(const char *listText, char separator, char typesep) {
2030 Clear();
2031 int count = strlen(listText) + 1;
2032 std::vector<char> words(listText, listText+count);
2033 char *startword = &words[0];
2034 char *numword = NULL;
2035 int i = 0;
2036 for (; words[i]; i++) {
2037 if (words[i] == separator) {
2038 words[i] = '\0';
2039 if (numword)
2040 *numword = '\0';
2041 Append(startword, numword?atoi(numword + 1):-1);
2042 startword = &words[0] + i + 1;
2043 numword = NULL;
2044 } else if (words[i] == typesep) {
2045 numword = &words[0] + i;
2048 if (startword) {
2049 if (numword)
2050 *numword = '\0';
2051 Append(startword, numword?atoi(numword + 1):-1);
2055 Menu::Menu() : mid(0) {}
2057 void Menu::CreatePopUp() {
2058 Destroy();
2059 mid = gtk_menu_new();
2060 g_object_ref_sink(G_OBJECT(mid));
2063 void Menu::Destroy() {
2064 if (mid)
2065 g_object_unref(G_OBJECT(mid));
2066 mid = 0;
2069 static void MenuPositionFunc(GtkMenu *, gint *x, gint *y, gboolean *, gpointer userData) {
2070 sptr_t intFromPointer = GPOINTER_TO_INT(userData);
2071 *x = intFromPointer & 0xffff;
2072 *y = intFromPointer >> 16;
2075 void Menu::Show(Point pt, Window &) {
2076 int screenHeight = gdk_screen_height();
2077 int screenWidth = gdk_screen_width();
2078 GtkMenu *widget = static_cast<GtkMenu *>(mid);
2079 gtk_widget_show_all(GTK_WIDGET(widget));
2080 GtkRequisition requisition;
2081 #if GTK_CHECK_VERSION(3,0,0)
2082 gtk_widget_get_preferred_size(GTK_WIDGET(widget), NULL, &requisition);
2083 #else
2084 gtk_widget_size_request(GTK_WIDGET(widget), &requisition);
2085 #endif
2086 if ((pt.x + requisition.width) > screenWidth) {
2087 pt.x = screenWidth - requisition.width;
2089 if ((pt.y + requisition.height) > screenHeight) {
2090 pt.y = screenHeight - requisition.height;
2092 gtk_menu_popup(widget, NULL, NULL, MenuPositionFunc,
2093 GINT_TO_POINTER((static_cast<int>(pt.y) << 16) | static_cast<int>(pt.x)), 0,
2094 gtk_get_current_event_time());
2097 ElapsedTime::ElapsedTime() {
2098 GTimeVal curTime;
2099 g_get_current_time(&curTime);
2100 bigBit = curTime.tv_sec;
2101 littleBit = curTime.tv_usec;
2104 class DynamicLibraryImpl : public DynamicLibrary {
2105 protected:
2106 GModule* m;
2107 public:
2108 explicit DynamicLibraryImpl(const char *modulePath) {
2109 m = g_module_open(modulePath, G_MODULE_BIND_LAZY);
2112 virtual ~DynamicLibraryImpl() {
2113 if (m != NULL)
2114 g_module_close(m);
2117 // Use g_module_symbol to get a pointer to the relevant function.
2118 virtual Function FindFunction(const char *name) {
2119 if (m != NULL) {
2120 gpointer fn_address = NULL;
2121 gboolean status = g_module_symbol(m, name, &fn_address);
2122 if (status)
2123 return static_cast<Function>(fn_address);
2124 else
2125 return NULL;
2126 } else {
2127 return NULL;
2131 virtual bool IsValid() {
2132 return m != NULL;
2136 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
2137 return static_cast<DynamicLibrary *>( new DynamicLibraryImpl(modulePath) );
2140 double ElapsedTime::Duration(bool reset) {
2141 GTimeVal curTime;
2142 g_get_current_time(&curTime);
2143 long endBigBit = curTime.tv_sec;
2144 long endLittleBit = curTime.tv_usec;
2145 double result = 1000000.0 * (endBigBit - bigBit);
2146 result += endLittleBit - littleBit;
2147 result /= 1000000.0;
2148 if (reset) {
2149 bigBit = endBigBit;
2150 littleBit = endLittleBit;
2152 return result;
2155 ColourDesired Platform::Chrome() {
2156 return ColourDesired(0xe0, 0xe0, 0xe0);
2159 ColourDesired Platform::ChromeHighlight() {
2160 return ColourDesired(0xff, 0xff, 0xff);
2163 const char *Platform::DefaultFont() {
2164 #ifdef G_OS_WIN32
2165 return "Lucida Console";
2166 #else
2167 return "!Sans";
2168 #endif
2171 int Platform::DefaultFontSize() {
2172 #ifdef G_OS_WIN32
2173 return 10;
2174 #else
2175 return 12;
2176 #endif
2179 unsigned int Platform::DoubleClickTime() {
2180 return 500; // Half a second
2183 bool Platform::MouseButtonBounce() {
2184 return true;
2187 void Platform::DebugDisplay(const char *s) {
2188 fprintf(stderr, "%s", s);
2191 bool Platform::IsKeyDown(int) {
2192 // TODO: discover state of keys in GTK+/X
2193 return false;
2196 long Platform::SendScintilla(
2197 WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
2198 return scintilla_send_message(SCINTILLA(w), msg, wParam, lParam);
2201 long Platform::SendScintillaPointer(
2202 WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
2203 return scintilla_send_message(SCINTILLA(w), msg, wParam,
2204 reinterpret_cast<sptr_t>(lParam));
2207 bool Platform::IsDBCSLeadByte(int codePage, char ch) {
2208 // Byte ranges found in Wikipedia articles with relevant search strings in each case
2209 unsigned char uch = static_cast<unsigned char>(ch);
2210 switch (codePage) {
2211 case 932:
2212 // Shift_jis
2213 return ((uch >= 0x81) && (uch <= 0x9F)) ||
2214 ((uch >= 0xE0) && (uch <= 0xFC));
2215 // Lead bytes F0 to FC may be a Microsoft addition.
2216 case 936:
2217 // GBK
2218 return (uch >= 0x81) && (uch <= 0xFE);
2219 case 950:
2220 // Big5
2221 return (uch >= 0x81) && (uch <= 0xFE);
2222 // Korean EUC-KR may be code page 949.
2224 return false;
2227 int Platform::DBCSCharLength(int codePage, const char *s) {
2228 if (codePage == 932 || codePage == 936 || codePage == 950) {
2229 return IsDBCSLeadByte(codePage, s[0]) ? 2 : 1;
2230 } else {
2231 int bytes = mblen(s, MB_CUR_MAX);
2232 if (bytes >= 1)
2233 return bytes;
2234 else
2235 return 1;
2239 int Platform::DBCSCharMaxLength() {
2240 return MB_CUR_MAX;
2241 //return 2;
2244 // These are utility functions not really tied to a platform
2246 int Platform::Minimum(int a, int b) {
2247 if (a < b)
2248 return a;
2249 else
2250 return b;
2253 int Platform::Maximum(int a, int b) {
2254 if (a > b)
2255 return a;
2256 else
2257 return b;
2260 //#define TRACE
2262 #ifdef TRACE
2263 void Platform::DebugPrintf(const char *format, ...) {
2264 char buffer[2000];
2265 va_list pArguments;
2266 va_start(pArguments, format);
2267 vsprintf(buffer, format, pArguments);
2268 va_end(pArguments);
2269 Platform::DebugDisplay(buffer);
2271 #else
2272 void Platform::DebugPrintf(const char *, ...) {}
2274 #endif
2276 // Not supported for GTK+
2277 static bool assertionPopUps = true;
2279 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
2280 bool ret = assertionPopUps;
2281 assertionPopUps = assertionPopUps_;
2282 return ret;
2285 void Platform::Assert(const char *c, const char *file, int line) {
2286 char buffer[2000];
2287 g_snprintf(buffer, sizeof(buffer), "Assertion [%s] failed at %s %d\r\n", c, file, line);
2288 Platform::DebugDisplay(buffer);
2289 abort();
2292 int Platform::Clamp(int val, int minVal, int maxVal) {
2293 if (val > maxVal)
2294 val = maxVal;
2295 if (val < minVal)
2296 val = minVal;
2297 return val;
2300 void Platform_Initialise() {
2301 FontMutexAllocate();
2304 void Platform_Finalise() {
2305 FontCached::ReleaseAll();
2306 FontMutexFree();