Update images for Geany0.19.
[geany-mirror.git] / scintilla / PlatGTK.cxx
blob55947113e517bb5f958f0360d6d4fd481e34d059
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 <string.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <stddef.h>
11 #include <glib.h>
12 #include <gmodule.h>
13 #include <gdk/gdk.h>
14 #include <gtk/gtk.h>
15 #include <gdk/gdkkeysyms.h>
17 #include "Platform.h"
19 #include "Scintilla.h"
20 #include "ScintillaWidget.h"
21 #include "UniConversion.h"
22 #include "XPM.h"
24 /* GLIB must be compiled with thread support, otherwise we
25 will bail on trying to use locks, and that could lead to
26 problems for someone. `glib-config --libs gthread` needs
27 to be used to get the glib libraries for linking, otherwise
28 g_thread_init will fail */
29 #define USE_LOCK defined(G_THREADS_ENABLED) && !defined(G_THREADS_IMPL_NONE)
30 /* Use fast way of getting char data on win32 to work around problems
31 with gdk_string_extents. */
32 #define FAST_WAY
34 #include "Converter.h"
36 #ifdef _MSC_VER
37 // Ignore unreferenced local functions in GTK+ headers
38 #pragma warning(disable: 4505)
39 #endif
41 #ifdef SCI_NAMESPACE
42 using namespace Scintilla;
43 #endif
45 enum encodingType { singleByte, UTF8, dbcs};
47 struct LOGFONT {
48 int size;
49 bool bold;
50 bool italic;
51 int characterSet;
52 char faceName[300];
55 #if USE_LOCK
56 static GMutex *fontMutex = NULL;
58 static void InitializeGLIBThreads() {
59 if (!g_thread_supported()) {
60 g_thread_init(NULL);
63 #endif
65 static void FontMutexAllocate() {
66 #if USE_LOCK
67 if (!fontMutex) {
68 InitializeGLIBThreads();
69 fontMutex = g_mutex_new();
71 #endif
74 static void FontMutexFree() {
75 #if USE_LOCK
76 if (fontMutex) {
77 g_mutex_free(fontMutex);
78 fontMutex = NULL;
80 #endif
83 static void FontMutexLock() {
84 #if USE_LOCK
85 g_mutex_lock(fontMutex);
86 #endif
89 static void FontMutexUnlock() {
90 #if USE_LOCK
91 if (fontMutex) {
92 g_mutex_unlock(fontMutex);
94 #endif
97 // On GTK+ 1.x holds a GdkFont* but on GTK+ 2.x can hold a GdkFont* or a
98 // PangoFontDescription*.
99 class FontHandle {
100 int width[128];
101 encodingType et;
102 public:
103 int ascent;
104 GdkFont *pfont;
105 PangoFontDescription *pfd;
106 int characterSet;
107 FontHandle(GdkFont *pfont_) {
108 et = singleByte;
109 ascent = 0;
110 pfont = pfont_;
111 pfd = 0;
112 characterSet = -1;
113 ResetWidths(et);
115 FontHandle(PangoFontDescription *pfd_, int characterSet_) {
116 et = singleByte;
117 ascent = 0;
118 pfont = 0;
119 pfd = pfd_;
120 characterSet = characterSet_;
121 ResetWidths(et);
123 ~FontHandle() {
124 if (pfont)
125 gdk_font_unref(pfont);
126 pfont = 0;
127 if (pfd)
128 pango_font_description_free(pfd);
129 pfd = 0;
131 void ResetWidths(encodingType et_) {
132 et = et_;
133 for (int i=0; i<=127; i++) {
134 width[i] = 0;
137 int CharWidth(unsigned char ch, encodingType et_) {
138 int w = 0;
139 FontMutexLock();
140 if ((ch <= 127) && (et == et_)) {
141 w = width[ch];
143 FontMutexUnlock();
144 return w;
146 void SetCharWidth(unsigned char ch, int w, encodingType et_) {
147 if (ch <= 127) {
148 FontMutexLock();
149 if (et != et_) {
150 ResetWidths(et_);
152 width[ch] = w;
153 FontMutexUnlock();
158 // X has a 16 bit coordinate space, so stop drawing here to avoid wrapping
159 static const int maxCoordinate = 32000;
161 static FontHandle *PFont(Font &f) {
162 return reinterpret_cast<FontHandle *>(f.GetID());
165 static GtkWidget *PWidget(WindowID wid) {
166 return reinterpret_cast<GtkWidget *>(wid);
169 static GtkWidget *PWidget(Window &w) {
170 return PWidget(w.GetID());
173 Point Point::FromLong(long lpoint) {
174 return Point(
175 Platform::LowShortFromLong(lpoint),
176 Platform::HighShortFromLong(lpoint));
179 Palette::Palette() {
180 used = 0;
181 allowRealization = false;
182 allocatedPalette = 0;
183 allocatedLen = 0;
184 size = 100;
185 entries = new ColourPair[size];
188 Palette::~Palette() {
189 Release();
190 delete []entries;
191 entries = 0;
194 void Palette::Release() {
195 used = 0;
196 delete [](reinterpret_cast<GdkColor *>(allocatedPalette));
197 allocatedPalette = 0;
198 allocatedLen = 0;
199 delete []entries;
200 size = 100;
201 entries = new ColourPair[size];
204 // This method either adds a colour to the list of wanted colours (want==true)
205 // or retrieves the allocated colour back to the ColourPair.
206 // This is one method to make it easier to keep the code for wanting and retrieving in sync.
207 void Palette::WantFind(ColourPair &cp, bool want) {
208 if (want) {
209 for (int i=0; i < used; i++) {
210 if (entries[i].desired == cp.desired)
211 return;
214 if (used >= size) {
215 int sizeNew = size * 2;
216 ColourPair *entriesNew = new ColourPair[sizeNew];
217 for (int j=0; j<size; j++) {
218 entriesNew[j] = entries[j];
220 delete []entries;
221 entries = entriesNew;
222 size = sizeNew;
225 entries[used].desired = cp.desired;
226 entries[used].allocated.Set(cp.desired.AsLong());
227 used++;
228 } else {
229 for (int i=0; i < used; i++) {
230 if (entries[i].desired == cp.desired) {
231 cp.allocated = entries[i].allocated;
232 return;
235 cp.allocated.Set(cp.desired.AsLong());
239 void Palette::Allocate(Window &w) {
240 if (allocatedPalette) {
241 gdk_colormap_free_colors(gtk_widget_get_colormap(PWidget(w)),
242 reinterpret_cast<GdkColor *>(allocatedPalette),
243 allocatedLen);
244 delete [](reinterpret_cast<GdkColor *>(allocatedPalette));
245 allocatedPalette = 0;
246 allocatedLen = 0;
248 GdkColor *paletteNew = new GdkColor[used];
249 allocatedPalette = paletteNew;
250 gboolean *successPalette = new gboolean[used];
251 if (paletteNew) {
252 allocatedLen = used;
253 int iPal = 0;
254 for (iPal = 0; iPal < used; iPal++) {
255 paletteNew[iPal].red = entries[iPal].desired.GetRed() * (65535 / 255);
256 paletteNew[iPal].green = entries[iPal].desired.GetGreen() * (65535 / 255);
257 paletteNew[iPal].blue = entries[iPal].desired.GetBlue() * (65535 / 255);
258 paletteNew[iPal].pixel = entries[iPal].desired.AsLong();
260 gdk_colormap_alloc_colors(gtk_widget_get_colormap(PWidget(w)),
261 paletteNew, allocatedLen, FALSE, TRUE,
262 successPalette);
263 for (iPal = 0; iPal < used; iPal++) {
264 entries[iPal].allocated.Set(paletteNew[iPal].pixel);
267 delete []successPalette;
270 static const char *CharacterSetName(int characterSet) {
271 switch (characterSet) {
272 case SC_CHARSET_ANSI:
273 return "iso8859-*";
274 case SC_CHARSET_DEFAULT:
275 return "iso8859-*";
276 case SC_CHARSET_BALTIC:
277 return "iso8859-13";
278 case SC_CHARSET_CHINESEBIG5:
279 return "*-*";
280 case SC_CHARSET_EASTEUROPE:
281 return "*-2";
282 case SC_CHARSET_GB2312:
283 return "gb2312.1980-*";
284 case SC_CHARSET_GREEK:
285 return "*-7";
286 case SC_CHARSET_HANGUL:
287 return "ksc5601.1987-*";
288 case SC_CHARSET_MAC:
289 return "*-*";
290 case SC_CHARSET_OEM:
291 return "*-*";
292 case SC_CHARSET_RUSSIAN:
293 return "*-r";
294 case SC_CHARSET_CYRILLIC:
295 return "*-cp1251";
296 case SC_CHARSET_SHIFTJIS:
297 return "jisx0208.1983-*";
298 case SC_CHARSET_SYMBOL:
299 return "*-*";
300 case SC_CHARSET_TURKISH:
301 return "*-9";
302 case SC_CHARSET_JOHAB:
303 return "*-*";
304 case SC_CHARSET_HEBREW:
305 return "*-8";
306 case SC_CHARSET_ARABIC:
307 return "*-6";
308 case SC_CHARSET_VIETNAMESE:
309 return "*-*";
310 case SC_CHARSET_THAI:
311 return "iso8859-11";
312 case SC_CHARSET_8859_15:
313 return "iso8859-15";
314 default:
315 return "*-*";
319 static bool IsDBCSCharacterSet(int characterSet) {
320 switch (characterSet) {
321 case SC_CHARSET_GB2312:
322 case SC_CHARSET_HANGUL:
323 case SC_CHARSET_SHIFTJIS:
324 case SC_CHARSET_CHINESEBIG5:
325 return true;
326 default:
327 return false;
331 static void GenerateFontSpecStrings(const char *fontName, int characterSet,
332 char *foundary, int foundary_len,
333 char *faceName, int faceName_len,
334 char *charset, int charset_len) {
335 // supported font strings include:
336 // foundary-fontface-isoxxx-x
337 // fontface-isoxxx-x
338 // foundary-fontface
339 // fontface
340 if (strchr(fontName, '-')) {
341 char tmp[300];
342 char *d1 = NULL, *d2 = NULL, *d3 = NULL;
343 strncpy(tmp, fontName, sizeof(tmp) - 1);
344 tmp[sizeof(tmp) - 1] = '\0';
345 d1 = strchr(tmp, '-');
346 // we know the first dash exists
347 d2 = strchr(d1 + 1, '-');
348 if (d2)
349 d3 = strchr(d2 + 1, '-');
350 if (d3) {
351 // foundary-fontface-isoxxx-x
352 *d2 = '\0';
353 foundary[0] = '-';
354 foundary[1] = '\0';
355 strncpy(faceName, tmp, foundary_len - 1);
356 strncpy(charset, d2 + 1, charset_len - 1);
357 } else if (d2) {
358 // fontface-isoxxx-x
359 *d1 = '\0';
360 strcpy(foundary, "-*-");
361 strncpy(faceName, tmp, faceName_len - 1);
362 strncpy(charset, d1 + 1, charset_len - 1);
363 } else {
364 // foundary-fontface
365 foundary[0] = '-';
366 foundary[1] = '\0';
367 strncpy(faceName, tmp, faceName_len - 1);
368 strncpy(charset, CharacterSetName(characterSet), charset_len - 1);
370 } else {
371 strncpy(foundary, "-*-", foundary_len);
372 strncpy(faceName, fontName, faceName_len - 1);
373 strncpy(charset, CharacterSetName(characterSet), charset_len - 1);
377 static void SetLogFont(LOGFONT &lf, const char *faceName, int characterSet, int size, bool bold, bool italic) {
378 memset(&lf, 0, sizeof(lf));
379 lf.size = size;
380 lf.bold = bold;
381 lf.italic = italic;
382 lf.characterSet = characterSet;
383 strncpy(lf.faceName, faceName, sizeof(lf.faceName) - 1);
387 * Create a hash from the parameters for a font to allow easy checking for identity.
388 * If one font is the same as another, its hash will be the same, but if the hash is the
389 * same then they may still be different.
391 static int HashFont(const char *faceName, int characterSet, int size, bool bold, bool italic) {
392 return
393 size ^
394 (characterSet << 10) ^
395 (bold ? 0x10000000 : 0) ^
396 (italic ? 0x20000000 : 0) ^
397 faceName[0];
400 class FontCached : Font {
401 FontCached *next;
402 int usage;
403 LOGFONT lf;
404 int hash;
405 FontCached(const char *faceName_, int characterSet_, int size_, bool bold_, bool italic_);
406 ~FontCached() {}
407 bool SameAs(const char *faceName_, int characterSet_, int size_, bool bold_, bool italic_);
408 virtual void Release();
409 static FontID CreateNewFont(const char *fontName, int characterSet,
410 int size, bool bold, bool italic);
411 static FontCached *first;
412 public:
413 static FontID FindOrCreate(const char *faceName_, int characterSet_, int size_, bool bold_, bool italic_);
414 static void ReleaseId(FontID fid_);
417 FontCached *FontCached::first = 0;
419 FontCached::FontCached(const char *faceName_, int characterSet_, int size_, bool bold_, bool italic_) :
420 next(0), usage(0), hash(0) {
421 ::SetLogFont(lf, faceName_, characterSet_, size_, bold_, italic_);
422 hash = HashFont(faceName_, characterSet_, size_, bold_, italic_);
423 fid = CreateNewFont(faceName_, characterSet_, size_, bold_, italic_);
424 usage = 1;
427 bool FontCached::SameAs(const char *faceName_, int characterSet_, int size_, bool bold_, bool italic_) {
428 return
429 lf.size == size_ &&
430 lf.bold == bold_ &&
431 lf.italic == italic_ &&
432 lf.characterSet == characterSet_ &&
433 0 == strcmp(lf.faceName, faceName_);
436 void FontCached::Release() {
437 if (fid)
438 delete PFont(*this);
439 fid = 0;
442 FontID FontCached::FindOrCreate(const char *faceName_, int characterSet_, int size_, bool bold_, bool italic_) {
443 FontID ret = 0;
444 FontMutexLock();
445 int hashFind = HashFont(faceName_, characterSet_, size_, bold_, italic_);
446 for (FontCached *cur = first; cur; cur = cur->next) {
447 if ((cur->hash == hashFind) &&
448 cur->SameAs(faceName_, characterSet_, size_, bold_, italic_)) {
449 cur->usage++;
450 ret = cur->fid;
453 if (ret == 0) {
454 FontCached *fc = new FontCached(faceName_, characterSet_, size_, bold_, italic_);
455 if (fc) {
456 fc->next = first;
457 first = fc;
458 ret = fc->fid;
461 FontMutexUnlock();
462 return ret;
465 void FontCached::ReleaseId(FontID fid_) {
466 FontMutexLock();
467 FontCached **pcur = &first;
468 for (FontCached *cur = first; cur; cur = cur->next) {
469 if (cur->fid == fid_) {
470 cur->usage--;
471 if (cur->usage == 0) {
472 *pcur = cur->next;
473 cur->Release();
474 cur->next = 0;
475 delete cur;
477 break;
479 pcur = &cur->next;
481 FontMutexUnlock();
484 static GdkFont *LoadFontOrSet(const char *fontspec, int characterSet) {
485 if (IsDBCSCharacterSet(characterSet)) {
486 return gdk_fontset_load(fontspec);
487 } else {
488 return gdk_font_load(fontspec);
492 FontID FontCached::CreateNewFont(const char *fontName, int characterSet,
493 int size, bool bold, bool italic) {
494 char fontset[1024];
495 char fontspec[300];
496 char foundary[50];
497 char faceName[100];
498 char charset[50];
499 fontset[0] = '\0';
500 fontspec[0] = '\0';
501 foundary[0] = '\0';
502 faceName[0] = '\0';
503 charset[0] = '\0';
505 if (fontName[0] == '!') {
506 PangoFontDescription *pfd = pango_font_description_new();
507 if (pfd) {
508 pango_font_description_set_family(pfd, fontName+1);
509 pango_font_description_set_size(pfd, size * PANGO_SCALE);
510 pango_font_description_set_weight(pfd, bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
511 pango_font_description_set_style(pfd, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
512 return new FontHandle(pfd, characterSet);
516 GdkFont *newid = 0;
517 // If name of the font begins with a '-', assume, that it is
518 // a full fontspec.
519 if (fontName[0] == '-') {
520 if (strchr(fontName, ',') || IsDBCSCharacterSet(characterSet)) {
521 newid = gdk_fontset_load(fontName);
522 } else {
523 newid = gdk_font_load(fontName);
525 if (!newid) {
526 // Font not available so substitute a reasonable code font
527 // iso8859 appears to only allow western characters.
528 newid = LoadFontOrSet("-*-*-*-*-*-*-*-*-*-*-*-*-iso8859-*",
529 characterSet);
531 return new FontHandle(newid);
534 // it's not a full fontspec, build one.
536 // This supports creating a FONT_SET
537 // in a method that allows us to also set size, slant and
538 // weight for the fontset. The expected input is multiple
539 // partial fontspecs seperated by comma
540 // eg. adobe-courier-iso10646-1,*-courier-iso10646-1,*-*-*-*
541 if (strchr(fontName, ',')) {
542 // build a fontspec and use gdk_fontset_load
543 int remaining = sizeof(fontset);
544 char fontNameCopy[1024];
545 strncpy(fontNameCopy, fontName, sizeof(fontNameCopy) - 1);
546 char *fn = fontNameCopy;
547 char *fp = strchr(fn, ',');
548 for (;;) {
549 const char *spec = "%s%s%s%s-*-*-*-%0d-*-*-*-*-%s";
550 if (fontset[0] != '\0') {
551 // if this is not the first font in the list,
552 // append a comma seperator
553 spec = ",%s%s%s%s-*-*-*-%0d-*-*-*-*-%s";
556 if (fp)
557 *fp = '\0'; // nullify the comma
558 GenerateFontSpecStrings(fn, characterSet,
559 foundary, sizeof(foundary),
560 faceName, sizeof(faceName),
561 charset, sizeof(charset));
563 g_snprintf(fontspec,
564 sizeof(fontspec) - 1,
565 spec,
566 foundary, faceName,
567 bold ? "-bold" : "-medium",
568 italic ? "-i" : "-r",
569 size * 10,
570 charset);
572 // if this is the first font in the list, and
573 // we are doing italic, add an oblique font
574 // to the list
575 if (italic && fontset[0] == '\0') {
576 strncat(fontset, fontspec, remaining - 1);
577 remaining -= strlen(fontset);
579 g_snprintf(fontspec,
580 sizeof(fontspec) - 1,
581 ",%s%s%s-o-*-*-*-%0d-*-*-*-*-%s",
582 foundary, faceName,
583 bold ? "-bold" : "-medium",
584 size * 10,
585 charset);
588 strncat(fontset, fontspec, remaining - 1);
589 remaining -= strlen(fontset);
591 if (!fp)
592 break;
594 fn = fp + 1;
595 fp = strchr(fn, ',');
598 newid = gdk_fontset_load(fontset);
599 if (newid)
600 return new FontHandle(newid);
602 // if fontset load failed, fall through, we'll use
603 // the last font entry and continue to try and
604 // get something that matches
607 // single fontspec support
609 GenerateFontSpecStrings(fontName, characterSet,
610 foundary, sizeof(foundary),
611 faceName, sizeof(faceName),
612 charset, sizeof(charset));
614 g_snprintf(fontspec,
615 sizeof(fontspec) - 1,
616 "%s%s%s%s-*-*-*-%0d-*-*-*-*-%s",
617 foundary, faceName,
618 bold ? "-bold" : "-medium",
619 italic ? "-i" : "-r",
620 size * 10,
621 charset);
622 newid = LoadFontOrSet(fontspec, characterSet);
623 if (!newid) {
624 // some fonts have oblique, not italic
625 g_snprintf(fontspec,
626 sizeof(fontspec) - 1,
627 "%s%s%s%s-*-*-*-%0d-*-*-*-*-%s",
628 foundary, faceName,
629 bold ? "-bold" : "-medium",
630 italic ? "-o" : "-r",
631 size * 10,
632 charset);
633 newid = LoadFontOrSet(fontspec, characterSet);
635 if (!newid) {
636 g_snprintf(fontspec,
637 sizeof(fontspec) - 1,
638 "-*-*-*-*-*-*-*-%0d-*-*-*-*-%s",
639 size * 10,
640 charset);
641 newid = gdk_font_load(fontspec);
643 if (!newid) {
644 // Font not available so substitute a reasonable code font
645 // iso8859 appears to only allow western characters.
646 newid = LoadFontOrSet("-*-*-*-*-*-*-*-*-*-*-*-*-iso8859-*",
647 characterSet);
649 return new FontHandle(newid);
652 Font::Font() : fid(0) {}
654 Font::~Font() {}
656 void Font::Create(const char *faceName, int characterSet, int size,
657 bool bold, bool italic, int) {
658 Release();
659 fid = FontCached::FindOrCreate(faceName, characterSet, size, bold, italic);
662 void Font::Release() {
663 if (fid)
664 FontCached::ReleaseId(fid);
665 fid = 0;
668 // Required on OS X
669 #ifdef SCI_NAMESPACE
670 class Scintilla::SurfaceImpl : public Surface
671 #else
672 class SurfaceImpl : public Surface
673 #endif
675 encodingType et;
676 GdkDrawable *drawable;
677 GdkGC *gc;
678 GdkPixmap *ppixmap;
679 int x;
680 int y;
681 bool inited;
682 bool createdGC;
683 PangoContext *pcontext;
684 PangoLayout *layout;
685 Converter conv;
686 int characterSet;
687 void SetConverter(int characterSet_);
688 public:
689 SurfaceImpl();
690 virtual ~SurfaceImpl();
692 void Init(WindowID wid);
693 void Init(SurfaceID sid, WindowID wid);
694 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
696 void Release();
697 bool Initialised();
698 void PenColour(ColourAllocated fore);
699 int LogPixelsY();
700 int DeviceHeightFont(int points);
701 void MoveTo(int x_, int y_);
702 void LineTo(int x_, int y_);
703 void Polygon(Point *pts, int npts, ColourAllocated fore, ColourAllocated back);
704 void RectangleDraw(PRectangle rc, ColourAllocated fore, ColourAllocated back);
705 void FillRectangle(PRectangle rc, ColourAllocated back);
706 void FillRectangle(PRectangle rc, Surface &surfacePattern);
707 void RoundedRectangle(PRectangle rc, ColourAllocated fore, ColourAllocated back);
708 void AlphaRectangle(PRectangle rc, int cornerSize, ColourAllocated fill, int alphaFill,
709 ColourAllocated outline, int alphaOutline, int flags);
710 void Ellipse(PRectangle rc, ColourAllocated fore, ColourAllocated back);
711 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
713 void DrawTextBase(PRectangle rc, Font &font_, int ybase, const char *s, int len, ColourAllocated fore);
714 void DrawTextNoClip(PRectangle rc, Font &font_, int ybase, const char *s, int len, ColourAllocated fore, ColourAllocated back);
715 void DrawTextClipped(PRectangle rc, Font &font_, int ybase, const char *s, int len, ColourAllocated fore, ColourAllocated back);
716 void DrawTextTransparent(PRectangle rc, Font &font_, int ybase, const char *s, int len, ColourAllocated fore);
717 void MeasureWidths(Font &font_, const char *s, int len, int *positions);
718 int WidthText(Font &font_, const char *s, int len);
719 int WidthChar(Font &font_, char ch);
720 int Ascent(Font &font_);
721 int Descent(Font &font_);
722 int InternalLeading(Font &font_);
723 int ExternalLeading(Font &font_);
724 int Height(Font &font_);
725 int AverageCharWidth(Font &font_);
727 int SetPalette(Palette *pal, bool inBackGround);
728 void SetClip(PRectangle rc);
729 void FlushCachedState();
731 void SetUnicodeMode(bool unicodeMode_);
732 void SetDBCSMode(int codePage);
735 const char *CharacterSetID(int characterSet) {
736 switch (characterSet) {
737 case SC_CHARSET_ANSI:
738 return "";
739 case SC_CHARSET_DEFAULT:
740 return "ISO-8859-1";
741 case SC_CHARSET_BALTIC:
742 return "ISO-8859-13";
743 case SC_CHARSET_CHINESEBIG5:
744 return "BIG-5";
745 case SC_CHARSET_EASTEUROPE:
746 return "ISO-8859-2";
747 case SC_CHARSET_GB2312:
748 return "GB2312";
749 case SC_CHARSET_GREEK:
750 return "ISO-8859-7";
751 case SC_CHARSET_HANGUL:
752 return "";
753 case SC_CHARSET_MAC:
754 return "MACINTOSH";
755 case SC_CHARSET_OEM:
756 return "ASCII";
757 case SC_CHARSET_RUSSIAN:
758 return "KOI8-R";
759 case SC_CHARSET_CYRILLIC:
760 return "CP1251";
761 case SC_CHARSET_SHIFTJIS:
762 return "SHIFT-JIS";
763 case SC_CHARSET_SYMBOL:
764 return "";
765 case SC_CHARSET_TURKISH:
766 return "ISO-8859-9";
767 case SC_CHARSET_JOHAB:
768 return "JOHAB";
769 case SC_CHARSET_HEBREW:
770 return "ISO-8859-8";
771 case SC_CHARSET_ARABIC:
772 return "ISO-8859-6";
773 case SC_CHARSET_VIETNAMESE:
774 return "";
775 case SC_CHARSET_THAI:
776 return "ISO-8859-11";
777 case SC_CHARSET_8859_15:
778 return "ISO-8859-15";
779 default:
780 return "";
784 void SurfaceImpl::SetConverter(int characterSet_) {
785 if (characterSet != characterSet_) {
786 characterSet = characterSet_;
787 conv.Open("UTF-8", CharacterSetID(characterSet), false);
791 SurfaceImpl::SurfaceImpl() : et(singleByte), drawable(0), gc(0), ppixmap(0),
792 x(0), y(0), inited(false), createdGC(false)
793 , pcontext(0), layout(0), characterSet(-1) {
796 SurfaceImpl::~SurfaceImpl() {
797 Release();
800 void SurfaceImpl::Release() {
801 et = singleByte;
802 drawable = 0;
803 if (createdGC) {
804 createdGC = false;
805 gdk_gc_unref(gc);
807 gc = 0;
808 if (ppixmap)
809 gdk_pixmap_unref(ppixmap);
810 ppixmap = 0;
811 if (layout)
812 g_object_unref(layout);
813 layout = 0;
814 if (pcontext)
815 g_object_unref(pcontext);
816 pcontext = 0;
817 conv.Close();
818 characterSet = -1;
819 x = 0;
820 y = 0;
821 inited = false;
822 createdGC = false;
825 bool SurfaceImpl::Initialised() {
826 return inited;
829 void SurfaceImpl::Init(WindowID wid) {
830 Release();
831 PLATFORM_ASSERT(wid);
832 pcontext = gtk_widget_create_pango_context(PWidget(wid));
833 PLATFORM_ASSERT(pcontext);
834 layout = pango_layout_new(pcontext);
835 PLATFORM_ASSERT(layout);
836 inited = true;
839 void SurfaceImpl::Init(SurfaceID sid, WindowID wid) {
840 PLATFORM_ASSERT(sid);
841 GdkDrawable *drawable_ = reinterpret_cast<GdkDrawable *>(sid);
842 Release();
843 PLATFORM_ASSERT(wid);
844 pcontext = gtk_widget_create_pango_context(PWidget(wid));
845 layout = pango_layout_new(pcontext);
846 drawable = drawable_;
847 gc = gdk_gc_new(drawable_);
848 // Ask for lines that do not paint the last pixel so is like Win32
849 gdk_gc_set_line_attributes(gc, 0, GDK_LINE_SOLID, GDK_CAP_NOT_LAST, GDK_JOIN_MITER);
850 createdGC = true;
851 inited = true;
854 void SurfaceImpl::InitPixMap(int width, int height, Surface *surface_, WindowID wid) {
855 PLATFORM_ASSERT(surface_);
856 Release();
857 SurfaceImpl *surfImpl = static_cast<SurfaceImpl *>(surface_);
858 PLATFORM_ASSERT(surfImpl->drawable);
859 PLATFORM_ASSERT(wid);
860 pcontext = gtk_widget_create_pango_context(PWidget(wid));
861 PLATFORM_ASSERT(pcontext);
862 layout = pango_layout_new(pcontext);
863 PLATFORM_ASSERT(layout);
864 if (height > 0 && width > 0)
865 ppixmap = gdk_pixmap_new(surfImpl->drawable, width, height, -1);
866 drawable = ppixmap;
867 gc = gdk_gc_new(surfImpl->drawable);
868 // Ask for lines that do not paint the last pixel so is like Win32
869 gdk_gc_set_line_attributes(gc, 0, GDK_LINE_SOLID, GDK_CAP_NOT_LAST, GDK_JOIN_MITER);
870 createdGC = true;
871 inited = true;
874 void SurfaceImpl::PenColour(ColourAllocated fore) {
875 if (gc) {
876 GdkColor co;
877 co.pixel = fore.AsLong();
878 gdk_gc_set_foreground(gc, &co);
882 int SurfaceImpl::LogPixelsY() {
883 return 72;
886 int SurfaceImpl::DeviceHeightFont(int points) {
887 int logPix = LogPixelsY();
888 return (points * logPix + logPix / 2) / 72;
891 void SurfaceImpl::MoveTo(int x_, int y_) {
892 x = x_;
893 y = y_;
896 void SurfaceImpl::LineTo(int x_, int y_) {
897 if (drawable && gc) {
898 gdk_draw_line(drawable, gc,
899 x, y,
900 x_, y_);
902 x = x_;
903 y = y_;
906 void SurfaceImpl::Polygon(Point *pts, int npts, ColourAllocated fore,
907 ColourAllocated back) {
908 GdkPoint gpts[20];
909 if (npts < static_cast<int>((sizeof(gpts) / sizeof(gpts[0])))) {
910 for (int i = 0;i < npts;i++) {
911 gpts[i].x = pts[i].x;
912 gpts[i].y = pts[i].y;
914 PenColour(back);
915 gdk_draw_polygon(drawable, gc, 1, gpts, npts);
916 PenColour(fore);
917 gdk_draw_polygon(drawable, gc, 0, gpts, npts);
921 void SurfaceImpl::RectangleDraw(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
922 if (gc && drawable) {
923 PenColour(back);
924 gdk_draw_rectangle(drawable, gc, 1,
925 rc.left + 1, rc.top + 1,
926 rc.right - rc.left - 2, rc.bottom - rc.top - 2);
928 PenColour(fore);
929 // The subtraction of 1 off the width and height here shouldn't be needed but
930 // otherwise a different rectangle is drawn than would be done if the fill parameter == 1
931 gdk_draw_rectangle(drawable, gc, 0,
932 rc.left, rc.top,
933 rc.right - rc.left - 1, rc.bottom - rc.top - 1);
937 void SurfaceImpl::FillRectangle(PRectangle rc, ColourAllocated back) {
938 PenColour(back);
939 if (drawable && (rc.left < maxCoordinate)) { // Protect against out of range
940 gdk_draw_rectangle(drawable, gc, 1,
941 rc.left, rc.top,
942 rc.right - rc.left, rc.bottom - rc.top);
946 void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
947 if (static_cast<SurfaceImpl &>(surfacePattern).drawable) {
948 // Tile pattern over rectangle
949 // Currently assumes 8x8 pattern
950 int widthPat = 8;
951 int heightPat = 8;
952 for (int xTile = rc.left; xTile < rc.right; xTile += widthPat) {
953 int widthx = (xTile + widthPat > rc.right) ? rc.right - xTile : widthPat;
954 for (int yTile = rc.top; yTile < rc.bottom; yTile += heightPat) {
955 int heighty = (yTile + heightPat > rc.bottom) ? rc.bottom - yTile : heightPat;
956 gdk_draw_pixmap(drawable,
958 static_cast<SurfaceImpl &>(surfacePattern).drawable,
959 0, 0,
960 xTile, yTile,
961 widthx, heighty);
964 } else {
965 // Something is wrong so try to show anyway
966 // Shows up black because colour not allocated
967 FillRectangle(rc, ColourAllocated(0));
971 void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
972 if (((rc.right - rc.left) > 4) && ((rc.bottom - rc.top) > 4)) {
973 // Approximate a round rect with some cut off corners
974 Point pts[] = {
975 Point(rc.left + 2, rc.top),
976 Point(rc.right - 2, rc.top),
977 Point(rc.right, rc.top + 2),
978 Point(rc.right, rc.bottom - 2),
979 Point(rc.right - 2, rc.bottom),
980 Point(rc.left + 2, rc.bottom),
981 Point(rc.left, rc.bottom - 2),
982 Point(rc.left, rc.top + 2),
984 Polygon(pts, sizeof(pts) / sizeof(pts[0]), fore, back);
985 } else {
986 RectangleDraw(rc, fore, back);
990 // Plot a point into a guint32 buffer symetrically to all 4 qudrants
991 static void AllFour(guint32 *pixels, int stride, int width, int height, int x, int y, guint32 val) {
992 pixels[y*stride+x] = val;
993 pixels[y*stride+width-1-x] = val;
994 pixels[(height-1-y)*stride+x] = val;
995 pixels[(height-1-y)*stride+width-1-x] = val;
998 static unsigned int GetRValue(unsigned int co) {
999 return (co >> 16) & 0xff;
1002 static unsigned int GetGValue(unsigned int co) {
1003 return (co >> 8) & 0xff;
1006 static unsigned int GetBValue(unsigned int co) {
1007 return co & 0xff;
1010 static guint32 u32FromRGBA(guint8 r, guint8 g, guint8 b, guint8 a) {
1011 union {
1012 guint8 pixVal[4];
1013 guint32 val;
1014 } converter;
1015 converter.pixVal[0] = r;
1016 converter.pixVal[1] = g;
1017 converter.pixVal[2] = b;
1018 converter.pixVal[3] = a;
1019 return converter.val;
1022 void SurfaceImpl::AlphaRectangle(PRectangle rc, int cornerSize, ColourAllocated fill, int alphaFill,
1023 ColourAllocated outline, int alphaOutline, int flags) {
1024 if (gc && drawable && rc.Width() > 0) {
1025 int width = rc.Width();
1026 int height = rc.Height();
1027 // Ensure not distorted too much by corners when small
1028 cornerSize = Platform::Minimum(cornerSize, (Platform::Minimum(width, height) / 2) - 2);
1029 // Make a 32 bit deep pixbuf with alpha
1030 GdkPixbuf *pixalpha = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
1032 guint32 valEmpty = u32FromRGBA(0,0,0,0);
1033 guint32 valFill = u32FromRGBA(GetRValue(fill.AsLong()),
1034 GetGValue(fill.AsLong()), GetBValue(fill.AsLong()), alphaFill);
1035 guint32 valOutline = u32FromRGBA(GetRValue(outline.AsLong()),
1036 GetGValue(outline.AsLong()), GetBValue(outline.AsLong()), alphaOutline);
1037 guint32 *pixels = reinterpret_cast<guint32 *>(gdk_pixbuf_get_pixels(pixalpha));
1038 int stride = gdk_pixbuf_get_rowstride(pixalpha) / 4;
1039 for (int yr=0; yr<height; yr++) {
1040 for (int xr=0; xr<width; xr++) {
1041 if ((xr==0) || (xr==width-1) || (yr == 0) || (yr == height-1)) {
1042 pixels[yr*stride+xr] = valOutline;
1043 } else {
1044 pixels[yr*stride+xr] = valFill;
1048 for (int c=0;c<cornerSize; c++) {
1049 for (int xr=0;xr<c+1; xr++) {
1050 AllFour(pixels, stride, width, height, xr, c-xr, valEmpty);
1053 for (int xr=1;xr<cornerSize; xr++) {
1054 AllFour(pixels, stride, width, height, xr, cornerSize-xr, valOutline);
1057 // Draw with alpha
1058 gdk_draw_pixbuf(drawable, gc, pixalpha,
1059 0,0, rc.left,rc.top, width,height, GDK_RGB_DITHER_NORMAL, 0, 0);
1061 g_object_unref(pixalpha);
1065 void SurfaceImpl::Ellipse(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
1066 PenColour(back);
1067 gdk_draw_arc(drawable, gc, 1,
1068 rc.left + 1, rc.top + 1,
1069 rc.right - rc.left - 2, rc.bottom - rc.top - 2,
1070 0, 32767);
1072 // The subtraction of 1 here is similar to the case for RectangleDraw
1073 PenColour(fore);
1074 gdk_draw_arc(drawable, gc, 0,
1075 rc.left, rc.top,
1076 rc.right - rc.left - 1, rc.bottom - rc.top - 1,
1077 0, 32767);
1080 void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
1081 if (static_cast<SurfaceImpl &>(surfaceSource).drawable) {
1082 gdk_draw_pixmap(drawable,
1084 static_cast<SurfaceImpl &>(surfaceSource).drawable,
1085 from.x, from.y,
1086 rc.left, rc.top,
1087 rc.right - rc.left, rc.bottom - rc.top);
1091 static size_t UTF8Len(char ch) {
1092 unsigned char uch = static_cast<unsigned char>(ch);
1093 if (uch < 0x80)
1094 return 1;
1095 else if (uch < (0x80 + 0x40 + 0x20))
1096 return 2;
1097 else
1098 return 3;
1101 char *UTF8FromLatin1(const char *s, int &len) {
1102 char *utfForm = new char[len*2+1];
1103 size_t lenU = 0;
1104 for (int i=0;i<len;i++) {
1105 unsigned int uch = static_cast<unsigned char>(s[i]);
1106 if (uch < 0x80) {
1107 utfForm[lenU++] = uch;
1108 } else {
1109 utfForm[lenU++] = static_cast<char>(0xC0 | (uch >> 6));
1110 utfForm[lenU++] = static_cast<char>(0x80 | (uch & 0x3f));
1113 utfForm[lenU] = '\0';
1114 len = lenU;
1115 return utfForm;
1118 static char *UTF8FromIconv(const Converter &conv, const char *s, int &len) {
1119 if (conv) {
1120 char *utfForm = new char[len*3+1];
1121 char *pin = const_cast<char *>(s);
1122 size_t inLeft = len;
1123 char *pout = utfForm;
1124 size_t outLeft = len*3+1;
1125 size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
1126 if (conversions != ((size_t)(-1))) {
1127 *pout = '\0';
1128 len = pout - utfForm;
1129 return utfForm;
1131 delete []utfForm;
1133 return 0;
1136 // Work out how many bytes are in a character by trying to convert using iconv,
1137 // returning the first length that succeeds.
1138 static size_t MultiByteLenFromIconv(const Converter &conv, const char *s, size_t len) {
1139 for (size_t lenMB=1; (lenMB<4) && (lenMB <= len); lenMB++) {
1140 char wcForm[2];
1141 char *pin = const_cast<char *>(s);
1142 size_t inLeft = lenMB;
1143 char *pout = wcForm;
1144 size_t outLeft = 2;
1145 size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
1146 if (conversions != ((size_t)(-1))) {
1147 return lenMB;
1150 return 1;
1153 static char *UTF8FromGdkWChar(GdkWChar *wctext, int wclen) {
1154 char *utfForm = new char[wclen*3+1]; // Maximum of 3 UTF-8 bytes per character
1155 size_t lenU = 0;
1156 for (int i = 0; i < wclen && wctext[i]; i++) {
1157 unsigned int uch = wctext[i];
1158 if (uch < 0x80) {
1159 utfForm[lenU++] = static_cast<char>(uch);
1160 } else if (uch < 0x800) {
1161 utfForm[lenU++] = static_cast<char>(0xC0 | (uch >> 6));
1162 utfForm[lenU++] = static_cast<char>(0x80 | (uch & 0x3f));
1163 } else {
1164 utfForm[lenU++] = static_cast<char>(0xE0 | (uch >> 12));
1165 utfForm[lenU++] = static_cast<char>(0x80 | ((uch >> 6) & 0x3f));
1166 utfForm[lenU++] = static_cast<char>(0x80 | (uch & 0x3f));
1169 utfForm[lenU] = '\0';
1170 return utfForm;
1173 static char *UTF8FromDBCS(const char *s, int &len) {
1174 GdkWChar *wctext = new GdkWChar[len + 1];
1175 GdkWChar *wcp = wctext;
1176 int wclen = gdk_mbstowcs(wcp, s, len);
1177 if (wclen < 1) {
1178 // In the annoying case when non-locale chars in the line.
1179 // e.g. latin1 chars in Japanese locale.
1180 delete []wctext;
1181 return 0;
1184 char *utfForm = UTF8FromGdkWChar(wctext, wclen);
1185 delete []wctext;
1186 len = strlen(utfForm);
1187 return utfForm;
1190 static size_t UTF8CharLength(const char *s) {
1191 const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
1192 unsigned char ch = *us;
1193 if (ch < 0x80) {
1194 return 1;
1195 } else if (ch < 0x80 + 0x40 + 0x20) {
1196 return 2;
1197 } else {
1198 return 3;
1202 // On GTK+, wchar_t is 4 bytes
1204 const int maxLengthTextRun = 10000;
1206 void SurfaceImpl::DrawTextBase(PRectangle rc, Font &font_, int ybase, const char *s, int len,
1207 ColourAllocated fore) {
1208 PenColour(fore);
1209 if (gc && drawable) {
1210 int xText = rc.left;
1211 if (PFont(font_)->pfd) {
1212 char *utfForm = 0;
1213 bool useGFree = false;
1214 if (et == UTF8) {
1215 pango_layout_set_text(layout, s, len);
1216 } else {
1217 if (!utfForm) {
1218 SetConverter(PFont(font_)->characterSet);
1219 utfForm = UTF8FromIconv(conv, s, len);
1221 if (!utfForm) { // iconv failed so try DBCS if DBCS mode
1222 if (et == dbcs) {
1223 // Convert to utf8
1224 utfForm = UTF8FromDBCS(s, len);
1227 if (!utfForm) { // iconv and DBCS failed so treat as Latin1
1228 utfForm = UTF8FromLatin1(s, len);
1230 pango_layout_set_text(layout, utfForm, len);
1232 pango_layout_set_font_description(layout, PFont(font_)->pfd);
1233 #ifdef PANGO_VERSION
1234 PangoLayoutLine *pll = pango_layout_get_line_readonly(layout,0);
1235 #else
1236 PangoLayoutLine *pll = pango_layout_get_line(layout,0);
1237 #endif
1238 gdk_draw_layout_line(drawable, gc, xText, ybase, pll);
1239 if (useGFree) {
1240 g_free(utfForm);
1241 } else {
1242 delete []utfForm;
1244 return;
1246 // Draw text as a series of segments to avoid limitations in X servers
1247 const int segmentLength = 1000;
1248 bool draw8bit = true;
1249 if (et != singleByte) {
1250 GdkWChar wctext[maxLengthTextRun];
1251 if (len >= maxLengthTextRun)
1252 len = maxLengthTextRun-1;
1253 int wclen;
1254 if (et == UTF8) {
1255 wclen = UTF16FromUTF8(s, len,
1256 static_cast<wchar_t *>(static_cast<void *>(wctext)), maxLengthTextRun - 1);
1257 } else { // dbcs, so convert using current locale
1258 char sMeasure[maxLengthTextRun];
1259 memcpy(sMeasure, s, len);
1260 sMeasure[len] = '\0';
1261 wclen = gdk_mbstowcs(
1262 wctext, sMeasure, maxLengthTextRun - 1);
1264 if (wclen > 0) {
1265 draw8bit = false;
1266 wctext[wclen] = L'\0';
1267 GdkWChar *wcp = wctext;
1268 while ((wclen > 0) && (xText < maxCoordinate)) {
1269 int lenDraw = Platform::Minimum(wclen, segmentLength);
1270 gdk_draw_text_wc(drawable, PFont(font_)->pfont, gc,
1271 xText, ybase, wcp, lenDraw);
1272 wclen -= lenDraw;
1273 if (wclen > 0) { // Avoid next calculation if possible as may be expensive
1274 xText += gdk_text_width_wc(PFont(font_)->pfont,
1275 wcp, lenDraw);
1277 wcp += lenDraw;
1281 if (draw8bit) {
1282 while ((len > 0) && (xText < maxCoordinate)) {
1283 int lenDraw = Platform::Minimum(len, segmentLength);
1284 gdk_draw_text(drawable, PFont(font_)->pfont, gc,
1285 xText, ybase, s, lenDraw);
1286 len -= lenDraw;
1287 if (len > 0) { // Avoid next calculation if possible as may be expensive
1288 xText += gdk_text_width(PFont(font_)->pfont, s, lenDraw);
1290 s += lenDraw;
1296 void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, int ybase, const char *s, int len,
1297 ColourAllocated fore, ColourAllocated back) {
1298 FillRectangle(rc, back);
1299 DrawTextBase(rc, font_, ybase, s, len, fore);
1302 // On GTK+, exactly same as DrawTextNoClip
1303 void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, int ybase, const char *s, int len,
1304 ColourAllocated fore, ColourAllocated back) {
1305 FillRectangle(rc, back);
1306 DrawTextBase(rc, font_, ybase, s, len, fore);
1309 void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, int ybase, const char *s, int len,
1310 ColourAllocated fore) {
1311 // Avoid drawing spaces in transparent mode
1312 for (int i=0;i<len;i++) {
1313 if (s[i] != ' ') {
1314 DrawTextBase(rc, font_, ybase, s, len, fore);
1315 return;
1320 class ClusterIterator {
1321 PangoLayoutIter *iter;
1322 PangoRectangle pos;
1323 int lenPositions;
1324 public:
1325 bool finished;
1326 int positionStart;
1327 int position;
1328 int distance;
1329 int curIndex;
1330 ClusterIterator(PangoLayout *layout, int len) : lenPositions(len), finished(false),
1331 positionStart(0), position(0), distance(0), curIndex(0) {
1332 iter = pango_layout_get_iter(layout);
1333 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
1335 ~ClusterIterator() {
1336 pango_layout_iter_free(iter);
1339 void Next() {
1340 positionStart = position;
1341 if (pango_layout_iter_next_cluster(iter)) {
1342 pango_layout_iter_get_cluster_extents(iter, NULL, &pos);
1343 position = PANGO_PIXELS(pos.x);
1344 curIndex = pango_layout_iter_get_index(iter);
1345 } else {
1346 finished = true;
1347 position = PANGO_PIXELS(pos.x + pos.width);
1348 curIndex = lenPositions;
1350 distance = position - positionStart;
1354 void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, int *positions) {
1355 if (font_.GetID()) {
1356 int totalWidth = 0;
1357 const int lenPositions = len;
1358 if (PFont(font_)->pfd) {
1359 if (len == 1) {
1360 int width = PFont(font_)->CharWidth(*s, et);
1361 if (width) {
1362 positions[0] = width;
1363 return;
1366 pango_layout_set_font_description(layout, PFont(font_)->pfd);
1367 if (et == UTF8) {
1368 // Simple and direct as UTF-8 is native Pango encoding
1369 int i = 0;
1370 pango_layout_set_text(layout, s, len);
1371 ClusterIterator iti(layout, lenPositions);
1372 while (!iti.finished) {
1373 iti.Next();
1374 int places = iti.curIndex - i;
1375 while (i < iti.curIndex) {
1376 // Evenly distribute space among bytes of this cluster.
1377 // Would be better to find number of characters and then
1378 // divide evenly between characters with each byte of a character
1379 // being at the same position.
1380 positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places;
1381 i++;
1384 PLATFORM_ASSERT(i == lenPositions);
1385 } else {
1386 int positionsCalculated = 0;
1387 if (et == dbcs) {
1388 SetConverter(PFont(font_)->characterSet);
1389 char *utfForm = UTF8FromIconv(conv, s, len);
1390 if (utfForm) {
1391 // Convert to UTF-8 so can ask Pango for widths, then
1392 // Loop through UTF-8 and DBCS forms, taking account of different
1393 // character byte lengths.
1394 Converter convMeasure("UCS-2", CharacterSetID(characterSet), false);
1395 pango_layout_set_text(layout, utfForm, strlen(utfForm));
1396 int i = 0;
1397 int clusterStart = 0;
1398 ClusterIterator iti(layout, strlen(utfForm));
1399 while (!iti.finished) {
1400 iti.Next();
1401 int clusterEnd = iti.curIndex;
1402 int places = g_utf8_strlen(utfForm + clusterStart, clusterEnd - clusterStart);
1403 int place = 1;
1404 while (clusterStart < clusterEnd) {
1405 size_t lenChar = MultiByteLenFromIconv(convMeasure, s+i, len-i);
1406 while (lenChar--) {
1407 positions[i++] = iti.position - (places - place) * iti.distance / places;
1408 positionsCalculated++;
1410 clusterStart += UTF8CharLength(utfForm+clusterStart);
1411 place++;
1414 delete []utfForm;
1415 PLATFORM_ASSERT(i == lenPositions);
1418 if (positionsCalculated < 1 ) {
1419 // Either Latin1 or DBCS conversion failed so treat as Latin1.
1420 bool useGFree = false;
1421 SetConverter(PFont(font_)->characterSet);
1422 char *utfForm = UTF8FromIconv(conv, s, len);
1423 if (!utfForm) {
1424 utfForm = UTF8FromLatin1(s, len);
1426 pango_layout_set_text(layout, utfForm, len);
1427 int i = 0;
1428 int clusterStart = 0;
1429 // Each Latin1 input character may take 1 or 2 bytes in UTF-8
1430 // and groups of up to 3 may be represented as ligatures.
1431 ClusterIterator iti(layout, strlen(utfForm));
1432 while (!iti.finished) {
1433 iti.Next();
1434 int clusterEnd = iti.curIndex;
1435 int ligatureLength = g_utf8_strlen(utfForm + clusterStart, clusterEnd - clusterStart);
1436 PLATFORM_ASSERT(ligatureLength > 0 && ligatureLength <= 3);
1437 for (int charInLig=0; charInLig<ligatureLength; charInLig++) {
1438 positions[i++] = iti.position - (ligatureLength - 1 - charInLig) * iti.distance / ligatureLength;
1440 clusterStart = clusterEnd;
1442 if (useGFree) {
1443 g_free(utfForm);
1444 } else {
1445 delete []utfForm;
1447 PLATFORM_ASSERT(i == lenPositions);
1450 if (len == 1) {
1451 PFont(font_)->SetCharWidth(*s, positions[0], et);
1453 return;
1455 GdkFont *gf = PFont(font_)->pfont;
1456 bool measure8bit = true;
1457 if (et != singleByte) {
1458 GdkWChar wctext[maxLengthTextRun];
1459 if (len >= maxLengthTextRun)
1460 len = maxLengthTextRun-1;
1461 int wclen;
1462 if (et == UTF8) {
1463 wclen = UTF16FromUTF8(s, len,
1464 static_cast<wchar_t *>(static_cast<void *>(wctext)), maxLengthTextRun - 1);
1465 } else { // dbcsMode, so convert using current locale
1466 char sDraw[maxLengthTextRun];
1467 memcpy(sDraw, s, len);
1468 sDraw[len] = '\0';
1469 wclen = gdk_mbstowcs(
1470 wctext, sDraw, maxLengthTextRun - 1);
1472 if (wclen > 0) {
1473 measure8bit = false;
1474 wctext[wclen] = L'\0';
1475 // Map widths back to utf-8 or DBCS input string
1476 int i = 0;
1477 for (int iU = 0; iU < wclen; iU++) {
1478 int width = gdk_char_width_wc(gf, wctext[iU]);
1479 totalWidth += width;
1480 int lenChar;
1481 if (et == UTF8) {
1482 lenChar = UTF8Len(s[i]);
1483 } else {
1484 lenChar = mblen(s+i, MB_CUR_MAX);
1485 if (lenChar < 0)
1486 lenChar = 1;
1488 while (lenChar--) {
1489 positions[i++] = totalWidth;
1492 while (i < len) { // In case of problems with lengths
1493 positions[i++] = totalWidth;
1497 if (measure8bit) {
1498 // Either Latin1 or conversion failed so treat as Latin1.
1499 for (int i = 0; i < len; i++) {
1500 int width = gdk_char_width(gf, s[i]);
1501 totalWidth += width;
1502 positions[i] = totalWidth;
1505 } else {
1506 // No font so return an ascending range of values
1507 for (int i = 0; i < len; i++) {
1508 positions[i] = i + 1;
1513 int SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
1514 if (font_.GetID()) {
1515 if (PFont(font_)->pfd) {
1516 char *utfForm = 0;
1517 pango_layout_set_font_description(layout, PFont(font_)->pfd);
1518 PangoRectangle pos;
1519 bool useGFree = false;
1520 if (et == UTF8) {
1521 pango_layout_set_text(layout, s, len);
1522 } else {
1523 if (et == dbcs) {
1524 // Convert to utf8
1525 utfForm = UTF8FromDBCS(s, len);
1527 if (!utfForm) { // DBCS failed so treat as iconv
1528 SetConverter(PFont(font_)->characterSet);
1529 utfForm = UTF8FromIconv(conv, s, len);
1531 if (!utfForm) { // g_locale_to_utf8 failed so treat as Latin1
1532 utfForm = UTF8FromLatin1(s, len);
1534 pango_layout_set_text(layout, utfForm, len);
1536 #ifdef PANGO_VERSION
1537 PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout,0);
1538 #else
1539 PangoLayoutLine *pangoLine = pango_layout_get_line(layout,0);
1540 #endif
1541 pango_layout_line_get_extents(pangoLine, NULL, &pos);
1542 if (useGFree) {
1543 g_free(utfForm);
1544 } else {
1545 delete []utfForm;
1547 return PANGO_PIXELS(pos.width);
1549 if (et == UTF8) {
1550 GdkWChar wctext[maxLengthTextRun];
1551 size_t wclen = UTF16FromUTF8(s, len, static_cast<wchar_t *>(static_cast<void *>(wctext)),
1552 sizeof(wctext) / sizeof(GdkWChar) - 1);
1553 wctext[wclen] = L'\0';
1554 return gdk_text_width_wc(PFont(font_)->pfont, wctext, wclen);
1555 } else {
1556 return gdk_text_width(PFont(font_)->pfont, s, len);
1558 } else {
1559 return 1;
1563 int SurfaceImpl::WidthChar(Font &font_, char ch) {
1564 if (font_.GetID()) {
1565 if (PFont(font_)->pfd) {
1566 return WidthText(font_, &ch, 1);
1568 return gdk_char_width(PFont(font_)->pfont, ch);
1569 } else {
1570 return 1;
1574 // Three possible strategies for determining ascent and descent of font:
1575 // 1) Call gdk_string_extents with string containing all letters, numbers and punctuation.
1576 // 2) Use the ascent and descent fields of GdkFont.
1577 // 3) Call gdk_string_extents with string as 1 but also including accented capitals.
1578 // Smallest values given by 1 and largest by 3 with 2 in between.
1579 // Techniques 1 and 2 sometimes chop off extreme portions of ascenders and
1580 // descenders but are mostly OK except for accented characters like Ã… which are
1581 // rarely used in code.
1583 // This string contains a good range of characters to test for size.
1584 //const char largeSizeString[] = "ÂÃÅÄ `~!@#$%^&*()-_=+\\|[]{};:\"\'<,>.?/1234567890"
1585 // "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1586 #ifndef FAST_WAY
1587 const char sizeString[] = "`~!@#$%^&*()-_=+\\|[]{};:\"\'<,>.?/1234567890"
1588 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1589 #endif
1591 int SurfaceImpl::Ascent(Font &font_) {
1592 if (!(font_.GetID()))
1593 return 1;
1594 #ifdef FAST_WAY
1595 FontMutexLock();
1596 int ascent = PFont(font_)->ascent;
1597 if ((ascent == 0) && (PFont(font_)->pfd)) {
1598 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
1599 PFont(font_)->pfd, pango_context_get_language(pcontext));
1600 PFont(font_)->ascent =
1601 PANGO_PIXELS(pango_font_metrics_get_ascent(metrics));
1602 pango_font_metrics_unref(metrics);
1603 ascent = PFont(font_)->ascent;
1605 if ((ascent == 0) && (PFont(font_)->pfont)) {
1606 ascent = PFont(font_)->pfont->ascent;
1608 if (ascent == 0) {
1609 ascent = 1;
1611 FontMutexUnlock();
1612 return ascent;
1613 #else
1615 gint lbearing;
1616 gint rbearing;
1617 gint width;
1618 gint ascent;
1619 gint descent;
1621 gdk_string_extents(PFont(font_)->pfont, sizeString,
1622 &lbearing, &rbearing, &width, &ascent, &descent);
1623 return ascent;
1624 #endif
1627 int SurfaceImpl::Descent(Font &font_) {
1628 if (!(font_.GetID()))
1629 return 1;
1630 #ifdef FAST_WAY
1632 if (PFont(font_)->pfd) {
1633 PangoFontMetrics *metrics = pango_context_get_metrics(pcontext,
1634 PFont(font_)->pfd, pango_context_get_language(pcontext));
1635 int descent = PANGO_PIXELS(pango_font_metrics_get_descent(metrics));
1636 pango_font_metrics_unref(metrics);
1637 return descent;
1639 return PFont(font_)->pfont->descent;
1640 #else
1642 gint lbearing;
1643 gint rbearing;
1644 gint width;
1645 gint ascent;
1646 gint descent;
1648 gdk_string_extents(PFont(font_)->pfont, sizeString,
1649 &lbearing, &rbearing, &width, &ascent, &descent);
1650 return descent;
1651 #endif
1654 int SurfaceImpl::InternalLeading(Font &) {
1655 return 0;
1658 int SurfaceImpl::ExternalLeading(Font &) {
1659 return 0;
1662 int SurfaceImpl::Height(Font &font_) {
1663 return Ascent(font_) + Descent(font_);
1666 int SurfaceImpl::AverageCharWidth(Font &font_) {
1667 return WidthChar(font_, 'n');
1670 int SurfaceImpl::SetPalette(Palette *, bool) {
1671 // Handled in palette allocation for GTK so this does nothing
1672 return 0;
1675 void SurfaceImpl::SetClip(PRectangle rc) {
1676 GdkRectangle area = {rc.left, rc.top,
1677 rc.right - rc.left, rc.bottom - rc.top};
1678 gdk_gc_set_clip_rectangle(gc, &area);
1681 void SurfaceImpl::FlushCachedState() {}
1683 void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
1684 if (unicodeMode_)
1685 et = UTF8;
1688 void SurfaceImpl::SetDBCSMode(int codePage) {
1689 if (codePage && (codePage != SC_CP_UTF8))
1690 et = dbcs;
1693 Surface *Surface::Allocate() {
1694 return new SurfaceImpl;
1697 Window::~Window() {}
1699 void Window::Destroy() {
1700 if (wid)
1701 gtk_widget_destroy(GTK_WIDGET(wid));
1702 wid = 0;
1705 bool Window::HasFocus() {
1706 return GTK_WIDGET_HAS_FOCUS(wid);
1709 PRectangle Window::GetPosition() {
1710 // Before any size allocated pretend its 1000 wide so not scrolled
1711 PRectangle rc(0, 0, 1000, 1000);
1712 if (wid) {
1713 rc.left = PWidget(wid)->allocation.x;
1714 rc.top = PWidget(wid)->allocation.y;
1715 if (PWidget(wid)->allocation.width > 20) {
1716 rc.right = rc.left + PWidget(wid)->allocation.width;
1717 rc.bottom = rc.top + PWidget(wid)->allocation.height;
1720 return rc;
1723 void Window::SetPosition(PRectangle rc) {
1724 GtkAllocation alloc;
1725 alloc.x = rc.left;
1726 alloc.y = rc.top;
1727 alloc.width = rc.Width();
1728 alloc.height = rc.Height();
1729 gtk_widget_size_allocate(PWidget(wid), &alloc);
1732 void Window::SetPositionRelative(PRectangle rc, Window relativeTo) {
1733 int ox = 0;
1734 int oy = 0;
1735 gdk_window_get_origin(PWidget(relativeTo.wid)->window, &ox, &oy);
1736 ox += rc.left;
1737 if (ox < 0)
1738 ox = 0;
1739 oy += rc.top;
1740 if (oy < 0)
1741 oy = 0;
1743 /* do some corrections to fit into screen */
1744 int sizex = rc.right - rc.left;
1745 int sizey = rc.bottom - rc.top;
1746 int screenWidth = gdk_screen_width();
1747 int screenHeight = gdk_screen_height();
1748 if (sizex > screenWidth)
1749 ox = 0; /* the best we can do */
1750 else if (ox + sizex > screenWidth)
1751 ox = screenWidth - sizex;
1752 if (oy + sizey > screenHeight)
1753 oy = screenHeight - sizey;
1755 gtk_window_move(GTK_WINDOW(PWidget(wid)), ox, oy);
1757 gtk_widget_set_usize(PWidget(wid), sizex, sizey);
1760 PRectangle Window::GetClientPosition() {
1761 // On GTK+, the client position is the window position
1762 return GetPosition();
1765 void Window::Show(bool show) {
1766 if (show)
1767 gtk_widget_show(PWidget(wid));
1770 void Window::InvalidateAll() {
1771 if (wid) {
1772 gtk_widget_queue_draw(PWidget(wid));
1776 void Window::InvalidateRectangle(PRectangle rc) {
1777 if (wid) {
1778 gtk_widget_queue_draw_area(PWidget(wid),
1779 rc.left, rc.top,
1780 rc.right - rc.left, rc.bottom - rc.top);
1784 void Window::SetFont(Font &) {
1785 // Can not be done generically but only needed for ListBox
1788 void Window::SetCursor(Cursor curs) {
1789 // We don't set the cursor to same value numerous times under gtk because
1790 // it stores the cursor in the window once it's set
1791 if (curs == cursorLast)
1792 return;
1794 cursorLast = curs;
1795 GdkCursor *gdkCurs;
1796 switch (curs) {
1797 case cursorText:
1798 gdkCurs = gdk_cursor_new(GDK_XTERM);
1799 break;
1800 case cursorArrow:
1801 gdkCurs = gdk_cursor_new(GDK_LEFT_PTR);
1802 break;
1803 case cursorUp:
1804 gdkCurs = gdk_cursor_new(GDK_CENTER_PTR);
1805 break;
1806 case cursorWait:
1807 gdkCurs = gdk_cursor_new(GDK_WATCH);
1808 break;
1809 case cursorHand:
1810 gdkCurs = gdk_cursor_new(GDK_HAND2);
1811 break;
1812 case cursorReverseArrow:
1813 gdkCurs = gdk_cursor_new(GDK_RIGHT_PTR);
1814 break;
1815 default:
1816 gdkCurs = gdk_cursor_new(GDK_LEFT_PTR);
1817 cursorLast = cursorArrow;
1818 break;
1821 if (PWidget(wid)->window)
1822 gdk_window_set_cursor(PWidget(wid)->window, gdkCurs);
1823 gdk_cursor_destroy(gdkCurs);
1826 void Window::SetTitle(const char *s) {
1827 gtk_window_set_title(GTK_WINDOW(wid), s);
1830 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1831 gdk window coordinates */
1832 PRectangle Window::GetMonitorRect(Point pt) {
1833 gint x_offset, y_offset;
1834 pt = pt;
1836 gdk_window_get_origin(PWidget(wid)->window, &x_offset, &y_offset);
1838 #if GTK_CHECK_VERSION(2,2,0)
1839 // GTK+ 2.2+
1841 GdkScreen* screen;
1842 gint monitor_num;
1843 GdkRectangle rect;
1845 screen = gtk_widget_get_screen(PWidget(wid));
1846 monitor_num = gdk_screen_get_monitor_at_point(screen, pt.x + x_offset, pt.y + y_offset);
1847 gdk_screen_get_monitor_geometry(screen, monitor_num, &rect);
1848 rect.x -= x_offset;
1849 rect.y -= y_offset;
1850 return PRectangle(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
1852 #else
1853 return PRectangle(-x_offset, -y_offset, (-x_offset) + gdk_screen_width(),
1854 (-y_offset) + gdk_screen_height());
1855 #endif
1858 struct ListImage {
1859 const char *xpm_data;
1860 GdkPixbuf *pixbuf;
1863 static void list_image_free(gpointer, gpointer value, gpointer) {
1864 ListImage *list_image = (ListImage *) value;
1865 if (list_image->pixbuf)
1866 gdk_pixbuf_unref (list_image->pixbuf);
1867 g_free(list_image);
1870 ListBox::ListBox() {
1873 ListBox::~ListBox() {
1876 enum {
1877 PIXBUF_COLUMN,
1878 TEXT_COLUMN,
1879 N_COLUMNS
1882 class ListBoxX : public ListBox {
1883 WindowID list;
1884 WindowID scroller;
1885 void *pixhash;
1886 GtkCellRenderer* pixbuf_renderer;
1887 XPMSet xset;
1888 int desiredVisibleRows;
1889 unsigned int maxItemCharacters;
1890 unsigned int aveCharWidth;
1891 public:
1892 CallBackAction doubleClickAction;
1893 void *doubleClickActionData;
1895 ListBoxX() : list(0), pixhash(NULL), pixbuf_renderer(0),
1896 desiredVisibleRows(5), maxItemCharacters(0),
1897 aveCharWidth(1), doubleClickAction(NULL), doubleClickActionData(NULL) {
1899 virtual ~ListBoxX() {
1900 if (pixhash) {
1901 g_hash_table_foreach((GHashTable *) pixhash, list_image_free, NULL);
1902 g_hash_table_destroy((GHashTable *) pixhash);
1905 virtual void SetFont(Font &font);
1906 virtual void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_);
1907 virtual void SetAverageCharWidth(int width);
1908 virtual void SetVisibleRows(int rows);
1909 virtual int GetVisibleRows() const;
1910 virtual PRectangle GetDesiredRect();
1911 virtual int CaretFromEdge();
1912 virtual void Clear();
1913 virtual void Append(char *s, int type = -1);
1914 virtual int Length();
1915 virtual void Select(int n);
1916 virtual int GetSelection();
1917 virtual int Find(const char *prefix);
1918 virtual void GetValue(int n, char *value, int len);
1919 virtual void RegisterImage(int type, const char *xpm_data);
1920 virtual void ClearRegisteredImages();
1921 virtual void SetDoubleClickAction(CallBackAction action, void *data) {
1922 doubleClickAction = action;
1923 doubleClickActionData = data;
1925 virtual void SetList(const char *listText, char separator, char typesep);
1928 ListBox *ListBox::Allocate() {
1929 ListBoxX *lb = new ListBoxX();
1930 return lb;
1933 static gboolean ButtonPress(GtkWidget *, GdkEventButton* ev, gpointer p) {
1934 try {
1935 ListBoxX* lb = reinterpret_cast<ListBoxX*>(p);
1936 if (ev->type == GDK_2BUTTON_PRESS && lb->doubleClickAction != NULL) {
1937 lb->doubleClickAction(lb->doubleClickActionData);
1938 return TRUE;
1941 } catch (...) {
1942 // No pointer back to Scintilla to save status
1944 return FALSE;
1947 /* Change the active color to the selected color so the listbox uses the color
1948 scheme that it would use if it had the focus. */
1949 static void StyleSet(GtkWidget *w, GtkStyle*, void*) {
1950 GtkStyle* style;
1952 g_return_if_fail(w != NULL);
1954 /* Copy the selected color to active. Note that the modify calls will cause
1955 recursive calls to this function after the value is updated and w->style to
1956 be set to a new object */
1957 style = gtk_widget_get_style(w);
1958 if (style == NULL)
1959 return;
1960 if (!gdk_color_equal(&style->base[GTK_STATE_SELECTED], &style->base[GTK_STATE_ACTIVE]))
1961 gtk_widget_modify_base(w, GTK_STATE_ACTIVE, &style->base[GTK_STATE_SELECTED]);
1963 style = gtk_widget_get_style(w);
1964 if (style == NULL)
1965 return;
1966 if (!gdk_color_equal(&style->text[GTK_STATE_SELECTED], &style->text[GTK_STATE_ACTIVE]))
1967 gtk_widget_modify_text(w, GTK_STATE_ACTIVE, &style->text[GTK_STATE_SELECTED]);
1970 void ListBoxX::Create(Window &, int, Point, int, bool) {
1971 wid = gtk_window_new(GTK_WINDOW_POPUP);
1973 GtkWidget *frame = gtk_frame_new(NULL);
1974 gtk_widget_show(frame);
1975 gtk_container_add(GTK_CONTAINER(GetID()), frame);
1976 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1977 gtk_container_set_border_width(GTK_CONTAINER(frame), 0);
1979 scroller = gtk_scrolled_window_new(NULL, NULL);
1980 gtk_container_set_border_width(GTK_CONTAINER(scroller), 0);
1981 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
1982 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1983 gtk_container_add(GTK_CONTAINER(frame), PWidget(scroller));
1984 gtk_widget_show(PWidget(scroller));
1986 /* Tree and its model */
1987 GtkListStore *store =
1988 gtk_list_store_new(N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING);
1990 list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1991 g_signal_connect(G_OBJECT(list), "style-set", G_CALLBACK(StyleSet), NULL);
1993 GtkTreeSelection *selection =
1994 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1995 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1996 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
1997 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list), FALSE);
1999 /* Columns */
2000 GtkTreeViewColumn *column = gtk_tree_view_column_new();
2001 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2002 gtk_tree_view_column_set_title(column, "Autocomplete");
2004 pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
2005 gtk_cell_renderer_set_fixed_size(pixbuf_renderer, 0, -1);
2006 gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE);
2007 gtk_tree_view_column_add_attribute(column, pixbuf_renderer,
2008 "pixbuf", PIXBUF_COLUMN);
2010 GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
2011 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer), 1);
2012 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2013 gtk_tree_view_column_add_attribute(column, renderer,
2014 "text", TEXT_COLUMN);
2016 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
2017 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list), "fixed-height-mode"))
2018 g_object_set(G_OBJECT(list), "fixed-height-mode", TRUE, NULL);
2020 GtkWidget *wid = PWidget(list); // No code inside the G_OBJECT macro
2021 gtk_container_add(GTK_CONTAINER(PWidget(scroller)), wid);
2022 gtk_widget_show(wid);
2023 g_signal_connect(G_OBJECT(wid), "button_press_event",
2024 G_CALLBACK(ButtonPress), this);
2025 gtk_widget_realize(PWidget(wid));
2028 void ListBoxX::SetFont(Font &scint_font) {
2029 // Only do for Pango font as there have been crashes for GDK fonts
2030 if (Created() && PFont(scint_font)->pfd) {
2031 // Current font is Pango font
2032 gtk_widget_modify_font(PWidget(list), PFont(scint_font)->pfd);
2036 void ListBoxX::SetAverageCharWidth(int width) {
2037 aveCharWidth = width;
2040 void ListBoxX::SetVisibleRows(int rows) {
2041 desiredVisibleRows = rows;
2044 int ListBoxX::GetVisibleRows() const {
2045 return desiredVisibleRows;
2048 PRectangle ListBoxX::GetDesiredRect() {
2049 // Before any size allocated pretend its 100 wide so not scrolled
2050 PRectangle rc(0, 0, 100, 100);
2051 if (wid) {
2052 int rows = Length();
2053 if ((rows == 0) || (rows > desiredVisibleRows))
2054 rows = desiredVisibleRows;
2056 GtkRequisition req;
2057 int height;
2059 // First calculate height of the clist for our desired visible
2060 // row count otherwise it tries to expand to the total # of rows
2061 // Get cell height
2062 int row_width=0;
2063 int row_height=0;
2064 GtkTreeViewColumn * column =
2065 gtk_tree_view_get_column(GTK_TREE_VIEW(list), 0);
2066 gtk_tree_view_column_cell_get_size(column, NULL,
2067 NULL, NULL, &row_width, &row_height);
2068 int ythickness = PWidget(list)->style->ythickness;
2069 height = (rows * row_height
2070 + 2 * (ythickness
2071 + GTK_CONTAINER(PWidget(list))->border_width + 1));
2072 gtk_widget_set_usize(GTK_WIDGET(PWidget(list)), -1, height);
2074 // Get the size of the scroller because we set usize on the window
2075 gtk_widget_size_request(GTK_WIDGET(scroller), &req);
2076 rc.right = req.width;
2077 rc.bottom = req.height;
2079 gtk_widget_set_usize(GTK_WIDGET(list), -1, -1);
2080 int width = maxItemCharacters;
2081 if (width < 12)
2082 width = 12;
2083 rc.right = width * (aveCharWidth + aveCharWidth / 3);
2084 if (Length() > rows)
2085 rc.right = rc.right + 16;
2087 return rc;
2090 int ListBoxX::CaretFromEdge() {
2091 gint renderer_width, renderer_height;
2092 gtk_cell_renderer_get_fixed_size(pixbuf_renderer, &renderer_width,
2093 &renderer_height);
2094 return 4 + renderer_width;
2097 void ListBoxX::Clear() {
2098 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
2099 gtk_list_store_clear(GTK_LIST_STORE(model));
2100 maxItemCharacters = 0;
2103 static void init_pixmap(ListImage *list_image) {
2104 const char *textForm = list_image->xpm_data;
2105 const char * const * xpm_lineform = reinterpret_cast<const char * const *>(textForm);
2106 const char **xpm_lineformfromtext = 0;
2107 // The XPM data can be either in atext form as will be read from a file
2108 // or in a line form (array of char *) as will be used for images defined in code.
2109 // Test for text form and convert to line form
2110 if ((0 == memcmp(textForm, "/* X", 4)) && (0 == memcmp(textForm, "/* XPM */", 9))) {
2111 // Test done is two parts to avoid possibility of overstepping the memory
2112 // if memcmp implemented strangely. Must be 4 bytes at least at destination.
2113 xpm_lineformfromtext = XPM::LinesFormFromTextForm(textForm);
2114 xpm_lineform = xpm_lineformfromtext;
2117 // Drop any existing pixmap/bitmap as data may have changed
2118 if (list_image->pixbuf)
2119 gdk_pixbuf_unref(list_image->pixbuf);
2120 list_image->pixbuf =
2121 gdk_pixbuf_new_from_xpm_data((const gchar**)xpm_lineform);
2122 delete []xpm_lineformfromtext;
2125 #define SPACING 5
2127 void ListBoxX::Append(char *s, int type) {
2128 ListImage *list_image = NULL;
2129 if ((type >= 0) && pixhash) {
2130 list_image = (ListImage *) g_hash_table_lookup((GHashTable *) pixhash
2131 , (gconstpointer) GINT_TO_POINTER(type));
2133 GtkTreeIter iter;
2134 GtkListStore *store =
2135 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
2136 gtk_list_store_append(GTK_LIST_STORE(store), &iter);
2137 if (list_image) {
2138 if (NULL == list_image->pixbuf)
2139 init_pixmap(list_image);
2140 if (list_image->pixbuf) {
2141 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
2142 PIXBUF_COLUMN, list_image->pixbuf,
2143 TEXT_COLUMN, s, -1);
2145 gint pixbuf_width = gdk_pixbuf_get_width(list_image->pixbuf);
2146 gint renderer_height, renderer_width;
2147 gtk_cell_renderer_get_fixed_size(pixbuf_renderer,
2148 &renderer_width, &renderer_height);
2149 if (pixbuf_width > renderer_width)
2150 gtk_cell_renderer_set_fixed_size(pixbuf_renderer,
2151 pixbuf_width, -1);
2152 } else {
2153 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
2154 TEXT_COLUMN, s, -1);
2156 } else {
2157 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
2158 TEXT_COLUMN, s, -1);
2160 size_t len = strlen(s);
2161 if (maxItemCharacters < len)
2162 maxItemCharacters = len;
2165 int ListBoxX::Length() {
2166 if (wid)
2167 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
2168 (GTK_TREE_VIEW(list)), NULL);
2169 return 0;
2172 void ListBoxX::Select(int n) {
2173 GtkTreeIter iter;
2174 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
2175 GtkTreeSelection *selection =
2176 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
2178 if (n < 0) {
2179 gtk_tree_selection_unselect_all(selection);
2180 return;
2183 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
2184 if (valid) {
2185 gtk_tree_selection_select_iter(selection, &iter);
2187 // Move the scrollbar to show the selection.
2188 int total = Length();
2189 GtkAdjustment *adj =
2190 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list));
2191 gfloat value = ((gfloat)n / total) * (adj->upper - adj->lower)
2192 + adj->lower - adj->page_size / 2;
2194 // Get cell height
2195 int row_width;
2196 int row_height;
2197 GtkTreeViewColumn * column =
2198 gtk_tree_view_get_column(GTK_TREE_VIEW(list), 0);
2199 gtk_tree_view_column_cell_get_size(column, NULL, NULL,
2200 NULL, &row_width, &row_height);
2202 int rows = Length();
2203 if ((rows == 0) || (rows > desiredVisibleRows))
2204 rows = desiredVisibleRows;
2205 if (rows & 0x1) {
2206 // Odd rows to display -- We are now in the middle.
2207 // Align it so that we don't chop off rows.
2208 value += (gfloat)row_height / 2.0;
2210 // Clamp it.
2211 value = (value < 0)? 0 : value;
2212 value = (value > (adj->upper - adj->page_size))?
2213 (adj->upper - adj->page_size) : value;
2215 // Set it.
2216 gtk_adjustment_set_value(adj, value);
2217 } else {
2218 gtk_tree_selection_unselect_all(selection);
2222 int ListBoxX::GetSelection() {
2223 GtkTreeIter iter;
2224 GtkTreeModel *model;
2225 GtkTreeSelection *selection;
2226 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
2227 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
2228 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
2229 int *indices = gtk_tree_path_get_indices(path);
2230 // Don't free indices.
2231 if (indices)
2232 return indices[0];
2234 return -1;
2237 int ListBoxX::Find(const char *prefix) {
2238 GtkTreeIter iter;
2239 GtkTreeModel *model =
2240 gtk_tree_view_get_model(GTK_TREE_VIEW(list));
2241 bool valid = gtk_tree_model_get_iter_first(model, &iter) != FALSE;
2242 int i = 0;
2243 while(valid) {
2244 gchar *s;
2245 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &s, -1);
2246 if (s && (0 == strncmp(prefix, s, strlen(prefix)))) {
2247 return i;
2249 valid = gtk_tree_model_iter_next(model, &iter) != FALSE;
2250 i++;
2252 return -1;
2255 void ListBoxX::GetValue(int n, char *value, int len) {
2256 char *text = NULL;
2257 GtkTreeIter iter;
2258 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
2259 bool valid = gtk_tree_model_iter_nth_child(model, &iter, NULL, n) != FALSE;
2260 if (valid) {
2261 gtk_tree_model_get(model, &iter, TEXT_COLUMN, &text, -1);
2263 if (text && len > 0) {
2264 strncpy(value, text, len);
2265 value[len - 1] = '\0';
2266 } else {
2267 value[0] = '\0';
2271 // g_return_if_fail causes unnecessary compiler warning in release compile.
2272 #ifdef _MSC_VER
2273 #pragma warning(disable: 4127)
2274 #endif
2276 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
2277 g_return_if_fail(xpm_data);
2279 // Saved and use the saved copy so caller's copy can disappear.
2280 xset.Add(type, xpm_data);
2281 XPM *pxpm = xset.Get(type);
2282 xpm_data = reinterpret_cast<const char *>(pxpm->InLinesForm());
2284 if (!pixhash) {
2285 pixhash = g_hash_table_new(g_direct_hash, g_direct_equal);
2287 ListImage *list_image = (ListImage *) g_hash_table_lookup((GHashTable *) pixhash,
2288 (gconstpointer) GINT_TO_POINTER(type));
2289 if (list_image) {
2290 // Drop icon already registered
2291 if (list_image->pixbuf)
2292 gdk_pixbuf_unref(list_image->pixbuf);
2293 list_image->pixbuf = NULL;
2294 list_image->xpm_data = xpm_data;
2295 } else {
2296 list_image = g_new0(ListImage, 1);
2297 list_image->xpm_data = xpm_data;
2298 g_hash_table_insert((GHashTable *) pixhash, GINT_TO_POINTER(type),
2299 (gpointer) list_image);
2303 void ListBoxX::ClearRegisteredImages() {
2304 xset.Clear();
2307 void ListBoxX::SetList(const char *listText, char separator, char typesep) {
2308 Clear();
2309 int count = strlen(listText) + 1;
2310 char *words = new char[count];
2311 if (words) {
2312 memcpy(words, listText, count);
2313 char *startword = words;
2314 char *numword = NULL;
2315 int i = 0;
2316 for (; words[i]; i++) {
2317 if (words[i] == separator) {
2318 words[i] = '\0';
2319 if (numword)
2320 *numword = '\0';
2321 Append(startword, numword?atoi(numword + 1):-1);
2322 startword = words + i + 1;
2323 numword = NULL;
2324 } else if (words[i] == typesep) {
2325 numword = words + i;
2328 if (startword) {
2329 if (numword)
2330 *numword = '\0';
2331 Append(startword, numword?atoi(numword + 1):-1);
2333 delete []words;
2337 Menu::Menu() : mid(0) {}
2339 void Menu::CreatePopUp() {
2340 Destroy();
2341 mid = gtk_item_factory_new(GTK_TYPE_MENU, "<main>", NULL);
2344 void Menu::Destroy() {
2345 if (mid)
2346 g_object_unref(G_OBJECT(mid));
2347 mid = 0;
2350 void Menu::Show(Point pt, Window &) {
2351 int screenHeight = gdk_screen_height();
2352 int screenWidth = gdk_screen_width();
2353 GtkItemFactory *factory = reinterpret_cast<GtkItemFactory *>(mid);
2354 GtkWidget *widget = gtk_item_factory_get_widget(factory, "<main>");
2355 gtk_widget_show_all(widget);
2356 GtkRequisition requisition;
2357 gtk_widget_size_request(widget, &requisition);
2358 if ((pt.x + requisition.width) > screenWidth) {
2359 pt.x = screenWidth - requisition.width;
2361 if ((pt.y + requisition.height) > screenHeight) {
2362 pt.y = screenHeight - requisition.height;
2364 gtk_item_factory_popup(factory, pt.x - 4, pt.y - 4, 3,
2365 gtk_get_current_event_time());
2368 ElapsedTime::ElapsedTime() {
2369 GTimeVal curTime;
2370 g_get_current_time(&curTime);
2371 bigBit = curTime.tv_sec;
2372 littleBit = curTime.tv_usec;
2375 class DynamicLibraryImpl : public DynamicLibrary {
2376 protected:
2377 GModule* m;
2378 public:
2379 DynamicLibraryImpl(const char *modulePath) {
2380 m = g_module_open(modulePath, G_MODULE_BIND_LAZY);
2383 virtual ~DynamicLibraryImpl() {
2384 if (m != NULL)
2385 g_module_close(m);
2388 // Use g_module_symbol to get a pointer to the relevant function.
2389 virtual Function FindFunction(const char *name) {
2390 if (m != NULL) {
2391 gpointer fn_address = NULL;
2392 gboolean status = g_module_symbol(m, name, &fn_address);
2393 if (status)
2394 return static_cast<Function>(fn_address);
2395 else
2396 return NULL;
2397 } else
2398 return NULL;
2401 virtual bool IsValid() {
2402 return m != NULL;
2406 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
2407 return static_cast<DynamicLibrary *>( new DynamicLibraryImpl(modulePath) );
2410 double ElapsedTime::Duration(bool reset) {
2411 GTimeVal curTime;
2412 g_get_current_time(&curTime);
2413 long endBigBit = curTime.tv_sec;
2414 long endLittleBit = curTime.tv_usec;
2415 double result = 1000000.0 * (endBigBit - bigBit);
2416 result += endLittleBit - littleBit;
2417 result /= 1000000.0;
2418 if (reset) {
2419 bigBit = endBigBit;
2420 littleBit = endLittleBit;
2422 return result;
2425 ColourDesired Platform::Chrome() {
2426 return ColourDesired(0xe0, 0xe0, 0xe0);
2429 ColourDesired Platform::ChromeHighlight() {
2430 return ColourDesired(0xff, 0xff, 0xff);
2433 const char *Platform::DefaultFont() {
2434 #ifdef G_OS_WIN32
2435 return "Lucida Console";
2436 #else
2437 return "!Sans";
2438 #endif
2441 int Platform::DefaultFontSize() {
2442 #ifdef G_OS_WIN32
2443 return 10;
2444 #else
2445 return 12;
2446 #endif
2449 unsigned int Platform::DoubleClickTime() {
2450 return 500; // Half a second
2453 bool Platform::MouseButtonBounce() {
2454 return true;
2457 void Platform::DebugDisplay(const char *s) {
2458 fprintf(stderr, "%s", s);
2461 bool Platform::IsKeyDown(int) {
2462 // TODO: discover state of keys in GTK+/X
2463 return false;
2466 long Platform::SendScintilla(
2467 WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
2468 return scintilla_send_message(SCINTILLA(w), msg, wParam, lParam);
2471 long Platform::SendScintillaPointer(
2472 WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
2473 return scintilla_send_message(SCINTILLA(w), msg, wParam,
2474 reinterpret_cast<sptr_t>(lParam));
2477 bool Platform::IsDBCSLeadByte(int codePage, char ch) {
2478 // Byte ranges found in Wikipedia articles with relevant search strings in each case
2479 unsigned char uch = static_cast<unsigned char>(ch);
2480 switch (codePage) {
2481 case 932:
2482 // Shift_jis
2483 return ((uch >= 0x81) && (uch <= 0x9F)) ||
2484 ((uch >= 0xE0) && (uch <= 0xEF));
2485 case 936:
2486 // GBK
2487 return (uch >= 0x81) && (uch <= 0xFE);
2488 case 950:
2489 // Big5
2490 return (uch >= 0x81) && (uch <= 0xFE);
2491 // Korean EUC-KR may be code page 949.
2493 return false;
2496 int Platform::DBCSCharLength(int codePage, const char *s) {
2497 if (codePage == 932 || codePage == 936 || codePage == 950) {
2498 return IsDBCSLeadByte(codePage, s[0]) ? 2 : 1;
2499 } else {
2500 int bytes = mblen(s, MB_CUR_MAX);
2501 if (bytes >= 1)
2502 return bytes;
2503 else
2504 return 1;
2508 int Platform::DBCSCharMaxLength() {
2509 return MB_CUR_MAX;
2510 //return 2;
2513 // These are utility functions not really tied to a platform
2515 int Platform::Minimum(int a, int b) {
2516 if (a < b)
2517 return a;
2518 else
2519 return b;
2522 int Platform::Maximum(int a, int b) {
2523 if (a > b)
2524 return a;
2525 else
2526 return b;
2529 //#define TRACE
2531 #ifdef TRACE
2532 void Platform::DebugPrintf(const char *format, ...) {
2533 char buffer[2000];
2534 va_list pArguments;
2535 va_start(pArguments, format);
2536 vsprintf(buffer, format, pArguments);
2537 va_end(pArguments);
2538 Platform::DebugDisplay(buffer);
2540 #else
2541 void Platform::DebugPrintf(const char *, ...) {}
2543 #endif
2545 // Not supported for GTK+
2546 static bool assertionPopUps = true;
2548 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
2549 bool ret = assertionPopUps;
2550 assertionPopUps = assertionPopUps_;
2551 return ret;
2554 void Platform::Assert(const char *c, const char *file, int line) {
2555 char buffer[2000];
2556 sprintf(buffer, "Assertion [%s] failed at %s %d", c, file, line);
2557 strcat(buffer, "\r\n");
2558 Platform::DebugDisplay(buffer);
2559 abort();
2562 int Platform::Clamp(int val, int minVal, int maxVal) {
2563 if (val > maxVal)
2564 val = maxVal;
2565 if (val < minVal)
2566 val = minVal;
2567 return val;
2570 void Platform_Initialise() {
2571 FontMutexAllocate();
2574 void Platform_Finalise() {
2575 FontMutexFree();