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() {
533 void SurfaceImpl::Init(WindowID wid
) {
535 PLATFORM_ASSERT(wid
);
536 #if GTK_CHECK_VERSION(3,0,0)
537 GdkWindow
*drawable_
= gtk_widget_get_window(PWidget(wid
));
539 GdkDrawable
*drawable_
= GDK_DRAWABLE(PWidget(wid
)->window
);
542 context
= gdk_cairo_create(drawable_
);
543 PLATFORM_ASSERT(context
);
545 // Shouldn't happen with valid window but may when calls made before
546 // window completely allocated and mapped.
547 psurf
= cairo_image_surface_create(CAIRO_FORMAT_RGB24
, 1, 1);
548 context
= cairo_create(psurf
);
551 pcontext
= gtk_widget_create_pango_context(PWidget(wid
));
552 PLATFORM_ASSERT(pcontext
);
553 layout
= pango_layout_new(pcontext
);
554 PLATFORM_ASSERT(layout
);
558 void SurfaceImpl::Init(SurfaceID sid
, WindowID wid
) {
559 PLATFORM_ASSERT(sid
);
561 PLATFORM_ASSERT(wid
);
562 context
= cairo_reference(reinterpret_cast<cairo_t
*>(sid
));
563 pcontext
= gtk_widget_create_pango_context(PWidget(wid
));
564 // update the Pango context in case sid isn't the widget's surface
565 pango_cairo_update_context(context
, pcontext
);
566 layout
= pango_layout_new(pcontext
);
567 cairo_set_line_width(context
, 1);
572 void SurfaceImpl::InitPixMap(int width
, int height
, Surface
*surface_
, WindowID wid
) {
573 PLATFORM_ASSERT(surface_
);
575 SurfaceImpl
*surfImpl
= static_cast<SurfaceImpl
*>(surface_
);
576 PLATFORM_ASSERT(wid
);
577 context
= cairo_reference(surfImpl
->context
);
578 pcontext
= gtk_widget_create_pango_context(PWidget(wid
));
579 // update the Pango context in case surface_ isn't the widget's surface
580 pango_cairo_update_context(context
, pcontext
);
581 PLATFORM_ASSERT(pcontext
);
582 layout
= pango_layout_new(pcontext
);
583 PLATFORM_ASSERT(layout
);
584 if (height
> 0 && width
> 0)
585 psurf
= CreateSimilarSurface(
586 WindowFromWidget(PWidget(wid
)),
587 CAIRO_CONTENT_COLOR_ALPHA
, width
, height
);
588 cairo_destroy(context
);
589 context
= cairo_create(psurf
);
590 cairo_rectangle(context
, 0, 0, width
, height
);
591 cairo_set_source_rgb(context
, 1.0, 0, 0);
593 // This produces sharp drawing more similar to GDK:
594 //cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
595 cairo_set_line_width(context
, 1);
600 void SurfaceImpl::PenColour(ColourDesired fore
) {
602 ColourDesired
cdFore(fore
.AsLong());
603 cairo_set_source_rgb(context
,
604 cdFore
.GetRed() / 255.0,
605 cdFore
.GetGreen() / 255.0,
606 cdFore
.GetBlue() / 255.0);
610 int SurfaceImpl::LogPixelsY() {
614 int SurfaceImpl::DeviceHeightFont(int points
) {
615 int logPix
= LogPixelsY();
616 return (points
* logPix
+ logPix
/ 2) / 72;
619 void SurfaceImpl::MoveTo(int x_
, int y_
) {
624 static int Delta(int difference
) {
627 else if (difference
> 0)
633 void SurfaceImpl::LineTo(int x_
, int y_
) {
634 // cairo_line_to draws the end position, unlike Win32 or GDK with GDK_CAP_NOT_LAST.
635 // For simple cases, move back one pixel from end.
638 int xDelta
= Delta(xDiff
);
640 int yDelta
= Delta(yDiff
);
641 if ((xDiff
== 0) || (yDiff
== 0)) {
642 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
643 int xEnd
= x_
- xDelta
;
644 int left
= Platform::Minimum(x
, xEnd
);
645 int width
= abs(x
- xEnd
) + 1;
646 int yEnd
= y_
- yDelta
;
647 int top
= Platform::Minimum(y
, yEnd
);
648 int height
= abs(y
- yEnd
) + 1;
649 cairo_rectangle(context
, left
, top
, width
, height
);
651 } else if ((abs(xDiff
) == abs(yDiff
))) {
653 cairo_move_to(context
, x
+ 0.5, y
+ 0.5);
654 cairo_line_to(context
, x_
+ 0.5 - xDelta
, y_
+ 0.5 - yDelta
);
656 // Line has a different slope so difficult to avoid last pixel
657 cairo_move_to(context
, x
+ 0.5, y
+ 0.5);
658 cairo_line_to(context
, x_
+ 0.5, y_
+ 0.5);
660 cairo_stroke(context
);
666 void SurfaceImpl::Polygon(Point
*pts
, int npts
, ColourDesired fore
,
667 ColourDesired back
) {
669 cairo_move_to(context
, pts
[0].x
+ 0.5, pts
[0].y
+ 0.5);
670 for (int i
= 1; i
< npts
; i
++) {
671 cairo_line_to(context
, pts
[i
].x
+ 0.5, pts
[i
].y
+ 0.5);
673 cairo_close_path(context
);
674 cairo_fill_preserve(context
);
676 cairo_stroke(context
);
679 void SurfaceImpl::RectangleDraw(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
681 cairo_rectangle(context
, rc
.left
+ 0.5, rc
.top
+ 0.5,
682 rc
.right
- rc
.left
- 1, rc
.bottom
- rc
.top
- 1);
684 cairo_fill_preserve(context
);
686 cairo_stroke(context
);
690 void SurfaceImpl::FillRectangle(PRectangle rc
, ColourDesired back
) {
692 if (context
&& (rc
.left
< maxCoordinate
)) { // Protect against out of range
693 rc
.left
= lround(rc
.left
);
694 rc
.right
= lround(rc
.right
);
695 cairo_rectangle(context
, rc
.left
, rc
.top
,
696 rc
.right
- rc
.left
, rc
.bottom
- rc
.top
);
701 void SurfaceImpl::FillRectangle(PRectangle rc
, Surface
&surfacePattern
) {
702 SurfaceImpl
&surfi
= static_cast<SurfaceImpl
&>(surfacePattern
);
703 bool canDraw
= surfi
.psurf
;
705 // Tile pattern over rectangle
706 // Currently assumes 8x8 pattern
709 for (int xTile
= rc
.left
; xTile
< rc
.right
; xTile
+= widthPat
) {
710 int widthx
= (xTile
+ widthPat
> rc
.right
) ? rc
.right
- xTile
: widthPat
;
711 for (int yTile
= rc
.top
; yTile
< rc
.bottom
; yTile
+= heightPat
) {
712 int heighty
= (yTile
+ heightPat
> rc
.bottom
) ? rc
.bottom
- yTile
: heightPat
;
713 cairo_set_source_surface(context
, surfi
.psurf
, xTile
, yTile
);
714 cairo_rectangle(context
, xTile
, yTile
, widthx
, heighty
);
719 // Something is wrong so try to show anyway
720 // Shows up black because colour not allocated
721 FillRectangle(rc
, ColourDesired(0));
725 void SurfaceImpl::RoundedRectangle(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
726 if (((rc
.right
- rc
.left
) > 4) && ((rc
.bottom
- rc
.top
) > 4)) {
727 // Approximate a round rect with some cut off corners
729 Point(rc
.left
+ 2, rc
.top
),
730 Point(rc
.right
- 2, rc
.top
),
731 Point(rc
.right
, rc
.top
+ 2),
732 Point(rc
.right
, rc
.bottom
- 2),
733 Point(rc
.right
- 2, rc
.bottom
),
734 Point(rc
.left
+ 2, rc
.bottom
),
735 Point(rc
.left
, rc
.bottom
- 2),
736 Point(rc
.left
, rc
.top
+ 2),
738 Polygon(pts
, ELEMENTS(pts
), fore
, back
);
740 RectangleDraw(rc
, fore
, back
);
744 static void PathRoundRectangle(cairo_t
*context
, double left
, double top
, double width
, double height
, int radius
) {
745 double degrees
= kPi
/ 180.0;
747 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 2, 0)
748 cairo_new_sub_path(context
);
750 // First arc is in the top-right corner and starts from a point on the top line
751 cairo_move_to(context
, left
+ width
- radius
, top
);
753 cairo_arc(context
, left
+ width
- radius
, top
+ radius
, radius
, -90 * degrees
, 0 * degrees
);
754 cairo_arc(context
, left
+ width
- radius
, top
+ height
- radius
, radius
, 0 * degrees
, 90 * degrees
);
755 cairo_arc(context
, left
+ radius
, top
+ height
- radius
, radius
, 90 * degrees
, 180 * degrees
);
756 cairo_arc(context
, left
+ radius
, top
+ radius
, radius
, 180 * degrees
, 270 * degrees
);
757 cairo_close_path(context
);
760 void SurfaceImpl::AlphaRectangle(PRectangle rc
, int cornerSize
, ColourDesired fill
, int alphaFill
,
761 ColourDesired outline
, int alphaOutline
, int flags
) {
762 if (context
&& rc
.Width() > 0) {
763 ColourDesired
cdFill(fill
.AsLong());
764 cairo_set_source_rgba(context
,
765 cdFill
.GetRed() / 255.0,
766 cdFill
.GetGreen() / 255.0,
767 cdFill
.GetBlue() / 255.0,
770 PathRoundRectangle(context
, rc
.left
+ 1.0, rc
.top
+ 1.0, rc
.right
- rc
.left
- 2.0, rc
.bottom
- rc
.top
- 2.0, cornerSize
);
772 cairo_rectangle(context
, rc
.left
+ 1.0, rc
.top
+ 1.0, rc
.right
- rc
.left
- 2.0, rc
.bottom
- rc
.top
- 2.0);
775 ColourDesired
cdOutline(outline
.AsLong());
776 cairo_set_source_rgba(context
,
777 cdOutline
.GetRed() / 255.0,
778 cdOutline
.GetGreen() / 255.0,
779 cdOutline
.GetBlue() / 255.0,
780 alphaOutline
/ 255.0);
782 PathRoundRectangle(context
, rc
.left
+ 0.5, rc
.top
+ 0.5, rc
.right
- rc
.left
- 1, rc
.bottom
- rc
.top
- 1, cornerSize
);
784 cairo_rectangle(context
, rc
.left
+ 0.5, rc
.top
+ 0.5, rc
.right
- rc
.left
- 1, rc
.bottom
- rc
.top
- 1);
785 cairo_stroke(context
);
789 void SurfaceImpl::DrawRGBAImage(PRectangle rc
, int width
, int height
, const unsigned char *pixelsImage
) {
790 if (rc
.Width() > width
)
791 rc
.left
+= (rc
.Width() - width
) / 2;
792 rc
.right
= rc
.left
+ width
;
793 if (rc
.Height() > height
)
794 rc
.top
+= (rc
.Height() - height
) / 2;
795 rc
.bottom
= rc
.top
+ height
;
797 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
798 int stride
= cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32
, width
);
800 int stride
= width
* 4;
802 int ucs
= stride
* height
;
803 std::vector
<unsigned char> image(ucs
);
804 for (int y
=0; y
<height
; y
++) {
805 for (int x
=0; x
<width
; x
++) {
806 unsigned char *pixel
= &image
[0] + y
*stride
+ x
* 4;
807 unsigned char alpha
= pixelsImage
[3];
808 pixel
[2] = (*pixelsImage
++) * alpha
/ 255;
809 pixel
[1] = (*pixelsImage
++) * alpha
/ 255;
810 pixel
[0] = (*pixelsImage
++) * alpha
/ 255;
811 pixel
[3] = *pixelsImage
++;
815 cairo_surface_t
*psurf
= cairo_image_surface_create_for_data(&image
[0], CAIRO_FORMAT_ARGB32
, width
, height
, stride
);
816 cairo_set_source_surface(context
, psurf
, rc
.left
, rc
.top
);
817 cairo_rectangle(context
, rc
.left
, rc
.top
, rc
.right
-rc
.left
, rc
.bottom
-rc
.top
);
820 cairo_surface_destroy(psurf
);
823 void SurfaceImpl::Ellipse(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
825 cairo_arc(context
, (rc
.left
+ rc
.right
) / 2, (rc
.top
+ rc
.bottom
) / 2,
826 Platform::Minimum(rc
.Width(), rc
.Height()) / 2, 0, 2*kPi
);
827 cairo_fill_preserve(context
);
829 cairo_stroke(context
);
832 void SurfaceImpl::Copy(PRectangle rc
, Point from
, Surface
&surfaceSource
) {
833 SurfaceImpl
&surfi
= static_cast<SurfaceImpl
&>(surfaceSource
);
834 bool canDraw
= surfi
.psurf
;
836 cairo_set_source_surface(context
, surfi
.psurf
,
837 rc
.left
- from
.x
, rc
.top
- from
.y
);
838 cairo_rectangle(context
, rc
.left
, rc
.top
, rc
.right
-rc
.left
, rc
.bottom
-rc
.top
);
843 std::string
UTF8FromLatin1(const char *s
, int len
) {
844 std::string
utfForm(len
*2 + 1, '\0');
846 for (int i
=0; i
<len
; i
++) {
847 unsigned int uch
= static_cast<unsigned char>(s
[i
]);
849 utfForm
[lenU
++] = uch
;
851 utfForm
[lenU
++] = static_cast<char>(0xC0 | (uch
>> 6));
852 utfForm
[lenU
++] = static_cast<char>(0x80 | (uch
& 0x3f));
855 utfForm
.resize(lenU
);
859 static std::string
UTF8FromIconv(const Converter
&conv
, const char *s
, int len
) {
861 std::string
utfForm(len
*3+1, '\0');
862 char *pin
= const_cast<char *>(s
);
864 char *putf
= &utfForm
[0];
866 size_t outLeft
= len
*3+1;
867 size_t conversions
= conv
.Convert(&pin
, &inLeft
, &pout
, &outLeft
);
868 if (conversions
!= ((size_t)(-1))) {
870 utfForm
.resize(pout
- putf
);
874 return std::string();
877 // Work out how many bytes are in a character by trying to convert using iconv,
878 // returning the first length that succeeds.
879 static size_t MultiByteLenFromIconv(const Converter
&conv
, const char *s
, size_t len
) {
880 for (size_t lenMB
=1; (lenMB
<4) && (lenMB
<= len
); lenMB
++) {
882 char *pin
= const_cast<char *>(s
);
883 size_t inLeft
= lenMB
;
886 size_t conversions
= conv
.Convert(&pin
, &inLeft
, &pout
, &outLeft
);
887 if (conversions
!= ((size_t)(-1))) {
894 void SurfaceImpl::DrawTextBase(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
895 ColourDesired fore
) {
898 XYPOSITION xText
= rc
.left
;
899 if (PFont(font_
)->pfd
) {
902 pango_layout_set_text(layout
, s
, len
);
904 SetConverter(PFont(font_
)->characterSet
);
905 utfForm
= UTF8FromIconv(conv
, s
, len
);
906 if (utfForm
.empty()) { // iconv failed so treat as Latin1
907 utfForm
= UTF8FromLatin1(s
, len
);
909 pango_layout_set_text(layout
, utfForm
.c_str(), utfForm
.length());
911 pango_layout_set_font_description(layout
, PFont(font_
)->pfd
);
912 pango_cairo_update_layout(context
, layout
);
914 PangoLayoutLine
*pll
= pango_layout_get_line_readonly(layout
,0);
916 PangoLayoutLine
*pll
= pango_layout_get_line(layout
,0);
918 cairo_move_to(context
, xText
, ybase
);
919 pango_cairo_show_layout_line(context
, pll
);
924 void SurfaceImpl::DrawTextNoClip(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
925 ColourDesired fore
, ColourDesired back
) {
926 FillRectangle(rc
, back
);
927 DrawTextBase(rc
, font_
, ybase
, s
, len
, fore
);
930 // On GTK+, exactly same as DrawTextNoClip
931 void SurfaceImpl::DrawTextClipped(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
932 ColourDesired fore
, ColourDesired back
) {
933 FillRectangle(rc
, back
);
934 DrawTextBase(rc
, font_
, ybase
, s
, len
, fore
);
937 void SurfaceImpl::DrawTextTransparent(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
,
938 ColourDesired fore
) {
939 // Avoid drawing spaces in transparent mode
940 for (int i
=0; i
<len
; i
++) {
942 DrawTextBase(rc
, font_
, ybase
, s
, len
, fore
);
948 class ClusterIterator
{
949 PangoLayoutIter
*iter
;
954 XYPOSITION positionStart
;
958 ClusterIterator(PangoLayout
*layout
, int len
) : lenPositions(len
), finished(false),
959 positionStart(0), position(0), distance(0), curIndex(0) {
960 iter
= pango_layout_get_iter(layout
);
961 pango_layout_iter_get_cluster_extents(iter
, NULL
, &pos
);
964 pango_layout_iter_free(iter
);
968 positionStart
= position
;
969 if (pango_layout_iter_next_cluster(iter
)) {
970 pango_layout_iter_get_cluster_extents(iter
, NULL
, &pos
);
971 position
= doubleFromPangoUnits(pos
.x
);
972 curIndex
= pango_layout_iter_get_index(iter
);
975 position
= doubleFromPangoUnits(pos
.x
+ pos
.width
);
976 curIndex
= lenPositions
;
978 distance
= position
- positionStart
;
982 void SurfaceImpl::MeasureWidths(Font
&font_
, const char *s
, int len
, XYPOSITION
*positions
) {
984 const int lenPositions
= len
;
985 if (PFont(font_
)->pfd
) {
987 int width
= PFont(font_
)->CharWidth(*s
, et
);
989 positions
[0] = width
;
993 pango_layout_set_font_description(layout
, PFont(font_
)->pfd
);
995 // Simple and direct as UTF-8 is native Pango encoding
997 pango_layout_set_text(layout
, s
, len
);
998 ClusterIterator
iti(layout
, lenPositions
);
999 while (!iti
.finished
) {
1001 int places
= iti
.curIndex
- i
;
1002 while (i
< iti
.curIndex
) {
1003 // Evenly distribute space among bytes of this cluster.
1004 // Would be better to find number of characters and then
1005 // divide evenly between characters with each byte of a character
1006 // being at the same position.
1007 positions
[i
] = iti
.position
- (iti
.curIndex
- 1 - i
) * iti
.distance
/ places
;
1011 PLATFORM_ASSERT(i
== lenPositions
);
1013 int positionsCalculated
= 0;
1015 SetConverter(PFont(font_
)->characterSet
);
1016 std::string utfForm
= UTF8FromIconv(conv
, s
, len
);
1017 if (!utfForm
.empty()) {
1018 // Convert to UTF-8 so can ask Pango for widths, then
1019 // Loop through UTF-8 and DBCS forms, taking account of different
1020 // character byte lengths.
1021 Converter
convMeasure("UCS-2", CharacterSetID(characterSet
), false);
1022 pango_layout_set_text(layout
, utfForm
.c_str(), strlen(utfForm
.c_str()));
1024 int clusterStart
= 0;
1025 ClusterIterator
iti(layout
, strlen(utfForm
.c_str()));
1026 while (!iti
.finished
) {
1028 int clusterEnd
= iti
.curIndex
;
1029 int places
= g_utf8_strlen(utfForm
.c_str() + clusterStart
, clusterEnd
- clusterStart
);
1031 while (clusterStart
< clusterEnd
) {
1032 size_t lenChar
= MultiByteLenFromIconv(convMeasure
, s
+i
, len
-i
);
1034 positions
[i
++] = iti
.position
- (places
- place
) * iti
.distance
/ places
;
1035 positionsCalculated
++;
1037 clusterStart
+= UTF8CharLength(static_cast<unsigned char>(utfForm
.c_str()[clusterStart
]));
1041 PLATFORM_ASSERT(i
== lenPositions
);
1044 if (positionsCalculated
< 1 ) {
1045 // Either 8-bit or DBCS conversion failed so treat as 8-bit.
1046 SetConverter(PFont(font_
)->characterSet
);
1047 const bool rtlCheck
= PFont(font_
)->characterSet
== SC_CHARSET_HEBREW
||
1048 PFont(font_
)->characterSet
== SC_CHARSET_ARABIC
;
1049 std::string utfForm
= UTF8FromIconv(conv
, s
, len
);
1050 if (utfForm
.empty()) {
1051 utfForm
= UTF8FromLatin1(s
, len
);
1053 pango_layout_set_text(layout
, utfForm
.c_str(), utfForm
.length());
1055 int clusterStart
= 0;
1056 // Each 8-bit input character may take 1 or 2 bytes in UTF-8
1057 // and groups of up to 3 may be represented as ligatures.
1058 ClusterIterator
iti(layout
, utfForm
.length());
1059 while (!iti
.finished
) {
1061 int clusterEnd
= iti
.curIndex
;
1062 int ligatureLength
= g_utf8_strlen(utfForm
.c_str() + clusterStart
, clusterEnd
- clusterStart
);
1063 if (rtlCheck
&& ((clusterEnd
<= clusterStart
) || (ligatureLength
== 0) || (ligatureLength
> 3))) {
1064 // Something has gone wrong: exit quickly but pretend all the characters are equally spaced:
1065 int widthLayout
= 0;
1066 pango_layout_get_size(layout
, &widthLayout
, NULL
);
1067 XYPOSITION widthTotal
= doubleFromPangoUnits(widthLayout
);
1068 for (int bytePos
=0; bytePos
<lenPositions
; bytePos
++) {
1069 positions
[bytePos
] = widthTotal
/ lenPositions
* (bytePos
+ 1);
1073 PLATFORM_ASSERT(ligatureLength
> 0 && ligatureLength
<= 3);
1074 for (int charInLig
=0; charInLig
<ligatureLength
; charInLig
++) {
1075 positions
[i
++] = iti
.position
- (ligatureLength
- 1 - charInLig
) * iti
.distance
/ ligatureLength
;
1077 clusterStart
= clusterEnd
;
1079 PLATFORM_ASSERT(i
== lenPositions
);
1083 PFont(font_
)->SetCharWidth(*s
, positions
[0], et
);
1088 // No font so return an ascending range of values
1089 for (int i
= 0; i
< len
; i
++) {
1090 positions
[i
] = i
+ 1;
1095 XYPOSITION
SurfaceImpl::WidthText(Font
&font_
, const char *s
, int len
) {
1096 if (font_
.GetID()) {
1097 if (PFont(font_
)->pfd
) {
1098 std::string utfForm
;
1099 pango_layout_set_font_description(layout
, PFont(font_
)->pfd
);
1102 pango_layout_set_text(layout
, s
, len
);
1104 SetConverter(PFont(font_
)->characterSet
);
1105 utfForm
= UTF8FromIconv(conv
, s
, len
);
1106 if (utfForm
.empty()) { // iconv failed so treat as Latin1
1107 utfForm
= UTF8FromLatin1(s
, len
);
1109 pango_layout_set_text(layout
, utfForm
.c_str(), utfForm
.length());
1111 #ifdef PANGO_VERSION
1112 PangoLayoutLine
*pangoLine
= pango_layout_get_line_readonly(layout
,0);
1114 PangoLayoutLine
*pangoLine
= pango_layout_get_line(layout
,0);
1116 pango_layout_line_get_extents(pangoLine
, NULL
, &pos
);
1117 return doubleFromPangoUnits(pos
.width
);
1125 XYPOSITION
SurfaceImpl::WidthChar(Font
&font_
, char ch
) {
1126 if (font_
.GetID()) {
1127 if (PFont(font_
)->pfd
) {
1128 return WidthText(font_
, &ch
, 1);
1136 // Ascent and descent determined by Pango font metrics.
1138 XYPOSITION
SurfaceImpl::Ascent(Font
&font_
) {
1139 if (!(font_
.GetID()))
1142 int ascent
= PFont(font_
)->ascent
;
1143 if ((ascent
== 0) && (PFont(font_
)->pfd
)) {
1144 PangoFontMetrics
*metrics
= pango_context_get_metrics(pcontext
,
1145 PFont(font_
)->pfd
, pango_context_get_language(pcontext
));
1146 PFont(font_
)->ascent
=
1147 doubleFromPangoUnits(pango_font_metrics_get_ascent(metrics
));
1148 pango_font_metrics_unref(metrics
);
1149 ascent
= PFont(font_
)->ascent
;
1158 XYPOSITION
SurfaceImpl::Descent(Font
&font_
) {
1159 if (!(font_
.GetID()))
1161 if (PFont(font_
)->pfd
) {
1162 PangoFontMetrics
*metrics
= pango_context_get_metrics(pcontext
,
1163 PFont(font_
)->pfd
, pango_context_get_language(pcontext
));
1164 int descent
= doubleFromPangoUnits(pango_font_metrics_get_descent(metrics
));
1165 pango_font_metrics_unref(metrics
);
1171 XYPOSITION
SurfaceImpl::InternalLeading(Font
&) {
1175 XYPOSITION
SurfaceImpl::ExternalLeading(Font
&) {
1179 XYPOSITION
SurfaceImpl::Height(Font
&font_
) {
1180 return Ascent(font_
) + Descent(font_
);
1183 XYPOSITION
SurfaceImpl::AverageCharWidth(Font
&font_
) {
1184 return WidthChar(font_
, 'n');
1187 void SurfaceImpl::SetClip(PRectangle rc
) {
1188 cairo_rectangle(context
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
1189 cairo_clip(context
);
1192 void SurfaceImpl::FlushCachedState() {}
1194 void SurfaceImpl::SetUnicodeMode(bool unicodeMode_
) {
1199 void SurfaceImpl::SetDBCSMode(int codePage
) {
1200 if (codePage
&& (codePage
!= SC_CP_UTF8
))
1204 Surface
*Surface::Allocate(int) {
1205 return new SurfaceImpl();
1208 Window::~Window() {}
1210 void Window::Destroy() {
1212 gtk_widget_destroy(GTK_WIDGET(wid
));
1216 bool Window::HasFocus() {
1217 return IS_WIDGET_FOCUSSED(wid
);
1220 PRectangle
Window::GetPosition() {
1221 // Before any size allocated pretend its 1000 wide so not scrolled
1222 PRectangle
rc(0, 0, 1000, 1000);
1224 GtkAllocation allocation
;
1225 #if GTK_CHECK_VERSION(3,0,0)
1226 gtk_widget_get_allocation(PWidget(wid
), &allocation
);
1228 allocation
= PWidget(wid
)->allocation
;
1230 rc
.left
= allocation
.x
;
1231 rc
.top
= allocation
.y
;
1232 if (allocation
.width
> 20) {
1233 rc
.right
= rc
.left
+ allocation
.width
;
1234 rc
.bottom
= rc
.top
+ allocation
.height
;
1240 void Window::SetPosition(PRectangle rc
) {
1241 GtkAllocation alloc
;
1244 alloc
.width
= rc
.Width();
1245 alloc
.height
= rc
.Height();
1246 gtk_widget_size_allocate(PWidget(wid
), &alloc
);
1249 void Window::SetPositionRelative(PRectangle rc
, Window relativeTo
) {
1252 gdk_window_get_origin(WindowFromWidget(PWidget(relativeTo
.wid
)), &ox
, &oy
);
1260 /* do some corrections to fit into screen */
1261 int sizex
= rc
.right
- rc
.left
;
1262 int sizey
= rc
.bottom
- rc
.top
;
1263 int screenWidth
= gdk_screen_width();
1264 int screenHeight
= gdk_screen_height();
1265 if (sizex
> screenWidth
)
1266 ox
= 0; /* the best we can do */
1267 else if (ox
+ sizex
> screenWidth
)
1268 ox
= screenWidth
- sizex
;
1269 if (oy
+ sizey
> screenHeight
)
1270 oy
= screenHeight
- sizey
;
1272 gtk_window_move(GTK_WINDOW(PWidget(wid
)), ox
, oy
);
1274 gtk_widget_set_size_request(PWidget(wid
), sizex
, sizey
);
1277 PRectangle
Window::GetClientPosition() {
1278 // On GTK+, the client position is the window position
1279 return GetPosition();
1282 void Window::Show(bool show
) {
1284 gtk_widget_show(PWidget(wid
));
1287 void Window::InvalidateAll() {
1289 gtk_widget_queue_draw(PWidget(wid
));
1293 void Window::InvalidateRectangle(PRectangle rc
) {
1295 gtk_widget_queue_draw_area(PWidget(wid
),
1297 rc
.right
- rc
.left
, rc
.bottom
- rc
.top
);
1301 void Window::SetFont(Font
&) {
1302 // Can not be done generically but only needed for ListBox
1305 void Window::SetCursor(Cursor curs
) {
1306 // We don't set the cursor to same value numerous times under gtk because
1307 // it stores the cursor in the window once it's set
1308 if (curs
== cursorLast
)
1315 gdkCurs
= gdk_cursor_new(GDK_XTERM
);
1318 gdkCurs
= gdk_cursor_new(GDK_LEFT_PTR
);
1321 gdkCurs
= gdk_cursor_new(GDK_CENTER_PTR
);
1324 gdkCurs
= gdk_cursor_new(GDK_WATCH
);
1327 gdkCurs
= gdk_cursor_new(GDK_HAND2
);
1329 case cursorReverseArrow
:
1330 gdkCurs
= gdk_cursor_new(GDK_RIGHT_PTR
);
1333 gdkCurs
= gdk_cursor_new(GDK_LEFT_PTR
);
1334 cursorLast
= cursorArrow
;
1338 if (WindowFromWidget(PWidget(wid
)))
1339 gdk_window_set_cursor(WindowFromWidget(PWidget(wid
)), gdkCurs
);
1340 #if GTK_CHECK_VERSION(3,0,0)
1341 g_object_unref(gdkCurs
);
1343 gdk_cursor_unref(gdkCurs
);
1347 void Window::SetTitle(const char *s
) {
1348 gtk_window_set_title(GTK_WINDOW(wid
), s
);
1351 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1352 gdk window coordinates */
1353 PRectangle
Window::GetMonitorRect(Point pt
) {
1354 gint x_offset
, y_offset
;
1356 gdk_window_get_origin(WindowFromWidget(PWidget(wid
)), &x_offset
, &y_offset
);
1362 screen
= gtk_widget_get_screen(PWidget(wid
));
1363 monitor_num
= gdk_screen_get_monitor_at_point(screen
, pt
.x
+ x_offset
, pt
.y
+ y_offset
);
1364 gdk_screen_get_monitor_geometry(screen
, monitor_num
, &rect
);
1367 return PRectangle(rect
.x
, rect
.y
, rect
.x
+ rect
.width
, rect
.y
+ rect
.height
);
1370 typedef std::map
<int, RGBAImage
*> ImageMap
;
1373 const RGBAImage
*rgba_data
;
1377 static void list_image_free(gpointer
, gpointer value
, gpointer
) {
1378 ListImage
*list_image
= static_cast<ListImage
*>(value
);
1379 if (list_image
->pixbuf
)
1380 g_object_unref(list_image
->pixbuf
);
1384 ListBox::ListBox() {
1387 ListBox::~ListBox() {
1396 class ListBoxX
: public ListBox
{
1400 GtkCellRenderer
* pixbuf_renderer
;
1401 RGBAImageSet images
;
1402 int desiredVisibleRows
;
1403 unsigned int maxItemCharacters
;
1404 unsigned int aveCharWidth
;
1406 CallBackAction doubleClickAction
;
1407 void *doubleClickActionData
;
1409 ListBoxX() : list(0), scroller(0), pixhash(NULL
), pixbuf_renderer(0),
1410 desiredVisibleRows(5), maxItemCharacters(0),
1411 aveCharWidth(1), doubleClickAction(NULL
), doubleClickActionData(NULL
) {
1413 virtual ~ListBoxX() {
1415 g_hash_table_foreach((GHashTable
*) pixhash
, list_image_free
, NULL
);
1416 g_hash_table_destroy((GHashTable
*) pixhash
);
1419 virtual void SetFont(Font
&font
);
1420 virtual void Create(Window
&parent
, int ctrlID
, Point location_
, int lineHeight_
, bool unicodeMode_
, int technology_
);
1421 virtual void SetAverageCharWidth(int width
);
1422 virtual void SetVisibleRows(int rows
);
1423 virtual int GetVisibleRows() const;
1424 virtual PRectangle
GetDesiredRect();
1425 virtual int CaretFromEdge();
1426 virtual void Clear();
1427 virtual void Append(char *s
, int type
= -1);
1428 virtual int Length();
1429 virtual void Select(int n
);
1430 virtual int GetSelection();
1431 virtual int Find(const char *prefix
);
1432 virtual void GetValue(int n
, char *value
, int len
);
1433 void RegisterRGBA(int type
, RGBAImage
*image
);
1434 virtual void RegisterImage(int type
, const char *xpm_data
);
1435 virtual void RegisterRGBAImage(int type
, int width
, int height
, const unsigned char *pixelsImage
);
1436 virtual void ClearRegisteredImages();
1437 virtual void SetDoubleClickAction(CallBackAction action
, void *data
) {
1438 doubleClickAction
= action
;
1439 doubleClickActionData
= data
;
1441 virtual void SetList(const char *listText
, char separator
, char typesep
);
1444 ListBox
*ListBox::Allocate() {
1445 ListBoxX
*lb
= new ListBoxX();
1449 static gboolean
ButtonPress(GtkWidget
*, GdkEventButton
* ev
, gpointer p
) {
1451 ListBoxX
* lb
= reinterpret_cast<ListBoxX
*>(p
);
1452 if (ev
->type
== GDK_2BUTTON_PRESS
&& lb
->doubleClickAction
!= NULL
) {
1453 lb
->doubleClickAction(lb
->doubleClickActionData
);
1458 // No pointer back to Scintilla to save status
1463 /* Change the active color to the selected color so the listbox uses the color
1464 scheme that it would use if it had the focus. */
1465 static void StyleSet(GtkWidget
*w
, GtkStyle
*, void*) {
1467 g_return_if_fail(w
!= NULL
);
1469 /* Copy the selected color to active. Note that the modify calls will cause
1470 recursive calls to this function after the value is updated and w->style to
1471 be set to a new object */
1473 #if GTK_CHECK_VERSION(3,0,0)
1474 GtkStyleContext
*styleContext
= gtk_widget_get_style_context(w
);
1475 if (styleContext
== NULL
)
1478 GdkRGBA colourForeSelected
;
1479 gtk_style_context_get_color(styleContext
, GTK_STATE_FLAG_SELECTED
, &colourForeSelected
);
1480 GdkRGBA colourForeActive
;
1481 gtk_style_context_get_color(styleContext
, GTK_STATE_FLAG_ACTIVE
, &colourForeActive
);
1482 if (!gdk_rgba_equal(&colourForeSelected
, &colourForeActive
))
1483 gtk_widget_override_color(w
, GTK_STATE_FLAG_ACTIVE
, &colourForeSelected
);
1485 styleContext
= gtk_widget_get_style_context(w
);
1486 if (styleContext
== NULL
)
1489 GdkRGBA colourBaseSelected
;
1490 gtk_style_context_get_background_color(styleContext
, GTK_STATE_FLAG_SELECTED
, &colourBaseSelected
);
1491 GdkRGBA colourBaseActive
;
1492 gtk_style_context_get_background_color(styleContext
, GTK_STATE_FLAG_ACTIVE
, &colourBaseActive
);
1493 if (!gdk_rgba_equal(&colourBaseSelected
, &colourBaseActive
))
1494 gtk_widget_override_background_color(w
, GTK_STATE_FLAG_ACTIVE
, &colourBaseSelected
);
1496 GtkStyle
*style
= gtk_widget_get_style(w
);
1499 if (!gdk_color_equal(&style
->base
[GTK_STATE_SELECTED
], &style
->base
[GTK_STATE_ACTIVE
]))
1500 gtk_widget_modify_base(w
, GTK_STATE_ACTIVE
, &style
->base
[GTK_STATE_SELECTED
]);
1501 style
= gtk_widget_get_style(w
);
1504 if (!gdk_color_equal(&style
->text
[GTK_STATE_SELECTED
], &style
->text
[GTK_STATE_ACTIVE
]))
1505 gtk_widget_modify_text(w
, GTK_STATE_ACTIVE
, &style
->text
[GTK_STATE_SELECTED
]);
1509 void ListBoxX::Create(Window
&, int, Point
, int, bool, int) {
1510 wid
= gtk_window_new(GTK_WINDOW_POPUP
);
1512 GtkWidget
*frame
= gtk_frame_new(NULL
);
1513 gtk_widget_show(frame
);
1514 gtk_container_add(GTK_CONTAINER(GetID()), frame
);
1515 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_OUT
);
1516 gtk_container_set_border_width(GTK_CONTAINER(frame
), 0);
1518 scroller
= gtk_scrolled_window_new(NULL
, NULL
);
1519 gtk_container_set_border_width(GTK_CONTAINER(scroller
), 0);
1520 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller
),
1521 GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
1522 gtk_container_add(GTK_CONTAINER(frame
), PWidget(scroller
));
1523 gtk_widget_show(PWidget(scroller
));
1525 /* Tree and its model */
1526 GtkListStore
*store
=
1527 gtk_list_store_new(N_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
);
1529 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(store
));
1530 g_signal_connect(G_OBJECT(list
), "style-set", G_CALLBACK(StyleSet
), NULL
);
1532 GtkTreeSelection
*selection
=
1533 gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1534 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_SINGLE
);
1535 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), FALSE
);
1536 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list
), FALSE
);
1539 GtkTreeViewColumn
*column
= gtk_tree_view_column_new();
1540 gtk_tree_view_column_set_sizing(column
, GTK_TREE_VIEW_COLUMN_FIXED
);
1541 gtk_tree_view_column_set_title(column
, "Autocomplete");
1543 pixbuf_renderer
= gtk_cell_renderer_pixbuf_new();
1544 gtk_cell_renderer_set_fixed_size(pixbuf_renderer
, 0, -1);
1545 gtk_tree_view_column_pack_start(column
, pixbuf_renderer
, FALSE
);
1546 gtk_tree_view_column_add_attribute(column
, pixbuf_renderer
,
1547 "pixbuf", PIXBUF_COLUMN
);
1549 GtkCellRenderer
* renderer
= gtk_cell_renderer_text_new();
1550 gtk_cell_renderer_text_set_fixed_height_from_font(GTK_CELL_RENDERER_TEXT(renderer
), 1);
1551 gtk_tree_view_column_pack_start(column
, renderer
, TRUE
);
1552 gtk_tree_view_column_add_attribute(column
, renderer
,
1553 "text", TEXT_COLUMN
);
1555 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), column
);
1556 if (g_object_class_find_property(G_OBJECT_GET_CLASS(list
), "fixed-height-mode"))
1557 g_object_set(G_OBJECT(list
), "fixed-height-mode", TRUE
, NULL
);
1559 GtkWidget
*wid
= PWidget(list
); // No code inside the G_OBJECT macro
1560 gtk_container_add(GTK_CONTAINER(PWidget(scroller
)), wid
);
1561 gtk_widget_show(wid
);
1562 g_signal_connect(G_OBJECT(wid
), "button_press_event",
1563 G_CALLBACK(ButtonPress
), this);
1564 gtk_widget_realize(PWidget(wid
));
1567 void ListBoxX::SetFont(Font
&scint_font
) {
1568 // Only do for Pango font as there have been crashes for GDK fonts
1569 if (Created() && PFont(scint_font
)->pfd
) {
1570 // Current font is Pango font
1571 #if GTK_CHECK_VERSION(3,0,0)
1572 gtk_widget_override_font(PWidget(list
), PFont(scint_font
)->pfd
);
1574 gtk_widget_modify_font(PWidget(list
), PFont(scint_font
)->pfd
);
1579 void ListBoxX::SetAverageCharWidth(int width
) {
1580 aveCharWidth
= width
;
1583 void ListBoxX::SetVisibleRows(int rows
) {
1584 desiredVisibleRows
= rows
;
1587 int ListBoxX::GetVisibleRows() const {
1588 return desiredVisibleRows
;
1591 PRectangle
ListBoxX::GetDesiredRect() {
1592 // Before any size allocated pretend its 100 wide so not scrolled
1593 PRectangle
rc(0, 0, 100, 100);
1595 int rows
= Length();
1596 if ((rows
== 0) || (rows
> desiredVisibleRows
))
1597 rows
= desiredVisibleRows
;
1600 #if GTK_CHECK_VERSION(3,0,0)
1601 // This, apparently unnecessary call, ensures gtk_tree_view_column_cell_get_size
1602 // returns reasonable values.
1603 gtk_widget_get_preferred_size(GTK_WIDGET(scroller
), NULL
, &req
);
1607 // First calculate height of the clist for our desired visible
1608 // row count otherwise it tries to expand to the total # of rows
1612 GtkTreeViewColumn
* column
=
1613 gtk_tree_view_get_column(GTK_TREE_VIEW(list
), 0);
1614 gtk_tree_view_column_cell_get_size(column
, NULL
,
1615 NULL
, NULL
, &row_width
, &row_height
);
1616 #if GTK_CHECK_VERSION(3,0,0)
1617 GtkStyleContext
*styleContextList
= gtk_widget_get_style_context(PWidget(list
));
1619 gtk_style_context_get_padding(styleContextList
, GTK_STATE_FLAG_NORMAL
, &padding
);
1620 height
= (rows
* row_height
1621 + padding
.top
+ padding
.bottom
1622 + 2 * (gtk_container_get_border_width(GTK_CONTAINER(PWidget(list
))) + 1));
1624 int ythickness
= PWidget(list
)->style
->ythickness
;
1625 height
= (rows
* row_height
1627 + GTK_CONTAINER(PWidget(list
))->border_width
+ 1));
1629 gtk_widget_set_size_request(GTK_WIDGET(PWidget(list
)), -1, height
);
1631 // Get the size of the scroller because we set usize on the window
1632 #if GTK_CHECK_VERSION(3,0,0)
1633 gtk_widget_get_preferred_size(GTK_WIDGET(scroller
), NULL
, &req
);
1635 gtk_widget_size_request(GTK_WIDGET(scroller
), &req
);
1637 rc
.right
= req
.width
;
1638 rc
.bottom
= Platform::Maximum(height
, req
.height
);
1640 gtk_widget_set_size_request(GTK_WIDGET(list
), -1, -1);
1641 int width
= maxItemCharacters
;
1644 rc
.right
= width
* (aveCharWidth
+ aveCharWidth
/ 3);
1645 if (Length() > rows
)
1646 rc
.right
= rc
.right
+ 16;
1651 int ListBoxX::CaretFromEdge() {
1652 gint renderer_width
, renderer_height
;
1653 gtk_cell_renderer_get_fixed_size(pixbuf_renderer
, &renderer_width
,
1655 return 4 + renderer_width
;
1658 void ListBoxX::Clear() {
1659 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1660 gtk_list_store_clear(GTK_LIST_STORE(model
));
1661 maxItemCharacters
= 0;
1664 static void init_pixmap(ListImage
*list_image
) {
1665 if (list_image
->rgba_data
) {
1666 // Drop any existing pixmap/bitmap as data may have changed
1667 if (list_image
->pixbuf
)
1668 g_object_unref(list_image
->pixbuf
);
1669 list_image
->pixbuf
=
1670 gdk_pixbuf_new_from_data(list_image
->rgba_data
->Pixels(),
1674 list_image
->rgba_data
->GetWidth(),
1675 list_image
->rgba_data
->GetHeight(),
1676 list_image
->rgba_data
->GetWidth() * 4,
1684 void ListBoxX::Append(char *s
, int type
) {
1685 ListImage
*list_image
= NULL
;
1686 if ((type
>= 0) && pixhash
) {
1687 list_image
= static_cast<ListImage
*>(g_hash_table_lookup((GHashTable
*) pixhash
1688 , (gconstpointer
) GINT_TO_POINTER(type
)));
1691 GtkListStore
*store
=
1692 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list
)));
1693 gtk_list_store_append(GTK_LIST_STORE(store
), &iter
);
1695 if (NULL
== list_image
->pixbuf
)
1696 init_pixmap(list_image
);
1697 if (list_image
->pixbuf
) {
1698 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1699 PIXBUF_COLUMN
, list_image
->pixbuf
,
1700 TEXT_COLUMN
, s
, -1);
1702 gint pixbuf_width
= gdk_pixbuf_get_width(list_image
->pixbuf
);
1703 gint renderer_height
, renderer_width
;
1704 gtk_cell_renderer_get_fixed_size(pixbuf_renderer
,
1705 &renderer_width
, &renderer_height
);
1706 if (pixbuf_width
> renderer_width
)
1707 gtk_cell_renderer_set_fixed_size(pixbuf_renderer
,
1710 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1711 TEXT_COLUMN
, s
, -1);
1714 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
1715 TEXT_COLUMN
, s
, -1);
1717 size_t len
= strlen(s
);
1718 if (maxItemCharacters
< len
)
1719 maxItemCharacters
= len
;
1722 int ListBoxX::Length() {
1724 return gtk_tree_model_iter_n_children(gtk_tree_view_get_model
1725 (GTK_TREE_VIEW(list
)), NULL
);
1729 void ListBoxX::Select(int n
) {
1731 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1732 GtkTreeSelection
*selection
=
1733 gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1736 gtk_tree_selection_unselect_all(selection
);
1740 bool valid
= gtk_tree_model_iter_nth_child(model
, &iter
, NULL
, n
) != FALSE
;
1742 gtk_tree_selection_select_iter(selection
, &iter
);
1744 // Move the scrollbar to show the selection.
1745 int total
= Length();
1746 #if GTK_CHECK_VERSION(3,0,0)
1747 GtkAdjustment
*adj
=
1748 gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(list
));
1749 gfloat value
= ((gfloat
)n
/ total
) * (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_lower(adj
))
1750 + gtk_adjustment_get_lower(adj
) - gtk_adjustment_get_page_size(adj
) / 2;
1752 GtkAdjustment
*adj
=
1753 gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(list
));
1754 gfloat value
= ((gfloat
)n
/ total
) * (adj
->upper
- adj
->lower
)
1755 + adj
->lower
- adj
->page_size
/ 2;
1760 GtkTreeViewColumn
* column
=
1761 gtk_tree_view_get_column(GTK_TREE_VIEW(list
), 0);
1762 gtk_tree_view_column_cell_get_size(column
, NULL
, NULL
,
1763 NULL
, &row_width
, &row_height
);
1765 int rows
= Length();
1766 if ((rows
== 0) || (rows
> desiredVisibleRows
))
1767 rows
= desiredVisibleRows
;
1769 // Odd rows to display -- We are now in the middle.
1770 // Align it so that we don't chop off rows.
1771 value
+= (gfloat
)row_height
/ 2.0;
1774 value
= (value
< 0)? 0 : value
;
1775 #if GTK_CHECK_VERSION(3,0,0)
1776 value
= (value
> (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_page_size(adj
)))?
1777 (gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_page_size(adj
)) : value
;
1779 value
= (value
> (adj
->upper
- adj
->page_size
))?
1780 (adj
->upper
- adj
->page_size
) : value
;
1784 gtk_adjustment_set_value(adj
, value
);
1786 gtk_tree_selection_unselect_all(selection
);
1790 int ListBoxX::GetSelection() {
1792 GtkTreeModel
*model
;
1793 GtkTreeSelection
*selection
;
1794 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
1795 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
)) {
1796 GtkTreePath
*path
= gtk_tree_model_get_path(model
, &iter
);
1797 int *indices
= gtk_tree_path_get_indices(path
);
1798 // Don't free indices.
1805 int ListBoxX::Find(const char *prefix
) {
1807 GtkTreeModel
*model
=
1808 gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1809 bool valid
= gtk_tree_model_get_iter_first(model
, &iter
) != FALSE
;
1813 gtk_tree_model_get(model
, &iter
, TEXT_COLUMN
, &s
, -1);
1814 if (s
&& (0 == strncmp(prefix
, s
, strlen(prefix
)))) {
1819 valid
= gtk_tree_model_iter_next(model
, &iter
) != FALSE
;
1825 void ListBoxX::GetValue(int n
, char *value
, int len
) {
1828 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1829 bool valid
= gtk_tree_model_iter_nth_child(model
, &iter
, NULL
, n
) != FALSE
;
1831 gtk_tree_model_get(model
, &iter
, TEXT_COLUMN
, &text
, -1);
1833 if (text
&& len
> 0) {
1834 g_strlcpy(value
, text
, len
);
1841 // g_return_if_fail causes unnecessary compiler warning in release compile.
1843 #pragma warning(disable: 4127)
1846 void ListBoxX::RegisterRGBA(int type
, RGBAImage
*image
) {
1847 images
.Add(type
, image
);
1850 pixhash
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
1852 ListImage
*list_image
= static_cast<ListImage
*>(g_hash_table_lookup((GHashTable
*) pixhash
,
1853 (gconstpointer
) GINT_TO_POINTER(type
)));
1855 // Drop icon already registered
1856 if (list_image
->pixbuf
)
1857 g_object_unref(list_image
->pixbuf
);
1858 list_image
->pixbuf
= NULL
;
1859 list_image
->rgba_data
= image
;
1861 list_image
= g_new0(ListImage
, 1);
1862 list_image
->rgba_data
= image
;
1863 g_hash_table_insert((GHashTable
*) pixhash
, GINT_TO_POINTER(type
),
1864 (gpointer
) list_image
);
1868 void ListBoxX::RegisterImage(int type
, const char *xpm_data
) {
1869 g_return_if_fail(xpm_data
);
1870 XPM
xpmImage(xpm_data
);
1871 RegisterRGBA(type
, new RGBAImage(xpmImage
));
1874 void ListBoxX::RegisterRGBAImage(int type
, int width
, int height
, const unsigned char *pixelsImage
) {
1875 RegisterRGBA(type
, new RGBAImage(width
, height
, 1.0, pixelsImage
));
1878 void ListBoxX::ClearRegisteredImages() {
1882 void ListBoxX::SetList(const char *listText
, char separator
, char typesep
) {
1884 int count
= strlen(listText
) + 1;
1885 std::vector
<char> words(listText
, listText
+count
);
1886 char *startword
= &words
[0];
1887 char *numword
= NULL
;
1889 for (; words
[i
]; i
++) {
1890 if (words
[i
] == separator
) {
1894 Append(startword
, numword
?atoi(numword
+ 1):-1);
1895 startword
= &words
[0] + i
+ 1;
1897 } else if (words
[i
] == typesep
) {
1898 numword
= &words
[0] + i
;
1904 Append(startword
, numword
?atoi(numword
+ 1):-1);
1908 Menu::Menu() : mid(0) {}
1910 void Menu::CreatePopUp() {
1912 mid
= gtk_menu_new();
1913 #if GLIB_CHECK_VERSION(2,10,0)
1914 g_object_ref_sink(G_OBJECT(mid
));
1916 g_object_ref(G_OBJECT(mid
));
1917 gtk_object_sink(GTK_OBJECT(G_OBJECT(mid
)));
1921 void Menu::Destroy() {
1923 g_object_unref(G_OBJECT(mid
));
1927 static void MenuPositionFunc(GtkMenu
*, gint
*x
, gint
*y
, gboolean
*, gpointer userData
) {
1928 sptr_t intFromPointer
= reinterpret_cast<sptr_t
>(userData
);
1929 *x
= intFromPointer
& 0xffff;
1930 *y
= intFromPointer
>> 16;
1933 void Menu::Show(Point pt
, Window
&) {
1934 int screenHeight
= gdk_screen_height();
1935 int screenWidth
= gdk_screen_width();
1936 GtkMenu
*widget
= reinterpret_cast<GtkMenu
*>(mid
);
1937 gtk_widget_show_all(GTK_WIDGET(widget
));
1938 GtkRequisition requisition
;
1939 #if GTK_CHECK_VERSION(3,0,0)
1940 gtk_widget_get_preferred_size(GTK_WIDGET(widget
), NULL
, &requisition
);
1942 gtk_widget_size_request(GTK_WIDGET(widget
), &requisition
);
1944 if ((pt
.x
+ requisition
.width
) > screenWidth
) {
1945 pt
.x
= screenWidth
- requisition
.width
;
1947 if ((pt
.y
+ requisition
.height
) > screenHeight
) {
1948 pt
.y
= screenHeight
- requisition
.height
;
1950 gtk_menu_popup(widget
, NULL
, NULL
, MenuPositionFunc
,
1951 reinterpret_cast<void *>((static_cast<int>(pt
.y
) << 16) | static_cast<int>(pt
.x
)), 0,
1952 gtk_get_current_event_time());
1955 ElapsedTime::ElapsedTime() {
1957 g_get_current_time(&curTime
);
1958 bigBit
= curTime
.tv_sec
;
1959 littleBit
= curTime
.tv_usec
;
1962 class DynamicLibraryImpl
: public DynamicLibrary
{
1966 explicit DynamicLibraryImpl(const char *modulePath
) {
1967 m
= g_module_open(modulePath
, G_MODULE_BIND_LAZY
);
1970 virtual ~DynamicLibraryImpl() {
1975 // Use g_module_symbol to get a pointer to the relevant function.
1976 virtual Function
FindFunction(const char *name
) {
1978 gpointer fn_address
= NULL
;
1979 gboolean status
= g_module_symbol(m
, name
, &fn_address
);
1981 return static_cast<Function
>(fn_address
);
1989 virtual bool IsValid() {
1994 DynamicLibrary
*DynamicLibrary::Load(const char *modulePath
) {
1995 return static_cast<DynamicLibrary
*>( new DynamicLibraryImpl(modulePath
) );
1998 double ElapsedTime::Duration(bool reset
) {
2000 g_get_current_time(&curTime
);
2001 long endBigBit
= curTime
.tv_sec
;
2002 long endLittleBit
= curTime
.tv_usec
;
2003 double result
= 1000000.0 * (endBigBit
- bigBit
);
2004 result
+= endLittleBit
- littleBit
;
2005 result
/= 1000000.0;
2008 littleBit
= endLittleBit
;
2013 ColourDesired
Platform::Chrome() {
2014 return ColourDesired(0xe0, 0xe0, 0xe0);
2017 ColourDesired
Platform::ChromeHighlight() {
2018 return ColourDesired(0xff, 0xff, 0xff);
2021 const char *Platform::DefaultFont() {
2023 return "Lucida Console";
2029 int Platform::DefaultFontSize() {
2037 unsigned int Platform::DoubleClickTime() {
2038 return 500; // Half a second
2041 bool Platform::MouseButtonBounce() {
2045 void Platform::DebugDisplay(const char *s
) {
2046 fprintf(stderr
, "%s", s
);
2049 bool Platform::IsKeyDown(int) {
2050 // TODO: discover state of keys in GTK+/X
2054 long Platform::SendScintilla(
2055 WindowID w
, unsigned int msg
, unsigned long wParam
, long lParam
) {
2056 return scintilla_send_message(SCINTILLA(w
), msg
, wParam
, lParam
);
2059 long Platform::SendScintillaPointer(
2060 WindowID w
, unsigned int msg
, unsigned long wParam
, void *lParam
) {
2061 return scintilla_send_message(SCINTILLA(w
), msg
, wParam
,
2062 reinterpret_cast<sptr_t
>(lParam
));
2065 bool Platform::IsDBCSLeadByte(int codePage
, char ch
) {
2066 // Byte ranges found in Wikipedia articles with relevant search strings in each case
2067 unsigned char uch
= static_cast<unsigned char>(ch
);
2071 return ((uch
>= 0x81) && (uch
<= 0x9F)) ||
2072 ((uch
>= 0xE0) && (uch
<= 0xFC));
2073 // Lead bytes F0 to FC may be a Microsoft addition.
2076 return (uch
>= 0x81) && (uch
<= 0xFE);
2079 return (uch
>= 0x81) && (uch
<= 0xFE);
2080 // Korean EUC-KR may be code page 949.
2085 int Platform::DBCSCharLength(int codePage
, const char *s
) {
2086 if (codePage
== 932 || codePage
== 936 || codePage
== 950) {
2087 return IsDBCSLeadByte(codePage
, s
[0]) ? 2 : 1;
2089 int bytes
= mblen(s
, MB_CUR_MAX
);
2097 int Platform::DBCSCharMaxLength() {
2102 // These are utility functions not really tied to a platform
2104 int Platform::Minimum(int a
, int b
) {
2111 int Platform::Maximum(int a
, int b
) {
2121 void Platform::DebugPrintf(const char *format
, ...) {
2124 va_start(pArguments
, format
);
2125 vsprintf(buffer
, format
, pArguments
);
2127 Platform::DebugDisplay(buffer
);
2130 void Platform::DebugPrintf(const char *, ...) {}
2134 // Not supported for GTK+
2135 static bool assertionPopUps
= true;
2137 bool Platform::ShowAssertionPopUps(bool assertionPopUps_
) {
2138 bool ret
= assertionPopUps
;
2139 assertionPopUps
= assertionPopUps_
;
2143 void Platform::Assert(const char *c
, const char *file
, int line
) {
2145 g_snprintf(buffer
, sizeof(buffer
), "Assertion [%s] failed at %s %d\r\n", c
, file
, line
);
2146 Platform::DebugDisplay(buffer
);
2150 int Platform::Clamp(int val
, int minVal
, int maxVal
) {
2158 void Platform_Initialise() {
2159 FontMutexAllocate();
2162 void Platform_Finalise() {
2163 FontCached::ReleaseAll();