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);
608 void SurfaceImpl::PenColour(ColourDesired fore
) {
610 ColourDesired
cdFore(fore
.AsLong());
611 cairo_set_source_rgb(context
,
612 cdFore
.GetRed() / 255.0,
613 cdFore
.GetGreen() / 255.0,
614 cdFore
.GetBlue() / 255.0);
618 int SurfaceImpl::LogPixelsY() {
622 int SurfaceImpl::DeviceHeightFont(int points
) {
623 int logPix
= LogPixelsY();
624 return (points
* logPix
+ logPix
/ 2) / 72;
627 void SurfaceImpl::MoveTo(int x_
, int y_
) {
632 static int Delta(int difference
) {
635 else if (difference
> 0)
641 void SurfaceImpl::LineTo(int x_
, int y_
) {
642 // cairo_line_to draws the end position, unlike Win32 or GDK with GDK_CAP_NOT_LAST.
643 // For simple cases, move back one pixel from end.
646 int xDelta
= Delta(xDiff
);
648 int yDelta
= Delta(yDiff
);
649 if ((xDiff
== 0) || (yDiff
== 0)) {
650 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
651 int xEnd
= x_
- xDelta
;
652 int left
= Platform::Minimum(x
, xEnd
);
653 int width
= abs(x
- xEnd
) + 1;
654 int yEnd
= y_
- yDelta
;
655 int top
= Platform::Minimum(y
, yEnd
);
656 int height
= abs(y
- yEnd
) + 1;
657 cairo_rectangle(context
, left
, top
, width
, height
);
659 } else if ((abs(xDiff
) == abs(yDiff
))) {
661 cairo_move_to(context
, x
+ 0.5, y
+ 0.5);
662 cairo_line_to(context
, x_
+ 0.5 - xDelta
, y_
+ 0.5 - yDelta
);
664 // Line has a different slope so difficult to avoid last pixel
665 cairo_move_to(context
, x
+ 0.5, y
+ 0.5);
666 cairo_line_to(context
, x_
+ 0.5, y_
+ 0.5);
668 cairo_stroke(context
);
674 void SurfaceImpl::Polygon(Point
*pts
, int npts
, ColourDesired fore
,
675 ColourDesired back
) {
676 PLATFORM_ASSERT(context
);
678 cairo_move_to(context
, pts
[0].x
+ 0.5, pts
[0].y
+ 0.5);
679 for (int i
= 1; i
< npts
; i
++) {
680 cairo_line_to(context
, pts
[i
].x
+ 0.5, pts
[i
].y
+ 0.5);
682 cairo_close_path(context
);
683 cairo_fill_preserve(context
);
685 cairo_stroke(context
);
688 void SurfaceImpl::RectangleDraw(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
690 cairo_rectangle(context
, rc
.left
+ 0.5, rc
.top
+ 0.5,
691 rc
.right
- rc
.left
- 1, rc
.bottom
- rc
.top
- 1);
693 cairo_fill_preserve(context
);
695 cairo_stroke(context
);
699 void SurfaceImpl::FillRectangle(PRectangle rc
, ColourDesired back
) {
701 if (context
&& (rc
.left
< maxCoordinate
)) { // Protect against out of range
702 rc
.left
= lround(rc
.left
);
703 rc
.right
= lround(rc
.right
);
704 cairo_rectangle(context
, rc
.left
, rc
.top
,
705 rc
.right
- rc
.left
, rc
.bottom
- rc
.top
);
710 void SurfaceImpl::FillRectangle(PRectangle rc
, Surface
&surfacePattern
) {
711 SurfaceImpl
&surfi
= static_cast<SurfaceImpl
&>(surfacePattern
);
712 bool canDraw
= surfi
.psurf
!= NULL
;
714 PLATFORM_ASSERT(context
);
715 // Tile pattern over rectangle
716 // Currently assumes 8x8 pattern
719 for (int xTile
= rc
.left
; xTile
< rc
.right
; xTile
+= widthPat
) {
720 int widthx
= (xTile
+ widthPat
> rc
.right
) ? rc
.right
- xTile
: widthPat
;
721 for (int yTile
= rc
.top
; yTile
< rc
.bottom
; yTile
+= heightPat
) {
722 int heighty
= (yTile
+ heightPat
> rc
.bottom
) ? rc
.bottom
- yTile
: heightPat
;
723 cairo_set_source_surface(context
, surfi
.psurf
, xTile
, yTile
);
724 cairo_rectangle(context
, xTile
, yTile
, widthx
, heighty
);
729 // Something is wrong so try to show anyway
730 // Shows up black because colour not allocated
731 FillRectangle(rc
, ColourDesired(0));
735 void SurfaceImpl::RoundedRectangle(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
736 if (((rc
.right
- rc
.left
) > 4) && ((rc
.bottom
- rc
.top
) > 4)) {
737 // Approximate a round rect with some cut off corners
739 Point(rc
.left
+ 2, rc
.top
),
740 Point(rc
.right
- 2, rc
.top
),
741 Point(rc
.right
, rc
.top
+ 2),
742 Point(rc
.right
, rc
.bottom
- 2),
743 Point(rc
.right
- 2, rc
.bottom
),
744 Point(rc
.left
+ 2, rc
.bottom
),
745 Point(rc
.left
, rc
.bottom
- 2),
746 Point(rc
.left
, rc
.top
+ 2),
748 Polygon(pts
, ELEMENTS(pts
), fore
, back
);
750 RectangleDraw(rc
, fore
, back
);
754 static void PathRoundRectangle(cairo_t
*context
, double left
, double top
, double width
, double height
, int radius
) {
755 double degrees
= kPi
/ 180.0;
757 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 2, 0)
758 cairo_new_sub_path(context
);
760 // First arc is in the top-right corner and starts from a point on the top line
761 cairo_move_to(context
, left
+ width
- radius
, top
);
763 cairo_arc(context
, left
+ width
- radius
, top
+ radius
, radius
, -90 * degrees
, 0 * degrees
);
764 cairo_arc(context
, left
+ width
- radius
, top
+ height
- radius
, radius
, 0 * degrees
, 90 * degrees
);
765 cairo_arc(context
, left
+ radius
, top
+ height
- radius
, radius
, 90 * degrees
, 180 * degrees
);
766 cairo_arc(context
, left
+ radius
, top
+ radius
, radius
, 180 * degrees
, 270 * degrees
);
767 cairo_close_path(context
);
770 void SurfaceImpl::AlphaRectangle(PRectangle rc
, int cornerSize
, ColourDesired fill
, int alphaFill
,
771 ColourDesired outline
, int alphaOutline
, int flags
) {
772 if (context
&& rc
.Width() > 0) {
773 ColourDesired
cdFill(fill
.AsLong());
774 cairo_set_source_rgba(context
,
775 cdFill
.GetRed() / 255.0,
776 cdFill
.GetGreen() / 255.0,
777 cdFill
.GetBlue() / 255.0,
780 PathRoundRectangle(context
, rc
.left
+ 1.0, rc
.top
+ 1.0, rc
.right
- rc
.left
- 2.0, rc
.bottom
- rc
.top
- 2.0, cornerSize
);
782 cairo_rectangle(context
, rc
.left
+ 1.0, rc
.top
+ 1.0, rc
.right
- rc
.left
- 2.0, rc
.bottom
- rc
.top
- 2.0);
785 ColourDesired
cdOutline(outline
.AsLong());
786 cairo_set_source_rgba(context
,
787 cdOutline
.GetRed() / 255.0,
788 cdOutline
.GetGreen() / 255.0,
789 cdOutline
.GetBlue() / 255.0,
790 alphaOutline
/ 255.0);
792 PathRoundRectangle(context
, rc
.left
+ 0.5, rc
.top
+ 0.5, rc
.right
- rc
.left
- 1, rc
.bottom
- rc
.top
- 1, cornerSize
);
794 cairo_rectangle(context
, rc
.left
+ 0.5, rc
.top
+ 0.5, rc
.right
- rc
.left
- 1, rc
.bottom
- rc
.top
- 1);
795 cairo_stroke(context
);
799 void SurfaceImpl::DrawRGBAImage(PRectangle rc
, int width
, int height
, const unsigned char *pixelsImage
) {
800 PLATFORM_ASSERT(context
);
801 if (rc
.Width() > width
)
802 rc
.left
+= (rc
.Width() - width
) / 2;
803 rc
.right
= rc
.left
+ width
;
804 if (rc
.Height() > height
)
805 rc
.top
+= (rc
.Height() - height
) / 2;
806 rc
.bottom
= rc
.top
+ height
;
808 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
809 int stride
= cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32
, width
);
811 int stride
= width
* 4;
813 int ucs
= stride
* height
;
814 std::vector
<unsigned char> image(ucs
);
815 for (int iy
=0; iy
<height
; iy
++) {
816 for (int ix
=0; ix
<width
; ix
++) {
817 unsigned char *pixel
= &image
[0] + iy
*stride
+ ix
* 4;
818 unsigned char alpha
= pixelsImage
[3];
819 pixel
[2] = (*pixelsImage
++) * alpha
/ 255;
820 pixel
[1] = (*pixelsImage
++) * alpha
/ 255;
821 pixel
[0] = (*pixelsImage
++) * alpha
/ 255;
822 pixel
[3] = *pixelsImage
++;
826 cairo_surface_t
*psurfImage
= cairo_image_surface_create_for_data(&image
[0], CAIRO_FORMAT_ARGB32
, width
, height
, stride
);
827 cairo_set_source_surface(context
, psurfImage
, rc
.left
, rc
.top
);
828 cairo_rectangle(context
, rc
.left
, rc
.top
, rc
.right
-rc
.left
, rc
.bottom
-rc
.top
);
831 cairo_surface_destroy(psurfImage
);
834 void SurfaceImpl::Ellipse(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
835 PLATFORM_ASSERT(context
);
837 cairo_arc(context
, (rc
.left
+ rc
.right
) / 2, (rc
.top
+ rc
.bottom
) / 2,
838 Platform::Minimum(rc
.Width(), rc
.Height()) / 2, 0, 2*kPi
);
839 cairo_fill_preserve(context
);
841 cairo_stroke(context
);
844 void SurfaceImpl::Copy(PRectangle rc
, Point from
, Surface
&surfaceSource
) {
845 SurfaceImpl
&surfi
= static_cast<SurfaceImpl
&>(surfaceSource
);
846 bool canDraw
= surfi
.psurf
!= NULL
;
848 PLATFORM_ASSERT(context
);
849 cairo_set_source_surface(context
, surfi
.psurf
,
850 rc
.left
- from
.x
, rc
.top
- from
.y
);
851 cairo_rectangle(context
, rc
.left
, rc
.top
, rc
.right
-rc
.left
, rc
.bottom
-rc
.top
);
856 std::string
UTF8FromLatin1(const char *s
, int len
) {
857 std::string
utfForm(len
*2 + 1, '\0');
859 for (int i
=0; i
<len
; i
++) {
860 unsigned int uch
= static_cast<unsigned char>(s
[i
]);
862 utfForm
[lenU
++] = uch
;
864 utfForm
[lenU
++] = static_cast<char>(0xC0 | (uch
>> 6));
865 utfForm
[lenU
++] = static_cast<char>(0x80 | (uch
& 0x3f));
868 utfForm
.resize(lenU
);
872 static std::string
UTF8FromIconv(const Converter
&conv
, const char *s
, int len
) {
874 std::string
utfForm(len
*3+1, '\0');
875 char *pin
= const_cast<char *>(s
);
877 char *putf
= &utfForm
[0];
879 size_t outLeft
= len
*3+1;
880 size_t conversions
= conv
.Convert(&pin
, &inLeft
, &pout
, &outLeft
);
881 if (conversions
!= ((size_t)(-1))) {
883 utfForm
.resize(pout
- putf
);
887 return std::string();
890 // Work out how many bytes are in a character by trying to convert using iconv,
891 // returning the first length that succeeds.
892 static size_t MultiByteLenFromIconv(const Converter
&conv
, const char *s
, size_t len
) {
893 for (size_t lenMB
=1; (lenMB
<4) && (lenMB
<= len
); lenMB
++) {
895 char *pin
= const_cast<char *>(s
);
896 size_t inLeft
= lenMB
;
899 size_t conversions
= conv
.Convert(&pin
, &inLeft
, &pout
, &outLeft
);
900 if (conversions
!= ((size_t)(-1))) {
907 void SurfaceImpl::DrawTextBase(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
908 ColourDesired fore
) {
911 XYPOSITION xText
= rc
.left
;
912 if (PFont(font_
)->pfd
) {
915 pango_layout_set_text(layout
, s
, len
);
917 SetConverter(PFont(font_
)->characterSet
);
918 utfForm
= UTF8FromIconv(conv
, s
, len
);
919 if (utfForm
.empty()) { // iconv failed so treat as Latin1
920 utfForm
= UTF8FromLatin1(s
, len
);
922 pango_layout_set_text(layout
, utfForm
.c_str(), utfForm
.length());
924 pango_layout_set_font_description(layout
, PFont(font_
)->pfd
);
925 pango_cairo_update_layout(context
, layout
);
927 PangoLayoutLine
*pll
= pango_layout_get_line_readonly(layout
,0);
929 PangoLayoutLine
*pll
= pango_layout_get_line(layout
,0);
931 cairo_move_to(context
, xText
, ybase
);
932 pango_cairo_show_layout_line(context
, pll
);
937 void SurfaceImpl::DrawTextNoClip(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 // On GTK+, exactly same as DrawTextNoClip
944 void SurfaceImpl::DrawTextClipped(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
945 ColourDesired fore
, ColourDesired back
) {
946 FillRectangle(rc
, back
);
947 DrawTextBase(rc
, font_
, ybase
, s
, len
, fore
);
950 void SurfaceImpl::DrawTextTransparent(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
951 ColourDesired fore
) {
952 // Avoid drawing spaces in transparent mode
953 for (int i
=0; i
<len
; i
++) {
955 DrawTextBase(rc
, font_
, ybase
, s
, len
, fore
);
961 class ClusterIterator
{
962 PangoLayoutIter
*iter
;
967 XYPOSITION positionStart
;
971 ClusterIterator(PangoLayout
*layout
, int len
) : lenPositions(len
), finished(false),
972 positionStart(0), position(0), distance(0), curIndex(0) {
973 iter
= pango_layout_get_iter(layout
);
974 pango_layout_iter_get_cluster_extents(iter
, NULL
, &pos
);
977 pango_layout_iter_free(iter
);
981 positionStart
= position
;
982 if (pango_layout_iter_next_cluster(iter
)) {
983 pango_layout_iter_get_cluster_extents(iter
, NULL
, &pos
);
984 position
= doubleFromPangoUnits(pos
.x
);
985 curIndex
= pango_layout_iter_get_index(iter
);
988 position
= doubleFromPangoUnits(pos
.x
+ pos
.width
);
989 curIndex
= lenPositions
;
991 distance
= position
- positionStart
;
995 void SurfaceImpl::MeasureWidths(Font
&font_
, const char *s
, int len
, XYPOSITION
*positions
) {
997 const int lenPositions
= len
;
998 if (PFont(font_
)->pfd
) {
1000 int width
= PFont(font_
)->CharWidth(*s
, et
);
1002 positions
[0] = width
;
1006 pango_layout_set_font_description(layout
, PFont(font_
)->pfd
);
1008 // Simple and direct as UTF-8 is native Pango encoding
1010 pango_layout_set_text(layout
, s
, len
);
1011 ClusterIterator
iti(layout
, lenPositions
);
1012 while (!iti
.finished
) {
1014 int places
= iti
.curIndex
- i
;
1015 while (i
< iti
.curIndex
) {
1016 // Evenly distribute space among bytes of this cluster.
1017 // Would be better to find number of characters and then
1018 // divide evenly between characters with each byte of a character
1019 // being at the same position.
1020 positions
[i
] = iti
.position
- (iti
.curIndex
- 1 - i
) * iti
.distance
/ places
;
1024 PLATFORM_ASSERT(i
== lenPositions
);
1026 int positionsCalculated
= 0;
1028 SetConverter(PFont(font_
)->characterSet
);
1029 std::string utfForm
= UTF8FromIconv(conv
, s
, len
);
1030 if (!utfForm
.empty()) {
1031 // Convert to UTF-8 so can ask Pango for widths, then
1032 // Loop through UTF-8 and DBCS forms, taking account of different
1033 // character byte lengths.
1034 Converter
convMeasure("UCS-2", CharacterSetID(characterSet
), false);
1035 pango_layout_set_text(layout
, utfForm
.c_str(), strlen(utfForm
.c_str()));
1037 int clusterStart
= 0;
1038 ClusterIterator
iti(layout
, strlen(utfForm
.c_str()));
1039 while (!iti
.finished
) {
1041 int clusterEnd
= iti
.curIndex
;
1042 int places
= g_utf8_strlen(utfForm
.c_str() + clusterStart
, clusterEnd
- clusterStart
);
1044 while (clusterStart
< clusterEnd
) {
1045 size_t lenChar
= MultiByteLenFromIconv(convMeasure
, s
+i
, len
-i
);
1047 positions
[i
++] = iti
.position
- (places
- place
) * iti
.distance
/ places
;
1048 positionsCalculated
++;
1050 clusterStart
+= UTF8CharLength(static_cast<unsigned char>(utfForm
.c_str()[clusterStart
]));
1054 PLATFORM_ASSERT(i
== lenPositions
);
1057 if (positionsCalculated
< 1 ) {
1058 // Either 8-bit or DBCS conversion failed so treat as 8-bit.
1059 SetConverter(PFont(font_
)->characterSet
);
1060 const bool rtlCheck
= PFont(font_
)->characterSet
== SC_CHARSET_HEBREW
||
1061 PFont(font_
)->characterSet
== SC_CHARSET_ARABIC
;
1062 std::string utfForm
= UTF8FromIconv(conv
, s
, len
);
1063 if (utfForm
.empty()) {
1064 utfForm
= UTF8FromLatin1(s
, len
);
1066 pango_layout_set_text(layout
, utfForm
.c_str(), utfForm
.length());
1068 int clusterStart
= 0;
1069 // Each 8-bit input character may take 1 or 2 bytes in UTF-8
1070 // and groups of up to 3 may be represented as ligatures.
1071 ClusterIterator
iti(layout
, utfForm
.length());
1072 while (!iti
.finished
) {
1074 int clusterEnd
= iti
.curIndex
;
1075 int ligatureLength
= g_utf8_strlen(utfForm
.c_str() + clusterStart
, clusterEnd
- clusterStart
);
1076 if (rtlCheck
&& ((clusterEnd
<= clusterStart
) || (ligatureLength
== 0) || (ligatureLength
> 3))) {
1077 // Something has gone wrong: exit quickly but pretend all the characters are equally spaced:
1078 int widthLayout
= 0;
1079 pango_layout_get_size(layout
, &widthLayout
, NULL
);
1080 XYPOSITION widthTotal
= doubleFromPangoUnits(widthLayout
);
1081 for (int bytePos
=0; bytePos
<lenPositions
; bytePos
++) {
1082 positions
[bytePos
] = widthTotal
/ lenPositions
* (bytePos
+ 1);
1086 PLATFORM_ASSERT(ligatureLength
> 0 && ligatureLength
<= 3);
1087 for (int charInLig
=0; charInLig
<ligatureLength
; charInLig
++) {
1088 positions
[i
++] = iti
.position
- (ligatureLength
- 1 - charInLig
) * iti
.distance
/ ligatureLength
;
1090 clusterStart
= clusterEnd
;
1092 while (i
< lenPositions
) {
1093 // If something failed, fill in rest of the positions
1094 positions
[i
++] = clusterStart
;
1096 PLATFORM_ASSERT(i
== lenPositions
);
1100 PFont(font_
)->SetCharWidth(*s
, positions
[0], et
);
1105 // No font so return an ascending range of values
1106 for (int i
= 0; i
< len
; i
++) {
1107 positions
[i
] = i
+ 1;
1112 XYPOSITION
SurfaceImpl::WidthText(Font
&font_
, const char *s
, int len
) {
1113 if (font_
.GetID()) {
1114 if (PFont(font_
)->pfd
) {
1115 std::string utfForm
;
1116 pango_layout_set_font_description(layout
, PFont(font_
)->pfd
);
1119 pango_layout_set_text(layout
, s
, len
);
1121 SetConverter(PFont(font_
)->characterSet
);
1122 utfForm
= UTF8FromIconv(conv
, s
, len
);
1123 if (utfForm
.empty()) { // iconv failed so treat as Latin1
1124 utfForm
= UTF8FromLatin1(s
, len
);
1126 pango_layout_set_text(layout
, utfForm
.c_str(), utfForm
.length());
1128 #ifdef PANGO_VERSION
1129 PangoLayoutLine
*pangoLine
= pango_layout_get_line_readonly(layout
,0);
1131 PangoLayoutLine
*pangoLine
= pango_layout_get_line(layout
,0);
1133 pango_layout_line_get_extents(pangoLine
, NULL
, &pos
);
1134 return doubleFromPangoUnits(pos
.width
);
1142 XYPOSITION
SurfaceImpl::WidthChar(Font
&font_
, char ch
) {
1143 if (font_
.GetID()) {
1144 if (PFont(font_
)->pfd
) {
1145 return WidthText(font_
, &ch
, 1);
1153 // Ascent and descent determined by Pango font metrics.
1155 XYPOSITION
SurfaceImpl::Ascent(Font
&font_
) {
1156 if (!(font_
.GetID()))
1159 int ascent
= PFont(font_
)->ascent
;
1160 if ((ascent
== 0) && (PFont(font_
)->pfd
)) {
1161 PangoFontMetrics
*metrics
= pango_context_get_metrics(pcontext
,
1162 PFont(font_
)->pfd
, pango_context_get_language(pcontext
));
1163 PFont(font_
)->ascent
=
1164 doubleFromPangoUnits(pango_font_metrics_get_ascent(metrics
));
1165 pango_font_metrics_unref(metrics
);
1166 ascent
= PFont(font_
)->ascent
;
1175 XYPOSITION
SurfaceImpl::Descent(Font
&font_
) {
1176 if (!(font_
.GetID()))
1178 if (PFont(font_
)->pfd
) {
1179 PangoFontMetrics
*metrics
= pango_context_get_metrics(pcontext
,
1180 PFont(font_
)->pfd
, pango_context_get_language(pcontext
));
1181 int descent
= doubleFromPangoUnits(pango_font_metrics_get_descent(metrics
));
1182 pango_font_metrics_unref(metrics
);
1188 XYPOSITION
SurfaceImpl::InternalLeading(Font
&) {
1192 XYPOSITION
SurfaceImpl::ExternalLeading(Font
&) {
1196 XYPOSITION
SurfaceImpl::Height(Font
&font_
) {
1197 return Ascent(font_
) + Descent(font_
);
1200 XYPOSITION
SurfaceImpl::AverageCharWidth(Font
&font_
) {
1201 return WidthChar(font_
, 'n');
1204 void SurfaceImpl::SetClip(PRectangle rc
) {
1205 PLATFORM_ASSERT(context
);
1206 cairo_rectangle(context
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
1207 cairo_clip(context
);
1210 void SurfaceImpl::FlushCachedState() {}
1212 void SurfaceImpl::SetUnicodeMode(bool unicodeMode_
) {
1217 void SurfaceImpl::SetDBCSMode(int codePage
) {
1218 if (codePage
&& (codePage
!= SC_CP_UTF8
))
1222 Surface
*Surface::Allocate(int) {
1223 return new SurfaceImpl();
1226 Window::~Window() {}
1228 void Window::Destroy() {
1230 ListBox
*listbox
= dynamic_cast<ListBox
*>(this);
1232 gtk_widget_hide(GTK_WIDGET(wid
));
1233 // clear up window content
1235 // resize the window to the smallest possible size for it to adapt
1236 // to future content
1237 gtk_window_resize(GTK_WINDOW(wid
), 1, 1);
1239 gtk_widget_destroy(GTK_WIDGET(wid
));
1245 bool Window::HasFocus() {
1246 return IS_WIDGET_FOCUSSED(wid
);
1249 PRectangle
Window::GetPosition() {
1250 // Before any size allocated pretend its 1000 wide so not scrolled
1251 PRectangle
rc(0, 0, 1000, 1000);
1253 GtkAllocation allocation
;
1254 #if GTK_CHECK_VERSION(3,0,0)
1255 gtk_widget_get_allocation(PWidget(wid
), &allocation
);
1257 allocation
= PWidget(wid
)->allocation
;
1259 rc
.left
= allocation
.x
;
1260 rc
.top
= allocation
.y
;
1261 if (allocation
.width
> 20) {
1262 rc
.right
= rc
.left
+ allocation
.width
;
1263 rc
.bottom
= rc
.top
+ allocation
.height
;
1269 void Window::SetPosition(PRectangle rc
) {
1270 GtkAllocation alloc
;
1273 alloc
.width
= rc
.Width();
1274 alloc
.height
= rc
.Height();
1275 gtk_widget_size_allocate(PWidget(wid
), &alloc
);
1278 void Window::SetPositionRelative(PRectangle rc
, Window relativeTo
) {
1281 gdk_window_get_origin(WindowFromWidget(PWidget(relativeTo
.wid
)), &ox
, &oy
);
1289 /* do some corrections to fit into screen */
1290 int sizex
= rc
.right
- rc
.left
;
1291 int sizey
= rc
.bottom
- rc
.top
;
1292 int screenWidth
= gdk_screen_width();
1293 int screenHeight
= gdk_screen_height();
1294 if (sizex
> screenWidth
)
1295 ox
= 0; /* the best we can do */
1296 else if (ox
+ sizex
> screenWidth
)
1297 ox
= screenWidth
- sizex
;
1298 if (oy
+ sizey
> screenHeight
)
1299 oy
= screenHeight
- sizey
;
1301 gtk_window_move(GTK_WINDOW(PWidget(wid
)), ox
, oy
);
1303 gtk_window_resize(GTK_WINDOW(wid
), sizex
, sizey
);
1306 PRectangle
Window::GetClientPosition() {
1307 // On GTK+, the client position is the window position
1308 return GetPosition();
1311 void Window::Show(bool show
) {
1313 gtk_widget_show(PWidget(wid
));
1316 void Window::InvalidateAll() {
1318 gtk_widget_queue_draw(PWidget(wid
));
1322 void Window::InvalidateRectangle(PRectangle rc
) {
1324 gtk_widget_queue_draw_area(PWidget(wid
),
1326 rc
.right
- rc
.left
, rc
.bottom
- rc
.top
);
1330 void Window::SetFont(Font
&) {
1331 // Can not be done generically but only needed for ListBox
1334 void Window::SetCursor(Cursor curs
) {
1335 // We don't set the cursor to same value numerous times under gtk because
1336 // it stores the cursor in the window once it's set
1337 if (curs
== cursorLast
)
1344 gdkCurs
= gdk_cursor_new(GDK_XTERM
);
1347 gdkCurs
= gdk_cursor_new(GDK_LEFT_PTR
);
1350 gdkCurs
= gdk_cursor_new(GDK_CENTER_PTR
);
1353 gdkCurs
= gdk_cursor_new(GDK_WATCH
);
1356 gdkCurs
= gdk_cursor_new(GDK_HAND2
);
1358 case cursorReverseArrow
:
1359 gdkCurs
= gdk_cursor_new(GDK_RIGHT_PTR
);
1362 gdkCurs
= gdk_cursor_new(GDK_LEFT_PTR
);
1363 cursorLast
= cursorArrow
;
1367 if (WindowFromWidget(PWidget(wid
)))
1368 gdk_window_set_cursor(WindowFromWidget(PWidget(wid
)), gdkCurs
);
1369 #if GTK_CHECK_VERSION(3,0,0)
1370 g_object_unref(gdkCurs
);
1372 gdk_cursor_unref(gdkCurs
);
1376 void Window::SetTitle(const char *s
) {
1377 gtk_window_set_title(GTK_WINDOW(wid
), s
);
1380 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1381 gdk window coordinates */
1382 PRectangle
Window::GetMonitorRect(Point pt
) {
1383 gint x_offset
, y_offset
;
1385 gdk_window_get_origin(WindowFromWidget(PWidget(wid
)), &x_offset
, &y_offset
);
1391 screen
= gtk_widget_get_screen(PWidget(wid
));
1392 monitor_num
= gdk_screen_get_monitor_at_point(screen
, pt
.x
+ x_offset
, pt
.y
+ y_offset
);
1393 gdk_screen_get_monitor_geometry(screen
, monitor_num
, &rect
);
1396 return PRectangle(rect
.x
, rect
.y
, rect
.x
+ rect
.width
, rect
.y
+ rect
.height
);
1399 typedef std::map
<int, RGBAImage
*> ImageMap
;
1402 const RGBAImage
*rgba_data
;
1406 static void list_image_free(gpointer
, gpointer value
, gpointer
) {
1407 ListImage
*list_image
= static_cast<ListImage
*>(value
);
1408 if (list_image
->pixbuf
)
1409 g_object_unref(list_image
->pixbuf
);
1413 ListBox::ListBox() {
1416 ListBox::~ListBox() {
1425 class ListBoxX
: public ListBox
{
1431 GtkCellRenderer
* pixbuf_renderer
;
1432 RGBAImageSet images
;
1433 int desiredVisibleRows
;
1434 unsigned int maxItemCharacters
;
1435 unsigned int aveCharWidth
;
1437 CallBackAction doubleClickAction
;
1438 void *doubleClickActionData
;
1440 ListBoxX() : widCached(0), frame(0), list(0), scroller(0), pixhash(NULL
), pixbuf_renderer(0),
1441 desiredVisibleRows(5), maxItemCharacters(0),
1442 aveCharWidth(1), 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;
1454 virtual void SetFont(Font
&font
);
1455 virtual void Create(Window
&parent
, int ctrlID
, Point location_
, int lineHeight_
, bool unicodeMode_
, int technology_
);
1456 virtual void SetAverageCharWidth(int width
);
1457 virtual void SetVisibleRows(int rows
);
1458 virtual int GetVisibleRows() const;
1460 virtual PRectangle
GetDesiredRect();
1461 virtual int CaretFromEdge();
1462 virtual void Clear();
1463 virtual void Append(char *s
, int type
= -1);
1464 virtual int Length();
1465 virtual void Select(int n
);
1466 virtual int GetSelection();
1467 virtual int Find(const char *prefix
);
1468 virtual void GetValue(int n
, char *value
, int len
);
1469 void RegisterRGBA(int type
, RGBAImage
*image
);
1470 virtual void RegisterImage(int type
, const char *xpm_data
);
1471 virtual void RegisterRGBAImage(int type
, int width
, int height
, const unsigned char *pixelsImage
);
1472 virtual void ClearRegisteredImages();
1473 virtual void SetDoubleClickAction(CallBackAction action
, void *data
) {
1474 doubleClickAction
= action
;
1475 doubleClickActionData
= data
;
1477 virtual void SetList(const char *listText
, char separator
, char typesep
);
1480 ListBox
*ListBox::Allocate() {
1481 ListBoxX
*lb
= new ListBoxX();
1485 // SmallScroller, a GtkScrolledWindow that can shrink very small, as
1486 // gtk_widget_set_size_request() cannot shrink widgets on GTK3
1488 GtkScrolledWindow parent
;
1489 /* Workaround ABI issue with Windows GTK2 bundle and GCC > 3.
1490 See http://lists.geany.org/pipermail/devel/2015-April/thread.html#9379
1492 GtkScrolledWindow contains a bitfield, and GCC 3.4 and 4.8 don't agree
1493 on the size of the structure (regardless of -mms-bitfields):
1494 - GCC 3.4 has sizeof(GtkScrolledWindow)=88
1495 - GCC 4.8 has sizeof(GtkScrolledWindow)=84
1496 As Windows GTK2 bundle is built with GCC 3, it requires types derived
1497 from GtkScrolledWindow to be at least 88 bytes, which means we need to
1498 add some fake padding to fill in the extra 4 bytes.
1499 There is however no other issue with the layout difference as we never
1500 access any GtkScrolledWindow fields ourselves. */
1503 typedef GtkScrolledWindowClass SmallScrollerClass
;
1505 G_DEFINE_TYPE(SmallScroller
, small_scroller
, GTK_TYPE_SCROLLED_WINDOW
)
1507 #if GTK_CHECK_VERSION(3,0,0)
1508 static void small_scroller_get_preferred_height(GtkWidget
*widget
, gint
*min
, gint
*nat
) {
1509 GTK_WIDGET_CLASS(small_scroller_parent_class
)->get_preferred_height(widget
, min
, nat
);
1514 static void small_scroller_size_request(GtkWidget
*widget
, GtkRequisition
*req
) {
1515 GTK_WIDGET_CLASS(small_scroller_parent_class
)->size_request(widget
, req
);
1520 static void small_scroller_class_init(SmallScrollerClass
*klass
) {
1521 #if GTK_CHECK_VERSION(3,0,0)
1522 GTK_WIDGET_CLASS(klass
)->get_preferred_height
= small_scroller_get_preferred_height
;
1524 GTK_WIDGET_CLASS(klass
)->size_request
= small_scroller_size_request
;
1528 static void small_scroller_init(SmallScroller
*){}
1530 static gboolean
ButtonPress(GtkWidget
*, GdkEventButton
* ev
, gpointer p
) {
1532 ListBoxX
* lb
= reinterpret_cast<ListBoxX
*>(p
);
1533 if (ev
->type
== GDK_2BUTTON_PRESS
&& lb
->doubleClickAction
!= NULL
) {
1534 lb
->doubleClickAction(lb
->doubleClickActionData
);
1539 // No pointer back to Scintilla to save status
1544 /* Change the active color to the selected color so the listbox uses the color
1545 scheme that it would use if it had the focus. */
1546 static void StyleSet(GtkWidget
*w
, GtkStyle
*, void*) {
1548 g_return_if_fail(w
!= NULL
);
1550 /* Copy the selected color to active. Note that the modify calls will cause
1551 recursive calls to this function after the value is updated and w->style to
1552 be set to a new object */
1554 #if GTK_CHECK_VERSION(3,0,0)
1555 GtkStyleContext
*styleContext
= gtk_widget_get_style_context(w
);
1556 if (styleContext
== NULL
)
1559 GdkRGBA colourForeSelected
;
1560 gtk_style_context_get_color(styleContext
, GTK_STATE_FLAG_SELECTED
, &colourForeSelected
);
1561 GdkRGBA colourForeActive
;
1562 gtk_style_context_get_color(styleContext
, GTK_STATE_FLAG_ACTIVE
, &colourForeActive
);
1563 if (!gdk_rgba_equal(&colourForeSelected
, &colourForeActive
))
1564 gtk_widget_override_color(w
, GTK_STATE_FLAG_ACTIVE
, &colourForeSelected
);
1566 styleContext
= gtk_widget_get_style_context(w
);
1567 if (styleContext
== NULL
)
1570 GdkRGBA colourBaseSelected
;
1571 gtk_style_context_get_background_color(styleContext
, GTK_STATE_FLAG_SELECTED
, &colourBaseSelected
);
1572 GdkRGBA colourBaseActive
;
1573 gtk_style_context_get_background_color(styleContext
, GTK_STATE_FLAG_ACTIVE
, &colourBaseActive
);
1574 if (!gdk_rgba_equal(&colourBaseSelected
, &colourBaseActive
))
1575 gtk_widget_override_background_color(w
, GTK_STATE_FLAG_ACTIVE
, &colourBaseSelected
);
1577 GtkStyle
*style
= gtk_widget_get_style(w
);
1580 if (!gdk_color_equal(&style
->base
[GTK_STATE_SELECTED
], &style
->base
[GTK_STATE_ACTIVE
]))
1581 gtk_widget_modify_base(w
, GTK_STATE_ACTIVE
, &style
->base
[GTK_STATE_SELECTED
]);
1582 style
= gtk_widget_get_style(w
);
1585 if (!gdk_color_equal(&style
->text
[GTK_STATE_SELECTED
], &style
->text
[GTK_STATE_ACTIVE
]))
1586 gtk_widget_modify_text(w
, GTK_STATE_ACTIVE
, &style
->text
[GTK_STATE_SELECTED
]);
1590 void ListBoxX::Create(Window
&, int, Point
, int, bool, int) {
1591 if (widCached
!= 0) {
1596 wid
= widCached
= gtk_window_new(GTK_WINDOW_POPUP
);
1598 frame
= gtk_frame_new(NULL
);
1599 gtk_widget_show(PWidget(frame
));
1600 gtk_container_add(GTK_CONTAINER(GetID()), PWidget(frame
));
1601 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_OUT
);
1602 gtk_container_set_border_width(GTK_CONTAINER(frame
), 0);
1604 scroller
= g_object_new(small_scroller_get_type(), NULL
);
1605 gtk_container_set_border_width(GTK_CONTAINER(scroller
), 0);
1606 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller
),
1607 GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
1608 gtk_container_add(GTK_CONTAINER(frame
), PWidget(scroller
));
1609 gtk_widget_show(PWidget(scroller
));
1611 /* Tree and its model */
1612 GtkListStore
*store
=
1613 gtk_list_store_new(N_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
);
1615 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(store
));
1616 g_signal_connect(G_OBJECT(list
), "style-set", G_CALLBACK(StyleSet
), NULL
);
1618 GtkTreeSelection
*selection
=
1619 gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1620 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_SINGLE
);
1621 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), FALSE
);
1622 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list
), FALSE
);
1625 GtkTreeViewColumn
*column
= gtk_tree_view_column_new();
1626 gtk_tree_view_column_set_sizing(column
, GTK_TREE_VIEW_COLUMN_FIXED
);
1627 gtk_tree_view_column_set_title(column
, "Autocomplete");
1629 pixbuf_renderer
= gtk_cell_renderer_pixbuf_new();
1630 gtk_cell_renderer_set_fixed_size(pixbuf_renderer
, 0, -1);
1631 gtk_tree_view_column_pack_start(column
, pixbuf_renderer
, FALSE
);
1632 gtk_tree_view_column_add_attribute(column
, pixbuf_renderer
,
1633 "pixbuf", PIXBUF_COLUMN
);
1635 GtkCellRenderer
* renderer
= gtk_cell_renderer_text_new();
1636 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer
), 1);
1637 gtk_tree_view_column_pack_start(column
, renderer
, TRUE
);
1638 gtk_tree_view_column_add_attribute(column
, renderer
,
1639 "text", TEXT_COLUMN
);
1641 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), column
);
1642 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list
), "fixed-height-mode"))
1643 g_object_set(G_OBJECT(list
), "fixed-height-mode", TRUE
, NULL
);
1645 GtkWidget
*widget
= PWidget(list
); // No code inside the G_OBJECT macro
1646 gtk_container_add(GTK_CONTAINER(PWidget(scroller
)), widget
);
1647 gtk_widget_show(widget
);
1648 g_signal_connect(G_OBJECT(widget
), "button_press_event",
1649 G_CALLBACK(ButtonPress
), this);
1652 void ListBoxX::SetFont(Font
&scint_font
) {
1653 // Only do for Pango font as there have been crashes for GDK fonts
1654 if (Created() && PFont(scint_font
)->pfd
) {
1655 // Current font is Pango font
1656 #if GTK_CHECK_VERSION(3,0,0)
1657 gtk_widget_override_font(PWidget(list
), PFont(scint_font
)->pfd
);
1659 gtk_widget_modify_font(PWidget(list
), PFont(scint_font
)->pfd
);
1664 void ListBoxX::SetAverageCharWidth(int width
) {
1665 aveCharWidth
= width
;
1668 void ListBoxX::SetVisibleRows(int rows
) {
1669 desiredVisibleRows
= rows
;
1672 int ListBoxX::GetVisibleRows() const {
1673 return desiredVisibleRows
;
1676 int ListBoxX::GetRowHeight()
1678 #if GTK_CHECK_VERSION(3,0,0)
1679 // This version sometimes reports erroneous results on GTK2, but the GTK2
1680 // version is inaccurate for GTK 3.14.
1682 GtkTreePath
*path
= gtk_tree_path_new_first();
1683 gtk_tree_view_get_background_area(GTK_TREE_VIEW(list
), path
, NULL
, &rect
);
1687 int vertical_separator
=0;
1688 int expander_size
=0;
1689 GtkTreeViewColumn
*column
= gtk_tree_view_get_column(GTK_TREE_VIEW(list
), 0);
1690 gtk_tree_view_column_cell_get_size(column
, NULL
, NULL
, NULL
, NULL
, &row_height
);
1691 gtk_widget_style_get(PWidget(list
),
1692 "vertical-separator", &vertical_separator
,
1693 "expander-size", &expander_size
, NULL
);
1694 row_height
+= vertical_separator
;
1695 row_height
= Platform::Maximum(row_height
, expander_size
);
1700 PRectangle
ListBoxX::GetDesiredRect() {
1701 // Before any size allocated pretend its 100 wide so not scrolled
1702 PRectangle
rc(0, 0, 100, 100);
1704 int rows
= Length();
1705 if ((rows
== 0) || (rows
> desiredVisibleRows
))
1706 rows
= desiredVisibleRows
;
1709 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1710 // returns reasonable values.
1711 #if GTK_CHECK_VERSION(3,0,0)
1712 gtk_widget_get_preferred_size(GTK_WIDGET(frame
), NULL
, &req
);
1714 gtk_widget_size_request(GTK_WIDGET(frame
), &req
);
1718 // First calculate height of the clist for our desired visible
1719 // row count otherwise it tries to expand to the total # of rows
1721 int row_height
= GetRowHeight();
1722 #if GTK_CHECK_VERSION(3,0,0)
1723 GtkStyleContext
*styleContextFrame
= gtk_widget_get_style_context(PWidget(frame
));
1724 GtkBorder padding
, border
;
1725 gtk_style_context_get_padding(styleContextFrame
, GTK_STATE_FLAG_NORMAL
, &padding
);
1726 gtk_style_context_get_border(styleContextFrame
, GTK_STATE_FLAG_NORMAL
, &border
);
1727 height
= (rows
* row_height
1728 + padding
.top
+ padding
.bottom
1729 + border
.top
+ border
.bottom
1730 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list
))));
1732 height
= (rows
* row_height
1733 + 2 * (PWidget(frame
)->style
->ythickness
1734 + GTK_CONTAINER(PWidget(list
))->border_width
));
1738 int width
= maxItemCharacters
;
1741 rc
.right
= width
* (aveCharWidth
+ aveCharWidth
/ 3);
1742 // Add horizontal padding and borders
1743 int horizontal_separator
=0;
1744 gtk_widget_style_get(PWidget(list
),
1745 "horizontal-separator", &horizontal_separator
, NULL
);
1746 rc
.right
+= horizontal_separator
;
1747 #if GTK_CHECK_VERSION(3,0,0)
1748 rc
.right
+= (padding
.left
+ padding
.right
1749 + border
.left
+ border
.right
1750 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list
))));
1752 rc
.right
+= 2 * (PWidget(frame
)->style
->xthickness
1753 + GTK_CONTAINER(PWidget(list
))->border_width
);
1755 if (Length() > rows
) {
1756 // Add the width of the scrollbar
1757 GtkWidget
*vscrollbar
=
1758 gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(scroller
));
1759 #if GTK_CHECK_VERSION(3,0,0)
1760 gtk_widget_get_preferred_size(vscrollbar
, NULL
, &req
);
1762 gtk_widget_size_request(vscrollbar
, &req
);
1764 rc
.right
+= req
.width
;
1770 int ListBoxX::CaretFromEdge() {
1771 gint renderer_width
, renderer_height
;
1772 gtk_cell_renderer_get_fixed_size(pixbuf_renderer
, &renderer_width
,
1774 return 4 + renderer_width
;
1777 void ListBoxX::Clear() {
1778 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1779 gtk_list_store_clear(GTK_LIST_STORE(model
));
1780 maxItemCharacters
= 0;
1783 static void init_pixmap(ListImage
*list_image
) {
1784 if (list_image
->rgba_data
) {
1785 // Drop any existing pixmap/bitmap as data may have changed
1786 if (list_image
->pixbuf
)
1787 g_object_unref(list_image
->pixbuf
);
1788 list_image
->pixbuf
=
1789 gdk_pixbuf_new_from_data(list_image
->rgba_data
->Pixels(),
1793 list_image
->rgba_data
->GetWidth(),
1794 list_image
->rgba_data
->GetHeight(),
1795 list_image
->rgba_data
->GetWidth() * 4,
1803 void ListBoxX::Append(char *s
, int type
) {
1804 ListImage
*list_image
= NULL
;
1805 if ((type
>= 0) && pixhash
) {
1806 list_image
= static_cast<ListImage
*>(g_hash_table_lookup((GHashTable
*) pixhash
1807 , (gconstpointer
) GINT_TO_POINTER(type
)));
1810 GtkListStore
*store
=
1811 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list
)));
1812 gtk_list_store_append(GTK_LIST_STORE(store
), &iter
);
1814 if (NULL
== list_image
->pixbuf
)
1815 init_pixmap(list_image
);
1816 if (list_image
->pixbuf
) {
1817 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1818 PIXBUF_COLUMN
, list_image
->pixbuf
,
1819 TEXT_COLUMN
, s
, -1);
1821 gint pixbuf_width
= gdk_pixbuf_get_width(list_image
->pixbuf
);
1822 gint renderer_height
, renderer_width
;
1823 gtk_cell_renderer_get_fixed_size(pixbuf_renderer
,
1824 &renderer_width
, &renderer_height
);
1825 if (pixbuf_width
> renderer_width
)
1826 gtk_cell_renderer_set_fixed_size(pixbuf_renderer
,
1829 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1830 TEXT_COLUMN
, s
, -1);
1833 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1834 TEXT_COLUMN
, s
, -1);
1836 size_t len
= strlen(s
);
1837 if (maxItemCharacters
< len
)
1838 maxItemCharacters
= len
;
1841 int ListBoxX::Length() {
1843 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1844 (GTK_TREE_VIEW(list
)), NULL
);
1848 void ListBoxX::Select(int n
) {
1850 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1851 GtkTreeSelection
*selection
=
1852 gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1855 gtk_tree_selection_unselect_all(selection
);
1859 bool valid
= gtk_tree_model_iter_nth_child(model
, &iter
, NULL
, n
) != FALSE
;
1861 gtk_tree_selection_select_iter(selection
, &iter
);
1863 // Move the scrollbar to show the selection.
1864 int total
= Length();
1865 #if GTK_CHECK_VERSION(3,0,0)
1866 GtkAdjustment
*adj
=
1867 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list
));
1868 gfloat value
= ((gfloat
)n
/ total
) * (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_lower(adj
))
1869 + gtk_adjustment_get_lower(adj
) - gtk_adjustment_get_page_size(adj
) / 2;
1871 GtkAdjustment
*adj
=
1872 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list
));
1873 gfloat value
= ((gfloat
)n
/ total
) * (adj
->upper
- adj
->lower
)
1874 + adj
->lower
- adj
->page_size
/ 2;
1877 int row_height
= GetRowHeight();
1879 int rows
= Length();
1880 if ((rows
== 0) || (rows
> desiredVisibleRows
))
1881 rows
= desiredVisibleRows
;
1883 // Odd rows to display -- We are now in the middle.
1884 // Align it so that we don't chop off rows.
1885 value
+= (gfloat
)row_height
/ 2.0;
1888 value
= (value
< 0)? 0 : value
;
1889 #if GTK_CHECK_VERSION(3,0,0)
1890 value
= (value
> (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_page_size(adj
)))?
1891 (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_page_size(adj
)) : value
;
1893 value
= (value
> (adj
->upper
- adj
->page_size
))?
1894 (adj
->upper
- adj
->page_size
) : value
;
1898 gtk_adjustment_set_value(adj
, value
);
1900 gtk_tree_selection_unselect_all(selection
);
1904 int ListBoxX::GetSelection() {
1907 GtkTreeModel
*model
;
1908 GtkTreeSelection
*selection
;
1909 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1910 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
)) {
1911 GtkTreePath
*path
= gtk_tree_model_get_path(model
, &iter
);
1912 int *indices
= gtk_tree_path_get_indices(path
);
1913 // Don't free indices.
1916 gtk_tree_path_free(path
);
1921 int ListBoxX::Find(const char *prefix
) {
1923 GtkTreeModel
*model
=
1924 gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1925 bool valid
= gtk_tree_model_get_iter_first(model
, &iter
) != FALSE
;
1929 gtk_tree_model_get(model
, &iter
, TEXT_COLUMN
, &s
, -1);
1930 if (s
&& (0 == strncmp(prefix
, s
, strlen(prefix
)))) {
1935 valid
= gtk_tree_model_iter_next(model
, &iter
) != FALSE
;
1941 void ListBoxX::GetValue(int n
, char *value
, int len
) {
1944 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1945 bool valid
= gtk_tree_model_iter_nth_child(model
, &iter
, NULL
, n
) != FALSE
;
1947 gtk_tree_model_get(model
, &iter
, TEXT_COLUMN
, &text
, -1);
1949 if (text
&& len
> 0) {
1950 g_strlcpy(value
, text
, len
);
1957 // g_return_if_fail causes unnecessary compiler warning in release compile.
1959 #pragma warning(disable: 4127)
1962 void ListBoxX::RegisterRGBA(int type
, RGBAImage
*image
) {
1963 images
.Add(type
, image
);
1966 pixhash
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
1968 ListImage
*list_image
= static_cast<ListImage
*>(g_hash_table_lookup((GHashTable
*) pixhash
,
1969 (gconstpointer
) GINT_TO_POINTER(type
)));
1971 // Drop icon already registered
1972 if (list_image
->pixbuf
)
1973 g_object_unref(list_image
->pixbuf
);
1974 list_image
->pixbuf
= NULL
;
1975 list_image
->rgba_data
= image
;
1977 list_image
= g_new0(ListImage
, 1);
1978 list_image
->rgba_data
= image
;
1979 g_hash_table_insert((GHashTable
*) pixhash
, GINT_TO_POINTER(type
),
1980 (gpointer
) list_image
);
1984 void ListBoxX::RegisterImage(int type
, const char *xpm_data
) {
1985 g_return_if_fail(xpm_data
);
1986 XPM
xpmImage(xpm_data
);
1987 RegisterRGBA(type
, new RGBAImage(xpmImage
));
1990 void ListBoxX::RegisterRGBAImage(int type
, int width
, int height
, const unsigned char *pixelsImage
) {
1991 RegisterRGBA(type
, new RGBAImage(width
, height
, 1.0, pixelsImage
));
1994 void ListBoxX::ClearRegisteredImages() {
1998 void ListBoxX::SetList(const char *listText
, char separator
, char typesep
) {
2000 int count
= strlen(listText
) + 1;
2001 std::vector
<char> words(listText
, listText
+count
);
2002 char *startword
= &words
[0];
2003 char *numword
= NULL
;
2005 for (; words
[i
]; i
++) {
2006 if (words
[i
] == separator
) {
2010 Append(startword
, numword
?atoi(numword
+ 1):-1);
2011 startword
= &words
[0] + i
+ 1;
2013 } else if (words
[i
] == typesep
) {
2014 numword
= &words
[0] + i
;
2020 Append(startword
, numword
?atoi(numword
+ 1):-1);
2024 Menu::Menu() : mid(0) {}
2026 void Menu::CreatePopUp() {
2028 mid
= gtk_menu_new();
2029 #if GLIB_CHECK_VERSION(2,10,0)
2030 g_object_ref_sink(G_OBJECT(mid
));
2032 g_object_ref(G_OBJECT(mid
));
2033 gtk_object_sink(GTK_OBJECT(G_OBJECT(mid
)));
2037 void Menu::Destroy() {
2039 g_object_unref(G_OBJECT(mid
));
2043 static void MenuPositionFunc(GtkMenu
*, gint
*x
, gint
*y
, gboolean
*, gpointer userData
) {
2044 sptr_t intFromPointer
= reinterpret_cast<sptr_t
>(userData
);
2045 *x
= intFromPointer
& 0xffff;
2046 *y
= intFromPointer
>> 16;
2049 void Menu::Show(Point pt
, Window
&) {
2050 int screenHeight
= gdk_screen_height();
2051 int screenWidth
= gdk_screen_width();
2052 GtkMenu
*widget
= reinterpret_cast<GtkMenu
*>(mid
);
2053 gtk_widget_show_all(GTK_WIDGET(widget
));
2054 GtkRequisition requisition
;
2055 #if GTK_CHECK_VERSION(3,0,0)
2056 gtk_widget_get_preferred_size(GTK_WIDGET(widget
), NULL
, &requisition
);
2058 gtk_widget_size_request(GTK_WIDGET(widget
), &requisition
);
2060 if ((pt
.x
+ requisition
.width
) > screenWidth
) {
2061 pt
.x
= screenWidth
- requisition
.width
;
2063 if ((pt
.y
+ requisition
.height
) > screenHeight
) {
2064 pt
.y
= screenHeight
- requisition
.height
;
2066 gtk_menu_popup(widget
, NULL
, NULL
, MenuPositionFunc
,
2067 reinterpret_cast<void *>((static_cast<int>(pt
.y
) << 16) | static_cast<int>(pt
.x
)), 0,
2068 gtk_get_current_event_time());
2071 ElapsedTime::ElapsedTime() {
2073 g_get_current_time(&curTime
);
2074 bigBit
= curTime
.tv_sec
;
2075 littleBit
= curTime
.tv_usec
;
2078 class DynamicLibraryImpl
: public DynamicLibrary
{
2082 explicit DynamicLibraryImpl(const char *modulePath
) {
2083 m
= g_module_open(modulePath
, G_MODULE_BIND_LAZY
);
2086 virtual ~DynamicLibraryImpl() {
2091 // Use g_module_symbol to get a pointer to the relevant function.
2092 virtual Function
FindFunction(const char *name
) {
2094 gpointer fn_address
= NULL
;
2095 gboolean status
= g_module_symbol(m
, name
, &fn_address
);
2097 return static_cast<Function
>(fn_address
);
2105 virtual bool IsValid() {
2110 DynamicLibrary
*DynamicLibrary::Load(const char *modulePath
) {
2111 return static_cast<DynamicLibrary
*>( new DynamicLibraryImpl(modulePath
) );
2114 double ElapsedTime::Duration(bool reset
) {
2116 g_get_current_time(&curTime
);
2117 long endBigBit
= curTime
.tv_sec
;
2118 long endLittleBit
= curTime
.tv_usec
;
2119 double result
= 1000000.0 * (endBigBit
- bigBit
);
2120 result
+= endLittleBit
- littleBit
;
2121 result
/= 1000000.0;
2124 littleBit
= endLittleBit
;
2129 ColourDesired
Platform::Chrome() {
2130 return ColourDesired(0xe0, 0xe0, 0xe0);
2133 ColourDesired
Platform::ChromeHighlight() {
2134 return ColourDesired(0xff, 0xff, 0xff);
2137 const char *Platform::DefaultFont() {
2139 return "Lucida Console";
2145 int Platform::DefaultFontSize() {
2153 unsigned int Platform::DoubleClickTime() {
2154 return 500; // Half a second
2157 bool Platform::MouseButtonBounce() {
2161 void Platform::DebugDisplay(const char *s
) {
2162 fprintf(stderr
, "%s", s
);
2165 bool Platform::IsKeyDown(int) {
2166 // TODO: discover state of keys in GTK+/X
2170 long Platform::SendScintilla(
2171 WindowID w
, unsigned int msg
, unsigned long wParam
, long lParam
) {
2172 return scintilla_send_message(SCINTILLA(w
), msg
, wParam
, lParam
);
2175 long Platform::SendScintillaPointer(
2176 WindowID w
, unsigned int msg
, unsigned long wParam
, void *lParam
) {
2177 return scintilla_send_message(SCINTILLA(w
), msg
, wParam
,
2178 reinterpret_cast<sptr_t
>(lParam
));
2181 bool Platform::IsDBCSLeadByte(int codePage
, char ch
) {
2182 // Byte ranges found in Wikipedia articles with relevant search strings in each case
2183 unsigned char uch
= static_cast<unsigned char>(ch
);
2187 return ((uch
>= 0x81) && (uch
<= 0x9F)) ||
2188 ((uch
>= 0xE0) && (uch
<= 0xFC));
2189 // Lead bytes F0 to FC may be a Microsoft addition.
2192 return (uch
>= 0x81) && (uch
<= 0xFE);
2195 return (uch
>= 0x81) && (uch
<= 0xFE);
2196 // Korean EUC-KR may be code page 949.
2201 int Platform::DBCSCharLength(int codePage
, const char *s
) {
2202 if (codePage
== 932 || codePage
== 936 || codePage
== 950) {
2203 return IsDBCSLeadByte(codePage
, s
[0]) ? 2 : 1;
2205 int bytes
= mblen(s
, MB_CUR_MAX
);
2213 int Platform::DBCSCharMaxLength() {
2218 // These are utility functions not really tied to a platform
2220 int Platform::Minimum(int a
, int b
) {
2227 int Platform::Maximum(int a
, int b
) {
2237 void Platform::DebugPrintf(const char *format
, ...) {
2240 va_start(pArguments
, format
);
2241 vsprintf(buffer
, format
, pArguments
);
2243 Platform::DebugDisplay(buffer
);
2246 void Platform::DebugPrintf(const char *, ...) {}
2250 // Not supported for GTK+
2251 static bool assertionPopUps
= true;
2253 bool Platform::ShowAssertionPopUps(bool assertionPopUps_
) {
2254 bool ret
= assertionPopUps
;
2255 assertionPopUps
= assertionPopUps_
;
2259 void Platform::Assert(const char *c
, const char *file
, int line
) {
2261 g_snprintf(buffer
, sizeof(buffer
), "Assertion [%s] failed at %s %d\r\n", c
, file
, line
);
2262 Platform::DebugDisplay(buffer
);
2266 int Platform::Clamp(int val
, int minVal
, int maxVal
) {
2274 void Platform_Initialise() {
2275 FontMutexAllocate();
2278 void Platform_Finalise() {
2279 FontCached::ReleaseAll();