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.
5
6 #include <stdlib.h>
7 #include <string.h>
8 #include <stdio.h>
9 #include <stddef.h>
10 #include <math.h>
11
12 #include <string>
13 #include <vector>
14 #include <map>
15
16 #include <glib.h>
17 #include <gmodule.h>
18 #include <gdk/gdk.h>
19 #include <gtk/gtk.h>
20 #include <gdk/gdkkeysyms.h>
21
22 #include "Platform.h"
23
24 #include "Scintilla.h"
25 #include "ScintillaWidget.h"
26 #include "StringCopy.h"
27 #include "XPM.h"
28 #include "UniConversion.h"
29
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
34
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)
41
42 #include "Converter.h"
43
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
49
50 static const double kPi = 3.14159265358979323846;
51
52 // The Pango version guard for pango_units_from_double and pango_units_to_double
53 // is more complex than simply implementing these here.
54
pangoUnitsFromDouble(double d)55 static int pangoUnitsFromDouble(double d) {
56 return static_cast<int>(d * PANGO_SCALE + 0.5);
57 }
58
doubleFromPangoUnits(int pu)59 static double doubleFromPangoUnits(int pu) {
60 return static_cast<double>(pu) / PANGO_SCALE;
61 }
62
CreateSimilarSurface(GdkWindow * window,cairo_content_t content,int width,int height)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;
68
69 g_return_val_if_fail(GDK_IS_WINDOW(window), NULL);
70
71 window_surface = GDK_DRAWABLE_GET_CLASS(window)->ref_cairo_surface(window);
72
73 surface = cairo_surface_create_similar(window_surface, content, width, height);
74
75 cairo_surface_destroy(window_surface);
76
77 return surface;
78 #endif
79 }
80
WindowFromWidget(GtkWidget * w)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
87 }
88
89 #ifdef _MSC_VER
90 // Ignore unreferenced local functions in GTK+ headers
91 #pragma warning(disable: 4505)
92 #endif
93
94 #ifdef SCI_NAMESPACE
95 using namespace Scintilla;
96 #endif
97
98 enum encodingType { singleByte, UTF8, dbcs};
99
100 struct LOGFONT {
101 int size;
102 int weight;
103 bool italic;
104 int characterSet;
105 char faceName[300];
106 };
107
108 #if USE_LOCK
109 static GMutex *fontMutex = NULL;
110
InitializeGLIBThreads()111 static void InitializeGLIBThreads() {
112 #if !GLIB_CHECK_VERSION(2,31,0)
113 if (!g_thread_supported()) {
114 g_thread_init(NULL);
115 }
116 #endif
117 }
118 #endif
119
FontMutexAllocate()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
130 }
131 #endif
132 }
133
FontMutexFree()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;
144 }
145 #endif
146 }
147
FontMutexLock()148 static void FontMutexLock() {
149 #if USE_LOCK
150 g_mutex_lock(fontMutex);
151 #endif
152 }
153
FontMutexUnlock()154 static void FontMutexUnlock() {
155 #if USE_LOCK
156 if (fontMutex) {
157 g_mutex_unlock(fontMutex);
158 }
159 #endif
160 }
161
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;
FontHandle()170 FontHandle() : et(singleByte), ascent(0), pfd(0), characterSet(-1) {
171 ResetWidths(et);
172 }
FontHandle(PangoFontDescription * pfd_,int characterSet_)173 FontHandle(PangoFontDescription *pfd_, int characterSet_) {
174 et = singleByte;
175 ascent = 0;
176 pfd = pfd_;
177 characterSet = characterSet_;
178 ResetWidths(et);
179 }
~FontHandle()180 ~FontHandle() {
181 if (pfd)
182 pango_font_description_free(pfd);
183 pfd = 0;
184 }
ResetWidths(encodingType et_)185 void ResetWidths(encodingType et_) {
186 et = et_;
187 for (int i=0; i<=127; i++) {
188 width[i] = 0;
189 }
190 }
CharWidth(unsigned char ch,encodingType et_) const191 XYPOSITION CharWidth(unsigned char ch, encodingType et_) const {
192 XYPOSITION w = 0;
193 FontMutexLock();
194 if ((ch <= 127) && (et == et_)) {
195 w = width[ch];
196 }
197 FontMutexUnlock();
198 return w;
199 }
SetCharWidth(unsigned char ch,XYPOSITION w,encodingType et_)200 void SetCharWidth(unsigned char ch, XYPOSITION w, encodingType et_) {
201 if (ch <= 127) {
202 FontMutexLock();
203 if (et != et_) {
204 ResetWidths(et_);
205 }
206 width[ch] = w;
207 FontMutexUnlock();
208 }
209 }
210 };
211
212 // X has a 16 bit coordinate space, so stop drawing here to avoid wrapping
213 static const int maxCoordinate = 32000;
214
PFont(Font & f)215 static FontHandle *PFont(Font &f) {
216 return reinterpret_cast<FontHandle *>(f.GetID());
217 }
218
PWidget(WindowID wid)219 static GtkWidget *PWidget(WindowID wid) {
220 return reinterpret_cast<GtkWidget *>(wid);
221 }
222
FromLong(long lpoint)223 Point Point::FromLong(long lpoint) {
224 return Point(
225 Platform::LowShortFromLong(lpoint),
226 Platform::HighShortFromLong(lpoint));
227 }
228
SetLogFont(LOGFONT & lf,const char * faceName,int characterSet,float size,int weight,bool italic)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);
236 }
237
238 /**
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.
242 */
HashFont(const FontParameters & fp)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];
250 }
251
252 class FontCached : Font {
253 FontCached *next;
254 int usage;
255 LOGFONT lf;
256 int hash;
257 explicit FontCached(const FontParameters &fp);
~FontCached()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();
267 };
268
269 FontCached *FontCached::first = 0;
270
FontCached(const FontParameters & fp)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;
277 }
278
SameAs(const FontParameters & fp)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);
286 }
287
Release()288 void FontCached::Release() {
289 if (fid)
290 delete PFont(*this);
291 fid = 0;
292 }
293
FindOrCreate(const FontParameters & fp)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;
303 }
304 }
305 if (ret == 0) {
306 FontCached *fc = new FontCached(fp);
307 fc->next = first;
308 first = fc;
309 ret = fc->fid;
310 }
311 FontMutexUnlock();
312 return ret;
313 }
314
ReleaseId(FontID fid_)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;
326 }
327 break;
328 }
329 pcur = &cur->next;
330 }
331 FontMutexUnlock();
332 }
333
ReleaseAll()334 void FontCached::ReleaseAll() {
335 while (first) {
336 ReleaseId(first->GetID());
337 }
338 }
339
CreateNewFont(const FontParameters & fp)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);
349 }
350
351 return new FontHandle();
352 }
353
Font()354 Font::Font() : fid(0) {}
355
~Font()356 Font::~Font() {}
357
Create(const FontParameters & fp)358 void Font::Create(const FontParameters &fp) {
359 Release();
360 fid = FontCached::FindOrCreate(fp);
361 }
362
Release()363 void Font::Release() {
364 if (fid)
365 FontCached::ReleaseId(fid);
366 fid = 0;
367 }
368
369 // Required on OS X
370 #ifdef SCI_NAMESPACE
371 namespace Scintilla {
372 #endif
373
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();
391
392 void Init(WindowID wid);
393 void Init(SurfaceID sid, WindowID wid);
394 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
395
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);
413
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_);
427
428 void SetClip(PRectangle rc);
429 void FlushCachedState();
430
431 void SetUnicodeMode(bool unicodeMode_);
432 void SetDBCSMode(int codePage);
433 };
434 #ifdef SCI_NAMESPACE
435 }
436 #endif
437
CharacterSetID(int characterSet)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 "";
484 }
485 }
486
SetConverter(int characterSet_)487 void SurfaceImpl::SetConverter(int characterSet_) {
488 if (characterSet != characterSet_) {
489 characterSet = characterSet_;
490 conv.Open("UTF-8", CharacterSetID(characterSet), false);
491 }
492 }
493
SurfaceImpl()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) {
499 }
500
~SurfaceImpl()501 SurfaceImpl::~SurfaceImpl() {
502 Release();
503 }
504
Release()505 void SurfaceImpl::Release() {
506 et = singleByte;
507 if (createdGC) {
508 createdGC = false;
509 cairo_destroy(context);
510 }
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;
527 }
528
Initialised()529 bool SurfaceImpl::Initialised() {
530 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 8, 0)
531 if (inited && context) {
532 if (cairo_status(context) == CAIRO_STATUS_SUCCESS) {
533 // Even when status is success, the target surface may have been
534 // finished whch may cause an assertion to fail crashing the application.
535 // The cairo_surface_has_show_text_glyphs call checks the finished flag
536 // and when set, sets the status to CAIRO_STATUS_SURFACE_FINISHED
537 // which leads to warning messages instead of crashes.
538 // Performing the check in this method as it is called rarely and has no
539 // other side effects.
540 cairo_surface_t *psurfContext = cairo_get_target(context);
541 if (psurfContext) {
542 cairo_surface_has_show_text_glyphs(psurfContext);
543 }
544 }
545 return cairo_status(context) == CAIRO_STATUS_SUCCESS;
546 }
547 #endif
548 return inited;
549 }
550
Init(WindowID wid)551 void SurfaceImpl::Init(WindowID wid) {
552 Release();
553 PLATFORM_ASSERT(wid);
554 // if we are only created from a window ID, we can't perform drawing
555 psurf = 0;
556 context = 0;
557 createdGC = false;
558 pcontext = gtk_widget_create_pango_context(PWidget(wid));
559 PLATFORM_ASSERT(pcontext);
560 layout = pango_layout_new(pcontext);
561 PLATFORM_ASSERT(layout);
562 inited = true;
563 }
564
Init(SurfaceID sid,WindowID wid)565 void SurfaceImpl::Init(SurfaceID sid, WindowID wid) {
566 PLATFORM_ASSERT(sid);
567 Release();
568 PLATFORM_ASSERT(wid);
569 context = cairo_reference(reinterpret_cast<cairo_t *>(sid));
570 pcontext = gtk_widget_create_pango_context(PWidget(wid));
571 // update the Pango context in case sid isn't the widget's surface
572 pango_cairo_update_context(context, pcontext);
573 layout = pango_layout_new(pcontext);
574 cairo_set_line_width(context, 1);
575 createdGC = true;
576 inited = true;
577 }
578
InitPixMap(int width,int height,Surface * surface_,WindowID wid)579 void SurfaceImpl::InitPixMap(int width, int height, Surface *surface_, WindowID wid) {
580 PLATFORM_ASSERT(surface_);
581 Release();
582 SurfaceImpl *surfImpl = static_cast<SurfaceImpl *>(surface_);
583 PLATFORM_ASSERT(wid);
584 context = cairo_reference(surfImpl->context);
585 pcontext = gtk_widget_create_pango_context(PWidget(wid));
586 // update the Pango context in case surface_ isn't the widget's surface
587 pango_cairo_update_context(context, pcontext);
588 PLATFORM_ASSERT(pcontext);
589 layout = pango_layout_new(pcontext);
590 PLATFORM_ASSERT(layout);
591 if (height > 0 && width > 0)
592 psurf = CreateSimilarSurface(
593 WindowFromWidget(PWidget(wid)),
594 CAIRO_CONTENT_COLOR_ALPHA, width, height);
595 cairo_destroy(context);
596 context = cairo_create(psurf);
597 cairo_rectangle(context, 0, 0, width, height);
598 cairo_set_source_rgb(context, 1.0, 0, 0);
599 cairo_fill(context);
600 // This produces sharp drawing more similar to GDK:
601 //cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
602 cairo_set_line_width(context, 1);
603 createdGC = true;
604 inited = true;
605 }
606
PenColour(ColourDesired fore)607 void SurfaceImpl::PenColour(ColourDesired fore) {
608 if (context) {
609 ColourDesired cdFore(fore.AsLong());
610 cairo_set_source_rgb(context,
611 cdFore.GetRed() / 255.0,
612 cdFore.GetGreen() / 255.0,
613 cdFore.GetBlue() / 255.0);
614 }
615 }
616
LogPixelsY()617 int SurfaceImpl::LogPixelsY() {
618 return 72;
619 }
620
DeviceHeightFont(int points)621 int SurfaceImpl::DeviceHeightFont(int points) {
622 int logPix = LogPixelsY();
623 return (points * logPix + logPix / 2) / 72;
624 }
625
MoveTo(int x_,int y_)626 void SurfaceImpl::MoveTo(int x_, int y_) {
627 x = x_;
628 y = y_;
629 }
630
Delta(int difference)631 static int Delta(int difference) {
632 if (difference < 0)
633 return -1;
634 else if (difference > 0)
635 return 1;
636 else
637 return 0;
638 }
639
LineTo(int x_,int y_)640 void SurfaceImpl::LineTo(int x_, int y_) {
641 // cairo_line_to draws the end position, unlike Win32 or GDK with GDK_CAP_NOT_LAST.
642 // For simple cases, move back one pixel from end.
643 if (context) {
644 int xDiff = x_ - x;
645 int xDelta = Delta(xDiff);
646 int yDiff = y_ - y;
647 int yDelta = Delta(yDiff);
648 if ((xDiff == 0) || (yDiff == 0)) {
649 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
650 int xEnd = x_ - xDelta;
651 int left = Platform::Minimum(x, xEnd);
652 int width = abs(x - xEnd) + 1;
653 int yEnd = y_ - yDelta;
654 int top = Platform::Minimum(y, yEnd);
655 int height = abs(y - yEnd) + 1;
656 cairo_rectangle(context, left, top, width, height);
657 cairo_fill(context);
658 } else if ((abs(xDiff) == abs(yDiff))) {
659 // 45 degree slope
660 cairo_move_to(context, x + 0.5, y + 0.5);
661 cairo_line_to(context, x_ + 0.5 - xDelta, y_ + 0.5 - yDelta);
662 } else {
663 // Line has a different slope so difficult to avoid last pixel
664 cairo_move_to(context, x + 0.5, y + 0.5);
665 cairo_line_to(context, x_ + 0.5, y_ + 0.5);
666 }
667 cairo_stroke(context);
668 }
669 x = x_;
670 y = y_;
671 }
672
Polygon(Point * pts,int npts,ColourDesired fore,ColourDesired back)673 void SurfaceImpl::Polygon(Point *pts, int npts, ColourDesired fore,
674 ColourDesired back) {
675 PLATFORM_ASSERT(context);
676 PenColour(back);
677 cairo_move_to(context, pts[0].x + 0.5, pts[0].y + 0.5);
678 for (int i = 1; i < npts; i++) {
679 cairo_line_to(context, pts[i].x + 0.5, pts[i].y + 0.5);
680 }
681 cairo_close_path(context);
682 cairo_fill_preserve(context);
683 PenColour(fore);
684 cairo_stroke(context);
685 }
686
RectangleDraw(PRectangle rc,ColourDesired fore,ColourDesired back)687 void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
688 if (context) {
689 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5,
690 rc.right - rc.left - 1, rc.bottom - rc.top - 1);
691 PenColour(back);
692 cairo_fill_preserve(context);
693 PenColour(fore);
694 cairo_stroke(context);
695 }
696 }
697
FillRectangle(PRectangle rc,ColourDesired back)698 void SurfaceImpl::FillRectangle(PRectangle rc, ColourDesired back) {
699 PenColour(back);
700 if (context && (rc.left < maxCoordinate)) { // Protect against out of range
701 rc.left = lround(rc.left);
702 rc.right = lround(rc.right);
703 cairo_rectangle(context, rc.left, rc.top,
704 rc.right - rc.left, rc.bottom - rc.top);
705 cairo_fill(context);
706 }
707 }
708
FillRectangle(PRectangle rc,Surface & surfacePattern)709 void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
710 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfacePattern);
711 bool canDraw = surfi.psurf;
712 if (canDraw) {
713 PLATFORM_ASSERT(context);
714 // Tile pattern over rectangle
715 // Currently assumes 8x8 pattern
716 int widthPat = 8;
717 int heightPat = 8;
718 for (int xTile = rc.left; xTile < rc.right; xTile += widthPat) {
719 int widthx = (xTile + widthPat > rc.right) ? rc.right - xTile : widthPat;
720 for (int yTile = rc.top; yTile < rc.bottom; yTile += heightPat) {
721 int heighty = (yTile + heightPat > rc.bottom) ? rc.bottom - yTile : heightPat;
722 cairo_set_source_surface(context, surfi.psurf, xTile, yTile);
723 cairo_rectangle(context, xTile, yTile, widthx, heighty);
724 cairo_fill(context);
725 }
726 }
727 } else {
728 // Something is wrong so try to show anyway
729 // Shows up black because colour not allocated
730 FillRectangle(rc, ColourDesired(0));
731 }
732 }
733
RoundedRectangle(PRectangle rc,ColourDesired fore,ColourDesired back)734 void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
735 if (((rc.right - rc.left) > 4) && ((rc.bottom - rc.top) > 4)) {
736 // Approximate a round rect with some cut off corners
737 Point pts[] = {
738 Point(rc.left + 2, rc.top),
739 Point(rc.right - 2, rc.top),
740 Point(rc.right, rc.top + 2),
741 Point(rc.right, rc.bottom - 2),
742 Point(rc.right - 2, rc.bottom),
743 Point(rc.left + 2, rc.bottom),
744 Point(rc.left, rc.bottom - 2),
745 Point(rc.left, rc.top + 2),
746 };
747 Polygon(pts, ELEMENTS(pts), fore, back);
748 } else {
749 RectangleDraw(rc, fore, back);
750 }
751 }
752
PathRoundRectangle(cairo_t * context,double left,double top,double width,double height,int radius)753 static void PathRoundRectangle(cairo_t *context, double left, double top, double width, double height, int radius) {
754 double degrees = kPi / 180.0;
755
756 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 2, 0)
757 cairo_new_sub_path(context);
758 #else
759 // First arc is in the top-right corner and starts from a point on the top line
760 cairo_move_to(context, left + width - radius, top);
761 #endif
762 cairo_arc(context, left + width - radius, top + radius, radius, -90 * degrees, 0 * degrees);
763 cairo_arc(context, left + width - radius, top + height - radius, radius, 0 * degrees, 90 * degrees);
764 cairo_arc(context, left + radius, top + height - radius, radius, 90 * degrees, 180 * degrees);
765 cairo_arc(context, left + radius, top + radius, radius, 180 * degrees, 270 * degrees);
766 cairo_close_path(context);
767 }
768
AlphaRectangle(PRectangle rc,int cornerSize,ColourDesired fill,int alphaFill,ColourDesired outline,int alphaOutline,int flags)769 void SurfaceImpl::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
770 ColourDesired outline, int alphaOutline, int flags) {
771 if (context && rc.Width() > 0) {
772 ColourDesired cdFill(fill.AsLong());
773 cairo_set_source_rgba(context,
774 cdFill.GetRed() / 255.0,
775 cdFill.GetGreen() / 255.0,
776 cdFill.GetBlue() / 255.0,
777 alphaFill / 255.0);
778 if (cornerSize > 0)
779 PathRoundRectangle(context, rc.left + 1.0, rc.top + 1.0, rc.right - rc.left - 2.0, rc.bottom - rc.top - 2.0, cornerSize);
780 else
781 cairo_rectangle(context, rc.left + 1.0, rc.top + 1.0, rc.right - rc.left - 2.0, rc.bottom - rc.top - 2.0);
782 cairo_fill(context);
783
784 ColourDesired cdOutline(outline.AsLong());
785 cairo_set_source_rgba(context,
786 cdOutline.GetRed() / 255.0,
787 cdOutline.GetGreen() / 255.0,
788 cdOutline.GetBlue() / 255.0,
789 alphaOutline / 255.0);
790 if (cornerSize > 0)
791 PathRoundRectangle(context, rc.left + 0.5, rc.top + 0.5, rc.right - rc.left - 1, rc.bottom - rc.top - 1, cornerSize);
792 else
793 cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5, rc.right - rc.left - 1, rc.bottom - rc.top - 1);
794 cairo_stroke(context);
795 }
796 }
797
DrawRGBAImage(PRectangle rc,int width,int height,const unsigned char * pixelsImage)798 void SurfaceImpl::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
799 PLATFORM_ASSERT(context);
800 if (rc.Width() > width)
801 rc.left += (rc.Width() - width) / 2;
802 rc.right = rc.left + width;
803 if (rc.Height() > height)
804 rc.top += (rc.Height() - height) / 2;
805 rc.bottom = rc.top + height;
806
807 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
808 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
809 #else
810 int stride = width * 4;
811 #endif
812 int ucs = stride * height;
813 std::vector<unsigned char> image(ucs);
814 for (int y=0; y<height; y++) {
815 for (int x=0; x<width; x++) {
816 unsigned char *pixel = &image[0] + y*stride + x * 4;
817 unsigned char alpha = pixelsImage[3];
818 pixel[2] = (*pixelsImage++) * alpha / 255;
819 pixel[1] = (*pixelsImage++) * alpha / 255;
820 pixel[0] = (*pixelsImage++) * alpha / 255;
821 pixel[3] = *pixelsImage++;
822 }
823 }
824
825 cairo_surface_t *psurf = cairo_image_surface_create_for_data(&image[0], CAIRO_FORMAT_ARGB32, width, height, stride);
826 cairo_set_source_surface(context, psurf, rc.left, rc.top);
827 cairo_rectangle(context, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
828 cairo_fill(context);
829
830 cairo_surface_destroy(psurf);
831 }
832
Ellipse(PRectangle rc,ColourDesired fore,ColourDesired back)833 void SurfaceImpl::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
834 PLATFORM_ASSERT(context);
835 PenColour(back);
836 cairo_arc(context, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2,
837 Platform::Minimum(rc.Width(), rc.Height()) / 2, 0, 2*kPi);
838 cairo_fill_preserve(context);
839 PenColour(fore);
840 cairo_stroke(context);
841 }
842
Copy(PRectangle rc,Point from,Surface & surfaceSource)843 void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
844 SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfaceSource);
845 bool canDraw = surfi.psurf;
846 if (canDraw) {
847 PLATFORM_ASSERT(context);
848 cairo_set_source_surface(context, surfi.psurf,
849 rc.left - from.x, rc.top - from.y);
850 cairo_rectangle(context, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
851 cairo_fill(context);
852 }
853 }
854
UTF8FromLatin1(const char * s,int len)855 std::string UTF8FromLatin1(const char *s, int len) {
856 std::string utfForm(len*2 + 1, '\0');
857 size_t lenU = 0;
858 for (int i=0; i<len; i++) {
859 unsigned int uch = static_cast<unsigned char>(s[i]);
860 if (uch < 0x80) {
861 utfForm[lenU++] = uch;
862 } else {
863 utfForm[lenU++] = static_cast<char>(0xC0 | (uch >> 6));
864 utfForm[lenU++] = static_cast<char>(0x80 | (uch & 0x3f));
865 }
866 }
867 utfForm.resize(lenU);
868 return utfForm;
869 }
870
UTF8FromIconv(const Converter & conv,const char * s,int len)871 static std::string UTF8FromIconv(const Converter &conv, const char *s, int len) {
872 if (conv) {
873 std::string utfForm(len*3+1, '\0');
874 char *pin = const_cast<char *>(s);
875 size_t inLeft = len;
876 char *putf = &utfForm[0];
877 char *pout = putf;
878 size_t outLeft = len*3+1;
879 size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
880 if (conversions != ((size_t)(-1))) {
881 *pout = '\0';
882 utfForm.resize(pout - putf);
883 return utfForm;
884 }
885 }
886 return std::string();
887 }
888
889 // Work out how many bytes are in a character by trying to convert using iconv,
890 // returning the first length that succeeds.
MultiByteLenFromIconv(const Converter & conv,const char * s,size_t len)891 static size_t MultiByteLenFromIconv(const Converter &conv, const char *s, size_t len) {
892 for (size_t lenMB=1; (lenMB<4) && (lenMB <= len); lenMB++) {
893 char wcForm[2];
894 char *pin = const_cast<char *>(s);
895 size_t inLeft = lenMB;
896 char *pout = wcForm;
897 size_t outLeft = 2;
898 size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
899 if (conversions != ((size_t)(-1))) {
900 return lenMB;
901 }
902 }
903 return 1;
904 }
905
DrawTextBase(PRectangle rc,Font & font_,XYPOSITION ybase,const char * s,int len,ColourDesired fore)906 void SurfaceImpl::DrawTextBase(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
907 ColourDesired fore) {
908 PenColour(fore);
909 if (context) {
910 XYPOSITION xText = rc.left;
911 if (PFont(font_)->pfd) {
912 std::string utfForm;
913 if (et == UTF8) {
914 pango_layout_set_text(layout, s, len);
915 } else {
916 SetConverter(PFont(font_)->characterSet);
917 utfForm = UTF8FromIconv(conv, s, len);
918 if (utfForm.empty()) { // iconv failed so treat as Latin1
919 utfForm = UTF8FromLatin1(s, len);
920 }
921 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
922 }
923 pango_layout_set_font_description(layout, PFont(font_)->pfd);
924 pango_cairo_update_layout(context, layout);
925 #ifdef PANGO_VERSION
926 PangoLayoutLine *pll = pango_layout_get_line_readonly(layout,0);
927 #else
928 PangoLayoutLine *pll = pango_layout_get_line(layout,0);
929 #endif
930 cairo_move_to(context, xText, ybase);
931 pango_cairo_show_layout_line(context, pll);
932 }
933 }
934 }
935
DrawTextNoClip(PRectangle rc,Font & font_,XYPOSITION ybase,const char * s,int len,ColourDesired fore,ColourDesired back)936 void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
937 ColourDesired fore, ColourDesired back) {
938 FillRectangle(rc, back);
939 DrawTextBase(rc, font_, ybase, s, len, fore);
940 }
941
942 // On GTK+, exactly same as DrawTextNoClip
DrawTextClipped(PRectangle rc,Font & font_,XYPOSITION ybase,const char * s,int len,ColourDesired fore,ColourDesired back)943 void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
944 ColourDesired fore, ColourDesired back) {
945 FillRectangle(rc, back);
946 DrawTextBase(rc, font_, ybase, s, len, fore);
947 }
948
DrawTextTransparent(PRectangle rc,Font & font_,XYPOSITION ybase,const char * s,int len,ColourDesired fore)949 void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
950 ColourDesired fore) {
951 // Avoid drawing spaces in transparent mode
952 for (int i=0; i<len; i++) {
953 if (s[i] != ' ') {
954 DrawTextBase(rc, font_, ybase, s, len, fore);
955 return;
956 }
957 }
958 }
959
960 class ClusterIterator {
961 PangoLayoutIter *iter;
962 PangoRectangle pos;
963 int lenPositions;
964 public:
965 bool finished;
966 XYPOSITION positionStart;
967 XYPOSITION position;
968 XYPOSITION distance;
969 int curIndex;
ClusterIterator(PangoLayout * layout,int len)970 ClusterIterator(PangoLayout *layout, int len) : lenPositions(len), finished(false),
971 positionStart(0), position(0), distance(0), curIndex(0) {
972 iter = pango_layout_get_iter(layout);
973 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
974 }
~ClusterIterator()975 ~ClusterIterator() {
976 pango_layout_iter_free(iter);
977 }
978
Next()979 void Next() {
980 positionStart = position;
981 if (pango_layout_iter_next_cluster(iter)) {
982 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
983 position = doubleFromPangoUnits(pos.x);
984 curIndex = pango_layout_iter_get_index(iter);
985 } else {
986 finished = true;
987 position = doubleFromPangoUnits(pos.x + pos.width);
988 curIndex = lenPositions;
989 }
990 distance = position - positionStart;
991 }
992 };
993
MeasureWidths(Font & font_,const char * s,int len,XYPOSITION * positions)994 void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
995 if (font_.GetID()) {
996 const int lenPositions = len;
997 if (PFont(font_)->pfd) {
998 if (len == 1) {
999 int width = PFont(font_)->CharWidth(*s, et);
1000 if (width) {
1001 positions[0] = width;
1002 return;
1003 }
1004 }
1005 pango_layout_set_font_description(layout, PFont(font_)->pfd);
1006 if (et == UTF8) {
1007 // Simple and direct as UTF-8 is native Pango encoding
1008 int i = 0;
1009 pango_layout_set_text(layout, s, len);
1010 ClusterIterator iti(layout, lenPositions);
1011 while (!iti.finished) {
1012 iti.Next();
1013 int places = iti.curIndex - i;
1014 while (i < iti.curIndex) {
1015 // Evenly distribute space among bytes of this cluster.
1016 // Would be better to find number of characters and then
1017 // divide evenly between characters with each byte of a character
1018 // being at the same position.
1019 positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places;
1020 i++;
1021 }
1022 }
1023 PLATFORM_ASSERT(i == lenPositions);
1024 } else {
1025 int positionsCalculated = 0;
1026 if (et == dbcs) {
1027 SetConverter(PFont(font_)->characterSet);
1028 std::string utfForm = UTF8FromIconv(conv, s, len);
1029 if (!utfForm.empty()) {
1030 // Convert to UTF-8 so can ask Pango for widths, then
1031 // Loop through UTF-8 and DBCS forms, taking account of different
1032 // character byte lengths.
1033 Converter convMeasure("UCS-2", CharacterSetID(characterSet), false);
1034 pango_layout_set_text(layout, utfForm.c_str(), strlen(utfForm.c_str()));
1035 int i = 0;
1036 int clusterStart = 0;
1037 ClusterIterator iti(layout, strlen(utfForm.c_str()));
1038 while (!iti.finished) {
1039 iti.Next();
1040 int clusterEnd = iti.curIndex;
1041 int places = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
1042 int place = 1;
1043 while (clusterStart < clusterEnd) {
1044 size_t lenChar = MultiByteLenFromIconv(convMeasure, s+i, len-i);
1045 while (lenChar--) {
1046 positions[i++] = iti.position - (places - place) * iti.distance / places;
1047 positionsCalculated++;
1048 }
1049 clusterStart += UTF8CharLength(static_cast<unsigned char>(utfForm.c_str()[clusterStart]));
1050 place++;
1051 }
1052 }
1053 PLATFORM_ASSERT(i == lenPositions);
1054 }
1055 }
1056 if (positionsCalculated < 1 ) {
1057 // Either 8-bit or DBCS conversion failed so treat as 8-bit.
1058 SetConverter(PFont(font_)->characterSet);
1059 const bool rtlCheck = PFont(font_)->characterSet == SC_CHARSET_HEBREW ||
1060 PFont(font_)->characterSet == SC_CHARSET_ARABIC;
1061 std::string utfForm = UTF8FromIconv(conv, s, len);
1062 if (utfForm.empty()) {
1063 utfForm = UTF8FromLatin1(s, len);
1064 }
1065 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
1066 int i = 0;
1067 int clusterStart = 0;
1068 // Each 8-bit input character may take 1 or 2 bytes in UTF-8
1069 // and groups of up to 3 may be represented as ligatures.
1070 ClusterIterator iti(layout, utfForm.length());
1071 while (!iti.finished) {
1072 iti.Next();
1073 int clusterEnd = iti.curIndex;
1074 int ligatureLength = g_utf8_strlen(utfForm.c_str() + clusterStart, clusterEnd - clusterStart);
1075 if (rtlCheck && ((clusterEnd <= clusterStart) || (ligatureLength == 0) || (ligatureLength > 3))) {
1076 // Something has gone wrong: exit quickly but pretend all the characters are equally spaced:
1077 int widthLayout = 0;
1078 pango_layout_get_size(layout, &widthLayout, NULL);
1079 XYPOSITION widthTotal = doubleFromPangoUnits(widthLayout);
1080 for (int bytePos=0; bytePos<lenPositions; bytePos++) {
1081 positions[bytePos] = widthTotal / lenPositions * (bytePos + 1);
1082 }
1083 return;
1084 }
1085 PLATFORM_ASSERT(ligatureLength > 0 && ligatureLength <= 3);
1086 for (int charInLig=0; charInLig<ligatureLength; charInLig++) {
1087 positions[i++] = iti.position - (ligatureLength - 1 - charInLig) * iti.distance / ligatureLength;
1088 }
1089 clusterStart = clusterEnd;
1090 }
1091 while (i < lenPositions) {
1092 // If something failed, fill in rest of the positions
1093 positions[i++] = clusterStart;
1094 }
1095 PLATFORM_ASSERT(i == lenPositions);
1096 }
1097 }
1098 if (len == 1) {
1099 PFont(font_)->SetCharWidth(*s, positions[0], et);
1100 }
1101 return;
1102 }
1103 } else {
1104 // No font so return an ascending range of values
1105 for (int i = 0; i < len; i++) {
1106 positions[i] = i + 1;
1107 }
1108 }
1109 }
1110
WidthText(Font & font_,const char * s,int len)1111 XYPOSITION SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
1112 if (font_.GetID()) {
1113 if (PFont(font_)->pfd) {
1114 std::string utfForm;
1115 pango_layout_set_font_description(layout, PFont(font_)->pfd);
1116 PangoRectangle pos;
1117 if (et == UTF8) {
1118 pango_layout_set_text(layout, s, len);
1119 } else {
1120 SetConverter(PFont(font_)->characterSet);
1121 utfForm = UTF8FromIconv(conv, s, len);
1122 if (utfForm.empty()) { // iconv failed so treat as Latin1
1123 utfForm = UTF8FromLatin1(s, len);
1124 }
1125 pango_layout_set_text(layout, utfForm.c_str(), utfForm.length());
1126 }
1127 #ifdef PANGO_VERSION
1128 PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout,0);
1129 #else
1130 PangoLayoutLine *pangoLine = pango_layout_get_line(layout,0);
1131 #endif
1132 pango_layout_line_get_extents(pangoLine, NULL, &pos);
1133 return doubleFromPangoUnits(pos.width);
1134 }
1135 return 1;
1136 } else {
1137 return 1;
1138 }
1139 }
1140
WidthChar(Font & font_,char ch)1141 XYPOSITION SurfaceImpl::WidthChar(Font &font_, char ch) {
1142 if (font_.GetID()) {
1143 if (PFont(font_)->pfd) {
1144 return WidthText(font_, &ch, 1);
1145 }
1146 return 1;
1147 } else {
1148 return 1;
1149 }
1150 }
1151
1152 // Ascent and descent determined by Pango font metrics.
1153
Ascent(Font & font_)1154 XYPOSITION SurfaceImpl::Ascent(Font &font_) {
1155 if (!(font_.GetID()))
1156 return 1;
1157 FontMutexLock();
1158 int ascent = PFont(font_)->ascent;
1159 if ((ascent == 0) && (PFont(font_)->pfd)) {
1160 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
1161 PFont(font_)->pfd, pango_context_get_language(pcontext));
1162 PFont(font_)->ascent =
1163 doubleFromPangoUnits(pango_font_metrics_get_ascent(metrics));
1164 pango_font_metrics_unref(metrics);
1165 ascent = PFont(font_)->ascent;
1166 }
1167 if (ascent == 0) {
1168 ascent = 1;
1169 }
1170 FontMutexUnlock();
1171 return ascent;
1172 }
1173
Descent(Font & font_)1174 XYPOSITION SurfaceImpl::Descent(Font &font_) {
1175 if (!(font_.GetID()))
1176 return 1;
1177 if (PFont(font_)->pfd) {
1178 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
1179 PFont(font_)->pfd, pango_context_get_language(pcontext));
1180 int descent = doubleFromPangoUnits(pango_font_metrics_get_descent(metrics));
1181 pango_font_metrics_unref(metrics);
1182 return descent;
1183 }
1184 return 0;
1185 }
1186
InternalLeading(Font &)1187 XYPOSITION SurfaceImpl::InternalLeading(Font &) {
1188 return 0;
1189 }
1190
ExternalLeading(Font &)1191 XYPOSITION SurfaceImpl::ExternalLeading(Font &) {
1192 return 0;
1193 }
1194
Height(Font & font_)1195 XYPOSITION SurfaceImpl::Height(Font &font_) {
1196 return Ascent(font_) + Descent(font_);
1197 }
1198
AverageCharWidth(Font & font_)1199 XYPOSITION SurfaceImpl::AverageCharWidth(Font &font_) {
1200 return WidthChar(font_, 'n');
1201 }
1202
SetClip(PRectangle rc)1203 void SurfaceImpl::SetClip(PRectangle rc) {
1204 PLATFORM_ASSERT(context);
1205 cairo_rectangle(context, rc.left, rc.top, rc.right, rc.bottom);
1206 cairo_clip(context);
1207 }
1208
FlushCachedState()1209 void SurfaceImpl::FlushCachedState() {}
1210
SetUnicodeMode(bool unicodeMode_)1211 void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
1212 if (unicodeMode_)
1213 et = UTF8;
1214 }
1215
SetDBCSMode(int codePage)1216 void SurfaceImpl::SetDBCSMode(int codePage) {
1217 if (codePage && (codePage != SC_CP_UTF8))
1218 et = dbcs;
1219 }
1220
Allocate(int)1221 Surface *Surface::Allocate(int) {
1222 return new SurfaceImpl();
1223 }
1224
~Window()1225 Window::~Window() {}
1226
Destroy()1227 void Window::Destroy() {
1228 if (wid)
1229 gtk_widget_destroy(GTK_WIDGET(wid));
1230 wid = 0;
1231 }
1232
HasFocus()1233 bool Window::HasFocus() {
1234 return IS_WIDGET_FOCUSSED(wid);
1235 }
1236
GetPosition()1237 PRectangle Window::GetPosition() {
1238 // Before any size allocated pretend its 1000 wide so not scrolled
1239 PRectangle rc(0, 0, 1000, 1000);
1240 if (wid) {
1241 GtkAllocation allocation;
1242 #if GTK_CHECK_VERSION(3,0,0)
1243 gtk_widget_get_allocation(PWidget(wid), &allocation);
1244 #else
1245 allocation = PWidget(wid)->allocation;
1246 #endif
1247 rc.left = allocation.x;
1248 rc.top = allocation.y;
1249 if (allocation.width > 20) {
1250 rc.right = rc.left + allocation.width;
1251 rc.bottom = rc.top + allocation.height;
1252 }
1253 }
1254 return rc;
1255 }
1256
SetPosition(PRectangle rc)1257 void Window::SetPosition(PRectangle rc) {
1258 GtkAllocation alloc;
1259 alloc.x = rc.left;
1260 alloc.y = rc.top;
1261 alloc.width = rc.Width();
1262 alloc.height = rc.Height();
1263 gtk_widget_size_allocate(PWidget(wid), &alloc);
1264 }
1265
SetPositionRelative(PRectangle rc,Window relativeTo)1266 void Window::SetPositionRelative(PRectangle rc, Window relativeTo) {
1267 int ox = 0;
1268 int oy = 0;
1269 gdk_window_get_origin(WindowFromWidget(PWidget(relativeTo.wid)), &ox, &oy);
1270 ox += rc.left;
1271 if (ox < 0)
1272 ox = 0;
1273 oy += rc.top;
1274 if (oy < 0)
1275 oy = 0;
1276
1277 /* do some corrections to fit into screen */
1278 int sizex = rc.right - rc.left;
1279 int sizey = rc.bottom - rc.top;
1280 int screenWidth = gdk_screen_width();
1281 int screenHeight = gdk_screen_height();
1282 if (sizex > screenWidth)
1283 ox = 0; /* the best we can do */
1284 else if (ox + sizex > screenWidth)
1285 ox = screenWidth - sizex;
1286 if (oy + sizey > screenHeight)
1287 oy = screenHeight - sizey;
1288
1289 gtk_window_move(GTK_WINDOW(PWidget(wid)), ox, oy);
1290
1291 gtk_widget_set_size_request(PWidget(wid), sizex, sizey);
1292 }
1293
GetClientPosition()1294 PRectangle Window::GetClientPosition() {
1295 // On GTK+, the client position is the window position
1296 return GetPosition();
1297 }
1298
Show(bool show)1299 void Window::Show(bool show) {
1300 if (show)
1301 gtk_widget_show(PWidget(wid));
1302 }
1303
InvalidateAll()1304 void Window::InvalidateAll() {
1305 if (wid) {
1306 gtk_widget_queue_draw(PWidget(wid));
1307 }
1308 }
1309
InvalidateRectangle(PRectangle rc)1310 void Window::InvalidateRectangle(PRectangle rc) {
1311 if (wid) {
1312 gtk_widget_queue_draw_area(PWidget(wid),
1313 rc.left, rc.top,
1314 rc.right - rc.left, rc.bottom - rc.top);
1315 }
1316 }
1317
SetFont(Font &)1318 void Window::SetFont(Font &) {
1319 // Can not be done generically but only needed for ListBox
1320 }
1321
SetCursor(Cursor curs)1322 void Window::SetCursor(Cursor curs) {
1323 // We don't set the cursor to same value numerous times under gtk because
1324 // it stores the cursor in the window once it's set
1325 if (curs == cursorLast)
1326 return;
1327
1328 cursorLast = curs;
1329 GdkCursor *gdkCurs;
1330 switch (curs) {
1331 case cursorText:
1332 gdkCurs = gdk_cursor_new(GDK_XTERM);
1333 break;
1334 case cursorArrow:
1335 gdkCurs = gdk_cursor_new(GDK_LEFT_PTR);
1336 break;
1337 case cursorUp:
1338 gdkCurs = gdk_cursor_new(GDK_CENTER_PTR);
1339 break;
1340 case cursorWait:
1341 gdkCurs = gdk_cursor_new(GDK_WATCH);
1342 break;
1343 case cursorHand:
1344 gdkCurs = gdk_cursor_new(GDK_HAND2);
1345 break;
1346 case cursorReverseArrow:
1347 gdkCurs = gdk_cursor_new(GDK_RIGHT_PTR);
1348 break;
1349 default:
1350 gdkCurs = gdk_cursor_new(GDK_LEFT_PTR);
1351 cursorLast = cursorArrow;
1352 break;
1353 }
1354
1355 if (WindowFromWidget(PWidget(wid)))
1356 gdk_window_set_cursor(WindowFromWidget(PWidget(wid)), gdkCurs);
1357 #if GTK_CHECK_VERSION(3,0,0)
1358 g_object_unref(gdkCurs);
1359 #else
1360 gdk_cursor_unref(gdkCurs);
1361 #endif
1362 }
1363
SetTitle(const char * s)1364 void Window::SetTitle(const char *s) {
1365 gtk_window_set_title(GTK_WINDOW(wid), s);
1366 }
1367
1368 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1369 gdk window coordinates */
GetMonitorRect(Point pt)1370 PRectangle Window::GetMonitorRect(Point pt) {
1371 gint x_offset, y_offset;
1372
1373 gdk_window_get_origin(WindowFromWidget(PWidget(wid)), &x_offset, &y_offset);
1374
1375 GdkScreen* screen;
1376 gint monitor_num;
1377 GdkRectangle rect;
1378
1379 screen = gtk_widget_get_screen(PWidget(wid));
1380 monitor_num = gdk_screen_get_monitor_at_point(screen, pt.x + x_offset, pt.y + y_offset);
1381 gdk_screen_get_monitor_geometry(screen, monitor_num, &rect);
1382 rect.x -= x_offset;
1383 rect.y -= y_offset;
1384 return PRectangle(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
1385 }
1386
1387 typedef std::map<int, RGBAImage*> ImageMap;
1388
1389 struct ListImage {
1390 const RGBAImage *rgba_data;
1391 GdkPixbuf *pixbuf;
1392 };
1393
list_image_free(gpointer,gpointer value,gpointer)1394 static void list_image_free(gpointer, gpointer value, gpointer) {
1395 ListImage *list_image = static_cast<ListImage *>(value);
1396 if (list_image->pixbuf)
1397 g_object_unref(list_image->pixbuf);
1398 g_free(list_image);
1399 }
1400
ListBox()1401 ListBox::ListBox() {
1402 }
1403
~ListBox()1404 ListBox::~ListBox() {
1405 }
1406
1407 enum {
1408 PIXBUF_COLUMN,
1409 TEXT_COLUMN,
1410 N_COLUMNS
1411 };
1412
1413 class ListBoxX : public ListBox {
1414 WindowID list;
1415 WindowID scroller;
1416 void *pixhash;
1417 GtkCellRenderer* pixbuf_renderer;
1418 RGBAImageSet images;
1419 int desiredVisibleRows;
1420 unsigned int maxItemCharacters;
1421 unsigned int aveCharWidth;
1422 public:
1423 CallBackAction doubleClickAction;
1424 void *doubleClickActionData;
1425
ListBoxX()1426 ListBoxX() : list(0), scroller(0), pixhash(NULL), pixbuf_renderer(0),
1427 desiredVisibleRows(5), maxItemCharacters(0),
1428 aveCharWidth(1), doubleClickAction(NULL), doubleClickActionData(NULL) {
1429 }
~ListBoxX()1430 virtual ~ListBoxX() {
1431 if (pixhash) {
1432 g_hash_table_foreach((GHashTable *) pixhash, list_image_free, NULL);
1433 g_hash_table_destroy((GHashTable *) pixhash);
1434 }
1435 }
1436 virtual void SetFont(Font &font);
1437 virtual void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_);
1438 virtual void SetAverageCharWidth(int width);
1439 virtual void SetVisibleRows(int rows);
1440 virtual int GetVisibleRows() const;
1441 virtual PRectangle GetDesiredRect();
1442 virtual int CaretFromEdge();
1443 virtual void Clear();
1444 virtual void Append(char *s, int type = -1);
1445 virtual int Length();
1446 virtual void Select(int n);
1447 virtual int GetSelection();
1448 virtual int Find(const char *prefix);
1449 virtual void GetValue(int n, char *value, int len);
1450 void RegisterRGBA(int type, RGBAImage *image);
1451 virtual void RegisterImage(int type, const char *xpm_data);
1452 virtual void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage);
1453 virtual void ClearRegisteredImages();
SetDoubleClickAction(CallBackAction action,void * data)1454 virtual void SetDoubleClickAction(CallBackAction action, void *data) {
1455 doubleClickAction = action;
1456 doubleClickActionData = data;
1457 }
1458 virtual void SetList(const char *listText, char separator, char typesep);
1459 };
1460
Allocate()1461 ListBox *ListBox::Allocate() {
1462 ListBoxX *lb = new ListBoxX();
1463 return lb;
1464 }
1465
ButtonPress(GtkWidget *,GdkEventButton * ev,gpointer p)1466 static gboolean ButtonPress(GtkWidget *, GdkEventButton* ev, gpointer p) {
1467 try {
1468 ListBoxX* lb = reinterpret_cast<ListBoxX*>(p);
1469 if (ev->type == GDK_2BUTTON_PRESS && lb->doubleClickAction != NULL) {
1470 lb->doubleClickAction(lb->doubleClickActionData);
1471 return TRUE;
1472 }
1473
1474 } catch (...) {
1475 // No pointer back to Scintilla to save status
1476 }
1477 return FALSE;
1478 }
1479
1480 /* Change the active color to the selected color so the listbox uses the color
1481 scheme that it would use if it had the focus. */
StyleSet(GtkWidget * w,GtkStyle *,void *)1482 static void StyleSet(GtkWidget *w, GtkStyle*, void*) {
1483
1484 g_return_if_fail(w != NULL);
1485
1486 /* Copy the selected color to active. Note that the modify calls will cause
1487 recursive calls to this function after the value is updated and w->style to
1488 be set to a new object */
1489
1490 #if GTK_CHECK_VERSION(3,0,0)
1491 GtkStyleContext *styleContext = gtk_widget_get_style_context(w);
1492 if (styleContext == NULL)
1493 return;
1494
1495 GdkRGBA colourForeSelected;
1496 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourForeSelected);
1497 GdkRGBA colourForeActive;
1498 gtk_style_context_get_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourForeActive);
1499 if (!gdk_rgba_equal(&colourForeSelected, &colourForeActive))
1500 gtk_widget_override_color(w, GTK_STATE_FLAG_ACTIVE, &colourForeSelected);
1501
1502 styleContext = gtk_widget_get_style_context(w);
1503 if (styleContext == NULL)
1504 return;
1505
1506 GdkRGBA colourBaseSelected;
1507 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_SELECTED, &colourBaseSelected);
1508 GdkRGBA colourBaseActive;
1509 gtk_style_context_get_background_color(styleContext, GTK_STATE_FLAG_ACTIVE, &colourBaseActive);
1510 if (!gdk_rgba_equal(&colourBaseSelected, &colourBaseActive))
1511 gtk_widget_override_background_color(w, GTK_STATE_FLAG_ACTIVE, &colourBaseSelected);
1512 #else
1513 GtkStyle *style = gtk_widget_get_style(w);
1514 if (style == NULL)
1515 return;
1516 if (!gdk_color_equal(&style->base[GTK_STATE_SELECTED], &style->base[GTK_STATE_ACTIVE]))
1517 gtk_widget_modify_base(w, GTK_STATE_ACTIVE, &style->base[GTK_STATE_SELECTED]);
1518 style = gtk_widget_get_style(w);
1519 if (style == NULL)
1520 return;
1521 if (!gdk_color_equal(&style->text[GTK_STATE_SELECTED], &style->text[GTK_STATE_ACTIVE]))
1522 gtk_widget_modify_text(w, GTK_STATE_ACTIVE, &style->text[GTK_STATE_SELECTED]);
1523 #endif
1524 }
1525
Create(Window &,int,Point,int,bool,int)1526 void ListBoxX::Create(Window &, int, Point, int, bool, int) {
1527 wid = gtk_window_new(GTK_WINDOW_POPUP);
1528
1529 GtkWidget *frame = gtk_frame_new(NULL);
1530 gtk_widget_show(frame);
1531 gtk_container_add(GTK_CONTAINER(GetID()), frame);
1532 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1533 gtk_container_set_border_width(GTK_CONTAINER(frame), 0);
1534
1535 scroller = gtk_scrolled_window_new(NULL, NULL);
1536 gtk_container_set_border_width(GTK_CONTAINER(scroller), 0);
1537 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
1538 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1539 gtk_container_add(GTK_CONTAINER(frame), PWidget(scroller));
1540 gtk_widget_show(PWidget(scroller));
1541
1542 /* Tree and its model */
1543 GtkListStore *store =
1544 gtk_list_store_new(N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING);
1545
1546 list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1547 g_signal_connect(G_OBJECT(list), "style-set", G_CALLBACK(StyleSet), NULL);
1548
1549 GtkTreeSelection *selection =
1550 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1551 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1552 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
1553 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list), FALSE);
1554
1555 /* Columns */
1556 GtkTreeViewColumn *column = gtk_tree_view_column_new();
1557 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1558 gtk_tree_view_column_set_title(column, "Autocomplete");
1559
1560 pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
1561 gtk_cell_renderer_set_fixed_size(pixbuf_renderer, 0, -1);
1562 gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE);
1563 gtk_tree_view_column_add_attribute(column, pixbuf_renderer,
1564 "pixbuf", PIXBUF_COLUMN);
1565
1566 GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
1567 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
1568 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1569 gtk_tree_view_column_add_attribute(column, renderer,
1570 "text", TEXT_COLUMN);
1571
1572 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
1573 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list), "fixed-height-mode"))
1574 g_object_set(G_OBJECT(list), "fixed-height-mode", TRUE, NULL);
1575
1576 GtkWidget *wid = PWidget(list); // No code inside the G_OBJECT macro
1577 gtk_container_add(GTK_CONTAINER(PWidget(scroller)), wid);
1578 gtk_widget_show(wid);
1579 g_signal_connect(G_OBJECT(wid), "button_press_event",
1580 G_CALLBACK(ButtonPress), this);
1581 gtk_widget_realize(PWidget(wid));
1582 }
1583
SetFont(Font & scint_font)1584 void ListBoxX::SetFont(Font &scint_font) {
1585 // Only do for Pango font as there have been crashes for GDK fonts
1586 if (Created() && PFont(scint_font)->pfd) {
1587 // Current font is Pango font
1588 #if GTK_CHECK_VERSION(3,0,0)
1589 gtk_widget_override_font(PWidget(list), PFont(scint_font)->pfd);
1590 #else
1591 gtk_widget_modify_font(PWidget(list), PFont(scint_font)->pfd);
1592 #endif
1593 }
1594 }
1595
SetAverageCharWidth(int width)1596 void ListBoxX::SetAverageCharWidth(int width) {
1597 aveCharWidth = width;
1598 }
1599
SetVisibleRows(int rows)1600 void ListBoxX::SetVisibleRows(int rows) {
1601 desiredVisibleRows = rows;
1602 }
1603
GetVisibleRows() const1604 int ListBoxX::GetVisibleRows() const {
1605 return desiredVisibleRows;
1606 }
1607
GetDesiredRect()1608 PRectangle ListBoxX::GetDesiredRect() {
1609 // Before any size allocated pretend its 100 wide so not scrolled
1610 PRectangle rc(0, 0, 100, 100);
1611 if (wid) {
1612 int rows = Length();
1613 if ((rows == 0) || (rows > desiredVisibleRows))
1614 rows = desiredVisibleRows;
1615
1616 GtkRequisition req;
1617 #if GTK_CHECK_VERSION(3,0,0)
1618 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1619 // returns reasonable values.
1620 gtk_widget_get_preferred_size(GTK_WIDGET(scroller), NULL, &req);
1621 #endif
1622 int height;
1623
1624 // First calculate height of the clist for our desired visible
1625 // row count otherwise it tries to expand to the total # of rows
1626 // Get cell height
1627 int row_width=0;
1628 int row_height=0;
1629 GtkTreeViewColumn * column =
1630 gtk_tree_view_get_column(GTK_TREE_VIEW(list), 0);
1631 gtk_tree_view_column_cell_get_size(column, NULL,
1632 NULL, NULL, &row_width, &row_height);
1633 #if GTK_CHECK_VERSION(3,0,0)
1634 GtkStyleContext *styleContextList = gtk_widget_get_style_context(PWidget(list));
1635 GtkBorder padding;
1636 gtk_style_context_get_padding(styleContextList, GTK_STATE_FLAG_NORMAL, &padding);
1637 height = (rows * row_height
1638 + padding.top + padding.bottom
1639 + 2 * (gtk_container_get_border_width(GTK_CONTAINER(PWidget(list))) + 1));
1640 #else
1641 int ythickness = PWidget(list)->style->ythickness;
1642 height = (rows * row_height
1643 + 2 * (ythickness
1644 + GTK_CONTAINER(PWidget(list))->border_width + 1));
1645 #endif
1646 gtk_widget_set_size_request(GTK_WIDGET(PWidget(list)), -1, height);
1647
1648 // Get the size of the scroller because we set usize on the window
1649 #if GTK_CHECK_VERSION(3,0,0)
1650 gtk_widget_get_preferred_size(GTK_WIDGET(scroller), NULL, &req);
1651 #else
1652 gtk_widget_size_request(GTK_WIDGET(scroller), &req);
1653 #endif
1654 rc.right = req.width;
1655 rc.bottom = Platform::Maximum(height, req.height);
1656
1657 gtk_widget_set_size_request(GTK_WIDGET(list), -1, -1);
1658 int width = maxItemCharacters;
1659 if (width < 12)
1660 width = 12;
1661 rc.right = width * (aveCharWidth + aveCharWidth / 3);
1662 if (Length() > rows)
1663 rc.right = rc.right + 16;
1664 }
1665 return rc;
1666 }
1667
CaretFromEdge()1668 int ListBoxX::CaretFromEdge() {
1669 gint renderer_width, renderer_height;
1670 gtk_cell_renderer_get_fixed_size(pixbuf_renderer, &renderer_width,
1671 &renderer_height);
1672 return 4 + renderer_width;
1673 }
1674
Clear()1675 void ListBoxX::Clear() {
1676 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1677 gtk_list_store_clear(GTK_LIST_STORE(model));
1678 maxItemCharacters = 0;
1679 }
1680
init_pixmap(ListImage * list_image)1681 static void init_pixmap(ListImage *list_image) {
1682 if (list_image->rgba_data) {
1683 // Drop any existing pixmap/bitmap as data may have changed
1684 if (list_image->pixbuf)
1685 g_object_unref(list_image->pixbuf);
1686 list_image->pixbuf =
1687 gdk_pixbuf_new_from_data(list_image->rgba_data->Pixels(),
1688 GDK_COLORSPACE_RGB,
1689 TRUE,
1690 8,
1691 list_image->rgba_data->GetWidth(),
1692 list_image->rgba_data->GetHeight(),
1693 list_image->rgba_data->GetWidth() * 4,
1694 NULL,
1695 NULL);
1696 }
1697 }
1698
1699 #define SPACING 5
1700
Append(char * s,int type)1701 void ListBoxX::Append(char *s, int type) {
1702 ListImage *list_image = NULL;
1703 if ((type >= 0) && pixhash) {
1704 list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash
1705 , (gconstpointer) GINT_TO_POINTER(type)));
1706 }
1707 GtkTreeIter iter;
1708 GtkListStore *store =
1709 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
1710 gtk_list_store_append(GTK_LIST_STORE(store), &iter);
1711 if (list_image) {
1712 if (NULL == list_image->pixbuf)
1713 init_pixmap(list_image);
1714 if (list_image->pixbuf) {
1715 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1716 PIXBUF_COLUMN, list_image->pixbuf,
1717 TEXT_COLUMN, s, -1);
1718
1719 gint pixbuf_width = gdk_pixbuf_get_width(list_image->pixbuf);
1720 gint renderer_height, renderer_width;
1721 gtk_cell_renderer_get_fixed_size(pixbuf_renderer,
1722 &renderer_width, &renderer_height);
1723 if (pixbuf_width > renderer_width)
1724 gtk_cell_renderer_set_fixed_size(pixbuf_renderer,
1725 pixbuf_width, -1);
1726 } else {
1727 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1728 TEXT_COLUMN, s, -1);
1729 }
1730 } else {
1731 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1732 TEXT_COLUMN, s, -1);
1733 }
1734 size_t len = strlen(s);
1735 if (maxItemCharacters < len)
1736 maxItemCharacters = len;
1737 }
1738
Length()1739 int ListBoxX::Length() {
1740 if (wid)
1741 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1742 (GTK_TREE_VIEW(list)), NULL);
1743 return 0;
1744 }
1745
Select(int n)1746 void ListBoxX::Select(int n) {
1747 GtkTreeIter iter;
1748 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1749 GtkTreeSelection *selection =
1750 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1751
1752 if (n < 0) {
1753 gtk_tree_selection_unselect_all(selection);
1754 return;
1755 }
1756
1757 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
1758 if (valid) {
1759 gtk_tree_selection_select_iter(selection, &iter);
1760
1761 // Move the scrollbar to show the selection.
1762 int total = Length();
1763 #if GTK_CHECK_VERSION(3,0,0)
1764 GtkAdjustment *adj =
1765 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list));
1766 gfloat value = ((gfloat)n / total) * (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_lower(adj))
1767 + gtk_adjustment_get_lower(adj) - gtk_adjustment_get_page_size(adj) / 2;
1768 #else
1769 GtkAdjustment *adj =
1770 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list));
1771 gfloat value = ((gfloat)n / total) * (adj->upper - adj->lower)
1772 + adj->lower - adj->page_size / 2;
1773 #endif
1774 // Get cell height
1775 int row_width;
1776 int row_height;
1777 GtkTreeViewColumn * column =
1778 gtk_tree_view_get_column(GTK_TREE_VIEW(list), 0);
1779 gtk_tree_view_column_cell_get_size(column, NULL, NULL,
1780 NULL, &row_width, &row_height);
1781
1782 int rows = Length();
1783 if ((rows == 0) || (rows > desiredVisibleRows))
1784 rows = desiredVisibleRows;
1785 if (rows & 0x1) {
1786 // Odd rows to display -- We are now in the middle.
1787 // Align it so that we don't chop off rows.
1788 value += (gfloat)row_height / 2.0;
1789 }
1790 // Clamp it.
1791 value = (value < 0)? 0 : value;
1792 #if GTK_CHECK_VERSION(3,0,0)
1793 value = (value > (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)))?
1794 (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj)) : value;
1795 #else
1796 value = (value > (adj->upper - adj->page_size))?
1797 (adj->upper - adj->page_size) : value;
1798 #endif
1799
1800 // Set it.
1801 gtk_adjustment_set_value(adj, value);
1802 } else {
1803 gtk_tree_selection_unselect_all(selection);
1804 }
1805 }
1806
GetSelection()1807 int ListBoxX::GetSelection() {
1808 int index = -1;
1809 GtkTreeIter iter;
1810 GtkTreeModel *model;
1811 GtkTreeSelection *selection;
1812 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1813 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1814 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1815 int *indices = gtk_tree_path_get_indices(path);
1816 // Don't free indices.
1817 if (indices)
1818 index = indices[0];
1819 gtk_tree_path_free(path);
1820 }
1821 return index;
1822 }
1823
Find(const char * prefix)1824 int ListBoxX::Find(const char *prefix) {
1825 GtkTreeIter iter;
1826 GtkTreeModel *model =
1827 gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1828 bool valid = gtk_tree_model_get_iter_first(model, &iter) != FALSE;
1829 int i = 0;
1830 while(valid) {
1831 gchar *s;
1832 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &s, -1);
1833 if (s && (0 == strncmp(prefix, s, strlen(prefix)))) {
1834 g_free(s);
1835 return i;
1836 }
1837 g_free(s);
1838 valid = gtk_tree_model_iter_next(model, &iter) != FALSE;
1839 i++;
1840 }
1841 return -1;
1842 }
1843
GetValue(int n,char * value,int len)1844 void ListBoxX::GetValue(int n, char *value, int len) {
1845 char *text = NULL;
1846 GtkTreeIter iter;
1847 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
1848 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
1849 if (valid) {
1850 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &text, -1);
1851 }
1852 if (text && len > 0) {
1853 g_strlcpy(value, text, len);
1854 } else {
1855 value[0] = '\0';
1856 }
1857 g_free(text);
1858 }
1859
1860 // g_return_if_fail causes unnecessary compiler warning in release compile.
1861 #ifdef _MSC_VER
1862 #pragma warning(disable: 4127)
1863 #endif
1864
RegisterRGBA(int type,RGBAImage * image)1865 void ListBoxX::RegisterRGBA(int type, RGBAImage *image) {
1866 images.Add(type, image);
1867
1868 if (!pixhash) {
1869 pixhash = g_hash_table_new(g_direct_hash, g_direct_equal);
1870 }
1871 ListImage *list_image = static_cast<ListImage *>(g_hash_table_lookup((GHashTable *) pixhash,
1872 (gconstpointer) GINT_TO_POINTER(type)));
1873 if (list_image) {
1874 // Drop icon already registered
1875 if (list_image->pixbuf)
1876 g_object_unref(list_image->pixbuf);
1877 list_image->pixbuf = NULL;
1878 list_image->rgba_data = image;
1879 } else {
1880 list_image = g_new0(ListImage, 1);
1881 list_image->rgba_data = image;
1882 g_hash_table_insert((GHashTable *) pixhash, GINT_TO_POINTER(type),
1883 (gpointer) list_image);
1884 }
1885 }
1886
RegisterImage(int type,const char * xpm_data)1887 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
1888 g_return_if_fail(xpm_data);
1889 XPM xpmImage(xpm_data);
1890 RegisterRGBA(type, new RGBAImage(xpmImage));
1891 }
1892
RegisterRGBAImage(int type,int width,int height,const unsigned char * pixelsImage)1893 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
1894 RegisterRGBA(type, new RGBAImage(width, height, 1.0, pixelsImage));
1895 }
1896
ClearRegisteredImages()1897 void ListBoxX::ClearRegisteredImages() {
1898 images.Clear();
1899 }
1900
SetList(const char * listText,char separator,char typesep)1901 void ListBoxX::SetList(const char *listText, char separator, char typesep) {
1902 Clear();
1903 int count = strlen(listText) + 1;
1904 std::vector<char> words(listText, listText+count);
1905 char *startword = &words[0];
1906 char *numword = NULL;
1907 int i = 0;
1908 for (; words[i]; i++) {
1909 if (words[i] == separator) {
1910 words[i] = '\0';
1911 if (numword)
1912 *numword = '\0';
1913 Append(startword, numword?atoi(numword + 1):-1);
1914 startword = &words[0] + i + 1;
1915 numword = NULL;
1916 } else if (words[i] == typesep) {
1917 numword = &words[0] + i;
1918 }
1919 }
1920 if (startword) {
1921 if (numword)
1922 *numword = '\0';
1923 Append(startword, numword?atoi(numword + 1):-1);
1924 }
1925 }
1926
Menu()1927 Menu::Menu() : mid(0) {}
1928
CreatePopUp()1929 void Menu::CreatePopUp() {
1930 Destroy();
1931 mid = gtk_menu_new();
1932 #if GLIB_CHECK_VERSION(2,10,0)
1933 g_object_ref_sink(G_OBJECT(mid));
1934 #else
1935 g_object_ref(G_OBJECT(mid));
1936 gtk_object_sink(GTK_OBJECT(G_OBJECT(mid)));
1937 #endif
1938 }
1939
Destroy()1940 void Menu::Destroy() {
1941 if (mid)
1942 g_object_unref(G_OBJECT(mid));
1943 mid = 0;
1944 }
1945
MenuPositionFunc(GtkMenu *,gint * x,gint * y,gboolean *,gpointer userData)1946 static void MenuPositionFunc(GtkMenu *, gint *x, gint *y, gboolean *, gpointer userData) {
1947 sptr_t intFromPointer = reinterpret_cast<sptr_t>(userData);
1948 *x = intFromPointer & 0xffff;
1949 *y = intFromPointer >> 16;
1950 }
1951
Show(Point pt,Window &)1952 void Menu::Show(Point pt, Window &) {
1953 int screenHeight = gdk_screen_height();
1954 int screenWidth = gdk_screen_width();
1955 GtkMenu *widget = reinterpret_cast<GtkMenu *>(mid);
1956 gtk_widget_show_all(GTK_WIDGET(widget));
1957 GtkRequisition requisition;
1958 #if GTK_CHECK_VERSION(3,0,0)
1959 gtk_widget_get_preferred_size(GTK_WIDGET(widget), NULL, &requisition);
1960 #else
1961 gtk_widget_size_request(GTK_WIDGET(widget), &requisition);
1962 #endif
1963 if ((pt.x + requisition.width) > screenWidth) {
1964 pt.x = screenWidth - requisition.width;
1965 }
1966 if ((pt.y + requisition.height) > screenHeight) {
1967 pt.y = screenHeight - requisition.height;
1968 }
1969 gtk_menu_popup(widget, NULL, NULL, MenuPositionFunc,
1970 reinterpret_cast<void *>((static_cast<int>(pt.y) << 16) | static_cast<int>(pt.x)), 0,
1971 gtk_get_current_event_time());
1972 }
1973
ElapsedTime()1974 ElapsedTime::ElapsedTime() {
1975 GTimeVal curTime;
1976 g_get_current_time(&curTime);
1977 bigBit = curTime.tv_sec;
1978 littleBit = curTime.tv_usec;
1979 }
1980
1981 class DynamicLibraryImpl : public DynamicLibrary {
1982 protected:
1983 GModule* m;
1984 public:
DynamicLibraryImpl(const char * modulePath)1985 explicit DynamicLibraryImpl(const char *modulePath) {
1986 m = g_module_open(modulePath, G_MODULE_BIND_LAZY);
1987 }
1988
~DynamicLibraryImpl()1989 virtual ~DynamicLibraryImpl() {
1990 if (m != NULL)
1991 g_module_close(m);
1992 }
1993
1994 // Use g_module_symbol to get a pointer to the relevant function.
FindFunction(const char * name)1995 virtual Function FindFunction(const char *name) {
1996 if (m != NULL) {
1997 gpointer fn_address = NULL;
1998 gboolean status = g_module_symbol(m, name, &fn_address);
1999 if (status)
2000 return static_cast<Function>(fn_address);
2001 else
2002 return NULL;
2003 } else {
2004 return NULL;
2005 }
2006 }
2007
IsValid()2008 virtual bool IsValid() {
2009 return m != NULL;
2010 }
2011 };
2012
Load(const char * modulePath)2013 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
2014 return static_cast<DynamicLibrary *>( new DynamicLibraryImpl(modulePath) );
2015 }
2016
Duration(bool reset)2017 double ElapsedTime::Duration(bool reset) {
2018 GTimeVal curTime;
2019 g_get_current_time(&curTime);
2020 long endBigBit = curTime.tv_sec;
2021 long endLittleBit = curTime.tv_usec;
2022 double result = 1000000.0 * (endBigBit - bigBit);
2023 result += endLittleBit - littleBit;
2024 result /= 1000000.0;
2025 if (reset) {
2026 bigBit = endBigBit;
2027 littleBit = endLittleBit;
2028 }
2029 return result;
2030 }
2031
Chrome()2032 ColourDesired Platform::Chrome() {
2033 return ColourDesired(0xe0, 0xe0, 0xe0);
2034 }
2035
ChromeHighlight()2036 ColourDesired Platform::ChromeHighlight() {
2037 return ColourDesired(0xff, 0xff, 0xff);
2038 }
2039
DefaultFont()2040 const char *Platform::DefaultFont() {
2041 #ifdef G_OS_WIN32
2042 return "Lucida Console";
2043 #else
2044 return "!Sans";
2045 #endif
2046 }
2047
DefaultFontSize()2048 int Platform::DefaultFontSize() {
2049 #ifdef G_OS_WIN32
2050 return 10;
2051 #else
2052 return 12;
2053 #endif
2054 }
2055
DoubleClickTime()2056 unsigned int Platform::DoubleClickTime() {
2057 return 500; // Half a second
2058 }
2059
MouseButtonBounce()2060 bool Platform::MouseButtonBounce() {
2061 return true;
2062 }
2063
DebugDisplay(const char * s)2064 void Platform::DebugDisplay(const char *s) {
2065 fprintf(stderr, "%s", s);
2066 }
2067
IsKeyDown(int)2068 bool Platform::IsKeyDown(int) {
2069 // TODO: discover state of keys in GTK+/X
2070 return false;
2071 }
2072
SendScintilla(WindowID w,unsigned int msg,unsigned long wParam,long lParam)2073 long Platform::SendScintilla(
2074 WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
2075 return scintilla_send_message(SCINTILLA(w), msg, wParam, lParam);
2076 }
2077
SendScintillaPointer(WindowID w,unsigned int msg,unsigned long wParam,void * lParam)2078 long Platform::SendScintillaPointer(
2079 WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
2080 return scintilla_send_message(SCINTILLA(w), msg, wParam,
2081 reinterpret_cast<sptr_t>(lParam));
2082 }
2083
IsDBCSLeadByte(int codePage,char ch)2084 bool Platform::IsDBCSLeadByte(int codePage, char ch) {
2085 // Byte ranges found in Wikipedia articles with relevant search strings in each case
2086 unsigned char uch = static_cast<unsigned char>(ch);
2087 switch (codePage) {
2088 case 932:
2089 // Shift_jis
2090 return ((uch >= 0x81) && (uch <= 0x9F)) ||
2091 ((uch >= 0xE0) && (uch <= 0xFC));
2092 // Lead bytes F0 to FC may be a Microsoft addition.
2093 case 936:
2094 // GBK
2095 return (uch >= 0x81) && (uch <= 0xFE);
2096 case 950:
2097 // Big5
2098 return (uch >= 0x81) && (uch <= 0xFE);
2099 // Korean EUC-KR may be code page 949.
2100 }
2101 return false;
2102 }
2103
DBCSCharLength(int codePage,const char * s)2104 int Platform::DBCSCharLength(int codePage, const char *s) {
2105 if (codePage == 932 || codePage == 936 || codePage == 950) {
2106 return IsDBCSLeadByte(codePage, s[0]) ? 2 : 1;
2107 } else {
2108 int bytes = mblen(s, MB_CUR_MAX);
2109 if (bytes >= 1)
2110 return bytes;
2111 else
2112 return 1;
2113 }
2114 }
2115
DBCSCharMaxLength()2116 int Platform::DBCSCharMaxLength() {
2117 return MB_CUR_MAX;
2118 //return 2;
2119 }
2120
2121 // These are utility functions not really tied to a platform
2122
Minimum(int a,int b)2123 int Platform::Minimum(int a, int b) {
2124 if (a < b)
2125 return a;
2126 else
2127 return b;
2128 }
2129
Maximum(int a,int b)2130 int Platform::Maximum(int a, int b) {
2131 if (a > b)
2132 return a;
2133 else
2134 return b;
2135 }
2136
2137 //#define TRACE
2138
2139 #ifdef TRACE
DebugPrintf(const char * format,...)2140 void Platform::DebugPrintf(const char *format, ...) {
2141 char buffer[2000];
2142 va_list pArguments;
2143 va_start(pArguments, format);
2144 vsprintf(buffer, format, pArguments);
2145 va_end(pArguments);
2146 Platform::DebugDisplay(buffer);
2147 }
2148 #else
DebugPrintf(const char *,...)2149 void Platform::DebugPrintf(const char *, ...) {}
2150
2151 #endif
2152
2153 // Not supported for GTK+
2154 static bool assertionPopUps = true;
2155
ShowAssertionPopUps(bool assertionPopUps_)2156 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
2157 bool ret = assertionPopUps;
2158 assertionPopUps = assertionPopUps_;
2159 return ret;
2160 }
2161
Assert(const char * c,const char * file,int line)2162 void Platform::Assert(const char *c, const char *file, int line) {
2163 char buffer[2000];
2164 g_snprintf(buffer, sizeof(buffer), "Assertion [%s] failed at %s %d\r\n", c, file, line);
2165 Platform::DebugDisplay(buffer);
2166 abort();
2167 }
2168
Clamp(int val,int minVal,int maxVal)2169 int Platform::Clamp(int val, int minVal, int maxVal) {
2170 if (val > maxVal)
2171 val = maxVal;
2172 if (val < minVal)
2173 val = minVal;
2174 return val;
2175 }
2176
Platform_Initialise()2177 void Platform_Initialise() {
2178 FontMutexAllocate();
2179 }
2180
Platform_Finalise()2181 void Platform_Finalise() {
2182 FontCached::ReleaseAll();
2183 FontMutexFree();
2184 }
2185