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.
21 #include <gdk/gdkkeysyms.h>
25 #include "Scintilla.h"
26 #include "ScintillaWidget.h"
27 #include "StringCopy.h"
29 #include "UniConversion.h"
31 #if defined(__clang__)
32 // Clang 3.0 incorrectly displays sentinel warnings. Fixed by clang 3.1.
33 #pragma GCC diagnostic ignored "-Wsentinel"
36 /* GLIB must be compiled with thread support, otherwise we
37 will bail on trying to use locks, and that could lead to
38 problems for someone. `glib-config --libs gthread` needs
39 to be used to get the glib libraries for linking, otherwise
40 g_thread_init will fail */
41 #define USE_LOCK defined(G_THREADS_ENABLED) && !defined(G_THREADS_IMPL_NONE)
43 #include "Converter.h"
45 static const double kPi
= 3.14159265358979323846;
47 // The Pango version guard for pango_units_from_double and pango_units_to_double
48 // is more complex than simply implementing these here.
50 static int pangoUnitsFromDouble(double d
) {
51 return static_cast<int>(d
* PANGO_SCALE
+ 0.5);
54 static double doubleFromPangoUnits(int pu
) {
55 return static_cast<double>(pu
) / PANGO_SCALE
;
58 static cairo_surface_t
*CreateSimilarSurface(GdkWindow
*window
, cairo_content_t content
, int width
, int height
) {
59 #if GTK_CHECK_VERSION(2,22,0)
60 return gdk_window_create_similar_surface(window
, content
, width
, height
);
62 cairo_surface_t
*window_surface
, *surface
;
64 g_return_val_if_fail(GDK_IS_WINDOW(window
), NULL
);
66 window_surface
= GDK_DRAWABLE_GET_CLASS(window
)->ref_cairo_surface(window
);
68 surface
= cairo_surface_create_similar(window_surface
, content
, width
, height
);
70 cairo_surface_destroy(window_surface
);
76 static GdkWindow
*WindowFromWidget(GtkWidget
*w
) {
77 return gtk_widget_get_window(w
);
81 // Ignore unreferenced local functions in GTK+ headers
82 #pragma warning(disable: 4505)
86 using namespace Scintilla
;
89 enum encodingType
{ singleByte
, UTF8
, dbcs
};
100 static GMutex
*fontMutex
= NULL
;
102 static void InitializeGLIBThreads() {
103 #if !GLIB_CHECK_VERSION(2,31,0)
104 if (!g_thread_supported()) {
111 static void FontMutexAllocate() {
114 InitializeGLIBThreads();
115 #if GLIB_CHECK_VERSION(2,31,0)
116 fontMutex
= g_new(GMutex
, 1);
117 g_mutex_init(fontMutex
);
119 fontMutex
= g_mutex_new();
125 static void FontMutexFree() {
128 #if GLIB_CHECK_VERSION(2,31,0)
129 g_mutex_clear(fontMutex
);
132 g_mutex_free(fontMutex
);
139 static void FontMutexLock() {
141 g_mutex_lock(fontMutex
);
145 static void FontMutexUnlock() {
148 g_mutex_unlock(fontMutex
);
153 // Holds a PangoFontDescription*.
155 XYPOSITION width
[128];
159 PangoFontDescription
*pfd
;
161 FontHandle() : et(singleByte
), ascent(0), pfd(0), characterSet(-1) {
164 FontHandle(PangoFontDescription
*pfd_
, int characterSet_
) {
168 characterSet
= characterSet_
;
173 pango_font_description_free(pfd
);
176 void ResetWidths(encodingType et_
) {
178 for (int i
=0; i
<=127; i
++) {
182 XYPOSITION
CharWidth(unsigned char ch
, encodingType et_
) const {
185 if ((ch
<= 127) && (et
== et_
)) {
191 void SetCharWidth(unsigned char ch
, XYPOSITION w
, encodingType et_
) {
203 // X has a 16 bit coordinate space, so stop drawing here to avoid wrapping
204 static const int maxCoordinate
= 32000;
206 static FontHandle
*PFont(Font
&f
) {
207 return static_cast<FontHandle
*>(f
.GetID());
210 static GtkWidget
*PWidget(WindowID wid
) {
211 return static_cast<GtkWidget
*>(wid
);
214 Point
Point::FromLong(long lpoint
) {
216 Platform::LowShortFromLong(lpoint
),
217 Platform::HighShortFromLong(lpoint
));
220 static void SetLogFont(LOGFONT
&lf
, const char *faceName
, int characterSet
, float size
, int weight
, bool italic
) {
225 lf
.characterSet
= characterSet
;
226 StringCopy(lf
.faceName
, faceName
);
230 * Create a hash from the parameters for a font to allow easy checking for identity.
231 * If one font is the same as another, its hash will be the same, but if the hash is the
232 * same then they may still be different.
234 static int HashFont(const FontParameters
&fp
) {
236 static_cast<int>(fp
.size
+0.5) ^
237 (fp
.characterSet
<< 10) ^
238 ((fp
.weight
/ 100) << 12) ^
239 (fp
.italic
? 0x20000000 : 0) ^
243 class FontCached
: Font
{
248 explicit FontCached(const FontParameters
&fp
);
250 bool SameAs(const FontParameters
&fp
);
251 virtual void Release();
252 static FontID
CreateNewFont(const FontParameters
&fp
);
253 static FontCached
*first
;
255 static FontID
FindOrCreate(const FontParameters
&fp
);
256 static void ReleaseId(FontID fid_
);
257 static void ReleaseAll();
260 FontCached
*FontCached::first
= 0;
262 FontCached::FontCached(const FontParameters
&fp
) :
263 next(0), usage(0), hash(0) {
264 ::SetLogFont(lf
, fp
.faceName
, fp
.characterSet
, fp
.size
, fp
.weight
, fp
.italic
);
266 fid
= CreateNewFont(fp
);
270 bool FontCached::SameAs(const FontParameters
&fp
) {
272 lf
.size
== fp
.size
&&
273 lf
.weight
== fp
.weight
&&
274 lf
.italic
== fp
.italic
&&
275 lf
.characterSet
== fp
.characterSet
&&
276 0 == strcmp(lf
.faceName
, fp
.faceName
);
279 void FontCached::Release() {
285 FontID
FontCached::FindOrCreate(const FontParameters
&fp
) {
288 int hashFind
= HashFont(fp
);
289 for (FontCached
*cur
= first
; cur
; cur
= cur
->next
) {
290 if ((cur
->hash
== hashFind
) &&
297 FontCached
*fc
= new FontCached(fp
);
306 void FontCached::ReleaseId(FontID fid_
) {
308 FontCached
**pcur
= &first
;
309 for (FontCached
*cur
= first
; cur
; cur
= cur
->next
) {
310 if (cur
->fid
== fid_
) {
312 if (cur
->usage
== 0) {
325 void FontCached::ReleaseAll() {
327 ReleaseId(first
->GetID());
331 FontID
FontCached::CreateNewFont(const FontParameters
&fp
) {
332 PangoFontDescription
*pfd
= pango_font_description_new();
334 pango_font_description_set_family(pfd
,
335 (fp
.faceName
[0] == '!') ? fp
.faceName
+1 : fp
.faceName
);
336 pango_font_description_set_size(pfd
, pangoUnitsFromDouble(fp
.size
));
337 pango_font_description_set_weight(pfd
, static_cast<PangoWeight
>(fp
.weight
));
338 pango_font_description_set_style(pfd
, fp
.italic
? PANGO_STYLE_ITALIC
: PANGO_STYLE_NORMAL
);
339 return new FontHandle(pfd
, fp
.characterSet
);
342 return new FontHandle();
345 Font::Font() : fid(0) {}
349 void Font::Create(const FontParameters
&fp
) {
351 fid
= FontCached::FindOrCreate(fp
);
354 void Font::Release() {
356 FontCached::ReleaseId(fid
);
362 namespace Scintilla
{
365 // SurfaceID is a cairo_t*
366 class SurfaceImpl
: public Surface
{
369 cairo_surface_t
*psurf
;
374 PangoContext
*pcontext
;
378 void SetConverter(int characterSet_
);
381 virtual ~SurfaceImpl();
383 void Init(WindowID wid
);
384 void Init(SurfaceID sid
, WindowID wid
);
385 void InitPixMap(int width
, int height
, Surface
*surface_
, WindowID wid
);
389 void PenColour(ColourDesired fore
);
391 int DeviceHeightFont(int points
);
392 void MoveTo(int x_
, int y_
);
393 void LineTo(int x_
, int y_
);
394 void Polygon(Point
*pts
, int npts
, ColourDesired fore
, ColourDesired back
);
395 void RectangleDraw(PRectangle rc
, ColourDesired fore
, ColourDesired back
);
396 void FillRectangle(PRectangle rc
, ColourDesired back
);
397 void FillRectangle(PRectangle rc
, Surface
&surfacePattern
);
398 void RoundedRectangle(PRectangle rc
, ColourDesired fore
, ColourDesired back
);
399 void AlphaRectangle(PRectangle rc
, int cornerSize
, ColourDesired fill
, int alphaFill
,
400 ColourDesired outline
, int alphaOutline
, int flags
);
401 void DrawRGBAImage(PRectangle rc
, int width
, int height
, const unsigned char *pixelsImage
);
402 void Ellipse(PRectangle rc
, ColourDesired fore
, ColourDesired back
);
403 void Copy(PRectangle rc
, Point from
, Surface
&surfaceSource
);
405 void DrawTextBase(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
, ColourDesired fore
);
406 void DrawTextNoClip(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
, ColourDesired fore
, ColourDesired back
);
407 void DrawTextClipped(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
, ColourDesired fore
, ColourDesired back
);
408 void DrawTextTransparent(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
, ColourDesired fore
);
409 void MeasureWidths(Font
&font_
, const char *s
, int len
, XYPOSITION
*positions
);
410 XYPOSITION
WidthText(Font
&font_
, const char *s
, int len
);
411 XYPOSITION
WidthChar(Font
&font_
, char ch
);
412 XYPOSITION
Ascent(Font
&font_
);
413 XYPOSITION
Descent(Font
&font_
);
414 XYPOSITION
InternalLeading(Font
&font_
);
415 XYPOSITION
ExternalLeading(Font
&font_
);
416 XYPOSITION
Height(Font
&font_
);
417 XYPOSITION
AverageCharWidth(Font
&font_
);
419 void SetClip(PRectangle rc
);
420 void FlushCachedState();
422 void SetUnicodeMode(bool unicodeMode_
);
423 void SetDBCSMode(int codePage
);
429 const char *CharacterSetID(int characterSet
) {
430 switch (characterSet
) {
431 case SC_CHARSET_ANSI
:
433 case SC_CHARSET_DEFAULT
:
435 case SC_CHARSET_BALTIC
:
436 return "ISO-8859-13";
437 case SC_CHARSET_CHINESEBIG5
:
439 case SC_CHARSET_EASTEUROPE
:
441 case SC_CHARSET_GB2312
:
443 case SC_CHARSET_GREEK
:
445 case SC_CHARSET_HANGUL
:
451 case SC_CHARSET_RUSSIAN
:
453 case SC_CHARSET_OEM866
:
455 case SC_CHARSET_CYRILLIC
:
457 case SC_CHARSET_SHIFTJIS
:
459 case SC_CHARSET_SYMBOL
:
461 case SC_CHARSET_TURKISH
:
463 case SC_CHARSET_JOHAB
:
465 case SC_CHARSET_HEBREW
:
467 case SC_CHARSET_ARABIC
:
469 case SC_CHARSET_VIETNAMESE
:
471 case SC_CHARSET_THAI
:
472 return "ISO-8859-11";
473 case SC_CHARSET_8859_15
:
474 return "ISO-8859-15";
480 void SurfaceImpl::SetConverter(int characterSet_
) {
481 if (characterSet
!= characterSet_
) {
482 characterSet
= characterSet_
;
483 conv
.Open("UTF-8", CharacterSetID(characterSet
), false);
487 SurfaceImpl::SurfaceImpl() : et(singleByte
),
490 x(0), y(0), inited(false), createdGC(false)
491 , pcontext(0), layout(0), characterSet(-1) {
494 SurfaceImpl::~SurfaceImpl() {
498 void SurfaceImpl::Release() {
502 cairo_destroy(context
);
506 cairo_surface_destroy(psurf
);
509 g_object_unref(layout
);
512 g_object_unref(pcontext
);
522 bool SurfaceImpl::Initialised() {
523 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 8, 0)
524 if (inited
&& context
) {
525 if (cairo_status(context
) == CAIRO_STATUS_SUCCESS
) {
526 // Even when status is success, the target surface may have been
527 // finished whch may cause an assertion to fail crashing the application.
528 // The cairo_surface_has_show_text_glyphs call checks the finished flag
529 // and when set, sets the status to CAIRO_STATUS_SURFACE_FINISHED
530 // which leads to warning messages instead of crashes.
531 // Performing the check in this method as it is called rarely and has no
532 // other side effects.
533 cairo_surface_t
*psurfContext
= cairo_get_target(context
);
535 cairo_surface_has_show_text_glyphs(psurfContext
);
538 return cairo_status(context
) == CAIRO_STATUS_SUCCESS
;
544 void SurfaceImpl::Init(WindowID wid
) {
546 PLATFORM_ASSERT(wid
);
547 // if we are only created from a window ID, we can't perform drawing
551 pcontext
= gtk_widget_create_pango_context(PWidget(wid
));
552 PLATFORM_ASSERT(pcontext
);
553 layout
= pango_layout_new(pcontext
);
554 PLATFORM_ASSERT(layout
);
558 void SurfaceImpl::Init(SurfaceID sid
, WindowID wid
) {
559 PLATFORM_ASSERT(sid
);
561 PLATFORM_ASSERT(wid
);
562 context
= cairo_reference(static_cast<cairo_t
*>(sid
));
563 pcontext
= gtk_widget_create_pango_context(PWidget(wid
));
564 // update the Pango context in case sid isn't the widget's surface
565 pango_cairo_update_context(context
, pcontext
);
566 layout
= pango_layout_new(pcontext
);
567 cairo_set_line_width(context
, 1);
572 void SurfaceImpl::InitPixMap(int width
, int height
, Surface
*surface_
, WindowID wid
) {
573 PLATFORM_ASSERT(surface_
);
575 SurfaceImpl
*surfImpl
= static_cast<SurfaceImpl
*>(surface_
);
576 PLATFORM_ASSERT(wid
);
577 context
= cairo_reference(surfImpl
->context
);
578 pcontext
= gtk_widget_create_pango_context(PWidget(wid
));
579 // update the Pango context in case surface_ isn't the widget's surface
580 pango_cairo_update_context(context
, pcontext
);
581 PLATFORM_ASSERT(pcontext
);
582 layout
= pango_layout_new(pcontext
);
583 PLATFORM_ASSERT(layout
);
584 if (height
> 0 && width
> 0)
585 psurf
= CreateSimilarSurface(
586 WindowFromWidget(PWidget(wid
)),
587 CAIRO_CONTENT_COLOR_ALPHA
, width
, height
);
588 cairo_destroy(context
);
589 context
= cairo_create(psurf
);
590 cairo_rectangle(context
, 0, 0, width
, height
);
591 cairo_set_source_rgb(context
, 1.0, 0, 0);
593 // This produces sharp drawing more similar to GDK:
594 //cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
595 cairo_set_line_width(context
, 1);
601 void SurfaceImpl::PenColour(ColourDesired fore
) {
603 ColourDesired
cdFore(fore
.AsLong());
604 cairo_set_source_rgb(context
,
605 cdFore
.GetRed() / 255.0,
606 cdFore
.GetGreen() / 255.0,
607 cdFore
.GetBlue() / 255.0);
611 int SurfaceImpl::LogPixelsY() {
615 int SurfaceImpl::DeviceHeightFont(int points
) {
616 int logPix
= LogPixelsY();
617 return (points
* logPix
+ logPix
/ 2) / 72;
620 void SurfaceImpl::MoveTo(int x_
, int y_
) {
625 static int Delta(int difference
) {
628 else if (difference
> 0)
634 void SurfaceImpl::LineTo(int x_
, int y_
) {
635 // cairo_line_to draws the end position, unlike Win32 or GDK with GDK_CAP_NOT_LAST.
636 // For simple cases, move back one pixel from end.
639 int xDelta
= Delta(xDiff
);
641 int yDelta
= Delta(yDiff
);
642 if ((xDiff
== 0) || (yDiff
== 0)) {
643 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
644 int xEnd
= x_
- xDelta
;
645 int left
= Platform::Minimum(x
, xEnd
);
646 int width
= abs(x
- xEnd
) + 1;
647 int yEnd
= y_
- yDelta
;
648 int top
= Platform::Minimum(y
, yEnd
);
649 int height
= abs(y
- yEnd
) + 1;
650 cairo_rectangle(context
, left
, top
, width
, height
);
652 } else if ((abs(xDiff
) == abs(yDiff
))) {
654 cairo_move_to(context
, x
+ 0.5, y
+ 0.5);
655 cairo_line_to(context
, x_
+ 0.5 - xDelta
, y_
+ 0.5 - yDelta
);
657 // Line has a different slope so difficult to avoid last pixel
658 cairo_move_to(context
, x
+ 0.5, y
+ 0.5);
659 cairo_line_to(context
, x_
+ 0.5, y_
+ 0.5);
661 cairo_stroke(context
);
667 void SurfaceImpl::Polygon(Point
*pts
, int npts
, ColourDesired fore
,
668 ColourDesired back
) {
669 PLATFORM_ASSERT(context
);
671 cairo_move_to(context
, pts
[0].x
+ 0.5, pts
[0].y
+ 0.5);
672 for (int i
= 1; i
< npts
; i
++) {
673 cairo_line_to(context
, pts
[i
].x
+ 0.5, pts
[i
].y
+ 0.5);
675 cairo_close_path(context
);
676 cairo_fill_preserve(context
);
678 cairo_stroke(context
);
681 void SurfaceImpl::RectangleDraw(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
683 cairo_rectangle(context
, rc
.left
+ 0.5, rc
.top
+ 0.5,
684 rc
.right
- rc
.left
- 1, rc
.bottom
- rc
.top
- 1);
686 cairo_fill_preserve(context
);
688 cairo_stroke(context
);
692 void SurfaceImpl::FillRectangle(PRectangle rc
, ColourDesired back
) {
694 if (context
&& (rc
.left
< maxCoordinate
)) { // Protect against out of range
695 rc
.left
= lround(rc
.left
);
696 rc
.right
= lround(rc
.right
);
697 cairo_rectangle(context
, rc
.left
, rc
.top
,
698 rc
.right
- rc
.left
, rc
.bottom
- rc
.top
);
703 void SurfaceImpl::FillRectangle(PRectangle rc
, Surface
&surfacePattern
) {
704 SurfaceImpl
&surfi
= static_cast<SurfaceImpl
&>(surfacePattern
);
705 bool canDraw
= surfi
.psurf
!= NULL
;
707 PLATFORM_ASSERT(context
);
708 // Tile pattern over rectangle
709 // Currently assumes 8x8 pattern
712 for (int xTile
= rc
.left
; xTile
< rc
.right
; xTile
+= widthPat
) {
713 int widthx
= (xTile
+ widthPat
> rc
.right
) ? rc
.right
- xTile
: widthPat
;
714 for (int yTile
= rc
.top
; yTile
< rc
.bottom
; yTile
+= heightPat
) {
715 int heighty
= (yTile
+ heightPat
> rc
.bottom
) ? rc
.bottom
- yTile
: heightPat
;
716 cairo_set_source_surface(context
, surfi
.psurf
, xTile
, yTile
);
717 cairo_rectangle(context
, xTile
, yTile
, widthx
, heighty
);
722 // Something is wrong so try to show anyway
723 // Shows up black because colour not allocated
724 FillRectangle(rc
, ColourDesired(0));
728 void SurfaceImpl::RoundedRectangle(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
729 if (((rc
.right
- rc
.left
) > 4) && ((rc
.bottom
- rc
.top
) > 4)) {
730 // Approximate a round rect with some cut off corners
732 Point(rc
.left
+ 2, rc
.top
),
733 Point(rc
.right
- 2, rc
.top
),
734 Point(rc
.right
, rc
.top
+ 2),
735 Point(rc
.right
, rc
.bottom
- 2),
736 Point(rc
.right
- 2, rc
.bottom
),
737 Point(rc
.left
+ 2, rc
.bottom
),
738 Point(rc
.left
, rc
.bottom
- 2),
739 Point(rc
.left
, rc
.top
+ 2),
741 Polygon(pts
, ELEMENTS(pts
), fore
, back
);
743 RectangleDraw(rc
, fore
, back
);
747 static void PathRoundRectangle(cairo_t
*context
, double left
, double top
, double width
, double height
, int radius
) {
748 double degrees
= kPi
/ 180.0;
750 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 2, 0)
751 cairo_new_sub_path(context
);
753 // First arc is in the top-right corner and starts from a point on the top line
754 cairo_move_to(context
, left
+ width
- radius
, top
);
756 cairo_arc(context
, left
+ width
- radius
, top
+ radius
, radius
, -90 * degrees
, 0 * degrees
);
757 cairo_arc(context
, left
+ width
- radius
, top
+ height
- radius
, radius
, 0 * degrees
, 90 * degrees
);
758 cairo_arc(context
, left
+ radius
, top
+ height
- radius
, radius
, 90 * degrees
, 180 * degrees
);
759 cairo_arc(context
, left
+ radius
, top
+ radius
, radius
, 180 * degrees
, 270 * degrees
);
760 cairo_close_path(context
);
763 void SurfaceImpl::AlphaRectangle(PRectangle rc
, int cornerSize
, ColourDesired fill
, int alphaFill
,
764 ColourDesired outline
, int alphaOutline
, int flags
) {
765 if (context
&& rc
.Width() > 0) {
766 ColourDesired
cdFill(fill
.AsLong());
767 cairo_set_source_rgba(context
,
768 cdFill
.GetRed() / 255.0,
769 cdFill
.GetGreen() / 255.0,
770 cdFill
.GetBlue() / 255.0,
773 PathRoundRectangle(context
, rc
.left
+ 1.0, rc
.top
+ 1.0, rc
.right
- rc
.left
- 2.0, rc
.bottom
- rc
.top
- 2.0, cornerSize
);
775 cairo_rectangle(context
, rc
.left
+ 1.0, rc
.top
+ 1.0, rc
.right
- rc
.left
- 2.0, rc
.bottom
- rc
.top
- 2.0);
778 ColourDesired
cdOutline(outline
.AsLong());
779 cairo_set_source_rgba(context
,
780 cdOutline
.GetRed() / 255.0,
781 cdOutline
.GetGreen() / 255.0,
782 cdOutline
.GetBlue() / 255.0,
783 alphaOutline
/ 255.0);
785 PathRoundRectangle(context
, rc
.left
+ 0.5, rc
.top
+ 0.5, rc
.right
- rc
.left
- 1, rc
.bottom
- rc
.top
- 1, cornerSize
);
787 cairo_rectangle(context
, rc
.left
+ 0.5, rc
.top
+ 0.5, rc
.right
- rc
.left
- 1, rc
.bottom
- rc
.top
- 1);
788 cairo_stroke(context
);
792 void SurfaceImpl::DrawRGBAImage(PRectangle rc
, int width
, int height
, const unsigned char *pixelsImage
) {
793 PLATFORM_ASSERT(context
);
794 if (rc
.Width() > width
)
795 rc
.left
+= (rc
.Width() - width
) / 2;
796 rc
.right
= rc
.left
+ width
;
797 if (rc
.Height() > height
)
798 rc
.top
+= (rc
.Height() - height
) / 2;
799 rc
.bottom
= rc
.top
+ height
;
801 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
802 int stride
= cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32
, width
);
804 int stride
= width
* 4;
806 int ucs
= stride
* height
;
807 std::vector
<unsigned char> image(ucs
);
808 for (int iy
=0; iy
<height
; iy
++) {
809 for (int ix
=0; ix
<width
; ix
++) {
810 unsigned char *pixel
= &image
[0] + iy
*stride
+ ix
* 4;
811 unsigned char alpha
= pixelsImage
[3];
812 pixel
[2] = (*pixelsImage
++) * alpha
/ 255;
813 pixel
[1] = (*pixelsImage
++) * alpha
/ 255;
814 pixel
[0] = (*pixelsImage
++) * alpha
/ 255;
815 pixel
[3] = *pixelsImage
++;
819 cairo_surface_t
*psurfImage
= cairo_image_surface_create_for_data(&image
[0], CAIRO_FORMAT_ARGB32
, width
, height
, stride
);
820 cairo_set_source_surface(context
, psurfImage
, rc
.left
, rc
.top
);
821 cairo_rectangle(context
, rc
.left
, rc
.top
, rc
.right
-rc
.left
, rc
.bottom
-rc
.top
);
824 cairo_surface_destroy(psurfImage
);
827 void SurfaceImpl::Ellipse(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
828 PLATFORM_ASSERT(context
);
830 cairo_arc(context
, (rc
.left
+ rc
.right
) / 2, (rc
.top
+ rc
.bottom
) / 2,
831 Platform::Minimum(rc
.Width(), rc
.Height()) / 2, 0, 2*kPi
);
832 cairo_fill_preserve(context
);
834 cairo_stroke(context
);
837 void SurfaceImpl::Copy(PRectangle rc
, Point from
, Surface
&surfaceSource
) {
838 SurfaceImpl
&surfi
= static_cast<SurfaceImpl
&>(surfaceSource
);
839 bool canDraw
= surfi
.psurf
!= NULL
;
841 PLATFORM_ASSERT(context
);
842 cairo_set_source_surface(context
, surfi
.psurf
,
843 rc
.left
- from
.x
, rc
.top
- from
.y
);
844 cairo_rectangle(context
, rc
.left
, rc
.top
, rc
.right
-rc
.left
, rc
.bottom
-rc
.top
);
849 std::string
UTF8FromLatin1(const char *s
, int len
) {
850 std::string
utfForm(len
*2 + 1, '\0');
852 for (int i
=0; i
<len
; i
++) {
853 unsigned int uch
= static_cast<unsigned char>(s
[i
]);
855 utfForm
[lenU
++] = uch
;
857 utfForm
[lenU
++] = static_cast<char>(0xC0 | (uch
>> 6));
858 utfForm
[lenU
++] = static_cast<char>(0x80 | (uch
& 0x3f));
861 utfForm
.resize(lenU
);
865 static std::string
UTF8FromIconv(const Converter
&conv
, const char *s
, int len
) {
867 std::string
utfForm(len
*3+1, '\0');
868 char *pin
= const_cast<char *>(s
);
870 char *putf
= &utfForm
[0];
872 size_t outLeft
= len
*3+1;
873 size_t conversions
= conv
.Convert(&pin
, &inLeft
, &pout
, &outLeft
);
874 if (conversions
!= ((size_t)(-1))) {
876 utfForm
.resize(pout
- putf
);
880 return std::string();
883 // Work out how many bytes are in a character by trying to convert using iconv,
884 // returning the first length that succeeds.
885 static size_t MultiByteLenFromIconv(const Converter
&conv
, const char *s
, size_t len
) {
886 for (size_t lenMB
=1; (lenMB
<4) && (lenMB
<= len
); lenMB
++) {
888 char *pin
= const_cast<char *>(s
);
889 size_t inLeft
= lenMB
;
892 size_t conversions
= conv
.Convert(&pin
, &inLeft
, &pout
, &outLeft
);
893 if (conversions
!= ((size_t)(-1))) {
900 void SurfaceImpl::DrawTextBase(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
901 ColourDesired fore
) {
904 XYPOSITION xText
= rc
.left
;
905 if (PFont(font_
)->pfd
) {
908 pango_layout_set_text(layout
, s
, len
);
910 SetConverter(PFont(font_
)->characterSet
);
911 utfForm
= UTF8FromIconv(conv
, s
, len
);
912 if (utfForm
.empty()) { // iconv failed so treat as Latin1
913 utfForm
= UTF8FromLatin1(s
, len
);
915 pango_layout_set_text(layout
, utfForm
.c_str(), utfForm
.length());
917 pango_layout_set_font_description(layout
, PFont(font_
)->pfd
);
918 pango_cairo_update_layout(context
, layout
);
920 PangoLayoutLine
*pll
= pango_layout_get_line_readonly(layout
,0);
922 PangoLayoutLine
*pll
= pango_layout_get_line(layout
,0);
924 cairo_move_to(context
, xText
, ybase
);
925 pango_cairo_show_layout_line(context
, pll
);
930 void SurfaceImpl::DrawTextNoClip(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
931 ColourDesired fore
, ColourDesired back
) {
932 FillRectangle(rc
, back
);
933 DrawTextBase(rc
, font_
, ybase
, s
, len
, fore
);
936 // On GTK+, exactly same as DrawTextNoClip
937 void SurfaceImpl::DrawTextClipped(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
938 ColourDesired fore
, ColourDesired back
) {
939 FillRectangle(rc
, back
);
940 DrawTextBase(rc
, font_
, ybase
, s
, len
, fore
);
943 void SurfaceImpl::DrawTextTransparent(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
944 ColourDesired fore
) {
945 // Avoid drawing spaces in transparent mode
946 for (int i
=0; i
<len
; i
++) {
948 DrawTextBase(rc
, font_
, ybase
, s
, len
, fore
);
954 class ClusterIterator
{
955 PangoLayoutIter
*iter
;
960 XYPOSITION positionStart
;
964 ClusterIterator(PangoLayout
*layout
, int len
) : lenPositions(len
), finished(false),
965 positionStart(0), position(0), distance(0), curIndex(0) {
966 iter
= pango_layout_get_iter(layout
);
967 pango_layout_iter_get_cluster_extents(iter
, NULL
, &pos
);
970 pango_layout_iter_free(iter
);
974 positionStart
= position
;
975 if (pango_layout_iter_next_cluster(iter
)) {
976 pango_layout_iter_get_cluster_extents(iter
, NULL
, &pos
);
977 position
= doubleFromPangoUnits(pos
.x
);
978 curIndex
= pango_layout_iter_get_index(iter
);
981 position
= doubleFromPangoUnits(pos
.x
+ pos
.width
);
982 curIndex
= lenPositions
;
984 distance
= position
- positionStart
;
988 void SurfaceImpl::MeasureWidths(Font
&font_
, const char *s
, int len
, XYPOSITION
*positions
) {
990 const int lenPositions
= len
;
991 if (PFont(font_
)->pfd
) {
993 int width
= PFont(font_
)->CharWidth(*s
, et
);
995 positions
[0] = width
;
999 pango_layout_set_font_description(layout
, PFont(font_
)->pfd
);
1001 // Simple and direct as UTF-8 is native Pango encoding
1003 pango_layout_set_text(layout
, s
, len
);
1004 ClusterIterator
iti(layout
, lenPositions
);
1005 while (!iti
.finished
) {
1007 int places
= iti
.curIndex
- i
;
1008 while (i
< iti
.curIndex
) {
1009 // Evenly distribute space among bytes of this cluster.
1010 // Would be better to find number of characters and then
1011 // divide evenly between characters with each byte of a character
1012 // being at the same position.
1013 positions
[i
] = iti
.position
- (iti
.curIndex
- 1 - i
) * iti
.distance
/ places
;
1017 PLATFORM_ASSERT(i
== lenPositions
);
1019 int positionsCalculated
= 0;
1021 SetConverter(PFont(font_
)->characterSet
);
1022 std::string utfForm
= UTF8FromIconv(conv
, s
, len
);
1023 if (!utfForm
.empty()) {
1024 // Convert to UTF-8 so can ask Pango for widths, then
1025 // Loop through UTF-8 and DBCS forms, taking account of different
1026 // character byte lengths.
1027 Converter
convMeasure("UCS-2", CharacterSetID(characterSet
), false);
1028 pango_layout_set_text(layout
, utfForm
.c_str(), strlen(utfForm
.c_str()));
1030 int clusterStart
= 0;
1031 ClusterIterator
iti(layout
, strlen(utfForm
.c_str()));
1032 while (!iti
.finished
) {
1034 int clusterEnd
= iti
.curIndex
;
1035 int places
= g_utf8_strlen(utfForm
.c_str() + clusterStart
, clusterEnd
- clusterStart
);
1037 while (clusterStart
< clusterEnd
) {
1038 size_t lenChar
= MultiByteLenFromIconv(convMeasure
, s
+i
, len
-i
);
1040 positions
[i
++] = iti
.position
- (places
- place
) * iti
.distance
/ places
;
1041 positionsCalculated
++;
1043 clusterStart
+= UTF8CharLength(static_cast<unsigned char>(utfForm
.c_str()[clusterStart
]));
1047 PLATFORM_ASSERT(i
== lenPositions
);
1050 if (positionsCalculated
< 1 ) {
1051 // Either 8-bit or DBCS conversion failed so treat as 8-bit.
1052 SetConverter(PFont(font_
)->characterSet
);
1053 const bool rtlCheck
= PFont(font_
)->characterSet
== SC_CHARSET_HEBREW
||
1054 PFont(font_
)->characterSet
== SC_CHARSET_ARABIC
;
1055 std::string utfForm
= UTF8FromIconv(conv
, s
, len
);
1056 if (utfForm
.empty()) {
1057 utfForm
= UTF8FromLatin1(s
, len
);
1059 pango_layout_set_text(layout
, utfForm
.c_str(), utfForm
.length());
1061 int clusterStart
= 0;
1062 // Each 8-bit input character may take 1 or 2 bytes in UTF-8
1063 // and groups of up to 3 may be represented as ligatures.
1064 ClusterIterator
iti(layout
, utfForm
.length());
1065 while (!iti
.finished
) {
1067 int clusterEnd
= iti
.curIndex
;
1068 int ligatureLength
= g_utf8_strlen(utfForm
.c_str() + clusterStart
, clusterEnd
- clusterStart
);
1069 if (rtlCheck
&& ((clusterEnd
<= clusterStart
) || (ligatureLength
== 0) || (ligatureLength
> 3))) {
1070 // Something has gone wrong: exit quickly but pretend all the characters are equally spaced:
1071 int widthLayout
= 0;
1072 pango_layout_get_size(layout
, &widthLayout
, NULL
);
1073 XYPOSITION widthTotal
= doubleFromPangoUnits(widthLayout
);
1074 for (int bytePos
=0; bytePos
<lenPositions
; bytePos
++) {
1075 positions
[bytePos
] = widthTotal
/ lenPositions
* (bytePos
+ 1);
1079 PLATFORM_ASSERT(ligatureLength
> 0 && ligatureLength
<= 3);
1080 for (int charInLig
=0; charInLig
<ligatureLength
; charInLig
++) {
1081 positions
[i
++] = iti
.position
- (ligatureLength
- 1 - charInLig
) * iti
.distance
/ ligatureLength
;
1083 clusterStart
= clusterEnd
;
1085 while (i
< lenPositions
) {
1086 // If something failed, fill in rest of the positions
1087 positions
[i
++] = clusterStart
;
1089 PLATFORM_ASSERT(i
== lenPositions
);
1093 PFont(font_
)->SetCharWidth(*s
, positions
[0], et
);
1098 // No font so return an ascending range of values
1099 for (int i
= 0; i
< len
; i
++) {
1100 positions
[i
] = i
+ 1;
1105 XYPOSITION
SurfaceImpl::WidthText(Font
&font_
, const char *s
, int len
) {
1106 if (font_
.GetID()) {
1107 if (PFont(font_
)->pfd
) {
1108 std::string utfForm
;
1109 pango_layout_set_font_description(layout
, PFont(font_
)->pfd
);
1112 pango_layout_set_text(layout
, s
, len
);
1114 SetConverter(PFont(font_
)->characterSet
);
1115 utfForm
= UTF8FromIconv(conv
, s
, len
);
1116 if (utfForm
.empty()) { // iconv failed so treat as Latin1
1117 utfForm
= UTF8FromLatin1(s
, len
);
1119 pango_layout_set_text(layout
, utfForm
.c_str(), utfForm
.length());
1121 #ifdef PANGO_VERSION
1122 PangoLayoutLine
*pangoLine
= pango_layout_get_line_readonly(layout
,0);
1124 PangoLayoutLine
*pangoLine
= pango_layout_get_line(layout
,0);
1126 pango_layout_line_get_extents(pangoLine
, NULL
, &pos
);
1127 return doubleFromPangoUnits(pos
.width
);
1135 XYPOSITION
SurfaceImpl::WidthChar(Font
&font_
, char ch
) {
1136 if (font_
.GetID()) {
1137 if (PFont(font_
)->pfd
) {
1138 return WidthText(font_
, &ch
, 1);
1146 // Ascent and descent determined by Pango font metrics.
1148 XYPOSITION
SurfaceImpl::Ascent(Font
&font_
) {
1149 if (!(font_
.GetID()))
1152 int ascent
= PFont(font_
)->ascent
;
1153 if ((ascent
== 0) && (PFont(font_
)->pfd
)) {
1154 PangoFontMetrics
*metrics
= pango_context_get_metrics(pcontext
,
1155 PFont(font_
)->pfd
, pango_context_get_language(pcontext
));
1156 PFont(font_
)->ascent
=
1157 doubleFromPangoUnits(pango_font_metrics_get_ascent(metrics
));
1158 pango_font_metrics_unref(metrics
);
1159 ascent
= PFont(font_
)->ascent
;
1168 XYPOSITION
SurfaceImpl::Descent(Font
&font_
) {
1169 if (!(font_
.GetID()))
1171 if (PFont(font_
)->pfd
) {
1172 PangoFontMetrics
*metrics
= pango_context_get_metrics(pcontext
,
1173 PFont(font_
)->pfd
, pango_context_get_language(pcontext
));
1174 int descent
= doubleFromPangoUnits(pango_font_metrics_get_descent(metrics
));
1175 pango_font_metrics_unref(metrics
);
1181 XYPOSITION
SurfaceImpl::InternalLeading(Font
&) {
1185 XYPOSITION
SurfaceImpl::ExternalLeading(Font
&) {
1189 XYPOSITION
SurfaceImpl::Height(Font
&font_
) {
1190 return Ascent(font_
) + Descent(font_
);
1193 XYPOSITION
SurfaceImpl::AverageCharWidth(Font
&font_
) {
1194 return WidthChar(font_
, 'n');
1197 void SurfaceImpl::SetClip(PRectangle rc
) {
1198 PLATFORM_ASSERT(context
);
1199 cairo_rectangle(context
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
1200 cairo_clip(context
);
1203 void SurfaceImpl::FlushCachedState() {}
1205 void SurfaceImpl::SetUnicodeMode(bool unicodeMode_
) {
1210 void SurfaceImpl::SetDBCSMode(int codePage
) {
1211 if (codePage
&& (codePage
!= SC_CP_UTF8
))
1215 Surface
*Surface::Allocate(int) {
1216 return new SurfaceImpl();
1219 Window::~Window() {}
1221 void Window::Destroy() {
1223 ListBox
*listbox
= dynamic_cast<ListBox
*>(this);
1225 gtk_widget_hide(GTK_WIDGET(wid
));
1226 // clear up window content
1228 // resize the window to the smallest possible size for it to adapt
1229 // to future content
1230 gtk_window_resize(GTK_WINDOW(wid
), 1, 1);
1232 gtk_widget_destroy(GTK_WIDGET(wid
));
1238 bool Window::HasFocus() {
1239 return gtk_widget_has_focus(GTK_WIDGET(wid
));
1242 PRectangle
Window::GetPosition() {
1243 // Before any size allocated pretend its 1000 wide so not scrolled
1244 PRectangle
rc(0, 0, 1000, 1000);
1246 GtkAllocation allocation
;
1247 gtk_widget_get_allocation(PWidget(wid
), &allocation
);
1248 rc
.left
= allocation
.x
;
1249 rc
.top
= allocation
.y
;
1250 if (allocation
.width
> 20) {
1251 rc
.right
= rc
.left
+ allocation
.width
;
1252 rc
.bottom
= rc
.top
+ allocation
.height
;
1258 void Window::SetPosition(PRectangle rc
) {
1259 GtkAllocation alloc
;
1262 alloc
.width
= rc
.Width();
1263 alloc
.height
= rc
.Height();
1264 gtk_widget_size_allocate(PWidget(wid
), &alloc
);
1267 void Window::SetPositionRelative(PRectangle rc
, Window relativeTo
) {
1270 gdk_window_get_origin(WindowFromWidget(PWidget(relativeTo
.wid
)), &ox
, &oy
);
1278 /* do some corrections to fit into screen */
1279 int sizex
= rc
.right
- rc
.left
;
1280 int sizey
= rc
.bottom
- rc
.top
;
1281 int screenWidth
= gdk_screen_width();
1282 int screenHeight
= gdk_screen_height();
1283 if (sizex
> screenWidth
)
1284 ox
= 0; /* the best we can do */
1285 else if (ox
+ sizex
> screenWidth
)
1286 ox
= screenWidth
- sizex
;
1287 if (oy
+ sizey
> screenHeight
)
1288 oy
= screenHeight
- sizey
;
1290 gtk_window_move(GTK_WINDOW(PWidget(wid
)), ox
, oy
);
1292 gtk_window_resize(GTK_WINDOW(wid
), sizex
, sizey
);
1295 PRectangle
Window::GetClientPosition() {
1296 // On GTK+, the client position is the window position
1297 return GetPosition();
1300 void Window::Show(bool show
) {
1302 gtk_widget_show(PWidget(wid
));
1305 void Window::InvalidateAll() {
1307 gtk_widget_queue_draw(PWidget(wid
));
1311 void Window::InvalidateRectangle(PRectangle rc
) {
1313 gtk_widget_queue_draw_area(PWidget(wid
),
1315 rc
.right
- rc
.left
, rc
.bottom
- rc
.top
);
1319 void Window::SetFont(Font
&) {
1320 // Can not be done generically but only needed for ListBox
1323 void Window::SetCursor(Cursor curs
) {
1324 // We don't set the cursor to same value numerous times under gtk because
1325 // it stores the cursor in the window once it's set
1326 if (curs
== cursorLast
)
1330 GdkDisplay
*pdisplay
= gtk_widget_get_display(PWidget(wid
));
1335 gdkCurs
= gdk_cursor_new_for_display(pdisplay
, GDK_XTERM
);
1338 gdkCurs
= gdk_cursor_new_for_display(pdisplay
, GDK_LEFT_PTR
);
1341 gdkCurs
= gdk_cursor_new_for_display(pdisplay
, GDK_CENTER_PTR
);
1344 gdkCurs
= gdk_cursor_new_for_display(pdisplay
, GDK_WATCH
);
1347 gdkCurs
= gdk_cursor_new_for_display(pdisplay
, GDK_HAND2
);
1349 case cursorReverseArrow
:
1350 gdkCurs
= gdk_cursor_new_for_display(pdisplay
, GDK_RIGHT_PTR
);
1353 gdkCurs
= gdk_cursor_new_for_display(pdisplay
, GDK_LEFT_PTR
);
1354 cursorLast
= cursorArrow
;
1358 if (WindowFromWidget(PWidget(wid
)))
1359 gdk_window_set_cursor(WindowFromWidget(PWidget(wid
)), gdkCurs
);
1360 #if GTK_CHECK_VERSION(3,0,0)
1361 g_object_unref(gdkCurs
);
1363 gdk_cursor_unref(gdkCurs
);
1367 void Window::SetTitle(const char *s
) {
1368 gtk_window_set_title(GTK_WINDOW(wid
), s
);
1371 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1372 gdk window coordinates */
1373 PRectangle
Window::GetMonitorRect(Point pt
) {
1374 gint x_offset
, y_offset
;
1376 gdk_window_get_origin(WindowFromWidget(PWidget(wid
)), &x_offset
, &y_offset
);
1382 screen
= gtk_widget_get_screen(PWidget(wid
));
1383 monitor_num
= gdk_screen_get_monitor_at_point(screen
, pt
.x
+ x_offset
, pt
.y
+ y_offset
);
1384 gdk_screen_get_monitor_geometry(screen
, monitor_num
, &rect
);
1387 return PRectangle(rect
.x
, rect
.y
, rect
.x
+ rect
.width
, rect
.y
+ rect
.height
);
1390 typedef std::map
<int, RGBAImage
*> ImageMap
;
1393 const RGBAImage
*rgba_data
;
1397 static void list_image_free(gpointer
, gpointer value
, gpointer
) {
1398 ListImage
*list_image
= static_cast<ListImage
*>(value
);
1399 if (list_image
->pixbuf
)
1400 g_object_unref(list_image
->pixbuf
);
1404 ListBox::ListBox() {
1407 ListBox::~ListBox() {
1416 class ListBoxX
: public ListBox
{
1422 GtkCellRenderer
* pixbuf_renderer
;
1423 GtkCellRenderer
* renderer
;
1424 RGBAImageSet images
;
1425 int desiredVisibleRows
;
1426 unsigned int maxItemCharacters
;
1427 unsigned int aveCharWidth
;
1428 #if GTK_CHECK_VERSION(3,0,0)
1429 GtkCssProvider
*cssProvider
;
1432 CallBackAction doubleClickAction
;
1433 void *doubleClickActionData
;
1435 ListBoxX() : widCached(0), frame(0), list(0), scroller(0), pixhash(NULL
), pixbuf_renderer(0),
1437 desiredVisibleRows(5), maxItemCharacters(0),
1439 #if GTK_CHECK_VERSION(3,0,0)
1442 doubleClickAction(NULL
), doubleClickActionData(NULL
) {
1444 virtual ~ListBoxX() {
1446 g_hash_table_foreach((GHashTable
*) pixhash
, list_image_free
, NULL
);
1447 g_hash_table_destroy((GHashTable
*) pixhash
);
1450 gtk_widget_destroy(GTK_WIDGET(widCached
));
1451 wid
= widCached
= 0;
1453 #if GTK_CHECK_VERSION(3,0,0)
1455 g_object_unref(cssProvider
);
1460 virtual void SetFont(Font
&font
);
1461 virtual void Create(Window
&parent
, int ctrlID
, Point location_
, int lineHeight_
, bool unicodeMode_
, int technology_
);
1462 virtual void SetAverageCharWidth(int width
);
1463 virtual void SetVisibleRows(int rows
);
1464 virtual int GetVisibleRows() const;
1466 virtual PRectangle
GetDesiredRect();
1467 virtual int CaretFromEdge();
1468 virtual void Clear();
1469 virtual void Append(char *s
, int type
= -1);
1470 virtual int Length();
1471 virtual void Select(int n
);
1472 virtual int GetSelection();
1473 virtual int Find(const char *prefix
);
1474 virtual void GetValue(int n
, char *value
, int len
);
1475 void RegisterRGBA(int type
, RGBAImage
*image
);
1476 virtual void RegisterImage(int type
, const char *xpm_data
);
1477 virtual void RegisterRGBAImage(int type
, int width
, int height
, const unsigned char *pixelsImage
);
1478 virtual void ClearRegisteredImages();
1479 virtual void SetDoubleClickAction(CallBackAction action
, void *data
) {
1480 doubleClickAction
= action
;
1481 doubleClickActionData
= data
;
1483 virtual void SetList(const char *listText
, char separator
, char typesep
);
1486 ListBox
*ListBox::Allocate() {
1487 ListBoxX
*lb
= new ListBoxX();
1491 // SmallScroller, a GtkScrolledWindow that can shrink very small, as
1492 // gtk_widget_set_size_request() cannot shrink widgets on GTK3
1494 GtkScrolledWindow parent
;
1495 /* Workaround ABI issue with Windows GTK2 bundle and GCC > 3.
1496 See http://lists.geany.org/pipermail/devel/2015-April/thread.html#9379
1498 GtkScrolledWindow contains a bitfield, and GCC 3.4 and 4.8 don't agree
1499 on the size of the structure (regardless of -mms-bitfields):
1500 - GCC 3.4 has sizeof(GtkScrolledWindow)=88
1501 - GCC 4.8 has sizeof(GtkScrolledWindow)=84
1502 As Windows GTK2 bundle is built with GCC 3, it requires types derived
1503 from GtkScrolledWindow to be at least 88 bytes, which means we need to
1504 add some fake padding to fill in the extra 4 bytes.
1505 There is however no other issue with the layout difference as we never
1506 access any GtkScrolledWindow fields ourselves. */
1509 typedef GtkScrolledWindowClass SmallScrollerClass
;
1511 G_DEFINE_TYPE(SmallScroller
, small_scroller
, GTK_TYPE_SCROLLED_WINDOW
)
1513 #if GTK_CHECK_VERSION(3,0,0)
1514 static void small_scroller_get_preferred_height(GtkWidget
*widget
, gint
*min
, gint
*nat
) {
1515 GTK_WIDGET_CLASS(small_scroller_parent_class
)->get_preferred_height(widget
, min
, nat
);
1520 static void small_scroller_size_request(GtkWidget
*widget
, GtkRequisition
*req
) {
1521 GTK_WIDGET_CLASS(small_scroller_parent_class
)->size_request(widget
, req
);
1526 static void small_scroller_class_init(SmallScrollerClass
*klass
) {
1527 #if GTK_CHECK_VERSION(3,0,0)
1528 GTK_WIDGET_CLASS(klass
)->get_preferred_height
= small_scroller_get_preferred_height
;
1530 GTK_WIDGET_CLASS(klass
)->size_request
= small_scroller_size_request
;
1534 static void small_scroller_init(SmallScroller
*){}
1536 static gboolean
ButtonPress(GtkWidget
*, GdkEventButton
* ev
, gpointer p
) {
1538 ListBoxX
* lb
= static_cast<ListBoxX
*>(p
);
1539 if (ev
->type
== GDK_2BUTTON_PRESS
&& lb
->doubleClickAction
!= NULL
) {
1540 lb
->doubleClickAction(lb
->doubleClickActionData
);
1545 // No pointer back to Scintilla to save status
1550 /* Change the active color to the selected color so the listbox uses the color
1551 scheme that it would use if it had the focus. */
1552 static void StyleSet(GtkWidget
*w
, GtkStyle
*, void*) {
1554 g_return_if_fail(w
!= NULL
);
1556 /* Copy the selected color to active. Note that the modify calls will cause
1557 recursive calls to this function after the value is updated and w->style to
1558 be set to a new object */
1560 #if GTK_CHECK_VERSION(3,16,0)
1561 // On recent releases of GTK+, it does not appear necessary to set the list box colours.
1562 // This may be because of common themes and may be needed with other themes.
1563 // The *override* calls are deprecated now, so only call them for older versions of GTK+.
1564 #elif GTK_CHECK_VERSION(3,0,0)
1565 GtkStyleContext
*styleContext
= gtk_widget_get_style_context(w
);
1566 if (styleContext
== NULL
)
1569 GdkRGBA colourForeSelected
;
1570 gtk_style_context_get_color(styleContext
, GTK_STATE_FLAG_SELECTED
, &colourForeSelected
);
1571 GdkRGBA colourForeActive
;
1572 gtk_style_context_get_color(styleContext
, GTK_STATE_FLAG_ACTIVE
, &colourForeActive
);
1573 if (!gdk_rgba_equal(&colourForeSelected
, &colourForeActive
))
1574 gtk_widget_override_color(w
, GTK_STATE_FLAG_ACTIVE
, &colourForeSelected
);
1576 styleContext
= gtk_widget_get_style_context(w
);
1577 if (styleContext
== NULL
)
1580 GdkRGBA colourBaseSelected
;
1581 gtk_style_context_get_background_color(styleContext
, GTK_STATE_FLAG_SELECTED
, &colourBaseSelected
);
1582 GdkRGBA colourBaseActive
;
1583 gtk_style_context_get_background_color(styleContext
, GTK_STATE_FLAG_ACTIVE
, &colourBaseActive
);
1584 if (!gdk_rgba_equal(&colourBaseSelected
, &colourBaseActive
))
1585 gtk_widget_override_background_color(w
, GTK_STATE_FLAG_ACTIVE
, &colourBaseSelected
);
1587 GtkStyle
*style
= gtk_widget_get_style(w
);
1590 if (!gdk_color_equal(&style
->base
[GTK_STATE_SELECTED
], &style
->base
[GTK_STATE_ACTIVE
]))
1591 gtk_widget_modify_base(w
, GTK_STATE_ACTIVE
, &style
->base
[GTK_STATE_SELECTED
]);
1592 style
= gtk_widget_get_style(w
);
1595 if (!gdk_color_equal(&style
->text
[GTK_STATE_SELECTED
], &style
->text
[GTK_STATE_ACTIVE
]))
1596 gtk_widget_modify_text(w
, GTK_STATE_ACTIVE
, &style
->text
[GTK_STATE_SELECTED
]);
1600 void ListBoxX::Create(Window
&, int, Point
, int, bool, int) {
1601 if (widCached
!= 0) {
1606 #if GTK_CHECK_VERSION(3,0,0)
1608 cssProvider
= gtk_css_provider_new();
1612 wid
= widCached
= gtk_window_new(GTK_WINDOW_POPUP
);
1614 frame
= gtk_frame_new(NULL
);
1615 gtk_widget_show(PWidget(frame
));
1616 gtk_container_add(GTK_CONTAINER(GetID()), PWidget(frame
));
1617 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_OUT
);
1618 gtk_container_set_border_width(GTK_CONTAINER(frame
), 0);
1620 scroller
= g_object_new(small_scroller_get_type(), NULL
);
1621 gtk_container_set_border_width(GTK_CONTAINER(scroller
), 0);
1622 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller
),
1623 GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
1624 gtk_container_add(GTK_CONTAINER(frame
), PWidget(scroller
));
1625 gtk_widget_show(PWidget(scroller
));
1627 /* Tree and its model */
1628 GtkListStore
*store
=
1629 gtk_list_store_new(N_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
);
1631 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(store
));
1632 g_signal_connect(G_OBJECT(list
), "style-set", G_CALLBACK(StyleSet
), NULL
);
1634 #if GTK_CHECK_VERSION(3,0,0)
1635 GtkStyleContext
*styleContext
= gtk_widget_get_style_context(GTK_WIDGET(list
));
1637 gtk_style_context_add_provider(styleContext
, GTK_STYLE_PROVIDER(cssProvider
),
1638 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
1642 GtkTreeSelection
*selection
=
1643 gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1644 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_SINGLE
);
1645 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), FALSE
);
1646 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list
), FALSE
);
1649 GtkTreeViewColumn
*column
= gtk_tree_view_column_new();
1650 gtk_tree_view_column_set_sizing(column
, GTK_TREE_VIEW_COLUMN_FIXED
);
1651 gtk_tree_view_column_set_title(column
, "Autocomplete");
1653 pixbuf_renderer
= gtk_cell_renderer_pixbuf_new();
1654 gtk_cell_renderer_set_fixed_size(pixbuf_renderer
, 0, -1);
1655 gtk_tree_view_column_pack_start(column
, pixbuf_renderer
, FALSE
);
1656 gtk_tree_view_column_add_attribute(column
, pixbuf_renderer
,
1657 "pixbuf", PIXBUF_COLUMN
);
1659 renderer
= gtk_cell_renderer_text_new();
1660 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer
), 1);
1661 gtk_tree_view_column_pack_start(column
, renderer
, TRUE
);
1662 gtk_tree_view_column_add_attribute(column
, renderer
,
1663 "text", TEXT_COLUMN
);
1665 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), column
);
1666 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list
), "fixed-height-mode"))
1667 g_object_set(G_OBJECT(list
), "fixed-height-mode", TRUE
, NULL
);
1669 GtkWidget
*widget
= PWidget(list
); // No code inside the G_OBJECT macro
1670 gtk_container_add(GTK_CONTAINER(PWidget(scroller
)), widget
);
1671 gtk_widget_show(widget
);
1672 g_signal_connect(G_OBJECT(widget
), "button_press_event",
1673 G_CALLBACK(ButtonPress
), this);
1676 void ListBoxX::SetFont(Font
&scint_font
) {
1677 // Only do for Pango font as there have been crashes for GDK fonts
1678 if (Created() && PFont(scint_font
)->pfd
) {
1679 // Current font is Pango font
1680 #if GTK_CHECK_VERSION(3,0,0)
1682 PangoFontDescription
*pfd
= PFont(scint_font
)->pfd
;
1683 std::ostringstream ssFontSetting
;
1684 ssFontSetting
<< "GtkTreeView { ";
1685 ssFontSetting
<< "font-family: " << pango_font_description_get_family(pfd
) << "; ";
1686 ssFontSetting
<< "font-size:";
1687 ssFontSetting
<< static_cast<double>(pango_font_description_get_size(pfd
)) / PANGO_SCALE
;
1688 ssFontSetting
<< "px; ";
1689 ssFontSetting
<< "font-weight:"<< pango_font_description_get_weight(pfd
) << "; ";
1690 ssFontSetting
<< "}";
1691 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(cssProvider
),
1692 ssFontSetting
.str().c_str(), -1, NULL
);
1695 gtk_widget_modify_font(PWidget(list
), PFont(scint_font
)->pfd
);
1697 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer
), -1);
1698 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer
), 1);
1702 void ListBoxX::SetAverageCharWidth(int width
) {
1703 aveCharWidth
= width
;
1706 void ListBoxX::SetVisibleRows(int rows
) {
1707 desiredVisibleRows
= rows
;
1710 int ListBoxX::GetVisibleRows() const {
1711 return desiredVisibleRows
;
1714 int ListBoxX::GetRowHeight()
1716 #if GTK_CHECK_VERSION(3,0,0)
1717 // This version sometimes reports erroneous results on GTK2, but the GTK2
1718 // version is inaccurate for GTK 3.14.
1720 GtkTreePath
*path
= gtk_tree_path_new_first();
1721 gtk_tree_view_get_background_area(GTK_TREE_VIEW(list
), path
, NULL
, &rect
);
1725 int vertical_separator
=0;
1726 int expander_size
=0;
1727 GtkTreeViewColumn
*column
= gtk_tree_view_get_column(GTK_TREE_VIEW(list
), 0);
1728 gtk_tree_view_column_cell_get_size(column
, NULL
, NULL
, NULL
, NULL
, &row_height
);
1729 gtk_widget_style_get(PWidget(list
),
1730 "vertical-separator", &vertical_separator
,
1731 "expander-size", &expander_size
, NULL
);
1732 row_height
+= vertical_separator
;
1733 row_height
= Platform::Maximum(row_height
, expander_size
);
1738 PRectangle
ListBoxX::GetDesiredRect() {
1739 // Before any size allocated pretend its 100 wide so not scrolled
1740 PRectangle
rc(0, 0, 100, 100);
1742 int rows
= Length();
1743 if ((rows
== 0) || (rows
> desiredVisibleRows
))
1744 rows
= desiredVisibleRows
;
1747 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1748 // returns reasonable values.
1749 #if GTK_CHECK_VERSION(3,0,0)
1750 gtk_widget_get_preferred_size(GTK_WIDGET(frame
), NULL
, &req
);
1752 gtk_widget_size_request(GTK_WIDGET(frame
), &req
);
1756 // First calculate height of the clist for our desired visible
1757 // row count otherwise it tries to expand to the total # of rows
1759 int row_height
= GetRowHeight();
1760 #if GTK_CHECK_VERSION(3,0,0)
1761 GtkStyleContext
*styleContextFrame
= gtk_widget_get_style_context(PWidget(frame
));
1762 GtkBorder padding
, border
;
1763 gtk_style_context_get_padding(styleContextFrame
, GTK_STATE_FLAG_NORMAL
, &padding
);
1764 gtk_style_context_get_border(styleContextFrame
, GTK_STATE_FLAG_NORMAL
, &border
);
1765 height
= (rows
* row_height
1766 + padding
.top
+ padding
.bottom
1767 + border
.top
+ border
.bottom
1768 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list
))));
1770 height
= (rows
* row_height
1771 + 2 * (PWidget(frame
)->style
->ythickness
1772 + GTK_CONTAINER(PWidget(list
))->border_width
));
1776 int width
= maxItemCharacters
;
1779 rc
.right
= width
* (aveCharWidth
+ aveCharWidth
/ 3);
1780 // Add horizontal padding and borders
1781 int horizontal_separator
=0;
1782 gtk_widget_style_get(PWidget(list
),
1783 "horizontal-separator", &horizontal_separator
, NULL
);
1784 rc
.right
+= horizontal_separator
;
1785 #if GTK_CHECK_VERSION(3,0,0)
1786 rc
.right
+= (padding
.left
+ padding
.right
1787 + border
.left
+ border
.right
1788 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list
))));
1790 rc
.right
+= 2 * (PWidget(frame
)->style
->xthickness
1791 + GTK_CONTAINER(PWidget(list
))->border_width
);
1793 if (Length() > rows
) {
1794 // Add the width of the scrollbar
1795 GtkWidget
*vscrollbar
=
1796 gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(scroller
));
1797 #if GTK_CHECK_VERSION(3,0,0)
1798 gtk_widget_get_preferred_size(vscrollbar
, NULL
, &req
);
1800 gtk_widget_size_request(vscrollbar
, &req
);
1802 rc
.right
+= req
.width
;
1808 int ListBoxX::CaretFromEdge() {
1809 gint renderer_width
, renderer_height
;
1810 gtk_cell_renderer_get_fixed_size(pixbuf_renderer
, &renderer_width
,
1812 return 4 + renderer_width
;
1815 void ListBoxX::Clear() {
1816 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1817 gtk_list_store_clear(GTK_LIST_STORE(model
));
1818 maxItemCharacters
= 0;
1821 static void init_pixmap(ListImage
*list_image
) {
1822 if (list_image
->rgba_data
) {
1823 // Drop any existing pixmap/bitmap as data may have changed
1824 if (list_image
->pixbuf
)
1825 g_object_unref(list_image
->pixbuf
);
1826 list_image
->pixbuf
=
1827 gdk_pixbuf_new_from_data(list_image
->rgba_data
->Pixels(),
1831 list_image
->rgba_data
->GetWidth(),
1832 list_image
->rgba_data
->GetHeight(),
1833 list_image
->rgba_data
->GetWidth() * 4,
1841 void ListBoxX::Append(char *s
, int type
) {
1842 ListImage
*list_image
= NULL
;
1843 if ((type
>= 0) && pixhash
) {
1844 list_image
= static_cast<ListImage
*>(g_hash_table_lookup((GHashTable
*) pixhash
1845 , (gconstpointer
) GINT_TO_POINTER(type
)));
1848 GtkListStore
*store
=
1849 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list
)));
1850 gtk_list_store_append(GTK_LIST_STORE(store
), &iter
);
1852 if (NULL
== list_image
->pixbuf
)
1853 init_pixmap(list_image
);
1854 if (list_image
->pixbuf
) {
1855 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1856 PIXBUF_COLUMN
, list_image
->pixbuf
,
1857 TEXT_COLUMN
, s
, -1);
1859 gint pixbuf_width
= gdk_pixbuf_get_width(list_image
->pixbuf
);
1860 gint renderer_height
, renderer_width
;
1861 gtk_cell_renderer_get_fixed_size(pixbuf_renderer
,
1862 &renderer_width
, &renderer_height
);
1863 if (pixbuf_width
> renderer_width
)
1864 gtk_cell_renderer_set_fixed_size(pixbuf_renderer
,
1867 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1868 TEXT_COLUMN
, s
, -1);
1871 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1872 TEXT_COLUMN
, s
, -1);
1874 size_t len
= strlen(s
);
1875 if (maxItemCharacters
< len
)
1876 maxItemCharacters
= len
;
1879 int ListBoxX::Length() {
1881 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1882 (GTK_TREE_VIEW(list
)), NULL
);
1886 void ListBoxX::Select(int n
) {
1888 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1889 GtkTreeSelection
*selection
=
1890 gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1893 gtk_tree_selection_unselect_all(selection
);
1897 bool valid
= gtk_tree_model_iter_nth_child(model
, &iter
, NULL
, n
) != FALSE
;
1899 gtk_tree_selection_select_iter(selection
, &iter
);
1901 // Move the scrollbar to show the selection.
1902 int total
= Length();
1903 #if GTK_CHECK_VERSION(3,0,0)
1904 GtkAdjustment
*adj
=
1905 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list
));
1907 GtkAdjustment
*adj
=
1908 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list
));
1910 gfloat value
= ((gfloat
)n
/ total
) * (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_lower(adj
))
1911 + gtk_adjustment_get_lower(adj
) - gtk_adjustment_get_page_size(adj
) / 2;
1913 int row_height
= GetRowHeight();
1915 int rows
= Length();
1916 if ((rows
== 0) || (rows
> desiredVisibleRows
))
1917 rows
= desiredVisibleRows
;
1919 // Odd rows to display -- We are now in the middle.
1920 // Align it so that we don't chop off rows.
1921 value
+= (gfloat
)row_height
/ 2.0;
1924 value
= (value
< 0)? 0 : value
;
1925 value
= (value
> (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_page_size(adj
)))?
1926 (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_page_size(adj
)) : value
;
1929 gtk_adjustment_set_value(adj
, value
);
1931 gtk_tree_selection_unselect_all(selection
);
1935 int ListBoxX::GetSelection() {
1938 GtkTreeModel
*model
;
1939 GtkTreeSelection
*selection
;
1940 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1941 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
)) {
1942 GtkTreePath
*path
= gtk_tree_model_get_path(model
, &iter
);
1943 int *indices
= gtk_tree_path_get_indices(path
);
1944 // Don't free indices.
1947 gtk_tree_path_free(path
);
1952 int ListBoxX::Find(const char *prefix
) {
1954 GtkTreeModel
*model
=
1955 gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1956 bool valid
= gtk_tree_model_get_iter_first(model
, &iter
) != FALSE
;
1960 gtk_tree_model_get(model
, &iter
, TEXT_COLUMN
, &s
, -1);
1961 if (s
&& (0 == strncmp(prefix
, s
, strlen(prefix
)))) {
1966 valid
= gtk_tree_model_iter_next(model
, &iter
) != FALSE
;
1972 void ListBoxX::GetValue(int n
, char *value
, int len
) {
1975 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1976 bool valid
= gtk_tree_model_iter_nth_child(model
, &iter
, NULL
, n
) != FALSE
;
1978 gtk_tree_model_get(model
, &iter
, TEXT_COLUMN
, &text
, -1);
1980 if (text
&& len
> 0) {
1981 g_strlcpy(value
, text
, len
);
1988 // g_return_if_fail causes unnecessary compiler warning in release compile.
1990 #pragma warning(disable: 4127)
1993 void ListBoxX::RegisterRGBA(int type
, RGBAImage
*image
) {
1994 images
.Add(type
, image
);
1997 pixhash
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
1999 ListImage
*list_image
= static_cast<ListImage
*>(g_hash_table_lookup((GHashTable
*) pixhash
,
2000 (gconstpointer
) GINT_TO_POINTER(type
)));
2002 // Drop icon already registered
2003 if (list_image
->pixbuf
)
2004 g_object_unref(list_image
->pixbuf
);
2005 list_image
->pixbuf
= NULL
;
2006 list_image
->rgba_data
= image
;
2008 list_image
= g_new0(ListImage
, 1);
2009 list_image
->rgba_data
= image
;
2010 g_hash_table_insert((GHashTable
*) pixhash
, GINT_TO_POINTER(type
),
2011 (gpointer
) list_image
);
2015 void ListBoxX::RegisterImage(int type
, const char *xpm_data
) {
2016 g_return_if_fail(xpm_data
);
2017 XPM
xpmImage(xpm_data
);
2018 RegisterRGBA(type
, new RGBAImage(xpmImage
));
2021 void ListBoxX::RegisterRGBAImage(int type
, int width
, int height
, const unsigned char *pixelsImage
) {
2022 RegisterRGBA(type
, new RGBAImage(width
, height
, 1.0, pixelsImage
));
2025 void ListBoxX::ClearRegisteredImages() {
2029 void ListBoxX::SetList(const char *listText
, char separator
, char typesep
) {
2031 int count
= strlen(listText
) + 1;
2032 std::vector
<char> words(listText
, listText
+count
);
2033 char *startword
= &words
[0];
2034 char *numword
= NULL
;
2036 for (; words
[i
]; i
++) {
2037 if (words
[i
] == separator
) {
2041 Append(startword
, numword
?atoi(numword
+ 1):-1);
2042 startword
= &words
[0] + i
+ 1;
2044 } else if (words
[i
] == typesep
) {
2045 numword
= &words
[0] + i
;
2051 Append(startword
, numword
?atoi(numword
+ 1):-1);
2055 Menu::Menu() : mid(0) {}
2057 void Menu::CreatePopUp() {
2059 mid
= gtk_menu_new();
2060 g_object_ref_sink(G_OBJECT(mid
));
2063 void Menu::Destroy() {
2065 g_object_unref(G_OBJECT(mid
));
2069 static void MenuPositionFunc(GtkMenu
*, gint
*x
, gint
*y
, gboolean
*, gpointer userData
) {
2070 sptr_t intFromPointer
= GPOINTER_TO_INT(userData
);
2071 *x
= intFromPointer
& 0xffff;
2072 *y
= intFromPointer
>> 16;
2075 void Menu::Show(Point pt
, Window
&) {
2076 int screenHeight
= gdk_screen_height();
2077 int screenWidth
= gdk_screen_width();
2078 GtkMenu
*widget
= static_cast<GtkMenu
*>(mid
);
2079 gtk_widget_show_all(GTK_WIDGET(widget
));
2080 GtkRequisition requisition
;
2081 #if GTK_CHECK_VERSION(3,0,0)
2082 gtk_widget_get_preferred_size(GTK_WIDGET(widget
), NULL
, &requisition
);
2084 gtk_widget_size_request(GTK_WIDGET(widget
), &requisition
);
2086 if ((pt
.x
+ requisition
.width
) > screenWidth
) {
2087 pt
.x
= screenWidth
- requisition
.width
;
2089 if ((pt
.y
+ requisition
.height
) > screenHeight
) {
2090 pt
.y
= screenHeight
- requisition
.height
;
2092 gtk_menu_popup(widget
, NULL
, NULL
, MenuPositionFunc
,
2093 GINT_TO_POINTER((static_cast<int>(pt
.y
) << 16) | static_cast<int>(pt
.x
)), 0,
2094 gtk_get_current_event_time());
2097 ElapsedTime::ElapsedTime() {
2099 g_get_current_time(&curTime
);
2100 bigBit
= curTime
.tv_sec
;
2101 littleBit
= curTime
.tv_usec
;
2104 class DynamicLibraryImpl
: public DynamicLibrary
{
2108 explicit DynamicLibraryImpl(const char *modulePath
) {
2109 m
= g_module_open(modulePath
, G_MODULE_BIND_LAZY
);
2112 virtual ~DynamicLibraryImpl() {
2117 // Use g_module_symbol to get a pointer to the relevant function.
2118 virtual Function
FindFunction(const char *name
) {
2120 gpointer fn_address
= NULL
;
2121 gboolean status
= g_module_symbol(m
, name
, &fn_address
);
2123 return static_cast<Function
>(fn_address
);
2131 virtual bool IsValid() {
2136 DynamicLibrary
*DynamicLibrary::Load(const char *modulePath
) {
2137 return static_cast<DynamicLibrary
*>( new DynamicLibraryImpl(modulePath
) );
2140 double ElapsedTime::Duration(bool reset
) {
2142 g_get_current_time(&curTime
);
2143 long endBigBit
= curTime
.tv_sec
;
2144 long endLittleBit
= curTime
.tv_usec
;
2145 double result
= 1000000.0 * (endBigBit
- bigBit
);
2146 result
+= endLittleBit
- littleBit
;
2147 result
/= 1000000.0;
2150 littleBit
= endLittleBit
;
2155 ColourDesired
Platform::Chrome() {
2156 return ColourDesired(0xe0, 0xe0, 0xe0);
2159 ColourDesired
Platform::ChromeHighlight() {
2160 return ColourDesired(0xff, 0xff, 0xff);
2163 const char *Platform::DefaultFont() {
2165 return "Lucida Console";
2171 int Platform::DefaultFontSize() {
2179 unsigned int Platform::DoubleClickTime() {
2180 return 500; // Half a second
2183 bool Platform::MouseButtonBounce() {
2187 void Platform::DebugDisplay(const char *s
) {
2188 fprintf(stderr
, "%s", s
);
2191 bool Platform::IsKeyDown(int) {
2192 // TODO: discover state of keys in GTK+/X
2196 long Platform::SendScintilla(
2197 WindowID w
, unsigned int msg
, unsigned long wParam
, long lParam
) {
2198 return scintilla_send_message(SCINTILLA(w
), msg
, wParam
, lParam
);
2201 long Platform::SendScintillaPointer(
2202 WindowID w
, unsigned int msg
, unsigned long wParam
, void *lParam
) {
2203 return scintilla_send_message(SCINTILLA(w
), msg
, wParam
,
2204 reinterpret_cast<sptr_t
>(lParam
));
2207 bool Platform::IsDBCSLeadByte(int codePage
, char ch
) {
2208 // Byte ranges found in Wikipedia articles with relevant search strings in each case
2209 unsigned char uch
= static_cast<unsigned char>(ch
);
2213 return ((uch
>= 0x81) && (uch
<= 0x9F)) ||
2214 ((uch
>= 0xE0) && (uch
<= 0xFC));
2215 // Lead bytes F0 to FC may be a Microsoft addition.
2218 return (uch
>= 0x81) && (uch
<= 0xFE);
2221 return (uch
>= 0x81) && (uch
<= 0xFE);
2222 // Korean EUC-KR may be code page 949.
2227 int Platform::DBCSCharLength(int codePage
, const char *s
) {
2228 if (codePage
== 932 || codePage
== 936 || codePage
== 950) {
2229 return IsDBCSLeadByte(codePage
, s
[0]) ? 2 : 1;
2231 int bytes
= mblen(s
, MB_CUR_MAX
);
2239 int Platform::DBCSCharMaxLength() {
2244 // These are utility functions not really tied to a platform
2246 int Platform::Minimum(int a
, int b
) {
2253 int Platform::Maximum(int a
, int b
) {
2263 void Platform::DebugPrintf(const char *format
, ...) {
2266 va_start(pArguments
, format
);
2267 vsprintf(buffer
, format
, pArguments
);
2269 Platform::DebugDisplay(buffer
);
2272 void Platform::DebugPrintf(const char *, ...) {}
2276 // Not supported for GTK+
2277 static bool assertionPopUps
= true;
2279 bool Platform::ShowAssertionPopUps(bool assertionPopUps_
) {
2280 bool ret
= assertionPopUps
;
2281 assertionPopUps
= assertionPopUps_
;
2285 void Platform::Assert(const char *c
, const char *file
, int line
) {
2287 g_snprintf(buffer
, sizeof(buffer
), "Assertion [%s] failed at %s %d\r\n", c
, file
, line
);
2288 Platform::DebugDisplay(buffer
);
2292 int Platform::Clamp(int val
, int minVal
, int maxVal
) {
2300 void Platform_Initialise() {
2301 FontMutexAllocate();
2304 void Platform_Finalise() {
2305 FontCached::ReleaseAll();