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.
20 #include <gdk/gdkkeysyms.h>
24 #include "Scintilla.h"
25 #include "ScintillaWidget.h"
26 #include "StringCopy.h"
28 #include "UniConversion.h"
30 #if defined(__clang__)
31 // Clang 3.0 incorrectly displays sentinel warnings. Fixed by clang 3.1.
32 #pragma GCC diagnostic ignored "-Wsentinel"
35 /* GLIB must be compiled with thread support, otherwise we
36 will bail on trying to use locks, and that could lead to
37 problems for someone. `glib-config --libs gthread` needs
38 to be used to get the glib libraries for linking, otherwise
39 g_thread_init will fail */
40 #define USE_LOCK defined(G_THREADS_ENABLED) && !defined(G_THREADS_IMPL_NONE)
42 #include "Converter.h"
44 #if GTK_CHECK_VERSION(2,20,0)
45 #define IS_WIDGET_FOCUSSED(w) (gtk_widget_has_focus(GTK_WIDGET(w)))
47 #define IS_WIDGET_FOCUSSED(w) (GTK_WIDGET_HAS_FOCUS(w))
50 static const double kPi
= 3.14159265358979323846;
52 // The Pango version guard for pango_units_from_double and pango_units_to_double
53 // is more complex than simply implementing these here.
55 static int pangoUnitsFromDouble(double d
) {
56 return static_cast<int>(d
* PANGO_SCALE
+ 0.5);
59 static double doubleFromPangoUnits(int pu
) {
60 return static_cast<double>(pu
) / PANGO_SCALE
;
63 static cairo_surface_t
*CreateSimilarSurface(GdkWindow
*window
, cairo_content_t content
, int width
, int height
) {
64 #if GTK_CHECK_VERSION(2,22,0)
65 return gdk_window_create_similar_surface(window
, content
, width
, height
);
67 cairo_surface_t
*window_surface
, *surface
;
69 g_return_val_if_fail(GDK_IS_WINDOW(window
), NULL
);
71 window_surface
= GDK_DRAWABLE_GET_CLASS(window
)->ref_cairo_surface(window
);
73 surface
= cairo_surface_create_similar(window_surface
, content
, width
, height
);
75 cairo_surface_destroy(window_surface
);
81 static GdkWindow
*WindowFromWidget(GtkWidget
*w
) {
82 #if GTK_CHECK_VERSION(3,0,0)
83 return gtk_widget_get_window(w
);
90 // Ignore unreferenced local functions in GTK+ headers
91 #pragma warning(disable: 4505)
95 using namespace Scintilla
;
98 enum encodingType
{ singleByte
, UTF8
, dbcs
};
109 static GMutex
*fontMutex
= NULL
;
111 static void InitializeGLIBThreads() {
112 #if !GLIB_CHECK_VERSION(2,31,0)
113 if (!g_thread_supported()) {
120 static void FontMutexAllocate() {
123 InitializeGLIBThreads();
124 #if GLIB_CHECK_VERSION(2,31,0)
125 fontMutex
= g_new(GMutex
, 1);
126 g_mutex_init(fontMutex
);
128 fontMutex
= g_mutex_new();
134 static void FontMutexFree() {
137 #if GLIB_CHECK_VERSION(2,31,0)
138 g_mutex_clear(fontMutex
);
141 g_mutex_free(fontMutex
);
148 static void FontMutexLock() {
150 g_mutex_lock(fontMutex
);
154 static void FontMutexUnlock() {
157 g_mutex_unlock(fontMutex
);
162 // Holds a PangoFontDescription*.
164 XYPOSITION width
[128];
168 PangoFontDescription
*pfd
;
170 FontHandle() : et(singleByte
), ascent(0), pfd(0), characterSet(-1) {
173 FontHandle(PangoFontDescription
*pfd_
, int characterSet_
) {
177 characterSet
= characterSet_
;
182 pango_font_description_free(pfd
);
185 void ResetWidths(encodingType et_
) {
187 for (int i
=0; i
<=127; i
++) {
191 XYPOSITION
CharWidth(unsigned char ch
, encodingType et_
) const {
194 if ((ch
<= 127) && (et
== et_
)) {
200 void SetCharWidth(unsigned char ch
, XYPOSITION w
, encodingType et_
) {
212 // X has a 16 bit coordinate space, so stop drawing here to avoid wrapping
213 static const int maxCoordinate
= 32000;
215 static FontHandle
*PFont(Font
&f
) {
216 return reinterpret_cast<FontHandle
*>(f
.GetID());
219 static GtkWidget
*PWidget(WindowID wid
) {
220 return reinterpret_cast<GtkWidget
*>(wid
);
223 Point
Point::FromLong(long lpoint
) {
225 Platform::LowShortFromLong(lpoint
),
226 Platform::HighShortFromLong(lpoint
));
229 static void SetLogFont(LOGFONT
&lf
, const char *faceName
, int characterSet
, float size
, int weight
, bool italic
) {
234 lf
.characterSet
= characterSet
;
235 StringCopy(lf
.faceName
, faceName
);
239 * Create a hash from the parameters for a font to allow easy checking for identity.
240 * If one font is the same as another, its hash will be the same, but if the hash is the
241 * same then they may still be different.
243 static int HashFont(const FontParameters
&fp
) {
245 static_cast<int>(fp
.size
+0.5) ^
246 (fp
.characterSet
<< 10) ^
247 ((fp
.weight
/ 100) << 12) ^
248 (fp
.italic
? 0x20000000 : 0) ^
252 class FontCached
: Font
{
257 explicit FontCached(const FontParameters
&fp
);
259 bool SameAs(const FontParameters
&fp
);
260 virtual void Release();
261 static FontID
CreateNewFont(const FontParameters
&fp
);
262 static FontCached
*first
;
264 static FontID
FindOrCreate(const FontParameters
&fp
);
265 static void ReleaseId(FontID fid_
);
266 static void ReleaseAll();
269 FontCached
*FontCached::first
= 0;
271 FontCached::FontCached(const FontParameters
&fp
) :
272 next(0), usage(0), hash(0) {
273 ::SetLogFont(lf
, fp
.faceName
, fp
.characterSet
, fp
.size
, fp
.weight
, fp
.italic
);
275 fid
= CreateNewFont(fp
);
279 bool FontCached::SameAs(const FontParameters
&fp
) {
281 lf
.size
== fp
.size
&&
282 lf
.weight
== fp
.weight
&&
283 lf
.italic
== fp
.italic
&&
284 lf
.characterSet
== fp
.characterSet
&&
285 0 == strcmp(lf
.faceName
, fp
.faceName
);
288 void FontCached::Release() {
294 FontID
FontCached::FindOrCreate(const FontParameters
&fp
) {
297 int hashFind
= HashFont(fp
);
298 for (FontCached
*cur
= first
; cur
; cur
= cur
->next
) {
299 if ((cur
->hash
== hashFind
) &&
306 FontCached
*fc
= new FontCached(fp
);
315 void FontCached::ReleaseId(FontID fid_
) {
317 FontCached
**pcur
= &first
;
318 for (FontCached
*cur
= first
; cur
; cur
= cur
->next
) {
319 if (cur
->fid
== fid_
) {
321 if (cur
->usage
== 0) {
334 void FontCached::ReleaseAll() {
336 ReleaseId(first
->GetID());
340 FontID
FontCached::CreateNewFont(const FontParameters
&fp
) {
341 PangoFontDescription
*pfd
= pango_font_description_new();
343 pango_font_description_set_family(pfd
,
344 (fp
.faceName
[0] == '!') ? fp
.faceName
+1 : fp
.faceName
);
345 pango_font_description_set_size(pfd
, pangoUnitsFromDouble(fp
.size
));
346 pango_font_description_set_weight(pfd
, static_cast<PangoWeight
>(fp
.weight
));
347 pango_font_description_set_style(pfd
, fp
.italic
? PANGO_STYLE_ITALIC
: PANGO_STYLE_NORMAL
);
348 return new FontHandle(pfd
, fp
.characterSet
);
351 return new FontHandle();
354 Font::Font() : fid(0) {}
358 void Font::Create(const FontParameters
&fp
) {
360 fid
= FontCached::FindOrCreate(fp
);
363 void Font::Release() {
365 FontCached::ReleaseId(fid
);
371 namespace Scintilla
{
374 // SurfaceID is a cairo_t*
375 class SurfaceImpl
: public Surface
{
378 cairo_surface_t
*psurf
;
383 PangoContext
*pcontext
;
387 void SetConverter(int characterSet_
);
390 virtual ~SurfaceImpl();
392 void Init(WindowID wid
);
393 void Init(SurfaceID sid
, WindowID wid
);
394 void InitPixMap(int width
, int height
, Surface
*surface_
, WindowID wid
);
398 void PenColour(ColourDesired fore
);
400 int DeviceHeightFont(int points
);
401 void MoveTo(int x_
, int y_
);
402 void LineTo(int x_
, int y_
);
403 void Polygon(Point
*pts
, int npts
, ColourDesired fore
, ColourDesired back
);
404 void RectangleDraw(PRectangle rc
, ColourDesired fore
, ColourDesired back
);
405 void FillRectangle(PRectangle rc
, ColourDesired back
);
406 void FillRectangle(PRectangle rc
, Surface
&surfacePattern
);
407 void RoundedRectangle(PRectangle rc
, ColourDesired fore
, ColourDesired back
);
408 void AlphaRectangle(PRectangle rc
, int cornerSize
, ColourDesired fill
, int alphaFill
,
409 ColourDesired outline
, int alphaOutline
, int flags
);
410 void DrawRGBAImage(PRectangle rc
, int width
, int height
, const unsigned char *pixelsImage
);
411 void Ellipse(PRectangle rc
, ColourDesired fore
, ColourDesired back
);
412 void Copy(PRectangle rc
, Point from
, Surface
&surfaceSource
);
414 void DrawTextBase(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
, ColourDesired fore
);
415 void DrawTextNoClip(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
, ColourDesired fore
, ColourDesired back
);
416 void DrawTextClipped(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
, ColourDesired fore
, ColourDesired back
);
417 void DrawTextTransparent(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
, ColourDesired fore
);
418 void MeasureWidths(Font
&font_
, const char *s
, int len
, XYPOSITION
*positions
);
419 XYPOSITION
WidthText(Font
&font_
, const char *s
, int len
);
420 XYPOSITION
WidthChar(Font
&font_
, char ch
);
421 XYPOSITION
Ascent(Font
&font_
);
422 XYPOSITION
Descent(Font
&font_
);
423 XYPOSITION
InternalLeading(Font
&font_
);
424 XYPOSITION
ExternalLeading(Font
&font_
);
425 XYPOSITION
Height(Font
&font_
);
426 XYPOSITION
AverageCharWidth(Font
&font_
);
428 void SetClip(PRectangle rc
);
429 void FlushCachedState();
431 void SetUnicodeMode(bool unicodeMode_
);
432 void SetDBCSMode(int codePage
);
438 const char *CharacterSetID(int characterSet
) {
439 switch (characterSet
) {
440 case SC_CHARSET_ANSI
:
442 case SC_CHARSET_DEFAULT
:
444 case SC_CHARSET_BALTIC
:
445 return "ISO-8859-13";
446 case SC_CHARSET_CHINESEBIG5
:
448 case SC_CHARSET_EASTEUROPE
:
450 case SC_CHARSET_GB2312
:
452 case SC_CHARSET_GREEK
:
454 case SC_CHARSET_HANGUL
:
460 case SC_CHARSET_RUSSIAN
:
462 case SC_CHARSET_CYRILLIC
:
464 case SC_CHARSET_SHIFTJIS
:
466 case SC_CHARSET_SYMBOL
:
468 case SC_CHARSET_TURKISH
:
470 case SC_CHARSET_JOHAB
:
472 case SC_CHARSET_HEBREW
:
474 case SC_CHARSET_ARABIC
:
476 case SC_CHARSET_VIETNAMESE
:
478 case SC_CHARSET_THAI
:
479 return "ISO-8859-11";
480 case SC_CHARSET_8859_15
:
481 return "ISO-8859-15";
487 void SurfaceImpl::SetConverter(int characterSet_
) {
488 if (characterSet
!= characterSet_
) {
489 characterSet
= characterSet_
;
490 conv
.Open("UTF-8", CharacterSetID(characterSet
), false);
494 SurfaceImpl::SurfaceImpl() : et(singleByte
),
497 x(0), y(0), inited(false), createdGC(false)
498 , pcontext(0), layout(0), characterSet(-1) {
501 SurfaceImpl::~SurfaceImpl() {
505 void SurfaceImpl::Release() {
509 cairo_destroy(context
);
513 cairo_surface_destroy(psurf
);
516 g_object_unref(layout
);
519 g_object_unref(pcontext
);
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
);
542 cairo_surface_has_show_text_glyphs(psurfContext
);
545 return cairo_status(context
) == CAIRO_STATUS_SUCCESS
;
551 void SurfaceImpl::Init(WindowID wid
) {
553 PLATFORM_ASSERT(wid
);
554 // if we are only created from a window ID, we can't perform drawing
558 pcontext
= gtk_widget_create_pango_context(PWidget(wid
));
559 PLATFORM_ASSERT(pcontext
);
560 layout
= pango_layout_new(pcontext
);
561 PLATFORM_ASSERT(layout
);
565 void SurfaceImpl::Init(SurfaceID sid
, WindowID wid
) {
566 PLATFORM_ASSERT(sid
);
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);
579 void SurfaceImpl::InitPixMap(int width
, int height
, Surface
*surface_
, WindowID wid
) {
580 PLATFORM_ASSERT(surface_
);
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);
600 // This produces sharp drawing more similar to GDK:
601 //cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
602 cairo_set_line_width(context
, 1);
607 void SurfaceImpl::PenColour(ColourDesired fore
) {
609 ColourDesired
cdFore(fore
.AsLong());
610 cairo_set_source_rgb(context
,
611 cdFore
.GetRed() / 255.0,
612 cdFore
.GetGreen() / 255.0,
613 cdFore
.GetBlue() / 255.0);
617 int SurfaceImpl::LogPixelsY() {
621 int SurfaceImpl::DeviceHeightFont(int points
) {
622 int logPix
= LogPixelsY();
623 return (points
* logPix
+ logPix
/ 2) / 72;
626 void SurfaceImpl::MoveTo(int x_
, int y_
) {
631 static int Delta(int difference
) {
634 else if (difference
> 0)
640 void SurfaceImpl::LineTo(int x_
, int y_
) {
641 // cairo_line_to draws the end position, unlike Win32 or GDK with GDK_CAP_NOT_LAST.
642 // For simple cases, move back one pixel from end.
645 int xDelta
= Delta(xDiff
);
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
);
658 } else if ((abs(xDiff
) == abs(yDiff
))) {
660 cairo_move_to(context
, x
+ 0.5, y
+ 0.5);
661 cairo_line_to(context
, x_
+ 0.5 - xDelta
, y_
+ 0.5 - yDelta
);
663 // Line has a different slope so difficult to avoid last pixel
664 cairo_move_to(context
, x
+ 0.5, y
+ 0.5);
665 cairo_line_to(context
, x_
+ 0.5, y_
+ 0.5);
667 cairo_stroke(context
);
673 void SurfaceImpl::Polygon(Point
*pts
, int npts
, ColourDesired fore
,
674 ColourDesired back
) {
675 PLATFORM_ASSERT(context
);
677 cairo_move_to(context
, pts
[0].x
+ 0.5, pts
[0].y
+ 0.5);
678 for (int i
= 1; i
< npts
; i
++) {
679 cairo_line_to(context
, pts
[i
].x
+ 0.5, pts
[i
].y
+ 0.5);
681 cairo_close_path(context
);
682 cairo_fill_preserve(context
);
684 cairo_stroke(context
);
687 void SurfaceImpl::RectangleDraw(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
689 cairo_rectangle(context
, rc
.left
+ 0.5, rc
.top
+ 0.5,
690 rc
.right
- rc
.left
- 1, rc
.bottom
- rc
.top
- 1);
692 cairo_fill_preserve(context
);
694 cairo_stroke(context
);
698 void SurfaceImpl::FillRectangle(PRectangle rc
, ColourDesired 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
);
709 void SurfaceImpl::FillRectangle(PRectangle rc
, Surface
&surfacePattern
) {
710 SurfaceImpl
&surfi
= static_cast<SurfaceImpl
&>(surfacePattern
);
711 bool canDraw
= surfi
.psurf
!= NULL
;
713 PLATFORM_ASSERT(context
);
714 // Tile pattern over rectangle
715 // Currently assumes 8x8 pattern
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
);
728 // Something is wrong so try to show anyway
729 // Shows up black because colour not allocated
730 FillRectangle(rc
, ColourDesired(0));
734 void SurfaceImpl::RoundedRectangle(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
735 if (((rc
.right
- rc
.left
) > 4) && ((rc
.bottom
- rc
.top
) > 4)) {
736 // Approximate a round rect with some cut off corners
738 Point(rc
.left
+ 2, rc
.top
),
739 Point(rc
.right
- 2, rc
.top
),
740 Point(rc
.right
, rc
.top
+ 2),
741 Point(rc
.right
, rc
.bottom
- 2),
742 Point(rc
.right
- 2, rc
.bottom
),
743 Point(rc
.left
+ 2, rc
.bottom
),
744 Point(rc
.left
, rc
.bottom
- 2),
745 Point(rc
.left
, rc
.top
+ 2),
747 Polygon(pts
, ELEMENTS(pts
), fore
, back
);
749 RectangleDraw(rc
, fore
, back
);
753 static void PathRoundRectangle(cairo_t
*context
, double left
, double top
, double width
, double height
, int radius
) {
754 double degrees
= kPi
/ 180.0;
756 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 2, 0)
757 cairo_new_sub_path(context
);
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
);
762 cairo_arc(context
, left
+ width
- radius
, top
+ radius
, radius
, -90 * degrees
, 0 * degrees
);
763 cairo_arc(context
, left
+ width
- radius
, top
+ height
- radius
, radius
, 0 * degrees
, 90 * degrees
);
764 cairo_arc(context
, left
+ radius
, top
+ height
- radius
, radius
, 90 * degrees
, 180 * degrees
);
765 cairo_arc(context
, left
+ radius
, top
+ radius
, radius
, 180 * degrees
, 270 * degrees
);
766 cairo_close_path(context
);
769 void SurfaceImpl::AlphaRectangle(PRectangle rc
, int cornerSize
, ColourDesired fill
, int alphaFill
,
770 ColourDesired outline
, int alphaOutline
, int flags
) {
771 if (context
&& rc
.Width() > 0) {
772 ColourDesired
cdFill(fill
.AsLong());
773 cairo_set_source_rgba(context
,
774 cdFill
.GetRed() / 255.0,
775 cdFill
.GetGreen() / 255.0,
776 cdFill
.GetBlue() / 255.0,
779 PathRoundRectangle(context
, rc
.left
+ 1.0, rc
.top
+ 1.0, rc
.right
- rc
.left
- 2.0, rc
.bottom
- rc
.top
- 2.0, cornerSize
);
781 cairo_rectangle(context
, rc
.left
+ 1.0, rc
.top
+ 1.0, rc
.right
- rc
.left
- 2.0, rc
.bottom
- rc
.top
- 2.0);
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);
791 PathRoundRectangle(context
, rc
.left
+ 0.5, rc
.top
+ 0.5, rc
.right
- rc
.left
- 1, rc
.bottom
- rc
.top
- 1, cornerSize
);
793 cairo_rectangle(context
, rc
.left
+ 0.5, rc
.top
+ 0.5, rc
.right
- rc
.left
- 1, rc
.bottom
- rc
.top
- 1);
794 cairo_stroke(context
);
798 void SurfaceImpl::DrawRGBAImage(PRectangle rc
, int width
, int height
, const unsigned char *pixelsImage
) {
799 PLATFORM_ASSERT(context
);
800 if (rc
.Width() > width
)
801 rc
.left
+= (rc
.Width() - width
) / 2;
802 rc
.right
= rc
.left
+ width
;
803 if (rc
.Height() > height
)
804 rc
.top
+= (rc
.Height() - height
) / 2;
805 rc
.bottom
= rc
.top
+ height
;
807 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
808 int stride
= cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32
, width
);
810 int stride
= width
* 4;
812 int ucs
= stride
* height
;
813 std::vector
<unsigned char> image(ucs
);
814 for (int iy
=0; iy
<height
; iy
++) {
815 for (int ix
=0; ix
<width
; ix
++) {
816 unsigned char *pixel
= &image
[0] + iy
*stride
+ ix
* 4;
817 unsigned char alpha
= pixelsImage
[3];
818 pixel
[2] = (*pixelsImage
++) * alpha
/ 255;
819 pixel
[1] = (*pixelsImage
++) * alpha
/ 255;
820 pixel
[0] = (*pixelsImage
++) * alpha
/ 255;
821 pixel
[3] = *pixelsImage
++;
825 cairo_surface_t
*psurfImage
= cairo_image_surface_create_for_data(&image
[0], CAIRO_FORMAT_ARGB32
, width
, height
, stride
);
826 cairo_set_source_surface(context
, psurfImage
, rc
.left
, rc
.top
);
827 cairo_rectangle(context
, rc
.left
, rc
.top
, rc
.right
-rc
.left
, rc
.bottom
-rc
.top
);
830 cairo_surface_destroy(psurfImage
);
833 void SurfaceImpl::Ellipse(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
834 PLATFORM_ASSERT(context
);
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
);
840 cairo_stroke(context
);
843 void SurfaceImpl::Copy(PRectangle rc
, Point from
, Surface
&surfaceSource
) {
844 SurfaceImpl
&surfi
= static_cast<SurfaceImpl
&>(surfaceSource
);
845 bool canDraw
= surfi
.psurf
!= NULL
;
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
);
855 std::string
UTF8FromLatin1(const char *s
, int len
) {
856 std::string
utfForm(len
*2 + 1, '\0');
858 for (int i
=0; i
<len
; i
++) {
859 unsigned int uch
= static_cast<unsigned char>(s
[i
]);
861 utfForm
[lenU
++] = uch
;
863 utfForm
[lenU
++] = static_cast<char>(0xC0 | (uch
>> 6));
864 utfForm
[lenU
++] = static_cast<char>(0x80 | (uch
& 0x3f));
867 utfForm
.resize(lenU
);
871 static std::string
UTF8FromIconv(const Converter
&conv
, const char *s
, int len
) {
873 std::string
utfForm(len
*3+1, '\0');
874 char *pin
= const_cast<char *>(s
);
876 char *putf
= &utfForm
[0];
878 size_t outLeft
= len
*3+1;
879 size_t conversions
= conv
.Convert(&pin
, &inLeft
, &pout
, &outLeft
);
880 if (conversions
!= ((size_t)(-1))) {
882 utfForm
.resize(pout
- putf
);
886 return std::string();
889 // Work out how many bytes are in a character by trying to convert using iconv,
890 // returning the first length that succeeds.
891 static size_t MultiByteLenFromIconv(const Converter
&conv
, const char *s
, size_t len
) {
892 for (size_t lenMB
=1; (lenMB
<4) && (lenMB
<= len
); lenMB
++) {
894 char *pin
= const_cast<char *>(s
);
895 size_t inLeft
= lenMB
;
898 size_t conversions
= conv
.Convert(&pin
, &inLeft
, &pout
, &outLeft
);
899 if (conversions
!= ((size_t)(-1))) {
906 void SurfaceImpl::DrawTextBase(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
907 ColourDesired fore
) {
910 XYPOSITION xText
= rc
.left
;
911 if (PFont(font_
)->pfd
) {
914 pango_layout_set_text(layout
, s
, len
);
916 SetConverter(PFont(font_
)->characterSet
);
917 utfForm
= UTF8FromIconv(conv
, s
, len
);
918 if (utfForm
.empty()) { // iconv failed so treat as Latin1
919 utfForm
= UTF8FromLatin1(s
, len
);
921 pango_layout_set_text(layout
, utfForm
.c_str(), utfForm
.length());
923 pango_layout_set_font_description(layout
, PFont(font_
)->pfd
);
924 pango_cairo_update_layout(context
, layout
);
926 PangoLayoutLine
*pll
= pango_layout_get_line_readonly(layout
,0);
928 PangoLayoutLine
*pll
= pango_layout_get_line(layout
,0);
930 cairo_move_to(context
, xText
, ybase
);
931 pango_cairo_show_layout_line(context
, pll
);
936 void SurfaceImpl::DrawTextNoClip(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
937 ColourDesired fore
, ColourDesired back
) {
938 FillRectangle(rc
, back
);
939 DrawTextBase(rc
, font_
, ybase
, s
, len
, fore
);
942 // On GTK+, exactly same as DrawTextNoClip
943 void SurfaceImpl::DrawTextClipped(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
944 ColourDesired fore
, ColourDesired back
) {
945 FillRectangle(rc
, back
);
946 DrawTextBase(rc
, font_
, ybase
, s
, len
, fore
);
949 void SurfaceImpl::DrawTextTransparent(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
950 ColourDesired fore
) {
951 // Avoid drawing spaces in transparent mode
952 for (int i
=0; i
<len
; i
++) {
954 DrawTextBase(rc
, font_
, ybase
, s
, len
, fore
);
960 class ClusterIterator
{
961 PangoLayoutIter
*iter
;
966 XYPOSITION positionStart
;
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
);
976 pango_layout_iter_free(iter
);
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
);
987 position
= doubleFromPangoUnits(pos
.x
+ pos
.width
);
988 curIndex
= lenPositions
;
990 distance
= position
- positionStart
;
994 void SurfaceImpl::MeasureWidths(Font
&font_
, const char *s
, int len
, XYPOSITION
*positions
) {
996 const int lenPositions
= len
;
997 if (PFont(font_
)->pfd
) {
999 int width
= PFont(font_
)->CharWidth(*s
, et
);
1001 positions
[0] = width
;
1005 pango_layout_set_font_description(layout
, PFont(font_
)->pfd
);
1007 // Simple and direct as UTF-8 is native Pango encoding
1009 pango_layout_set_text(layout
, s
, len
);
1010 ClusterIterator
iti(layout
, lenPositions
);
1011 while (!iti
.finished
) {
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
;
1023 PLATFORM_ASSERT(i
== lenPositions
);
1025 int positionsCalculated
= 0;
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()));
1036 int clusterStart
= 0;
1037 ClusterIterator
iti(layout
, strlen(utfForm
.c_str()));
1038 while (!iti
.finished
) {
1040 int clusterEnd
= iti
.curIndex
;
1041 int places
= g_utf8_strlen(utfForm
.c_str() + clusterStart
, clusterEnd
- clusterStart
);
1043 while (clusterStart
< clusterEnd
) {
1044 size_t lenChar
= MultiByteLenFromIconv(convMeasure
, s
+i
, len
-i
);
1046 positions
[i
++] = iti
.position
- (places
- place
) * iti
.distance
/ places
;
1047 positionsCalculated
++;
1049 clusterStart
+= UTF8CharLength(static_cast<unsigned char>(utfForm
.c_str()[clusterStart
]));
1053 PLATFORM_ASSERT(i
== lenPositions
);
1056 if (positionsCalculated
< 1 ) {
1057 // Either 8-bit or DBCS conversion failed so treat as 8-bit.
1058 SetConverter(PFont(font_
)->characterSet
);
1059 const bool rtlCheck
= PFont(font_
)->characterSet
== SC_CHARSET_HEBREW
||
1060 PFont(font_
)->characterSet
== SC_CHARSET_ARABIC
;
1061 std::string utfForm
= UTF8FromIconv(conv
, s
, len
);
1062 if (utfForm
.empty()) {
1063 utfForm
= UTF8FromLatin1(s
, len
);
1065 pango_layout_set_text(layout
, utfForm
.c_str(), utfForm
.length());
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
) {
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);
1085 PLATFORM_ASSERT(ligatureLength
> 0 && ligatureLength
<= 3);
1086 for (int charInLig
=0; charInLig
<ligatureLength
; charInLig
++) {
1087 positions
[i
++] = iti
.position
- (ligatureLength
- 1 - charInLig
) * iti
.distance
/ ligatureLength
;
1089 clusterStart
= clusterEnd
;
1091 while (i
< lenPositions
) {
1092 // If something failed, fill in rest of the positions
1093 positions
[i
++] = clusterStart
;
1095 PLATFORM_ASSERT(i
== lenPositions
);
1099 PFont(font_
)->SetCharWidth(*s
, positions
[0], et
);
1104 // No font so return an ascending range of values
1105 for (int i
= 0; i
< len
; i
++) {
1106 positions
[i
] = i
+ 1;
1111 XYPOSITION
SurfaceImpl::WidthText(Font
&font_
, const char *s
, int len
) {
1112 if (font_
.GetID()) {
1113 if (PFont(font_
)->pfd
) {
1114 std::string utfForm
;
1115 pango_layout_set_font_description(layout
, PFont(font_
)->pfd
);
1118 pango_layout_set_text(layout
, s
, len
);
1120 SetConverter(PFont(font_
)->characterSet
);
1121 utfForm
= UTF8FromIconv(conv
, s
, len
);
1122 if (utfForm
.empty()) { // iconv failed so treat as Latin1
1123 utfForm
= UTF8FromLatin1(s
, len
);
1125 pango_layout_set_text(layout
, utfForm
.c_str(), utfForm
.length());
1127 #ifdef PANGO_VERSION
1128 PangoLayoutLine
*pangoLine
= pango_layout_get_line_readonly(layout
,0);
1130 PangoLayoutLine
*pangoLine
= pango_layout_get_line(layout
,0);
1132 pango_layout_line_get_extents(pangoLine
, NULL
, &pos
);
1133 return doubleFromPangoUnits(pos
.width
);
1141 XYPOSITION
SurfaceImpl::WidthChar(Font
&font_
, char ch
) {
1142 if (font_
.GetID()) {
1143 if (PFont(font_
)->pfd
) {
1144 return WidthText(font_
, &ch
, 1);
1152 // Ascent and descent determined by Pango font metrics.
1154 XYPOSITION
SurfaceImpl::Ascent(Font
&font_
) {
1155 if (!(font_
.GetID()))
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
;
1174 XYPOSITION
SurfaceImpl::Descent(Font
&font_
) {
1175 if (!(font_
.GetID()))
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
);
1187 XYPOSITION
SurfaceImpl::InternalLeading(Font
&) {
1191 XYPOSITION
SurfaceImpl::ExternalLeading(Font
&) {
1195 XYPOSITION
SurfaceImpl::Height(Font
&font_
) {
1196 return Ascent(font_
) + Descent(font_
);
1199 XYPOSITION
SurfaceImpl::AverageCharWidth(Font
&font_
) {
1200 return WidthChar(font_
, 'n');
1203 void SurfaceImpl::SetClip(PRectangle rc
) {
1204 PLATFORM_ASSERT(context
);
1205 cairo_rectangle(context
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
1206 cairo_clip(context
);
1209 void SurfaceImpl::FlushCachedState() {}
1211 void SurfaceImpl::SetUnicodeMode(bool unicodeMode_
) {
1216 void SurfaceImpl::SetDBCSMode(int codePage
) {
1217 if (codePage
&& (codePage
!= SC_CP_UTF8
))
1221 Surface
*Surface::Allocate(int) {
1222 return new SurfaceImpl();
1225 Window::~Window() {}
1227 void Window::Destroy() {
1229 ListBox
*listbox
= dynamic_cast<ListBox
*>(this);
1231 gtk_widget_hide(GTK_WIDGET(wid
));
1232 // clear up window content
1234 // resize the window to the smallest possible size for it to adapt
1235 // to future content
1236 gtk_window_resize(GTK_WINDOW(wid
), 1, 1);
1238 gtk_widget_destroy(GTK_WIDGET(wid
));
1244 bool Window::HasFocus() {
1245 return IS_WIDGET_FOCUSSED(wid
);
1248 PRectangle
Window::GetPosition() {
1249 // Before any size allocated pretend its 1000 wide so not scrolled
1250 PRectangle
rc(0, 0, 1000, 1000);
1252 GtkAllocation allocation
;
1253 #if GTK_CHECK_VERSION(3,0,0)
1254 gtk_widget_get_allocation(PWidget(wid
), &allocation
);
1256 allocation
= PWidget(wid
)->allocation
;
1258 rc
.left
= allocation
.x
;
1259 rc
.top
= allocation
.y
;
1260 if (allocation
.width
> 20) {
1261 rc
.right
= rc
.left
+ allocation
.width
;
1262 rc
.bottom
= rc
.top
+ allocation
.height
;
1268 void Window::SetPosition(PRectangle rc
) {
1269 GtkAllocation alloc
;
1272 alloc
.width
= rc
.Width();
1273 alloc
.height
= rc
.Height();
1274 gtk_widget_size_allocate(PWidget(wid
), &alloc
);
1277 void Window::SetPositionRelative(PRectangle rc
, Window relativeTo
) {
1280 gdk_window_get_origin(WindowFromWidget(PWidget(relativeTo
.wid
)), &ox
, &oy
);
1288 /* do some corrections to fit into screen */
1289 int sizex
= rc
.right
- rc
.left
;
1290 int sizey
= rc
.bottom
- rc
.top
;
1291 int screenWidth
= gdk_screen_width();
1292 int screenHeight
= gdk_screen_height();
1293 if (sizex
> screenWidth
)
1294 ox
= 0; /* the best we can do */
1295 else if (ox
+ sizex
> screenWidth
)
1296 ox
= screenWidth
- sizex
;
1297 if (oy
+ sizey
> screenHeight
)
1298 oy
= screenHeight
- sizey
;
1300 gtk_window_move(GTK_WINDOW(PWidget(wid
)), ox
, oy
);
1302 gtk_window_resize(GTK_WINDOW(wid
), sizex
, sizey
);
1305 PRectangle
Window::GetClientPosition() {
1306 // On GTK+, the client position is the window position
1307 return GetPosition();
1310 void Window::Show(bool show
) {
1312 gtk_widget_show(PWidget(wid
));
1315 void Window::InvalidateAll() {
1317 gtk_widget_queue_draw(PWidget(wid
));
1321 void Window::InvalidateRectangle(PRectangle rc
) {
1323 gtk_widget_queue_draw_area(PWidget(wid
),
1325 rc
.right
- rc
.left
, rc
.bottom
- rc
.top
);
1329 void Window::SetFont(Font
&) {
1330 // Can not be done generically but only needed for ListBox
1333 void Window::SetCursor(Cursor curs
) {
1334 // We don't set the cursor to same value numerous times under gtk because
1335 // it stores the cursor in the window once it's set
1336 if (curs
== cursorLast
)
1343 gdkCurs
= gdk_cursor_new(GDK_XTERM
);
1346 gdkCurs
= gdk_cursor_new(GDK_LEFT_PTR
);
1349 gdkCurs
= gdk_cursor_new(GDK_CENTER_PTR
);
1352 gdkCurs
= gdk_cursor_new(GDK_WATCH
);
1355 gdkCurs
= gdk_cursor_new(GDK_HAND2
);
1357 case cursorReverseArrow
:
1358 gdkCurs
= gdk_cursor_new(GDK_RIGHT_PTR
);
1361 gdkCurs
= gdk_cursor_new(GDK_LEFT_PTR
);
1362 cursorLast
= cursorArrow
;
1366 if (WindowFromWidget(PWidget(wid
)))
1367 gdk_window_set_cursor(WindowFromWidget(PWidget(wid
)), gdkCurs
);
1368 #if GTK_CHECK_VERSION(3,0,0)
1369 g_object_unref(gdkCurs
);
1371 gdk_cursor_unref(gdkCurs
);
1375 void Window::SetTitle(const char *s
) {
1376 gtk_window_set_title(GTK_WINDOW(wid
), s
);
1379 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1380 gdk window coordinates */
1381 PRectangle
Window::GetMonitorRect(Point pt
) {
1382 gint x_offset
, y_offset
;
1384 gdk_window_get_origin(WindowFromWidget(PWidget(wid
)), &x_offset
, &y_offset
);
1390 screen
= gtk_widget_get_screen(PWidget(wid
));
1391 monitor_num
= gdk_screen_get_monitor_at_point(screen
, pt
.x
+ x_offset
, pt
.y
+ y_offset
);
1392 gdk_screen_get_monitor_geometry(screen
, monitor_num
, &rect
);
1395 return PRectangle(rect
.x
, rect
.y
, rect
.x
+ rect
.width
, rect
.y
+ rect
.height
);
1398 typedef std::map
<int, RGBAImage
*> ImageMap
;
1401 const RGBAImage
*rgba_data
;
1405 static void list_image_free(gpointer
, gpointer value
, gpointer
) {
1406 ListImage
*list_image
= static_cast<ListImage
*>(value
);
1407 if (list_image
->pixbuf
)
1408 g_object_unref(list_image
->pixbuf
);
1412 ListBox::ListBox() {
1415 ListBox::~ListBox() {
1424 class ListBoxX
: public ListBox
{
1430 GtkCellRenderer
* pixbuf_renderer
;
1431 RGBAImageSet images
;
1432 int desiredVisibleRows
;
1433 unsigned int maxItemCharacters
;
1434 unsigned int aveCharWidth
;
1436 CallBackAction doubleClickAction
;
1437 void *doubleClickActionData
;
1439 ListBoxX() : widCached(0), frame(0), list(0), scroller(0), pixhash(NULL
), pixbuf_renderer(0),
1440 desiredVisibleRows(5), maxItemCharacters(0),
1441 aveCharWidth(1), doubleClickAction(NULL
), doubleClickActionData(NULL
) {
1443 virtual ~ListBoxX() {
1445 g_hash_table_foreach((GHashTable
*) pixhash
, list_image_free
, NULL
);
1446 g_hash_table_destroy((GHashTable
*) pixhash
);
1449 gtk_widget_destroy(GTK_WIDGET(widCached
));
1450 wid
= widCached
= 0;
1453 virtual void SetFont(Font
&font
);
1454 virtual void Create(Window
&parent
, int ctrlID
, Point location_
, int lineHeight_
, bool unicodeMode_
, int technology_
);
1455 virtual void SetAverageCharWidth(int width
);
1456 virtual void SetVisibleRows(int rows
);
1457 virtual int GetVisibleRows() const;
1459 virtual PRectangle
GetDesiredRect();
1460 virtual int CaretFromEdge();
1461 virtual void Clear();
1462 virtual void Append(char *s
, int type
= -1);
1463 virtual int Length();
1464 virtual void Select(int n
);
1465 virtual int GetSelection();
1466 virtual int Find(const char *prefix
);
1467 virtual void GetValue(int n
, char *value
, int len
);
1468 void RegisterRGBA(int type
, RGBAImage
*image
);
1469 virtual void RegisterImage(int type
, const char *xpm_data
);
1470 virtual void RegisterRGBAImage(int type
, int width
, int height
, const unsigned char *pixelsImage
);
1471 virtual void ClearRegisteredImages();
1472 virtual void SetDoubleClickAction(CallBackAction action
, void *data
) {
1473 doubleClickAction
= action
;
1474 doubleClickActionData
= data
;
1476 virtual void SetList(const char *listText
, char separator
, char typesep
);
1479 ListBox
*ListBox::Allocate() {
1480 ListBoxX
*lb
= new ListBoxX();
1484 // SmallScroller, a GtkScrolledWindow that can shrink very small, as
1485 // gtk_widget_set_size_request() cannot shrink widgets on GTK3
1486 typedef GtkScrolledWindow SmallScroller
;
1487 typedef GtkScrolledWindowClass SmallScrollerClass
;
1489 G_DEFINE_TYPE(SmallScroller
, small_scroller
, GTK_TYPE_SCROLLED_WINDOW
)
1491 #if GTK_CHECK_VERSION(3,0,0)
1492 static void small_scroller_get_preferred_height(GtkWidget
*widget
, gint
*min
, gint
*nat
) {
1493 GTK_WIDGET_CLASS(small_scroller_parent_class
)->get_preferred_height(widget
, min
, nat
);
1497 static void small_scroller_size_request(GtkWidget
*widget
, GtkRequisition
*req
) {
1498 GTK_WIDGET_CLASS(small_scroller_parent_class
)->size_request(widget
, req
);
1503 static void small_scroller_class_init(SmallScrollerClass
*klass
) {
1504 #if GTK_CHECK_VERSION(3,0,0)
1505 GTK_WIDGET_CLASS(klass
)->get_preferred_height
= small_scroller_get_preferred_height
;
1507 GTK_WIDGET_CLASS(klass
)->size_request
= small_scroller_size_request
;
1511 static void small_scroller_init(SmallScroller
*){}
1513 static gboolean
ButtonPress(GtkWidget
*, GdkEventButton
* ev
, gpointer p
) {
1515 ListBoxX
* lb
= reinterpret_cast<ListBoxX
*>(p
);
1516 if (ev
->type
== GDK_2BUTTON_PRESS
&& lb
->doubleClickAction
!= NULL
) {
1517 lb
->doubleClickAction(lb
->doubleClickActionData
);
1522 // No pointer back to Scintilla to save status
1527 /* Change the active color to the selected color so the listbox uses the color
1528 scheme that it would use if it had the focus. */
1529 static void StyleSet(GtkWidget
*w
, GtkStyle
*, void*) {
1531 g_return_if_fail(w
!= NULL
);
1533 /* Copy the selected color to active. Note that the modify calls will cause
1534 recursive calls to this function after the value is updated and w->style to
1535 be set to a new object */
1537 #if GTK_CHECK_VERSION(3,0,0)
1538 GtkStyleContext
*styleContext
= gtk_widget_get_style_context(w
);
1539 if (styleContext
== NULL
)
1542 GdkRGBA colourForeSelected
;
1543 gtk_style_context_get_color(styleContext
, GTK_STATE_FLAG_SELECTED
, &colourForeSelected
);
1544 GdkRGBA colourForeActive
;
1545 gtk_style_context_get_color(styleContext
, GTK_STATE_FLAG_ACTIVE
, &colourForeActive
);
1546 if (!gdk_rgba_equal(&colourForeSelected
, &colourForeActive
))
1547 gtk_widget_override_color(w
, GTK_STATE_FLAG_ACTIVE
, &colourForeSelected
);
1549 styleContext
= gtk_widget_get_style_context(w
);
1550 if (styleContext
== NULL
)
1553 GdkRGBA colourBaseSelected
;
1554 gtk_style_context_get_background_color(styleContext
, GTK_STATE_FLAG_SELECTED
, &colourBaseSelected
);
1555 GdkRGBA colourBaseActive
;
1556 gtk_style_context_get_background_color(styleContext
, GTK_STATE_FLAG_ACTIVE
, &colourBaseActive
);
1557 if (!gdk_rgba_equal(&colourBaseSelected
, &colourBaseActive
))
1558 gtk_widget_override_background_color(w
, GTK_STATE_FLAG_ACTIVE
, &colourBaseSelected
);
1560 GtkStyle
*style
= gtk_widget_get_style(w
);
1563 if (!gdk_color_equal(&style
->base
[GTK_STATE_SELECTED
], &style
->base
[GTK_STATE_ACTIVE
]))
1564 gtk_widget_modify_base(w
, GTK_STATE_ACTIVE
, &style
->base
[GTK_STATE_SELECTED
]);
1565 style
= gtk_widget_get_style(w
);
1568 if (!gdk_color_equal(&style
->text
[GTK_STATE_SELECTED
], &style
->text
[GTK_STATE_ACTIVE
]))
1569 gtk_widget_modify_text(w
, GTK_STATE_ACTIVE
, &style
->text
[GTK_STATE_SELECTED
]);
1573 void ListBoxX::Create(Window
&, int, Point
, int, bool, int) {
1574 if (widCached
!= 0) {
1579 wid
= widCached
= gtk_window_new(GTK_WINDOW_POPUP
);
1581 frame
= gtk_frame_new(NULL
);
1582 gtk_widget_show(PWidget(frame
));
1583 gtk_container_add(GTK_CONTAINER(GetID()), PWidget(frame
));
1584 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_OUT
);
1585 gtk_container_set_border_width(GTK_CONTAINER(frame
), 0);
1587 scroller
= g_object_new(small_scroller_get_type(), NULL
);
1588 gtk_container_set_border_width(GTK_CONTAINER(scroller
), 0);
1589 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller
),
1590 GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
1591 gtk_container_add(GTK_CONTAINER(frame
), PWidget(scroller
));
1592 gtk_widget_show(PWidget(scroller
));
1594 /* Tree and its model */
1595 GtkListStore
*store
=
1596 gtk_list_store_new(N_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
);
1598 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(store
));
1599 g_signal_connect(G_OBJECT(list
), "style-set", G_CALLBACK(StyleSet
), NULL
);
1601 GtkTreeSelection
*selection
=
1602 gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1603 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_SINGLE
);
1604 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), FALSE
);
1605 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list
), FALSE
);
1608 GtkTreeViewColumn
*column
= gtk_tree_view_column_new();
1609 gtk_tree_view_column_set_sizing(column
, GTK_TREE_VIEW_COLUMN_FIXED
);
1610 gtk_tree_view_column_set_title(column
, "Autocomplete");
1612 pixbuf_renderer
= gtk_cell_renderer_pixbuf_new();
1613 gtk_cell_renderer_set_fixed_size(pixbuf_renderer
, 0, -1);
1614 gtk_tree_view_column_pack_start(column
, pixbuf_renderer
, FALSE
);
1615 gtk_tree_view_column_add_attribute(column
, pixbuf_renderer
,
1616 "pixbuf", PIXBUF_COLUMN
);
1618 GtkCellRenderer
* renderer
= gtk_cell_renderer_text_new();
1619 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer
), 1);
1620 gtk_tree_view_column_pack_start(column
, renderer
, TRUE
);
1621 gtk_tree_view_column_add_attribute(column
, renderer
,
1622 "text", TEXT_COLUMN
);
1624 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), column
);
1625 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list
), "fixed-height-mode"))
1626 g_object_set(G_OBJECT(list
), "fixed-height-mode", TRUE
, NULL
);
1628 GtkWidget
*widget
= PWidget(list
); // No code inside the G_OBJECT macro
1629 gtk_container_add(GTK_CONTAINER(PWidget(scroller
)), widget
);
1630 gtk_widget_show(widget
);
1631 g_signal_connect(G_OBJECT(widget
), "button_press_event",
1632 G_CALLBACK(ButtonPress
), this);
1635 void ListBoxX::SetFont(Font
&scint_font
) {
1636 // Only do for Pango font as there have been crashes for GDK fonts
1637 if (Created() && PFont(scint_font
)->pfd
) {
1638 // Current font is Pango font
1639 #if GTK_CHECK_VERSION(3,0,0)
1640 gtk_widget_override_font(PWidget(list
), PFont(scint_font
)->pfd
);
1642 gtk_widget_modify_font(PWidget(list
), PFont(scint_font
)->pfd
);
1647 void ListBoxX::SetAverageCharWidth(int width
) {
1648 aveCharWidth
= width
;
1651 void ListBoxX::SetVisibleRows(int rows
) {
1652 desiredVisibleRows
= rows
;
1655 int ListBoxX::GetVisibleRows() const {
1656 return desiredVisibleRows
;
1659 int ListBoxX::GetRowHeight()
1661 #if GTK_CHECK_VERSION(3,0,0)
1662 // This version sometimes reports erroneous results on GTK2, but the GTK2
1663 // version is inaccurate for GTK 3.14.
1665 GtkTreePath
*path
= gtk_tree_path_new_first();
1666 gtk_tree_view_get_background_area(GTK_TREE_VIEW(list
), path
, NULL
, &rect
);
1670 int vertical_separator
=0;
1671 int expander_size
=0;
1672 GtkTreeViewColumn
*column
= gtk_tree_view_get_column(GTK_TREE_VIEW(list
), 0);
1673 gtk_tree_view_column_cell_get_size(column
, NULL
, NULL
, NULL
, NULL
, &row_height
);
1674 gtk_widget_style_get(PWidget(list
),
1675 "vertical-separator", &vertical_separator
,
1676 "expander-size", &expander_size
, NULL
);
1677 row_height
+= vertical_separator
;
1678 row_height
= Platform::Maximum(row_height
, expander_size
);
1683 PRectangle
ListBoxX::GetDesiredRect() {
1684 // Before any size allocated pretend its 100 wide so not scrolled
1685 PRectangle
rc(0, 0, 100, 100);
1687 int rows
= Length();
1688 if ((rows
== 0) || (rows
> desiredVisibleRows
))
1689 rows
= desiredVisibleRows
;
1692 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1693 // returns reasonable values.
1694 #if GTK_CHECK_VERSION(3,0,0)
1695 gtk_widget_get_preferred_size(GTK_WIDGET(frame
), NULL
, &req
);
1697 gtk_widget_size_request(GTK_WIDGET(frame
), &req
);
1701 // First calculate height of the clist for our desired visible
1702 // row count otherwise it tries to expand to the total # of rows
1704 int row_height
= GetRowHeight();
1705 #if GTK_CHECK_VERSION(3,0,0)
1706 GtkStyleContext
*styleContextFrame
= gtk_widget_get_style_context(PWidget(frame
));
1707 GtkBorder padding
, border
;
1708 gtk_style_context_get_padding(styleContextFrame
, GTK_STATE_FLAG_NORMAL
, &padding
);
1709 gtk_style_context_get_border(styleContextFrame
, GTK_STATE_FLAG_NORMAL
, &border
);
1710 height
= (rows
* row_height
1711 + padding
.top
+ padding
.bottom
1712 + border
.top
+ border
.bottom
1713 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list
))));
1715 height
= (rows
* row_height
1716 + 2 * (PWidget(frame
)->style
->ythickness
1717 + GTK_CONTAINER(PWidget(list
))->border_width
));
1721 int width
= maxItemCharacters
;
1724 rc
.right
= width
* (aveCharWidth
+ aveCharWidth
/ 3);
1725 // Add horizontal padding and borders
1726 int horizontal_separator
=0;
1727 gtk_widget_style_get(PWidget(list
),
1728 "horizontal-separator", &horizontal_separator
, NULL
);
1729 rc
.right
+= horizontal_separator
;
1730 #if GTK_CHECK_VERSION(3,0,0)
1731 rc
.right
+= (padding
.left
+ padding
.right
1732 + border
.left
+ border
.right
1733 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list
))));
1735 rc
.right
+= 2 * (PWidget(frame
)->style
->xthickness
1736 + GTK_CONTAINER(PWidget(list
))->border_width
);
1738 if (Length() > rows
) {
1739 // Add the width of the scrollbar
1740 GtkWidget
*vscrollbar
=
1741 gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(scroller
));
1742 #if GTK_CHECK_VERSION(3,0,0)
1743 gtk_widget_get_preferred_size(vscrollbar
, NULL
, &req
);
1745 gtk_widget_size_request(vscrollbar
, &req
);
1747 rc
.right
+= req
.width
;
1753 int ListBoxX::CaretFromEdge() {
1754 gint renderer_width
, renderer_height
;
1755 gtk_cell_renderer_get_fixed_size(pixbuf_renderer
, &renderer_width
,
1757 return 4 + renderer_width
;
1760 void ListBoxX::Clear() {
1761 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1762 gtk_list_store_clear(GTK_LIST_STORE(model
));
1763 maxItemCharacters
= 0;
1766 static void init_pixmap(ListImage
*list_image
) {
1767 if (list_image
->rgba_data
) {
1768 // Drop any existing pixmap/bitmap as data may have changed
1769 if (list_image
->pixbuf
)
1770 g_object_unref(list_image
->pixbuf
);
1771 list_image
->pixbuf
=
1772 gdk_pixbuf_new_from_data(list_image
->rgba_data
->Pixels(),
1776 list_image
->rgba_data
->GetWidth(),
1777 list_image
->rgba_data
->GetHeight(),
1778 list_image
->rgba_data
->GetWidth() * 4,
1786 void ListBoxX::Append(char *s
, int type
) {
1787 ListImage
*list_image
= NULL
;
1788 if ((type
>= 0) && pixhash
) {
1789 list_image
= static_cast<ListImage
*>(g_hash_table_lookup((GHashTable
*) pixhash
1790 , (gconstpointer
) GINT_TO_POINTER(type
)));
1793 GtkListStore
*store
=
1794 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list
)));
1795 gtk_list_store_append(GTK_LIST_STORE(store
), &iter
);
1797 if (NULL
== list_image
->pixbuf
)
1798 init_pixmap(list_image
);
1799 if (list_image
->pixbuf
) {
1800 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1801 PIXBUF_COLUMN
, list_image
->pixbuf
,
1802 TEXT_COLUMN
, s
, -1);
1804 gint pixbuf_width
= gdk_pixbuf_get_width(list_image
->pixbuf
);
1805 gint renderer_height
, renderer_width
;
1806 gtk_cell_renderer_get_fixed_size(pixbuf_renderer
,
1807 &renderer_width
, &renderer_height
);
1808 if (pixbuf_width
> renderer_width
)
1809 gtk_cell_renderer_set_fixed_size(pixbuf_renderer
,
1812 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1813 TEXT_COLUMN
, s
, -1);
1816 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1817 TEXT_COLUMN
, s
, -1);
1819 size_t len
= strlen(s
);
1820 if (maxItemCharacters
< len
)
1821 maxItemCharacters
= len
;
1824 int ListBoxX::Length() {
1826 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1827 (GTK_TREE_VIEW(list
)), NULL
);
1831 void ListBoxX::Select(int n
) {
1833 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1834 GtkTreeSelection
*selection
=
1835 gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1838 gtk_tree_selection_unselect_all(selection
);
1842 bool valid
= gtk_tree_model_iter_nth_child(model
, &iter
, NULL
, n
) != FALSE
;
1844 gtk_tree_selection_select_iter(selection
, &iter
);
1846 // Move the scrollbar to show the selection.
1847 int total
= Length();
1848 #if GTK_CHECK_VERSION(3,0,0)
1849 GtkAdjustment
*adj
=
1850 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list
));
1851 gfloat value
= ((gfloat
)n
/ total
) * (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_lower(adj
))
1852 + gtk_adjustment_get_lower(adj
) - gtk_adjustment_get_page_size(adj
) / 2;
1854 GtkAdjustment
*adj
=
1855 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list
));
1856 gfloat value
= ((gfloat
)n
/ total
) * (adj
->upper
- adj
->lower
)
1857 + adj
->lower
- adj
->page_size
/ 2;
1860 int row_height
= GetRowHeight();
1862 int rows
= Length();
1863 if ((rows
== 0) || (rows
> desiredVisibleRows
))
1864 rows
= desiredVisibleRows
;
1866 // Odd rows to display -- We are now in the middle.
1867 // Align it so that we don't chop off rows.
1868 value
+= (gfloat
)row_height
/ 2.0;
1871 value
= (value
< 0)? 0 : value
;
1872 #if GTK_CHECK_VERSION(3,0,0)
1873 value
= (value
> (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_page_size(adj
)))?
1874 (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_page_size(adj
)) : value
;
1876 value
= (value
> (adj
->upper
- adj
->page_size
))?
1877 (adj
->upper
- adj
->page_size
) : value
;
1881 gtk_adjustment_set_value(adj
, value
);
1883 gtk_tree_selection_unselect_all(selection
);
1887 int ListBoxX::GetSelection() {
1890 GtkTreeModel
*model
;
1891 GtkTreeSelection
*selection
;
1892 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1893 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
)) {
1894 GtkTreePath
*path
= gtk_tree_model_get_path(model
, &iter
);
1895 int *indices
= gtk_tree_path_get_indices(path
);
1896 // Don't free indices.
1899 gtk_tree_path_free(path
);
1904 int ListBoxX::Find(const char *prefix
) {
1906 GtkTreeModel
*model
=
1907 gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1908 bool valid
= gtk_tree_model_get_iter_first(model
, &iter
) != FALSE
;
1912 gtk_tree_model_get(model
, &iter
, TEXT_COLUMN
, &s
, -1);
1913 if (s
&& (0 == strncmp(prefix
, s
, strlen(prefix
)))) {
1918 valid
= gtk_tree_model_iter_next(model
, &iter
) != FALSE
;
1924 void ListBoxX::GetValue(int n
, char *value
, int len
) {
1927 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1928 bool valid
= gtk_tree_model_iter_nth_child(model
, &iter
, NULL
, n
) != FALSE
;
1930 gtk_tree_model_get(model
, &iter
, TEXT_COLUMN
, &text
, -1);
1932 if (text
&& len
> 0) {
1933 g_strlcpy(value
, text
, len
);
1940 // g_return_if_fail causes unnecessary compiler warning in release compile.
1942 #pragma warning(disable: 4127)
1945 void ListBoxX::RegisterRGBA(int type
, RGBAImage
*image
) {
1946 images
.Add(type
, image
);
1949 pixhash
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
1951 ListImage
*list_image
= static_cast<ListImage
*>(g_hash_table_lookup((GHashTable
*) pixhash
,
1952 (gconstpointer
) GINT_TO_POINTER(type
)));
1954 // Drop icon already registered
1955 if (list_image
->pixbuf
)
1956 g_object_unref(list_image
->pixbuf
);
1957 list_image
->pixbuf
= NULL
;
1958 list_image
->rgba_data
= image
;
1960 list_image
= g_new0(ListImage
, 1);
1961 list_image
->rgba_data
= image
;
1962 g_hash_table_insert((GHashTable
*) pixhash
, GINT_TO_POINTER(type
),
1963 (gpointer
) list_image
);
1967 void ListBoxX::RegisterImage(int type
, const char *xpm_data
) {
1968 g_return_if_fail(xpm_data
);
1969 XPM
xpmImage(xpm_data
);
1970 RegisterRGBA(type
, new RGBAImage(xpmImage
));
1973 void ListBoxX::RegisterRGBAImage(int type
, int width
, int height
, const unsigned char *pixelsImage
) {
1974 RegisterRGBA(type
, new RGBAImage(width
, height
, 1.0, pixelsImage
));
1977 void ListBoxX::ClearRegisteredImages() {
1981 void ListBoxX::SetList(const char *listText
, char separator
, char typesep
) {
1983 int count
= strlen(listText
) + 1;
1984 std::vector
<char> words(listText
, listText
+count
);
1985 char *startword
= &words
[0];
1986 char *numword
= NULL
;
1988 for (; words
[i
]; i
++) {
1989 if (words
[i
] == separator
) {
1993 Append(startword
, numword
?atoi(numword
+ 1):-1);
1994 startword
= &words
[0] + i
+ 1;
1996 } else if (words
[i
] == typesep
) {
1997 numword
= &words
[0] + i
;
2003 Append(startword
, numword
?atoi(numword
+ 1):-1);
2007 Menu::Menu() : mid(0) {}
2009 void Menu::CreatePopUp() {
2011 mid
= gtk_menu_new();
2012 #if GLIB_CHECK_VERSION(2,10,0)
2013 g_object_ref_sink(G_OBJECT(mid
));
2015 g_object_ref(G_OBJECT(mid
));
2016 gtk_object_sink(GTK_OBJECT(G_OBJECT(mid
)));
2020 void Menu::Destroy() {
2022 g_object_unref(G_OBJECT(mid
));
2026 static void MenuPositionFunc(GtkMenu
*, gint
*x
, gint
*y
, gboolean
*, gpointer userData
) {
2027 sptr_t intFromPointer
= reinterpret_cast<sptr_t
>(userData
);
2028 *x
= intFromPointer
& 0xffff;
2029 *y
= intFromPointer
>> 16;
2032 void Menu::Show(Point pt
, Window
&) {
2033 int screenHeight
= gdk_screen_height();
2034 int screenWidth
= gdk_screen_width();
2035 GtkMenu
*widget
= reinterpret_cast<GtkMenu
*>(mid
);
2036 gtk_widget_show_all(GTK_WIDGET(widget
));
2037 GtkRequisition requisition
;
2038 #if GTK_CHECK_VERSION(3,0,0)
2039 gtk_widget_get_preferred_size(GTK_WIDGET(widget
), NULL
, &requisition
);
2041 gtk_widget_size_request(GTK_WIDGET(widget
), &requisition
);
2043 if ((pt
.x
+ requisition
.width
) > screenWidth
) {
2044 pt
.x
= screenWidth
- requisition
.width
;
2046 if ((pt
.y
+ requisition
.height
) > screenHeight
) {
2047 pt
.y
= screenHeight
- requisition
.height
;
2049 gtk_menu_popup(widget
, NULL
, NULL
, MenuPositionFunc
,
2050 reinterpret_cast<void *>((static_cast<int>(pt
.y
) << 16) | static_cast<int>(pt
.x
)), 0,
2051 gtk_get_current_event_time());
2054 ElapsedTime::ElapsedTime() {
2056 g_get_current_time(&curTime
);
2057 bigBit
= curTime
.tv_sec
;
2058 littleBit
= curTime
.tv_usec
;
2061 class DynamicLibraryImpl
: public DynamicLibrary
{
2065 explicit DynamicLibraryImpl(const char *modulePath
) {
2066 m
= g_module_open(modulePath
, G_MODULE_BIND_LAZY
);
2069 virtual ~DynamicLibraryImpl() {
2074 // Use g_module_symbol to get a pointer to the relevant function.
2075 virtual Function
FindFunction(const char *name
) {
2077 gpointer fn_address
= NULL
;
2078 gboolean status
= g_module_symbol(m
, name
, &fn_address
);
2080 return static_cast<Function
>(fn_address
);
2088 virtual bool IsValid() {
2093 DynamicLibrary
*DynamicLibrary::Load(const char *modulePath
) {
2094 return static_cast<DynamicLibrary
*>( new DynamicLibraryImpl(modulePath
) );
2097 double ElapsedTime::Duration(bool reset
) {
2099 g_get_current_time(&curTime
);
2100 long endBigBit
= curTime
.tv_sec
;
2101 long endLittleBit
= curTime
.tv_usec
;
2102 double result
= 1000000.0 * (endBigBit
- bigBit
);
2103 result
+= endLittleBit
- littleBit
;
2104 result
/= 1000000.0;
2107 littleBit
= endLittleBit
;
2112 ColourDesired
Platform::Chrome() {
2113 return ColourDesired(0xe0, 0xe0, 0xe0);
2116 ColourDesired
Platform::ChromeHighlight() {
2117 return ColourDesired(0xff, 0xff, 0xff);
2120 const char *Platform::DefaultFont() {
2122 return "Lucida Console";
2128 int Platform::DefaultFontSize() {
2136 unsigned int Platform::DoubleClickTime() {
2137 return 500; // Half a second
2140 bool Platform::MouseButtonBounce() {
2144 void Platform::DebugDisplay(const char *s
) {
2145 fprintf(stderr
, "%s", s
);
2148 bool Platform::IsKeyDown(int) {
2149 // TODO: discover state of keys in GTK+/X
2153 long Platform::SendScintilla(
2154 WindowID w
, unsigned int msg
, unsigned long wParam
, long lParam
) {
2155 return scintilla_send_message(SCINTILLA(w
), msg
, wParam
, lParam
);
2158 long Platform::SendScintillaPointer(
2159 WindowID w
, unsigned int msg
, unsigned long wParam
, void *lParam
) {
2160 return scintilla_send_message(SCINTILLA(w
), msg
, wParam
,
2161 reinterpret_cast<sptr_t
>(lParam
));
2164 bool Platform::IsDBCSLeadByte(int codePage
, char ch
) {
2165 // Byte ranges found in Wikipedia articles with relevant search strings in each case
2166 unsigned char uch
= static_cast<unsigned char>(ch
);
2170 return ((uch
>= 0x81) && (uch
<= 0x9F)) ||
2171 ((uch
>= 0xE0) && (uch
<= 0xFC));
2172 // Lead bytes F0 to FC may be a Microsoft addition.
2175 return (uch
>= 0x81) && (uch
<= 0xFE);
2178 return (uch
>= 0x81) && (uch
<= 0xFE);
2179 // Korean EUC-KR may be code page 949.
2184 int Platform::DBCSCharLength(int codePage
, const char *s
) {
2185 if (codePage
== 932 || codePage
== 936 || codePage
== 950) {
2186 return IsDBCSLeadByte(codePage
, s
[0]) ? 2 : 1;
2188 int bytes
= mblen(s
, MB_CUR_MAX
);
2196 int Platform::DBCSCharMaxLength() {
2201 // These are utility functions not really tied to a platform
2203 int Platform::Minimum(int a
, int b
) {
2210 int Platform::Maximum(int a
, int b
) {
2220 void Platform::DebugPrintf(const char *format
, ...) {
2223 va_start(pArguments
, format
);
2224 vsprintf(buffer
, format
, pArguments
);
2226 Platform::DebugDisplay(buffer
);
2229 void Platform::DebugPrintf(const char *, ...) {}
2233 // Not supported for GTK+
2234 static bool assertionPopUps
= true;
2236 bool Platform::ShowAssertionPopUps(bool assertionPopUps_
) {
2237 bool ret
= assertionPopUps
;
2238 assertionPopUps
= assertionPopUps_
;
2242 void Platform::Assert(const char *c
, const char *file
, int line
) {
2244 g_snprintf(buffer
, sizeof(buffer
), "Assertion [%s] failed at %s %d\r\n", c
, file
, line
);
2245 Platform::DebugDisplay(buffer
);
2249 int Platform::Clamp(int val
, int minVal
, int maxVal
) {
2257 void Platform_Initialise() {
2258 FontMutexAllocate();
2261 void Platform_Finalise() {
2262 FontCached::ReleaseAll();