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
);
1513 static void small_scroller_size_request(GtkWidget
*widget
, GtkRequisition
*req
) {
1514 GTK_WIDGET_CLASS(small_scroller_parent_class
)->size_request(widget
, req
);
1519 static void small_scroller_class_init(SmallScrollerClass
*klass
) {
1520 #if GTK_CHECK_VERSION(3,0,0)
1521 GTK_WIDGET_CLASS(klass
)->get_preferred_height
= small_scroller_get_preferred_height
;
1523 GTK_WIDGET_CLASS(klass
)->size_request
= small_scroller_size_request
;
1527 static void small_scroller_init(SmallScroller
*){}
1529 static gboolean
ButtonPress(GtkWidget
*, GdkEventButton
* ev
, gpointer p
) {
1531 ListBoxX
* lb
= reinterpret_cast<ListBoxX
*>(p
);
1532 if (ev
->type
== GDK_2BUTTON_PRESS
&& lb
->doubleClickAction
!= NULL
) {
1533 lb
->doubleClickAction(lb
->doubleClickActionData
);
1538 // No pointer back to Scintilla to save status
1543 /* Change the active color to the selected color so the listbox uses the color
1544 scheme that it would use if it had the focus. */
1545 static void StyleSet(GtkWidget
*w
, GtkStyle
*, void*) {
1547 g_return_if_fail(w
!= NULL
);
1549 /* Copy the selected color to active. Note that the modify calls will cause
1550 recursive calls to this function after the value is updated and w->style to
1551 be set to a new object */
1553 #if GTK_CHECK_VERSION(3,0,0)
1554 GtkStyleContext
*styleContext
= gtk_widget_get_style_context(w
);
1555 if (styleContext
== NULL
)
1558 GdkRGBA colourForeSelected
;
1559 gtk_style_context_get_color(styleContext
, GTK_STATE_FLAG_SELECTED
, &colourForeSelected
);
1560 GdkRGBA colourForeActive
;
1561 gtk_style_context_get_color(styleContext
, GTK_STATE_FLAG_ACTIVE
, &colourForeActive
);
1562 if (!gdk_rgba_equal(&colourForeSelected
, &colourForeActive
))
1563 gtk_widget_override_color(w
, GTK_STATE_FLAG_ACTIVE
, &colourForeSelected
);
1565 styleContext
= gtk_widget_get_style_context(w
);
1566 if (styleContext
== NULL
)
1569 GdkRGBA colourBaseSelected
;
1570 gtk_style_context_get_background_color(styleContext
, GTK_STATE_FLAG_SELECTED
, &colourBaseSelected
);
1571 GdkRGBA colourBaseActive
;
1572 gtk_style_context_get_background_color(styleContext
, GTK_STATE_FLAG_ACTIVE
, &colourBaseActive
);
1573 if (!gdk_rgba_equal(&colourBaseSelected
, &colourBaseActive
))
1574 gtk_widget_override_background_color(w
, GTK_STATE_FLAG_ACTIVE
, &colourBaseSelected
);
1576 GtkStyle
*style
= gtk_widget_get_style(w
);
1579 if (!gdk_color_equal(&style
->base
[GTK_STATE_SELECTED
], &style
->base
[GTK_STATE_ACTIVE
]))
1580 gtk_widget_modify_base(w
, GTK_STATE_ACTIVE
, &style
->base
[GTK_STATE_SELECTED
]);
1581 style
= gtk_widget_get_style(w
);
1584 if (!gdk_color_equal(&style
->text
[GTK_STATE_SELECTED
], &style
->text
[GTK_STATE_ACTIVE
]))
1585 gtk_widget_modify_text(w
, GTK_STATE_ACTIVE
, &style
->text
[GTK_STATE_SELECTED
]);
1589 void ListBoxX::Create(Window
&, int, Point
, int, bool, int) {
1590 if (widCached
!= 0) {
1595 wid
= widCached
= gtk_window_new(GTK_WINDOW_POPUP
);
1597 frame
= gtk_frame_new(NULL
);
1598 gtk_widget_show(PWidget(frame
));
1599 gtk_container_add(GTK_CONTAINER(GetID()), PWidget(frame
));
1600 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_OUT
);
1601 gtk_container_set_border_width(GTK_CONTAINER(frame
), 0);
1603 scroller
= g_object_new(small_scroller_get_type(), NULL
);
1604 gtk_container_set_border_width(GTK_CONTAINER(scroller
), 0);
1605 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller
),
1606 GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
1607 gtk_container_add(GTK_CONTAINER(frame
), PWidget(scroller
));
1608 gtk_widget_show(PWidget(scroller
));
1610 /* Tree and its model */
1611 GtkListStore
*store
=
1612 gtk_list_store_new(N_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
);
1614 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(store
));
1615 g_signal_connect(G_OBJECT(list
), "style-set", G_CALLBACK(StyleSet
), NULL
);
1617 GtkTreeSelection
*selection
=
1618 gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1619 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_SINGLE
);
1620 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), FALSE
);
1621 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list
), FALSE
);
1624 GtkTreeViewColumn
*column
= gtk_tree_view_column_new();
1625 gtk_tree_view_column_set_sizing(column
, GTK_TREE_VIEW_COLUMN_FIXED
);
1626 gtk_tree_view_column_set_title(column
, "Autocomplete");
1628 pixbuf_renderer
= gtk_cell_renderer_pixbuf_new();
1629 gtk_cell_renderer_set_fixed_size(pixbuf_renderer
, 0, -1);
1630 gtk_tree_view_column_pack_start(column
, pixbuf_renderer
, FALSE
);
1631 gtk_tree_view_column_add_attribute(column
, pixbuf_renderer
,
1632 "pixbuf", PIXBUF_COLUMN
);
1634 GtkCellRenderer
* renderer
= gtk_cell_renderer_text_new();
1635 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer
), 1);
1636 gtk_tree_view_column_pack_start(column
, renderer
, TRUE
);
1637 gtk_tree_view_column_add_attribute(column
, renderer
,
1638 "text", TEXT_COLUMN
);
1640 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), column
);
1641 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list
), "fixed-height-mode"))
1642 g_object_set(G_OBJECT(list
), "fixed-height-mode", TRUE
, NULL
);
1644 GtkWidget
*widget
= PWidget(list
); // No code inside the G_OBJECT macro
1645 gtk_container_add(GTK_CONTAINER(PWidget(scroller
)), widget
);
1646 gtk_widget_show(widget
);
1647 g_signal_connect(G_OBJECT(widget
), "button_press_event",
1648 G_CALLBACK(ButtonPress
), this);
1651 void ListBoxX::SetFont(Font
&scint_font
) {
1652 // Only do for Pango font as there have been crashes for GDK fonts
1653 if (Created() && PFont(scint_font
)->pfd
) {
1654 // Current font is Pango font
1655 #if GTK_CHECK_VERSION(3,0,0)
1656 gtk_widget_override_font(PWidget(list
), PFont(scint_font
)->pfd
);
1658 gtk_widget_modify_font(PWidget(list
), PFont(scint_font
)->pfd
);
1663 void ListBoxX::SetAverageCharWidth(int width
) {
1664 aveCharWidth
= width
;
1667 void ListBoxX::SetVisibleRows(int rows
) {
1668 desiredVisibleRows
= rows
;
1671 int ListBoxX::GetVisibleRows() const {
1672 return desiredVisibleRows
;
1675 int ListBoxX::GetRowHeight()
1677 #if GTK_CHECK_VERSION(3,0,0)
1678 // This version sometimes reports erroneous results on GTK2, but the GTK2
1679 // version is inaccurate for GTK 3.14.
1681 GtkTreePath
*path
= gtk_tree_path_new_first();
1682 gtk_tree_view_get_background_area(GTK_TREE_VIEW(list
), path
, NULL
, &rect
);
1686 int vertical_separator
=0;
1687 int expander_size
=0;
1688 GtkTreeViewColumn
*column
= gtk_tree_view_get_column(GTK_TREE_VIEW(list
), 0);
1689 gtk_tree_view_column_cell_get_size(column
, NULL
, NULL
, NULL
, NULL
, &row_height
);
1690 gtk_widget_style_get(PWidget(list
),
1691 "vertical-separator", &vertical_separator
,
1692 "expander-size", &expander_size
, NULL
);
1693 row_height
+= vertical_separator
;
1694 row_height
= Platform::Maximum(row_height
, expander_size
);
1699 PRectangle
ListBoxX::GetDesiredRect() {
1700 // Before any size allocated pretend its 100 wide so not scrolled
1701 PRectangle
rc(0, 0, 100, 100);
1703 int rows
= Length();
1704 if ((rows
== 0) || (rows
> desiredVisibleRows
))
1705 rows
= desiredVisibleRows
;
1708 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1709 // returns reasonable values.
1710 #if GTK_CHECK_VERSION(3,0,0)
1711 gtk_widget_get_preferred_size(GTK_WIDGET(frame
), NULL
, &req
);
1713 gtk_widget_size_request(GTK_WIDGET(frame
), &req
);
1717 // First calculate height of the clist for our desired visible
1718 // row count otherwise it tries to expand to the total # of rows
1720 int row_height
= GetRowHeight();
1721 #if GTK_CHECK_VERSION(3,0,0)
1722 GtkStyleContext
*styleContextFrame
= gtk_widget_get_style_context(PWidget(frame
));
1723 GtkBorder padding
, border
;
1724 gtk_style_context_get_padding(styleContextFrame
, GTK_STATE_FLAG_NORMAL
, &padding
);
1725 gtk_style_context_get_border(styleContextFrame
, GTK_STATE_FLAG_NORMAL
, &border
);
1726 height
= (rows
* row_height
1727 + padding
.top
+ padding
.bottom
1728 + border
.top
+ border
.bottom
1729 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list
))));
1731 height
= (rows
* row_height
1732 + 2 * (PWidget(frame
)->style
->ythickness
1733 + GTK_CONTAINER(PWidget(list
))->border_width
));
1737 int width
= maxItemCharacters
;
1740 rc
.right
= width
* (aveCharWidth
+ aveCharWidth
/ 3);
1741 // Add horizontal padding and borders
1742 int horizontal_separator
=0;
1743 gtk_widget_style_get(PWidget(list
),
1744 "horizontal-separator", &horizontal_separator
, NULL
);
1745 rc
.right
+= horizontal_separator
;
1746 #if GTK_CHECK_VERSION(3,0,0)
1747 rc
.right
+= (padding
.left
+ padding
.right
1748 + border
.left
+ border
.right
1749 + 2 * gtk_container_get_border_width(GTK_CONTAINER(PWidget(list
))));
1751 rc
.right
+= 2 * (PWidget(frame
)->style
->xthickness
1752 + GTK_CONTAINER(PWidget(list
))->border_width
);
1754 if (Length() > rows
) {
1755 // Add the width of the scrollbar
1756 GtkWidget
*vscrollbar
=
1757 gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(scroller
));
1758 #if GTK_CHECK_VERSION(3,0,0)
1759 gtk_widget_get_preferred_size(vscrollbar
, NULL
, &req
);
1761 gtk_widget_size_request(vscrollbar
, &req
);
1763 rc
.right
+= req
.width
;
1769 int ListBoxX::CaretFromEdge() {
1770 gint renderer_width
, renderer_height
;
1771 gtk_cell_renderer_get_fixed_size(pixbuf_renderer
, &renderer_width
,
1773 return 4 + renderer_width
;
1776 void ListBoxX::Clear() {
1777 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1778 gtk_list_store_clear(GTK_LIST_STORE(model
));
1779 maxItemCharacters
= 0;
1782 static void init_pixmap(ListImage
*list_image
) {
1783 if (list_image
->rgba_data
) {
1784 // Drop any existing pixmap/bitmap as data may have changed
1785 if (list_image
->pixbuf
)
1786 g_object_unref(list_image
->pixbuf
);
1787 list_image
->pixbuf
=
1788 gdk_pixbuf_new_from_data(list_image
->rgba_data
->Pixels(),
1792 list_image
->rgba_data
->GetWidth(),
1793 list_image
->rgba_data
->GetHeight(),
1794 list_image
->rgba_data
->GetWidth() * 4,
1802 void ListBoxX::Append(char *s
, int type
) {
1803 ListImage
*list_image
= NULL
;
1804 if ((type
>= 0) && pixhash
) {
1805 list_image
= static_cast<ListImage
*>(g_hash_table_lookup((GHashTable
*) pixhash
1806 , (gconstpointer
) GINT_TO_POINTER(type
)));
1809 GtkListStore
*store
=
1810 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list
)));
1811 gtk_list_store_append(GTK_LIST_STORE(store
), &iter
);
1813 if (NULL
== list_image
->pixbuf
)
1814 init_pixmap(list_image
);
1815 if (list_image
->pixbuf
) {
1816 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1817 PIXBUF_COLUMN
, list_image
->pixbuf
,
1818 TEXT_COLUMN
, s
, -1);
1820 gint pixbuf_width
= gdk_pixbuf_get_width(list_image
->pixbuf
);
1821 gint renderer_height
, renderer_width
;
1822 gtk_cell_renderer_get_fixed_size(pixbuf_renderer
,
1823 &renderer_width
, &renderer_height
);
1824 if (pixbuf_width
> renderer_width
)
1825 gtk_cell_renderer_set_fixed_size(pixbuf_renderer
,
1828 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1829 TEXT_COLUMN
, s
, -1);
1832 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1833 TEXT_COLUMN
, s
, -1);
1835 size_t len
= strlen(s
);
1836 if (maxItemCharacters
< len
)
1837 maxItemCharacters
= len
;
1840 int ListBoxX::Length() {
1842 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1843 (GTK_TREE_VIEW(list
)), NULL
);
1847 void ListBoxX::Select(int n
) {
1849 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1850 GtkTreeSelection
*selection
=
1851 gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1854 gtk_tree_selection_unselect_all(selection
);
1858 bool valid
= gtk_tree_model_iter_nth_child(model
, &iter
, NULL
, n
) != FALSE
;
1860 gtk_tree_selection_select_iter(selection
, &iter
);
1862 // Move the scrollbar to show the selection.
1863 int total
= Length();
1864 #if GTK_CHECK_VERSION(3,0,0)
1865 GtkAdjustment
*adj
=
1866 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list
));
1867 gfloat value
= ((gfloat
)n
/ total
) * (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_lower(adj
))
1868 + gtk_adjustment_get_lower(adj
) - gtk_adjustment_get_page_size(adj
) / 2;
1870 GtkAdjustment
*adj
=
1871 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list
));
1872 gfloat value
= ((gfloat
)n
/ total
) * (adj
->upper
- adj
->lower
)
1873 + adj
->lower
- adj
->page_size
/ 2;
1876 int row_height
= GetRowHeight();
1878 int rows
= Length();
1879 if ((rows
== 0) || (rows
> desiredVisibleRows
))
1880 rows
= desiredVisibleRows
;
1882 // Odd rows to display -- We are now in the middle.
1883 // Align it so that we don't chop off rows.
1884 value
+= (gfloat
)row_height
/ 2.0;
1887 value
= (value
< 0)? 0 : value
;
1888 #if GTK_CHECK_VERSION(3,0,0)
1889 value
= (value
> (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_page_size(adj
)))?
1890 (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_page_size(adj
)) : value
;
1892 value
= (value
> (adj
->upper
- adj
->page_size
))?
1893 (adj
->upper
- adj
->page_size
) : value
;
1897 gtk_adjustment_set_value(adj
, value
);
1899 gtk_tree_selection_unselect_all(selection
);
1903 int ListBoxX::GetSelection() {
1906 GtkTreeModel
*model
;
1907 GtkTreeSelection
*selection
;
1908 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1909 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
)) {
1910 GtkTreePath
*path
= gtk_tree_model_get_path(model
, &iter
);
1911 int *indices
= gtk_tree_path_get_indices(path
);
1912 // Don't free indices.
1915 gtk_tree_path_free(path
);
1920 int ListBoxX::Find(const char *prefix
) {
1922 GtkTreeModel
*model
=
1923 gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1924 bool valid
= gtk_tree_model_get_iter_first(model
, &iter
) != FALSE
;
1928 gtk_tree_model_get(model
, &iter
, TEXT_COLUMN
, &s
, -1);
1929 if (s
&& (0 == strncmp(prefix
, s
, strlen(prefix
)))) {
1934 valid
= gtk_tree_model_iter_next(model
, &iter
) != FALSE
;
1940 void ListBoxX::GetValue(int n
, char *value
, int len
) {
1943 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1944 bool valid
= gtk_tree_model_iter_nth_child(model
, &iter
, NULL
, n
) != FALSE
;
1946 gtk_tree_model_get(model
, &iter
, TEXT_COLUMN
, &text
, -1);
1948 if (text
&& len
> 0) {
1949 g_strlcpy(value
, text
, len
);
1956 // g_return_if_fail causes unnecessary compiler warning in release compile.
1958 #pragma warning(disable: 4127)
1961 void ListBoxX::RegisterRGBA(int type
, RGBAImage
*image
) {
1962 images
.Add(type
, image
);
1965 pixhash
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
1967 ListImage
*list_image
= static_cast<ListImage
*>(g_hash_table_lookup((GHashTable
*) pixhash
,
1968 (gconstpointer
) GINT_TO_POINTER(type
)));
1970 // Drop icon already registered
1971 if (list_image
->pixbuf
)
1972 g_object_unref(list_image
->pixbuf
);
1973 list_image
->pixbuf
= NULL
;
1974 list_image
->rgba_data
= image
;
1976 list_image
= g_new0(ListImage
, 1);
1977 list_image
->rgba_data
= image
;
1978 g_hash_table_insert((GHashTable
*) pixhash
, GINT_TO_POINTER(type
),
1979 (gpointer
) list_image
);
1983 void ListBoxX::RegisterImage(int type
, const char *xpm_data
) {
1984 g_return_if_fail(xpm_data
);
1985 XPM
xpmImage(xpm_data
);
1986 RegisterRGBA(type
, new RGBAImage(xpmImage
));
1989 void ListBoxX::RegisterRGBAImage(int type
, int width
, int height
, const unsigned char *pixelsImage
) {
1990 RegisterRGBA(type
, new RGBAImage(width
, height
, 1.0, pixelsImage
));
1993 void ListBoxX::ClearRegisteredImages() {
1997 void ListBoxX::SetList(const char *listText
, char separator
, char typesep
) {
1999 int count
= strlen(listText
) + 1;
2000 std::vector
<char> words(listText
, listText
+count
);
2001 char *startword
= &words
[0];
2002 char *numword
= NULL
;
2004 for (; words
[i
]; i
++) {
2005 if (words
[i
] == separator
) {
2009 Append(startword
, numword
?atoi(numword
+ 1):-1);
2010 startword
= &words
[0] + i
+ 1;
2012 } else if (words
[i
] == typesep
) {
2013 numword
= &words
[0] + i
;
2019 Append(startword
, numword
?atoi(numword
+ 1):-1);
2023 Menu::Menu() : mid(0) {}
2025 void Menu::CreatePopUp() {
2027 mid
= gtk_menu_new();
2028 #if GLIB_CHECK_VERSION(2,10,0)
2029 g_object_ref_sink(G_OBJECT(mid
));
2031 g_object_ref(G_OBJECT(mid
));
2032 gtk_object_sink(GTK_OBJECT(G_OBJECT(mid
)));
2036 void Menu::Destroy() {
2038 g_object_unref(G_OBJECT(mid
));
2042 static void MenuPositionFunc(GtkMenu
*, gint
*x
, gint
*y
, gboolean
*, gpointer userData
) {
2043 sptr_t intFromPointer
= reinterpret_cast<sptr_t
>(userData
);
2044 *x
= intFromPointer
& 0xffff;
2045 *y
= intFromPointer
>> 16;
2048 void Menu::Show(Point pt
, Window
&) {
2049 int screenHeight
= gdk_screen_height();
2050 int screenWidth
= gdk_screen_width();
2051 GtkMenu
*widget
= reinterpret_cast<GtkMenu
*>(mid
);
2052 gtk_widget_show_all(GTK_WIDGET(widget
));
2053 GtkRequisition requisition
;
2054 #if GTK_CHECK_VERSION(3,0,0)
2055 gtk_widget_get_preferred_size(GTK_WIDGET(widget
), NULL
, &requisition
);
2057 gtk_widget_size_request(GTK_WIDGET(widget
), &requisition
);
2059 if ((pt
.x
+ requisition
.width
) > screenWidth
) {
2060 pt
.x
= screenWidth
- requisition
.width
;
2062 if ((pt
.y
+ requisition
.height
) > screenHeight
) {
2063 pt
.y
= screenHeight
- requisition
.height
;
2065 gtk_menu_popup(widget
, NULL
, NULL
, MenuPositionFunc
,
2066 reinterpret_cast<void *>((static_cast<int>(pt
.y
) << 16) | static_cast<int>(pt
.x
)), 0,
2067 gtk_get_current_event_time());
2070 ElapsedTime::ElapsedTime() {
2072 g_get_current_time(&curTime
);
2073 bigBit
= curTime
.tv_sec
;
2074 littleBit
= curTime
.tv_usec
;
2077 class DynamicLibraryImpl
: public DynamicLibrary
{
2081 explicit DynamicLibraryImpl(const char *modulePath
) {
2082 m
= g_module_open(modulePath
, G_MODULE_BIND_LAZY
);
2085 virtual ~DynamicLibraryImpl() {
2090 // Use g_module_symbol to get a pointer to the relevant function.
2091 virtual Function
FindFunction(const char *name
) {
2093 gpointer fn_address
= NULL
;
2094 gboolean status
= g_module_symbol(m
, name
, &fn_address
);
2096 return static_cast<Function
>(fn_address
);
2104 virtual bool IsValid() {
2109 DynamicLibrary
*DynamicLibrary::Load(const char *modulePath
) {
2110 return static_cast<DynamicLibrary
*>( new DynamicLibraryImpl(modulePath
) );
2113 double ElapsedTime::Duration(bool reset
) {
2115 g_get_current_time(&curTime
);
2116 long endBigBit
= curTime
.tv_sec
;
2117 long endLittleBit
= curTime
.tv_usec
;
2118 double result
= 1000000.0 * (endBigBit
- bigBit
);
2119 result
+= endLittleBit
- littleBit
;
2120 result
/= 1000000.0;
2123 littleBit
= endLittleBit
;
2128 ColourDesired
Platform::Chrome() {
2129 return ColourDesired(0xe0, 0xe0, 0xe0);
2132 ColourDesired
Platform::ChromeHighlight() {
2133 return ColourDesired(0xff, 0xff, 0xff);
2136 const char *Platform::DefaultFont() {
2138 return "Lucida Console";
2144 int Platform::DefaultFontSize() {
2152 unsigned int Platform::DoubleClickTime() {
2153 return 500; // Half a second
2156 bool Platform::MouseButtonBounce() {
2160 void Platform::DebugDisplay(const char *s
) {
2161 fprintf(stderr
, "%s", s
);
2164 bool Platform::IsKeyDown(int) {
2165 // TODO: discover state of keys in GTK+/X
2169 long Platform::SendScintilla(
2170 WindowID w
, unsigned int msg
, unsigned long wParam
, long lParam
) {
2171 return scintilla_send_message(SCINTILLA(w
), msg
, wParam
, lParam
);
2174 long Platform::SendScintillaPointer(
2175 WindowID w
, unsigned int msg
, unsigned long wParam
, void *lParam
) {
2176 return scintilla_send_message(SCINTILLA(w
), msg
, wParam
,
2177 reinterpret_cast<sptr_t
>(lParam
));
2180 bool Platform::IsDBCSLeadByte(int codePage
, char ch
) {
2181 // Byte ranges found in Wikipedia articles with relevant search strings in each case
2182 unsigned char uch
= static_cast<unsigned char>(ch
);
2186 return ((uch
>= 0x81) && (uch
<= 0x9F)) ||
2187 ((uch
>= 0xE0) && (uch
<= 0xFC));
2188 // Lead bytes F0 to FC may be a Microsoft addition.
2191 return (uch
>= 0x81) && (uch
<= 0xFE);
2194 return (uch
>= 0x81) && (uch
<= 0xFE);
2195 // Korean EUC-KR may be code page 949.
2200 int Platform::DBCSCharLength(int codePage
, const char *s
) {
2201 if (codePage
== 932 || codePage
== 936 || codePage
== 950) {
2202 return IsDBCSLeadByte(codePage
, s
[0]) ? 2 : 1;
2204 int bytes
= mblen(s
, MB_CUR_MAX
);
2212 int Platform::DBCSCharMaxLength() {
2217 // These are utility functions not really tied to a platform
2219 int Platform::Minimum(int a
, int b
) {
2226 int Platform::Maximum(int a
, int b
) {
2236 void Platform::DebugPrintf(const char *format
, ...) {
2239 va_start(pArguments
, format
);
2240 vsprintf(buffer
, format
, pArguments
);
2242 Platform::DebugDisplay(buffer
);
2245 void Platform::DebugPrintf(const char *, ...) {}
2249 // Not supported for GTK+
2250 static bool assertionPopUps
= true;
2252 bool Platform::ShowAssertionPopUps(bool assertionPopUps_
) {
2253 bool ret
= assertionPopUps
;
2254 assertionPopUps
= assertionPopUps_
;
2258 void Platform::Assert(const char *c
, const char *file
, int line
) {
2260 g_snprintf(buffer
, sizeof(buffer
), "Assertion [%s] failed at %s %d\r\n", c
, file
, line
);
2261 Platform::DebugDisplay(buffer
);
2265 int Platform::Clamp(int val
, int minVal
, int maxVal
) {
2273 void Platform_Initialise() {
2274 FontMutexAllocate();
2277 void Platform_Finalise() {
2278 FontCached::ReleaseAll();