diff --git a/include/lgi/common/Gdc2.h b/include/lgi/common/Gdc2.h --- a/include/lgi/common/Gdc2.h +++ b/include/lgi/common/Gdc2.h @@ -1,1461 +1,1474 @@ /** \file \author Matthew Allen \date 20/2/1997 \brief GDC v2.xx header */ #ifndef __GDC2_H_ #define __GDC2_H_ #include #include "LgiOsDefs.h" // Platform specific // Alpha Bliting #ifdef WINNATIVE #include #include #pragma warning(disable:4263) #include #pragma warning(error:4263) #pragma comment (lib,"Gdiplus.lib") #endif // sub-system headers #include "lgi/common/LgiInc.h" #include "lgi/common/LgiUiBase.h" #include "lgi/common/File.h" #include "lgi/common/Mem.h" #include "lgi/common/Core.h" #include "lgi/common/Containers.h" #include "lgi/common/Capabilities.h" #include "lgi/common/RefCount.h" #include "lgi/common/Palette.h" #include "lgi/common/ColourSpace.h" #include "lgi/common/Rect.h" #include "lgi/common/Point.h" #include "lgi/common/Colour.h" #ifndef AC_SRC_OVER #define AC_SRC_OVER 0 #endif #ifndef AC_SRC_ALPHA #define AC_SRC_ALPHA 1 #endif #include "lgi/common/Library.h" // Defines /// The default gamma curve (none) used by the gamma LUT GdcDevice::GetGamma #define LGI_DEFAULT_GAMMA 1.0 #ifndef LGI_PI /// The value of PI #define LGI_PI 3.141592654 #endif /// Converts degrees to radians #define LGI_DegToRad(i) ((i)*LGI_PI/180) /// Converts radians to degrees #define LGI_RadToDeg(i) ((i)*180/LGI_PI) #if defined(WIN32) && !defined(_WIN64) /// Use intel assembly instructions, comment out for porting #define GDC_USE_ASM #endif /// Blending mode: overwrite #define GDC_SET 0 /// Blending mode: bitwise AND with background #define GDC_AND 1 /// Blending mode: bitwise OR with background #define GDC_OR 2 /// Blending mode: bitwise XOR with background #define GDC_XOR 3 /// Blending mode: alpha blend with background #define GDC_ALPHA 4 #define GDC_REMAP 5 #define GDC_MAXOP 6 #define GDC_CACHE_SIZE 4 // Channel types #define GDCC_MONO 0 #define GDCC_GREY 1 #define GDCC_INDEX 2 #define GDCC_R 3 #define GDCC_G 4 #define GDCC_B 5 #define GDCC_ALPHA 6 // Native data formats #define GDC_8BIT 0 #define GDC_16BIT 1 #define GDC_24BIT 2 #define GDC_32BIT 3 #define GDC_MAXFMT 4 // Colour spaces #define GDC_8I 0 // 8 bit paletted #define GDC_5R6G5B 1 // 16 bit #define GDC_8B8G8B 2 // 24 bit #define GDC_8A8R8G8B 3 // 32 bit #define GDC_MAXSPACE 4 // Update types #define GDC_PAL_CHANGE 0x1 #define GDC_BITS_CHANGE 0x2 // Flood fill types /// LSurface::FloodFill to a different colour #define GDC_FILL_TO_DIFFERENT 0 /// LSurface::FloodFill to a certain colour #define GDC_FILL_TO_BORDER 1 /// LSurface::FloodFill while colour is near to the seed colour #define GDC_FILL_NEAR 2 // Gdc options /// Used in GdcApp8Set::Blt when doing colour depth reduction to 8 bit. #define GDC_REDUCE_TYPE 0 /// No conversion #define REDUCE_NONE 0 /// Nearest colour pixels #define REDUCE_NEAREST 1 /// Halftone the pixels #define REDUCE_HALFTONE 2 /// Error diffuse the pixels #define REDUCE_ERROR_DIFFUSION 3 /// Not used. #define REDUCE_DL1 4 /// Not used. #define GDC_HALFTONE_BASE_INDEX 1 /// When in 8-bit defined the behaviour of GdcDevice::GetColour #define GDC_PALETTE_TYPE 2 /// Allocate colours from the palette #define PALTYPE_ALLOC 0 /// Use an RGB cube #define PALTYPE_RGB_CUBE 1 /// Use a HSL palette #define PALTYPE_HSL 2 /// Converts images to the specified bit-depth on load, does nothing if 0 #define GDC_PROMOTE_ON_LOAD 3 #define GDC_MAX_OPTION 4 // LSurface Flags #define GDC_ON_SCREEN 0x0002 #define GDC_ALPHA_CHANNEL 0x0004 #define GDC_UPDATED_PALETTE 0x0008 #define GDC_CAPTURE_CURSOR 0x0010 #define GDC_OWN_APPLICATOR 0x0020 #define GDC_CACHED_APPLICATOR 0x0040 #define GDC_OWN_PALETTE 0x0080 #define GDC_DRAW_ON_ALPHA 0x0100 #define GDC_ANTI_ALIAS 0x0200 // Region types #define GDC_RGN_NONE 0 // No clipping #define GDC_RGN_SIMPLE 1 // Single rectangle #define GDC_RGN_COMPLEX 2 // Many rectangles // Error codes #define GDCERR_NONE 0 #define GDCERR_ERROR 1 #define GDCERR_CANT_SET_SCAN_WIDTH 2 #define GDC_INVALIDMODE -1 // Font display flags #define GDCFNT_TRANSPARENT 0x0001 // not set - SOLID #define GDCFNT_UNDERLINE 0x0002 // Palette file types #define GDCPAL_JASC 1 #define GDCPAL_MICROSOFT 2 // Misc #define BMPWIDTH(bits) ((((bits)+31)/32)<<2) // Look up tables #define Div255Lut (GdcDevice::GetInst()->GetDiv255()) // Classes class LFilter; class LSurface; class LgiClass LBmpMem { public: enum GdcMemFlags { BmpOwnMemory = 0x1, BmpPreMulAlpha = 0x2, }; uchar *Base; int x, y; ssize_t Line; LColourSpace Cs; int Flags; LBmpMem(); ~LBmpMem(); + int GetBits() + { + return LColourSpaceToBits(Cs); + } + + int BytesPerPx() + { + return LColourSpaceToBits(Cs) >> 3; + } + + uchar *AddressOf(int ox, int oy) + { + LAssert(ox >= 0 && ox < x && + oy >= 0 && oy < y); + + return Base + (oy * Line) + (ox * BytesPerPx()); + } + bool PreMul() { return (Flags & BmpPreMulAlpha) != 0; } bool PreMul(bool set) { if (set) Flags |= BmpPreMulAlpha; else Flags &= ~BmpPreMulAlpha; return PreMul(); } bool OwnMem() { return (Flags & BmpOwnMemory) != 0; } bool OwnMem(bool set) { if (set) Flags |= BmpOwnMemory; else Flags &= ~BmpOwnMemory; return OwnMem(); } - int GetBits() - { - return LColourSpaceToBits(Cs); - } - void GetMemoryExtents(uchar *&Start, uchar *&End) { if (Line < 0) { Start = Base + (y * Line); End = Base + Line; } else { Start = Base; End = Base + (y * Line); } } bool Overlap(LBmpMem *Mem) { uchar *ThisStart, *ThisEnd; GetMemoryExtents(ThisStart, ThisEnd); uchar *MemStart, *MemEnd; GetMemoryExtents(MemStart, MemEnd); if (ThisEnd < MemStart) return false; if (ThisStart > MemEnd) return false; return true; } }; #define GAPP_ALPHA_A 1 #define GAPP_ALPHA_PAL 2 #define GAPP_BACKGROUND 3 #define GAPP_ANGLE 4 #define GAPP_BOUNDS 5 /// \brief Class to draw onto a memory bitmap /// /// This class assumes that all clipping is done by the layer above. /// It can then implement very simple loops to do the work of filling /// pixels class LgiClass LApplicator { protected: LBmpMem *Dest; LBmpMem *Alpha; LPalette *Pal; int Op; public: union { COLOUR c; // main colour System24BitPixel p24; System32BitPixel p32; }; LApplicator() { c = 0; Dest = NULL; Alpha = NULL; Pal = NULL; } LApplicator(COLOUR Colour) { c = Colour; } virtual ~LApplicator() { } virtual const char *GetClass() { return "LApplicator"; } /// Get a parameter virtual int GetVar(int Var) { return 0; } /// Set a parameter virtual int SetVar(int Var, NativeInt Value) { return 0; } LColourSpace GetColourSpace() { return Dest ? Dest->Cs : CsNone; } /// Sets the operator void SetOp(int o, int Param = -1) { Op = o; } /// Gets the operator int GetOp() { return Op; } /// Gets the bit depth int GetBits() { return (Dest) ? LColourSpaceToBits(Dest->Cs) : 0; } /// Gets the flags in operation int GetFlags() { return (Dest) ? Dest->Flags : 0; } /// Gets the palette LPalette *GetPal() { return Pal; } /// Sets the bitmap to write onto virtual bool SetSurface(LBmpMem *d, LPalette *p = 0, LBmpMem *a = 0) = 0; // sets Dest, returns FALSE on error /// Sets the current position to an x,y virtual void SetPtr(int x, int y) = 0; // calculates Ptr from x, y /// Moves the current position one pixel left virtual void IncX() = 0; /// Moves the current position one scanline down virtual void IncY() = 0; /// Offset the current position virtual void IncPtr(int X, int Y) = 0; /// Sets the pixel at the current location with the current colour virtual void Set() = 0; /// Gets the colour of the pixel at the current location virtual COLOUR Get() = 0; /// Draws a vertical line from the current position down 'height' scanlines virtual void VLine(int height) = 0; /// Draws a rectangle starting from the current position, 'x' pixels across and 'y' pixels down virtual void Rectangle(int x, int y) = 0; /// Copies bitmap data to the current position virtual bool Blt(LBmpMem *Src, LPalette *SPal, LBmpMem *SrcAlpha = 0) = 0; }; /// Creates applications from parameters. class LgiClass LApplicatorFactory { public: LApplicatorFactory(); virtual ~LApplicatorFactory(); /// Find the application factory and create the appropriate object. static LApplicator *NewApp(LColourSpace Cs, int Op); virtual LApplicator *Create(LColourSpace Cs, int Op) = 0; }; class LgiClass LApp15 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LgiClass LApp16 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LgiClass LApp24 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LgiClass LApp32 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LgiClass LApp8 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LAlphaFactory : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; #define OrgX(x) x -= OriginX #define OrgY(y) y -= OriginY #define OrgXy(x, y) x -= OriginX; y -= OriginY #define OrgPt(p) p.x -= OriginX; p.y -= OriginY #define OrgRgn(r) r.Offset(-OriginX, -OriginY) /// Base class API for graphics operations class LgiClass LSurface : public LRefCount, public LDom { friend class LFilter; friend class LView; friend class LWindow; friend class LVariant; friend class LRegionClipDC; friend class LMemDC; void Init(); protected: int Flags; int PrevOp; LRect Clip; LColourSpace ColourSpace; LBmpMem *pMem; LSurface *pAlphaDC; LPalette *pPalette; LApplicator *pApp; LApplicator *pAppCache[GDC_CACHE_SIZE]; int OriginX, OriginY; // Protected functions LApplicator *CreateApplicator(int Op, LColourSpace Cs = CsNone); uint32_t LineBits; uint32_t LineMask; uint32_t LineReset; #if WINNATIVE OsPainter hDC; OsBitmap hBmp; LAutoPtr GdiplusGfx; #endif public: LSurface(); LSurface(LSurface *pDC); virtual ~LSurface(); // Win32 #if defined(__GTK_H__) /// Gets the drawable size, regardless of clipping or client rect virtual LPoint GetSize() { LPoint p; return p; } virtual Gtk::GtkPrintContext *GetPrintContext() { return NULL; } virtual Gtk::GdkPixbuf *CreatePixBuf() { return NULL; } #elif defined(WINNATIVE) virtual HDC StartDC() { return hDC; } virtual void EndDC() {} Gdiplus::Graphics *GetGfx(); Gdiplus::Color GdiColour(); #elif defined MAC virtual CGColorSpaceRef GetColourSpaceRef() { return 0; } #endif virtual const char *GetClass() { return "LSurface"; } virtual OsBitmap GetBitmap(); virtual OsPainter Handle(); virtual void SetClient(LRect *c) {} virtual bool GetClient(LRect *c) { return false; } // Creation enum SurfaceCreateFlags { SurfaceCreateNone, SurfaceRequireNative, SurfaceRequireExactCs, }; virtual bool Create(int x, int y, LColourSpace Cs, int Flags = SurfaceCreateNone) { return false; } virtual void Update(int Flags) {} // Alpha channel /// Returns true if this Surface has an alpha channel virtual bool HasAlpha() { return pAlphaDC != 0; } /// Creates or destroys the alpha channel for this surface virtual bool HasAlpha(bool b); /// Returns true if we are drawing on the alpha channel bool DrawOnAlpha() { return ((Flags & GDC_DRAW_ON_ALPHA) != 0); } /// True if you want to edit the alpha channel rather than the colour bits bool DrawOnAlpha(bool Draw); /// Returns the surface of the alpha channel. LSurface *AlphaDC() { return pAlphaDC; } /// \returns the anti-alias setting bool AntiAlias(); /// Set the anti-alias setting bool AntiAlias(bool antiAlias); /// Lowers the alpha of the whole image to Alpha/255.0. /// Only works on bitmaps with an alpha channel (i.e. CsRgba32 or it's variants) bool SetConstantAlpha(uint8_t Alpha); // Create sub-images (that reference the memory of this object) LSurface *SubImage(LRect r); LSurface *SubImage(int x1, int y1, int x2, int y2) { LRect r(x1, y1, x2, y2); return SubImage(r); } // Applicator virtual bool Applicator(LApplicator *pApp); virtual LApplicator *Applicator(); // Palette virtual LPalette *Palette(); virtual void Palette(LPalette *pPal, bool bOwnIt = true); // Clip region virtual LRect ClipRgn(LRect *Rgn); virtual LRect ClipRgn(); /// Gets the current colour virtual COLOUR Colour() { return pApp->c; } /// Sets the current colour virtual COLOUR Colour ( /// The new colour COLOUR c, /// The bit depth of the new colour or 0 to indicate the depth is the same as the current Surface int Bits = 0 ); /// Sets the current colour virtual LColour Colour ( /// The new colour LColour c ); /// Sets the colour to a system colour virtual LColour Colour(LSystemColour SysCol); /// Gets the current blending mode in operation virtual int Op() { return (pApp) ? pApp->GetOp() : GDC_SET; } /// Sets the current blending mode in operation /// \sa GDC_SET, GDC_AND, GDC_OR, GDC_XOR and GDC_ALPHA virtual int Op(int Op, NativeInt Param = -1); /// Gets the width in pixels virtual int X() { return (pMem) ? pMem->x : 0; } /// Gets the height in pixels virtual int Y() { return (pMem) ? pMem->y : 0; } /// Gets the bounds of the image as a LRect LRect Bounds() { return LRect(0, 0, X()-1, Y()-1); } /// Gets the length of a scanline in bytes virtual ssize_t GetRowStep() { return (pMem) ? pMem->Line : 0; } /// Returns the resolution of the device virtual LPoint GetDpi() { return LPoint(96,96); } /// Gets the bits per pixel virtual int GetBits() { return (pMem) ? LColourSpaceToBits(pMem->Cs) : 0; } /// Gets the colour space of the pixels virtual LColourSpace GetColourSpace() { return ColourSpace; } /// Gets any flags associated with the surface virtual int GetFlags() { return Flags; } /// Returns true if the surface is on the screen virtual class LScreenDC *IsScreen() { return 0; } /// Returns true if the surface is for printing virtual bool IsPrint() { return false; } /// Returns a pointer to the start of a scanline, or NULL if not available virtual uchar *operator[](int y); /// Dumps various debug information virtual LString Dump() { return LString("Not implemented."); } /// Returns true if this surface supports alpha compositing when using Blt virtual bool SupportsAlphaCompositing() { return false; } /// \returns whether if pixel data is pre-multiplied alpha virtual bool IsPreMultipliedAlpha(); /// Converts the pixel data between pre-mul alpha or non-pre-mul alpha virtual bool ConvertPreMulAlpha(bool ToPreMul); /// Makes the alpha channel opaque virtual bool MakeOpaque(); /// Gets the surface origin virtual void GetOrigin(int &x, int &y) { x = OriginX; y = OriginY; } /// Gets the surface origin LPoint GetOrigin() { int x, y; GetOrigin(x, y); return LPoint(x, y); } /// Sets the surface origin virtual void SetOrigin(int x, int y) { OriginX = x; OriginY = y; } /// Sets the surface origin void SetOrigin(LPoint p) { SetOrigin(p.x, p.y); } /// Sets a pixel with the current colour virtual void Set(int x, int y); /// Gets a pixel (doesn't work on some types of image, i.e. LScreenDC) virtual COLOUR Get(int x, int y); // Line /// Draw a horizontal line in the current colour virtual void HLine(int x1, int x2, int y); /// Draw a vertical line in the current colour virtual void VLine(int x, int y1, int y2); /// Draw a line in the current colour virtual void Line(int x1, int y1, int x2, int y2); /// Some surfaces only support specific line styles (e.g. GDI/Win) enum LineStyles { LineNone = 0x0, LineSolid = 0xffffffff, LineAlternate = 0xaaaaaaaa, LineDash = 0xf0f0f0f0, LineDot = 0xcccccccc, LineDashDot = 0xF33CCF30, LineDashDotDot = 0xf0ccf0cc, }; virtual uint LineStyle(uint32_t Bits, uint32_t Reset = 0x80000000) { uint32_t B = LineBits; LineBits = Bits; LineMask = LineReset = Reset; return B; } virtual uint LineStyle() { return LineBits; } // Curve /// Stroke a circle in the current colour virtual void Circle(double cx, double cy, double radius); /// Fill a circle in the current colour virtual void FilledCircle(double cx, double cy, double radius); /// Stroke an arc in the current colour virtual void Arc(double cx, double cy, double radius, double start, double end); /// Fill an arc in the current colour virtual void FilledArc(double cx, double cy, double radius, double start, double end); /// Stroke an ellipse in the current colour virtual void Ellipse(double cx, double cy, double x, double y); /// Fill an ellipse in the current colour virtual void FilledEllipse(double cx, double cy, double x, double y); // Rectangular /// Stroke a rectangle in the current colour virtual void Box(int x1, int y1, int x2, int y2); /// Stroke a rectangle in the current colour virtual void Box ( /// The rectangle, or NULL to stroke the edge of the entire surface LRect *a = NULL ); /// Fill a rectangle in the current colour virtual void Rectangle(int x1, int y1, int x2, int y2); /// Fill a rectangle in the current colour virtual void Rectangle ( /// The rectangle, or NULL to fill the entire surface LRect *a = NULL ); /// Copy an image onto the surface virtual void Blt ( /// The destination x coord int x, /// The destination y coord int y, /// The source surface LSurface *Src, /// The optional area of the source to use, if not specified the whole source is used LRect *a = NULL ); void Blt(int x, int y, LSurface *Src, LRect a) { Blt(x, y, Src, &a); } /// Not implemented virtual void StretchBlt(LRect *d, LSurface *Src, LRect *s); // Other /// Fill a polygon in the current colour virtual void Polygon(int Points, LPoint *Data); /// Stroke a bezier in the current colour virtual void Bezier(int Threshold, LPoint *Pt); /// Flood fill in the current colour (doesn't work on a LScreenDC) virtual void FloodFill ( /// Start x coordinate int x, /// Start y coordinate int y, /// Use #GDC_FILL_TO_DIFFERENT, #GDC_FILL_TO_BORDER or #GDC_FILL_NEAR int Mode, /// Fill colour COLOUR Border = 0, /// The bounds of the filled area or NULL if you don't care LRect *Bounds = NULL ); /// Describes the image virtual LString GetStr(); // LDom interface bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool CallMethod(const char *Name, LVariant *ReturnValue, LArray &Args); }; #if defined(MAC) && !defined(__GTK_H__) struct LPrintDcParams { #if LGI_COCOA #else PMRect Page; CGContextRef Ctx; PMResolution Dpi; #endif }; #endif #if defined(__GTK_H__) typedef Gtk::GdkWindow OsDrawable; #endif /// \brief An implemenation of LSurface to draw onto the screen. /// /// This is the class given to LView::OnPaint() most of the time. Which most of /// the time doesn't matter unless your doing something unusual. class LgiClass LScreenDC : public LSurface { class LScreenPrivate *d; public: LScreenDC(); virtual ~LScreenDC(); const char *GetClass() override { return "LScreenDC"; } // OS Sepcific #if WINNATIVE LScreenDC(LViewI *view); LScreenDC(HWND hwnd); LScreenDC(HDC hdc, HWND hwnd, bool Release = false); LScreenDC(HBITMAP hBmp, int Sx, int Sy); bool CreateFromHandle(HDC hdc); void SetSize(int x, int y); #else /// Construct a wrapper to draw on a window LScreenDC(LView *view, void *Param = 0); #if defined(LGI_SDL) #elif defined(__GTK_H__) /// Constructs a server size pixmap LScreenDC(int x, int y, int bits); /// Constructs a wrapper around a drawable LScreenDC(OsDrawable *Drawable); /// Constructs a DC for drawing on a cairo context LScreenDC(Gtk::cairo_t *cr, int x, int y); // Gtk::cairo_surface_t *GetSurface(bool Render); LPoint GetSize(); #elif defined(MAC) LScreenDC(LWindow *wnd, void *Param = 0); LScreenDC(LPrintDcParams *Params); // Used by LPrintDC LRect GetPos(); void PushState(); void PopState(); #endif OsPainter Handle() override; LView *GetView(); int GetFlags() override; LRect *GetClient(); #endif // Properties bool GetClient(LRect *c) override; void SetClient(LRect *c) override; int X() override; int Y() override; LPalette *Palette() override; void Palette(LPalette *pPal, bool bOwnIt = true) override; uint LineStyle() override; uint LineStyle(uint Bits, uint32_t Reset = 0x80000000) override; int GetBits() override; LScreenDC *IsScreen() override { return this; } bool SupportsAlphaCompositing() override; LPoint GetDpi() override; #ifndef LGI_SDL uchar *operator[](int y) override { return NULL; } void GetOrigin(int &x, int &y) override; void SetOrigin(int x, int y) override; LRect ClipRgn() override; LRect ClipRgn(LRect *Rgn) override; COLOUR Colour() override; COLOUR Colour(COLOUR c, int Bits = 0) override; LColour Colour(LColour c) override; LString Dump() override; int Op() override; int Op(int Op, NativeInt Param = -1) override; // Primitives void Set(int x, int y) override; COLOUR Get(int x, int y) override; void HLine(int x1, int x2, int y) override; void VLine(int x, int y1, int y2) override; void Line(int x1, int y1, int x2, int y2) override; void Circle(double cx, double cy, double radius) override; void FilledCircle(double cx, double cy, double radius) override; void Arc(double cx, double cy, double radius, double start, double end) override; void FilledArc(double cx, double cy, double radius, double start, double end) override; void Ellipse(double cx, double cy, double x, double y) override; void FilledEllipse(double cx, double cy, double x, double y) override; void Box(int x1, int y1, int x2, int y2) override; void Box(LRect *a) override; void Rectangle(int x1, int y1, int x2, int y2) override; void Rectangle(LRect *a = NULL) override; void Blt(int x, int y, LSurface *Src, LRect *a = NULL) override; void StretchBlt(LRect *d, LSurface *Src, LRect *s = NULL) override; void Polygon(int Points, LPoint *Data) override; void Bezier(int Threshold, LPoint *Pt) override; void FloodFill(int x, int y, int Mode, COLOUR Border = 0, LRect *Bounds = NULL) override; #endif }; /// \brief Blitting region helper class, can calculate the right source and dest rectangles /// for a blt operation including propagating clipping back to the source rect. class LBlitRegions { // Raw image bounds LRect SrcBounds; LRect DstBounds; // Unclipped blit regions LRect SrcBlt; LRect DstBlt; public: /// Clipped blit region in destination co-ords LRect SrcClip; /// Clipped blit region in source co-ords LRect DstClip; /// Calculate the rectangles. LBlitRegions ( /// Destination surface LSurface *Dst, /// Destination blt x offset int x1, /// Destination blt y offset int y1, /// Source surface LSurface *Src, /// [Optional] Crop the source surface first, else whole surface is blt LRect *SrcRc = 0 ) { // Calc full image bounds if (Src) SrcBounds.Set(0, 0, Src->X()-1, Src->Y()-1); else SrcBounds.ZOff(-1, -1); if (Dst) { DstBounds = Dst->Bounds(); int x = 0, y = 0; Dst->GetOrigin(x, y); DstBounds.Offset(x, y); } else DstBounds.ZOff(-1, -1); // Calc full sized blt regions if (SrcRc) { SrcBlt = *SrcRc; SrcBlt.Bound(&SrcBounds); } else SrcBlt = SrcBounds; DstBlt = SrcBlt; DstBlt.Offset(x1-DstBlt.x1, y1-DstBlt.y1); // Dest clipped to dest bounds DstClip = DstBlt; DstClip.Bound(&DstBounds); // Now map the dest clipping back to the source SrcClip = SrcBlt; SrcClip.x1 += DstClip.x1 - DstBlt.x1; SrcClip.y1 += DstClip.y1 - DstBlt.y1; SrcClip.x2 -= DstBlt.x2 - DstClip.x2; SrcClip.y2 -= DstBlt.y2 - DstClip.y2; } /// Returns non-zero if both clipped rectangles are valid. bool Valid() { return DstClip.Valid() && SrcClip.Valid(); } void Dump() { printf("SrcBounds: %s\n", SrcBounds.GetStr()); printf("DstBounds: %s\n", DstBounds.GetStr()); printf("SrcBlt: %s\n", SrcBlt.GetStr()); printf("DstBlt: %s\n", DstBlt.GetStr()); printf("SrcClip: %s\n", SrcClip.GetStr()); printf("DstClip: %s\n", DstClip.GetStr()); } }; #if defined(MAC) && !defined(__GTK_H__) class CGImg { class CGImgPriv *d; void Create(int x, int y, int Bits, ssize_t Line, uchar *data, uchar *palette, LRect *r); public: CGImg(int x, int y, int Bits, ssize_t Line, uchar *data, uchar *palette, LRect *r, int Debug = 0); CGImg(LSurface *pDC); ~CGImg(); operator CGImageRef(); void Release(); }; #endif #ifdef __GTK_H__ #include "lgi/common/CairoSurface.h" #endif /// \brief An implemenation of LSurface to draw into a memory bitmap. /// /// This class uses a block of memory to represent an image. You have direct /// pixel access as well as higher level functions to manipulate the bits. class LgiClass LMemDC : public LSurface { protected: class LMemDCPrivate *d; #if defined WINNATIVE PBITMAPINFO GetInfo(); #endif // This is called between capturing the screen and overlaying the // cursor in LMemDC::Blt(x, y, ScreenDC, Src). It can be used to // overlay effects between the screen and cursor layers. virtual void OnCaptureScreen() {} public: /// Creates a memory bitmap LMemDC ( /// The width int x = 0, /// The height int y = 0, /// The colour space to use. CsNone will default to the /// current screen colour space. LColourSpace cs = CsNone, /// Optional creation flags int Flags = SurfaceCreateNone ); LMemDC(LSurface *pDC); virtual ~LMemDC(); const char *GetClass() { return "LMemDC"; } #if WINNATIVE HDC StartDC(); void EndDC(); void Update(int Flags); void UpsideDown(bool upsidedown); #else LRect ClipRgn() { return Clip; } #if defined(__GTK_H__) LPoint GetSize(); /// This returns the surface owned by the LMemDC Gtk::cairo_surface_t *GetSurface(); /// This returns a sub-image, caller is responsible to free via /// calling cairo_surface_destroy LCairoSurfaceT GetSubImage(LRect &r); LColourSpace GetCreateCs(); Gtk::GdkPixbuf *CreatePixBuf(); #elif defined MAC OsBitmap GetBitmap(); #if LGI_COCOA && defined(__OBJC__) LMemDC(NSImage *img); NSImage *NsImage(LRect *rc = NULL); #endif #if !defined(LGI_SDL) CGColorSpaceRef GetColourSpaceRef(); CGImg *GetImg(LRect *Sub = 0, int Debug = 0); #endif #elif defined(LGI_SDL) || defined(HAIKU) OsBitmap GetBitmap(); #endif OsPainter Handle(); #endif // Set new clipping region LRect ClipRgn(LRect *Rgn); void SetClient(LRect *c); /// Locks the bits for access. LMemDC's start in the locked state. bool Lock(); /// Unlocks the bits to optimize for display. While the bitmap is unlocked you /// can't access the data for read or write. On linux this converts the XImage /// to pixmap. On other systems it doesn't do much. As a general rule if you /// don't need access to a bitmap after creating / loading it then unlock it. bool Unlock(); #if !WINNATIVE && !LGI_CARBON && !LGI_COCOA void GetOrigin(int &x, int &y); #endif void SetOrigin(int x, int y); void Empty(); bool SupportsAlphaCompositing(); bool SwapRedAndBlue(); bool Create(int x, int y, LColourSpace Cs, int Flags = SurfaceCreateNone); void Blt(int x, int y, LSurface *Src, LRect *a = NULL); void StretchBlt(LRect *d, LSurface *Src, LRect *s = NULL); void HorzLine(int x1, int x2, int y, COLOUR a, COLOUR b); void VertLine(int x, int y1, int y2, COLOUR a, COLOUR b); }; /// \brief An implemenation of LSurface to print to a printer. /// /// This class redirects standard graphics calls to print a page. /// /// \sa GPrinter class LgiClass LPrintDC #if defined(WIN32) || defined(MAC) : public LScreenDC #else : public LSurface #endif { class LPrintDCPrivate *d; public: LPrintDC(void *Handle, const char *PrintJobName, const char *PrinterName = NULL); ~LPrintDC(); const char *GetClass() override { return "LPrintDC"; } bool IsPrint() override { return true; } const char *GetOutputFileName(); int X() override; int Y() override; int GetBits() override; /// Returns the DPI of the printer or 0,0 on error LPoint GetDpi() override; #if defined __GTK_H__ Gtk::GtkPrintContext *GetPrintContext(); int Op() { return GDC_SET; } int Op(int Op, NativeInt Param = -1) { return GDC_SET; } LRect ClipRgn(LRect *Rgn); LRect ClipRgn(); COLOUR Colour(); COLOUR Colour(COLOUR c, int Bits = 0); LColour Colour(LColour c); void Set(int x, int y); void HLine(int x1, int x2, int y); void VLine(int x, int y1, int y2); void Line(int x1, int y1, int x2, int y2); void Circle(double cx, double cy, double radius); void FilledCircle(double cx, double cy, double radius); void Arc(double cx, double cy, double radius, double start, double end); void FilledArc(double cx, double cy, double radius, double start, double end); void Ellipse(double cx, double cy, double x, double y); void FilledEllipse(double cx, double cy, double x, double y); void Box(int x1, int y1, int x2, int y2); void Box(LRect *a = NULL); void Rectangle(int x1, int y1, int x2, int y2); void Rectangle(LRect *a = NULL); void Blt(int x, int y, LSurface *Src, LRect *a = NULL); void StretchBlt(LRect *d, LSurface *Src, LRect *s); void Polygon(int Points, LPoint *Data); void Bezier(int Threshold, LPoint *Pt); #endif }; ////////////////////////////////////////////////////////////////////////////// class LgiClass LGlobalColour { class LGlobalColourPrivate *d; public: LGlobalColour(); ~LGlobalColour(); // Add all the colours first COLOUR AddColour(COLOUR c24); bool AddBitmap(LSurface *pDC); bool AddBitmap(LImageList *il); // Then call this bool MakeGlobalPalette(); // Which will give you a palette that // includes everything LPalette *GetPalette(); // Convert a bitmap to the global palette COLOUR GetColour(COLOUR c24); bool RemapBitmap(LSurface *pDC); }; /// This class is useful for double buffering in an OnPaint handler... class LDoubleBuffer { LSurface **In; LSurface *Screen; LMemDC Mem; LRect Rgn; bool Valid; public: LDoubleBuffer(LSurface *&pDC, LRect *Sub = NULL) : In(&pDC) { Rgn = Sub ? *Sub : pDC->Bounds(); Screen = pDC; Valid = pDC && Mem.Create(Rgn.X(), Rgn.Y(), pDC->GetColourSpace()); if (Valid) { *In = &Mem; if (Sub) pDC->SetOrigin(Sub->x1, Sub->y1); } } ~LDoubleBuffer() { if (Valid) { #if WINDOWS if (Mem.Handle()) Mem.EndDC(); #endif Mem.SetOrigin(0, 0); Screen->Blt(Rgn.x1, Rgn.y1, &Mem); } // Restore state *In = Screen; } LMemDC *GetMem() { return &Mem; } }; #ifdef WIN32 typedef int (__stdcall *MsImg32_AlphaBlend)(HDC,int,int,int,int,HDC,int,int,int,int,BLENDFUNCTION); #endif /// Main singleton graphics device class. Holds all global data for graphics rendering. class LgiClass GdcDevice : public LCapabilityClient { friend class LScreenDC; friend class LMemDC; friend class LImageList; static GdcDevice *pInstance; class GdcDevicePrivate *d; #ifdef WIN32 MsImg32_AlphaBlend AlphaBlend; #endif public: GdcDevice(); ~GdcDevice(); static GdcDevice *GetInst() { return pInstance; } /// Returns the colour space of the screen LColourSpace GetColourSpace(); /// Returns the current screen bit depth int GetBits(); /// Returns the current screen width int X(); /// Returns the current screen height int Y(); /// Returns the size of the screen as a rectangle. LRect Bounds() { return LRect(0, 0, X()-1, Y()-1); } LGlobalColour *GetGlobalColour(); /// Set a global graphics option int GetOption(int Opt); /// Get a global graphics option int SetOption(int Opt, int Value); /// 256 lut for squares ulong *GetCharSquares(); /// Divide by 255 lut, 64k entries long. uchar *GetDiv255(); // Palette/Colour void SetGamma(double Gamma); double GetGamma(); // Palette void SetSystemPalette(int Start, int Size, LPalette *Pal); LPalette *GetSystemPalette(); void SetColourPaletteType(int Type); // Type = PALTYPE_xxx define COLOUR GetColour(COLOUR Rgb24, LSurface *pDC = NULL); // File I/O /// \brief Loads a image from a file /// /// This function uses the compiled in codecs, some of which require external /// shared libraries / DLL's to function. Other just need the right source to /// be compiled in. /// /// Lgi comes with the following image codecs: ///
    ///
  • Windows or OS/2 Bitmap: GdcBmp (LFilter.cpp) ///
  • PCX: GdcPcx (Pcx.cpp) ///
  • GIF: GdcGif (Gif.cpp and Lzw.cpp) ///
  • JPEG: GdcJpeg (Jpeg.cpp + libjpeg library) ///
  • PNG: GdcPng (Png.cpp + libpng library) ///
/// LSurface *Load ( /// The full path of the file const char *FileName, /// [Optional] Enable OS based loaders bool UseOSLoader = true ); /// The stream version of the file loader... LSurface *Load ( /// The full path of the file LStream *In, /// [Optional] File name hint for selecting a filter const char *Name = NULL, /// [Optional] Enable OS based loaders bool UseOSLoader = true ); /// Save an image to a stream. bool Save ( /// The file to write to LStream *Out, /// The pixels to store LSurface *In, /// Dummy file name to determine the file type, eg: "img.jpg" const char *FileType ); /// Save an image to a file. bool Save ( /// The file to write to const char *Name, /// The pixels to store LSurface *pDC ); #if LGI_SDL SDL_Surface *Handle(); #endif }; /// \brief Defines a bitmap inline in C++ code. /// /// The easiest way I know of create the raw data for an LInlineBmp /// is to use i.Mage to /// load a file or create a image and then use the Edit->Copy As Code /// menu. Then paste into your C++ and put a uint32 array declaration /// around it. Then point the Data member to the uint32 array. Just be /// sure to get the dimensions right. /// /// I use this for embeding resource images directly into the code so /// that a) they load instantly and b) they can't get lost as a separate file. class LgiClass LInlineBmp { public: /// The width of the image. int X; /// The height of the image. int Y; /// The bitdepth of the image (8, 15, 16, 24, 32). int Bits; /// Pointer to the raw data. uint32_t *Data; /// Creates a memory DC of the image. LSurface *Create(uint32_t TransparentPx = 0xffffffff); }; // file filter support #include "lgi/common/Filter.h" // globals #define GdcD GdcDevice::GetInst() /// Converts a context to a different bit depth LgiFunc LSurface *ConvertDC ( /// The source image LSurface *pDC, /// The destination bit depth int Bits ); /// Converts a colour to a different bit depth LgiFunc COLOUR CBit(int DstBits, COLOUR c, int SrcBits = 24, LPalette *Pal = 0); #ifdef __cplusplus /// blends 2 colours by the amount specified LgiClass LColour GdcMixColour(LColour a, LColour b, float HowMuchA = 0.5); #endif /// Colour reduction option to define what palette to go to enum LColourReducePalette { CR_PAL_NONE = -1, CR_PAL_CUBE = 0, CR_PAL_OPT, CR_PAL_FILE }; /// Colour reduction option to define how to deal with reduction error enum LColourReduceMatch { CR_MATCH_NONE = -1, CR_MATCH_NEAR = 0, CR_MATCH_HALFTONE, CR_MATCH_ERROR }; /// Colour reduction options class LReduceOptions { public: /// Type of palette LColourReducePalette PalType; /// Reduction error handling LColourReduceMatch MatchType; /// 1-256 int Colours; /// Specific palette to reduce to LPalette *Palette; LReduceOptions() { Palette = 0; Colours = 256; PalType = CR_PAL_NONE; MatchType = CR_MATCH_NONE; } }; /// Reduces a images colour depth LgiFunc bool LReduceBitDepth(LSurface *pDC, int Bits, LPalette *Pal = 0, LReduceOptions *Reduce = 0); struct LColourStop { LColour Colour; float Pos; void Set(float p, LColour c) { Pos = p; Colour = c; } }; /// Draws a horizontal or vertical gradient LgiFunc void LFillGradient(LSurface *pDC, LRect &r, bool Vert, LArray &Stops); #ifdef WIN32 /// Draws a windows HICON onto a surface at Dx, Dy LgiFunc void LDrawIcon(LSurface *pDC, int Dx, int Dy, HICON ico); #endif /// Row copy operator for full RGB (8 bit components) LgiFunc bool LRopRgb ( // Pointer to destination pixel buffer uint8_t *Dst, // Destination colour space (must be 8bit components) LColourSpace DstCs, // Pointer to source pixel buffer (if this overlaps 'Dst', set 'Overlap' to true) uint8_t *Src, // Source colour space (must be 8bit components) LColourSpace SrcCs, // Number of pixels to convert int Px, // Whether to composite using alpha or copy blt bool Composite ); /// Universal bit blt method LgiFunc bool LRopUniversal(LBmpMem *Dst, LBmpMem *Src, bool Composite); /// Gets the screens DPI LgiClass LPoint LScreenDpi(); /// Find the bounds of an image. /// \return true if there is some non-transparent image in 'rc' LgiFunc bool LFindBounds ( /// [in] The image LSurface *pDC, /// [in/out] Starts off as the initial bounds to search. /// Returns the non-background area. LRect *rc ); #if defined(LGI_SDL) LgiFunc LColourSpace PixelFormat2ColourSpace(SDL_PixelFormat *pf); #endif #endif diff --git a/src/common/Gdc2/Filters/Gif.cpp b/src/common/Gdc2/Filters/Gif.cpp --- a/src/common/Gdc2/Filters/Gif.cpp +++ b/src/common/Gdc2/Filters/Gif.cpp @@ -1,1054 +1,1092 @@ /*hdr ** FILE: Gif.cpp ** AUTHOR: Matthew Allen ** DATE: 8/9/1998 ** DESCRIPTION: Gif file filter ** ** Copyright (C) 1997-8, Matthew Allen ** fret@memecode.com */ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Lzw.h" #include "lgi/common/Variant.h" #include "lgi/common/Palette.h" #ifdef FILTER_UI // define the symbol FILTER_UI to access the gif save options dialog #include "TransparentDlg.h" #endif #define MAX_CODES 4095 class GdcGif : public LFilter { LSurface *pDC; LStream *s; int ProcessedScanlines; + uint8_t BackgroundColour = 0; + bool Transparent = false; + // Old GIF coder stuff short linewidth; int lines; int pass; short curr_size; /* The current code size */ short clearc; /* Value for a clear code */ short ending; /* Value for a ending code */ short newcodes; /* First available code */ short top_slot; /* Highest code for current size */ short slot; /* Last read code */ short navail_bytes; /* # bytes left in block */ short nbits_left; /* # bits left in current byte */ uchar b1; /* Current byte */ uchar byte_buff[257]; /* Current block */ uchar *pbytes; /* Pointer to next byte in block */ uchar stack[MAX_CODES+1]; /* Stack for storing pixels */ uchar suffix[MAX_CODES+1]; /* Suffix table */ ushort prefix[MAX_CODES+1]; /* Prefix linked list */ int bad_code_count; int get_byte(); int out_line(uchar *pixels, int linewidth, int interlaced, int BitDepth); short init_exp(short size); short get_next_code(); short decoder(int BitDepth, uchar interlaced); public: GdcGif(); Format GetFormat() { return FmtGif; } int GetCapabilites() { return FILTER_CAP_READ | FILTER_CAP_WRITE; } IoStatus ReadImage(LSurface *pDC, LStream *In); IoStatus WriteImage(LStream *Out, LSurface *pDC); bool GetVariant(const char *n, LVariant &v, const char *a) { if (!_stricmp(n, LGI_FILTER_TYPE)) { v = "Gif"; } else if (!_stricmp(n, LGI_FILTER_EXTENSIONS)) { v = "GIF"; } else return false; return true; } }; // Filter factory // tells the application we're here class GdcGifFactory : public LFilterFactory { bool CheckFile(const char *File, int Access, const uchar *Hint) { if (Hint) { if (Hint[0] == 'G' && Hint[1] == 'I' && Hint[2] == 'F' && Hint[3] == '8' && Hint[4] == '9') { return true; } } return (File) ? stristr(File, ".gif") != 0 : false; } LFilter *NewObject() { return new GdcGif; } } GifFactory; // gif error codes #define OUT_OF_MEMORY -10 #define BAD_CODE_SIZE -20 #define READ_ERROR -1 #define WRITE_ERROR -2 #define OPEN_ERROR -3 #define CREATE_ERROR -4 long code_mask[13] = { 0, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF}; int GdcGif::get_byte() { uchar c; if (s->Read(&c, 1) == 1) return c; return READ_ERROR; } int GdcGif::out_line(uchar *pixels, int linewidth, int interlaced, int BitDepth) { - // static int p; - // if (lines == 0) p = 0; - if (lines >= pDC->Y()) return -1; + auto pal = pDC->Palette(); + + /* + static bool first = true; + if (first) + { + first = false; + printf("cs=%s BackgroundColour=%i\n", LColourSpaceToString(pDC->GetColourSpace()), BackgroundColour); + for (int i=0; pal && i<3; i++) + { + auto *p = (*pal)[pixels[i]]; + printf("px=%i, %i,%i,%i\n", pixels[i], p->r, p->g, p->b); + } + } + */ + switch (pDC->GetColourSpace()) { case CsIndex8: case CsAlpha8: { memcpy((*pDC)[lines], pixels, pDC->X()); break; } case CsBgr16: { LBgr16 *s = (LBgr16*) (*pDC)[lines]; LBgr16 *e = s + pDC->X(); - LPalette *pal = pDC->Palette(); GdcRGB *p = (*pal)[0], *pix; while (s < e) { pix = p + *pixels++; s->r = pix->r >> 3; s->g = pix->g >> 2; s->b = pix->b >> 3; s++; } break; } case CsRgb16: { LRgb16 *s = (LRgb16*) (*pDC)[lines]; LRgb16 *e = s + pDC->X(); - LPalette *pal = pDC->Palette(); GdcRGB *p = (*pal)[0], *pix; while (s < e) { pix = p + *pixels++; s->r = pix->r >> 3; s->g = pix->g >> 2; s->b = pix->b >> 3; s++; } break; } case System32BitColourSpace: { System32BitPixel *s = (System32BitPixel*) (*pDC)[lines]; System32BitPixel *e = s + pDC->X(); - LPalette *pal = pDC->Palette(); GdcRGB *p = (*pal)[0], *pix; - while (s < e) - { - pix = p + *pixels++; - s->r = pix->r; - s->g = pix->g; - s->b = pix->b; - s->a = 255; - s++; - } + if (Transparent) + { + while (s < e) + { + if (*pixels != BackgroundColour) + { + pix = p + *pixels; + s->r = pix->r; + s->g = pix->g; + s->b = pix->b; + s->a = 255; + } + else + { + s->r = 0; + s->g = 0; + s->b = 0; + s->a = 0; + } + + pixels++; + s++; + } + } + else // no transparent colour + { + while (s < e) + { + pix = p + *pixels++; + s->r = pix->r; + s->g = pix->g; + s->b = pix->b; + s->a = 255; + s++; + } + } break; } default: { LAssert(!"Unsupported colour space"); break; } } ProcessedScanlines++; if (interlaced) { switch (pass) { case 0: lines += 8; if (lines >= pDC->Y()) { lines = 4; pass++; } break; case 1: lines += 8; if (lines >= pDC->Y()) { lines = 2; pass++; } break; case 2: lines += 4; if (lines >= pDC->Y()) { lines = 1; pass++; } break; case 3: lines += 2; break; } } else { lines++; } if (Meter) { int a = (int)Meter->Value() * 100 / pDC->Y(); int b = lines * 100 / pDC->Y(); if (abs(a-b) > 5) { Meter->Value(lines); } } return 0; } short GdcGif::init_exp(short size) { curr_size = size + 1; top_slot = 1 << curr_size; clearc = 1 << size; ending = clearc + 1; slot = newcodes = ending + 1; navail_bytes = nbits_left = 0; return 0; } short GdcGif::get_next_code() { short i, x; ulong ret; if (nbits_left == 0) { if (navail_bytes <= 0) { /* Out of bytes in current block, so read next block */ pbytes = byte_buff; if ((navail_bytes = get_byte()) < 0) { return(navail_bytes); } else if (navail_bytes) { for (i = 0; i < navail_bytes; ++i) { if ((x = get_byte()) < 0) { return(x); } byte_buff[i] = (uchar)x; } } } b1 = *pbytes++; nbits_left = 8; --navail_bytes; } ret = b1 >> (8 - nbits_left); while (curr_size > nbits_left) { if (navail_bytes <= 0) { /* Out of bytes in current block, so read next block */ pbytes = byte_buff; if ((navail_bytes = get_byte()) < 0) { return(navail_bytes); } else if (navail_bytes) { for (i = 0; i < navail_bytes; ++i) { if ((x = get_byte()) < 0) { return(x); } byte_buff[i] = (uchar)x; } } } b1 = *pbytes++; ret |= b1 << nbits_left; nbits_left += 8; --navail_bytes; } nbits_left -= curr_size; ret &= code_mask[curr_size]; return ((short) ret); } short GdcGif::decoder(int BitDepth, uchar interlaced) { uchar *sp, *bufptr; uchar *buf; short code, fc, oc, bufcnt; short c, size, ret; lines = 0; pass = 0; /* Initialize for decoding a new image... */ if ((size = get_byte()) < 0) { return(size); } if (size < 2 || 9 < size) { return(BAD_CODE_SIZE); } init_exp(size); /* Initialize in case they forgot to put in a clearc code. * (This shouldn't happen, but we'll try and decode it anyway...) */ oc = fc = 0; /* Allocate space for the decode buffer */ buf = new uchar[linewidth+1]; if (buf == NULL) { return (OUT_OF_MEMORY); } /* Set up the stack pointer and decode buffer pointer */ uchar *EndOfStack = stack + sizeof(stack); sp = stack; bufptr = buf; bufcnt = linewidth; /* This is the main loop. For each code we get we pass through the * linked list of prefix codes, pushing the corresponding "character" for * each code onto the stack. When the list reaches a single "character" * we push that on the stack too, and then start unstacking each * character for output in the correct order. Special handling is * included for the clearc code, and the whole thing ends when we get * an ending code. */ while ((c = get_next_code()) != ending) { /* If we had a file error, return without completing the decode */ if (c < 0) { DeleteArray(buf); return(0); } /* If the code is a clearc code, reinitialize all necessary items. */ if (c == clearc) { curr_size = size + 1; slot = newcodes; top_slot = 1 << curr_size; /* Continue reading codes until we get a non-clearc code * (Another unlikely, but possible case...) */ while ((c = get_next_code()) == clearc) ; /* If we get an ending code immediately after a clearc code * (Yet another unlikely case), then break out of the loop. */ if (c == ending) { break; } /* Finally, if the code is beyond the range of already set codes, * (This one had better !happen... I have no idea what will * result from this, but I doubt it will look good...) then set it * to color zero. */ if (c >= slot) { c = 0; } oc = fc = c; /* And let us not forget to put the char into the buffer... And * if, on the off chance, we were exactly one pixel from the end * of the line, we have to send the buffer to the out_line() * routine... */ *bufptr++ = (uchar)c; if (--bufcnt == 0) { if ((ret = out_line(buf, linewidth, interlaced, BitDepth)) < 0) { DeleteArray(buf); return (ret); } bufptr = buf; bufcnt = linewidth; } } else { /* In this case, it's not a clearc code or an ending code, so * it must be a code code... So we can now decode the code into * a stack of character codes. (Clear as mud, right?) */ code = c; /* Here we go again with one of those off chances... If, on the * off chance, the code we got is beyond the range of those already * set up (Another thing which had better !happen...) we trick * the decoder into thinking it actually got the last code read. * (Hmmn... I'm not sure why this works... But it does...) */ if (code >= slot) { if (code > slot) { ++bad_code_count; } code = oc; *sp++ = (uchar)fc; } /* Here we scan back along the linked list of prefixes, pushing * helpless characters (ie. suffixes) onto the stack as we do so. */ while (code >= newcodes) { if (sp >= EndOfStack || code >= MAX_CODES + 1) { return -1; } *sp++ = suffix[code]; code = prefix[code]; } /* Push the last character on the stack, and set up the new * prefix and suffix, and if the required slot number is greater * than that allowed by the current bit size, increase the bit * size. (NOTE - If we are all full, we *don't* save the new * suffix and prefix... I'm not certain if this is correct... * it might be more proper to overwrite the last code... */ *sp++ = (uchar)code; if (slot < top_slot) { suffix[slot] = (uchar)(fc = code); prefix[slot++] = oc; oc = c; } if (slot >= top_slot) { if (curr_size < 12) { top_slot <<= 1; ++curr_size; } } /* Now that we've pushed the decoded string (in reverse order) * onto the stack, lets pop it off and put it into our decode * buffer... And when the decode buffer is full, write another * line... */ while (sp > stack) { *bufptr++ = *(--sp); if (--bufcnt == 0) { if ((ret = out_line(buf, linewidth, interlaced, BitDepth)) < 0) { DeleteArray(buf); return(ret); } bufptr = buf; bufcnt = linewidth; } } } } ret = 0; if (bufcnt != linewidth) { ret = out_line(buf, (linewidth - bufcnt), interlaced, BitDepth); } DeleteArray(buf); return(ret); } union LogicalScreenBits { uint8_t u8; struct { uint8_t TableSize : 3; uint8_t SortFlag : 1; uint8_t ColourRes : 3; uint8_t GlobalColorTable : 1; }; }; union LocalColourBits { uint8_t u8; struct { uint8_t TableBits : 3; uint8_t Reserved : 2; uint8_t SortFlag : 1; uint8_t Interlaced : 1; uint8_t LocalColorTable : 1; }; }; union GfxCtrlExtBits { uint8_t u8; struct { uint8_t Transparent : 1; uint8_t UserInput : 1; uint8_t DisposalMethod : 3; uint8_t Reserved : 3; }; }; bool GifLoadPalette(LStream *s, LSurface *pDC, int TableBits) { LRgb24 Rgb[256]; int Colours = 1 << (TableBits + 1); int Bytes = Colours * sizeof(Rgb[0]); memset(Rgb, 0xFF, sizeof(Rgb)); if (s->Read(Rgb, Bytes) != Bytes) return false; LPalette *Pal = new LPalette((uint8_t*)Rgb, 256); if (!Pal) return false; pDC->Palette(Pal); return true; } LFilter::IoStatus GdcGif::ReadImage(LSurface *pdc, LStream *in) { LFilter::IoStatus Status = IoError; pDC = pdc; s = in; ProcessedScanlines = 0; if (pDC && s) { bad_code_count = 0; if (!FindHeader(0, "GIF8?a", s)) { // not a gif file } else { - bool Transparent = false; + Transparent = false; LogicalScreenBits LogBits; uchar interlace = false; uint16 LogicalX = 0; uint16 LogicalY = 0; - uint8_t BackgroundColour = 0; + BackgroundColour = 0; uint8_t PixelAspectRatio = 0; // read header Read(s, &LogicalX, sizeof(LogicalX)); Read(s, &LogicalY, sizeof(LogicalY)); Read(s, &LogBits.u8, sizeof(LogBits.u8)); int Bits = LogBits.ColourRes + 1; Read(s, &BackgroundColour, sizeof(BackgroundColour)); Read(s, &PixelAspectRatio, sizeof(PixelAspectRatio)); if (LogBits.GlobalColorTable) { GifLoadPalette(s, pDC, LogBits.TableSize); } // Start reading the block stream bool Done = false; uchar BlockCode = 0; uchar BlockLabel = 0; uchar BlockSize = 0; while (!Done) { #define Rd(Var) \ if (!Read(s, &Var, sizeof(Var))) \ { \ Done = true; \ LgiTrace("%s:%i - Failed to read %i (" LPrintfInt64 " of " LPrintfInt64 ")\n", \ _FL, (int)sizeof(Var), in->GetPos(), in->GetSize()); \ break; \ } Rd(BlockCode); switch (BlockCode) { case 0x2C: { // Image Descriptor uint16 x1, y1, sx, sy; LocalColourBits LocalBits; Rd(x1); Rd(y1); Rd(sx); Rd(sy); Rd(LocalBits.u8); linewidth = sx; interlace = LocalBits.Interlaced != 0; if (pDC->Create(sx, sy, CsIndex8)) { if (LocalBits.LocalColorTable) { GifLoadPalette(s, pDC, LocalBits.TableBits); } // Progress if (Meter) { Meter->SetDescription("scanlines"); Meter->SetRange(sy); } // Decode image decoder(Bits, interlace); if (ProcessedScanlines == pDC->Y()) Status = IoSuccess; - if (Transparent) + if (Transparent && !LColourSpaceHasAlpha(pDC->GetColourSpace())) { // Setup alpha channel pDC->HasAlpha(true); LSurface *Alpha = pDC->AlphaDC(); if (Alpha) { for (int y=0; yY(); y++) { uchar *C = (*pDC)[y]; uchar *A = (*Alpha)[y]; for (int x=0; xX(); x++) - { A[x] = C[x] == BackgroundColour ? 0x00 : 0xff; - } } } } } else { LgiTrace("%s:%i - Failed to create output surface.\n", _FL); } Done = true; break; } case 0x21: { uint8_t GraphicControlLabel; uint8_t BlockSize; GfxCtrlExtBits ExtBits; uint16 Delay; Rd(GraphicControlLabel); Rd(BlockSize); switch (GraphicControlLabel) { case 0xF9: { Rd(ExtBits.u8); Rd(Delay); Rd(BackgroundColour); Transparent = ExtBits.Transparent != 0; break; } default: { s->SetPos(s->GetPos() + BlockSize); break; } } Rd(BlockSize); while (BlockSize) { int64 NewPos = s->GetPos() + BlockSize; if (s->SetPos(NewPos) != NewPos || !Read(s, &BlockSize, sizeof(BlockSize))) break; } break; } default: { // unknown block Rd(BlockLabel); Rd(BlockSize); while (BlockSize) { int64 NewPos = s->GetPos() + BlockSize; if (s->SetPos(NewPos) != NewPos || !Read(s, &BlockSize, sizeof(BlockSize))) break; } break; } } } } } return Status; } LFilter::IoStatus GdcGif::WriteImage(LStream *Out, LSurface *pDC) { LVariant Transparent; int Back = -1; LVariant v; if (!Out || !pDC) return LFilter::IoError; if (pDC->GetBits() > 8) { if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "The GIF format only supports 1 to 8 bit graphics."); return LFilter::IoUnsupportedFormat; } #ifdef FILTER_UI LVariant Parent; if (Props) { Props->GetValue(LGI_FILTER_PARENT_WND, Parent); if (Props->GetValue(LGI_FILTER_BACKGROUND, v)) { Back = v.CastInt32(); } if (Parent.Type == GV_GVIEW) { // If the source document has an alpha channel then we use // that to create transparent pixels in the output, otherwise // we ask the user if they want the background transparent... if (pDC->AlphaDC()) { Transparent = true; // However we have to pick an unused colour to set as the // "background" pixel value bool Used[256]; ZeroObj(Used); for (int y=0; yY(); y++) { uint8_t *p = (*pDC)[y]; uint8_t *a = (*pDC->AlphaDC())[y]; LAssert(p && a); if (!p || !a) break; uint8_t *e = p + pDC->X(); while (p < e) { if (*a) Used[*p] = true; a++; p++; } } Back = -1; for (int i=0; i<256; i++) { if (!Used[i]) { Back = i; break; } } if (Back < 0) { if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "No unused colour for transparent pixels??"); return IoError; } } else { // put up a dialog to ask about transparent colour LTransparentDlg Dlg((LView*)Parent.Value.Ptr, &Transparent); if (!Dlg.DoModal()) { Props->SetValue("Cancel", v = 1); return IoCancel; } } if (Transparent.CastInt32() && Back < 0) { LAssert(!"No background colour available??"); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "Transparency requested, but no background colour set."); return IoError; } } } #endif LPalette *Pal = pDC->Palette(); // Intel byte ordering Out->SetSize(0); // Header Out->Write((void*)"GIF89a", 6); // Logical screen descriptor int16 s = pDC->X(); Write(Out, &s, sizeof(s)); s = pDC->Y(); Write(Out, &s, sizeof(s)); bool Ordered = false; uint8_t c = ((Pal != 0) ? 0x80 : 0) | // global colour table/transparent (pDC->GetBits() - 1) | // bits per pixel ((Ordered) ? 0x08 : 0) | // colours are sorted (pDC->GetBits() - 1); Out->Write(&c, 1); c = 0; Out->Write(&c, 1); // background colour c = 0; Out->Write(&c, 1); // aspect ratio // global colour table if (Pal) { uchar Buf[768]; uchar *d = Buf; int Colours = 1 << pDC->GetBits(); for (int i=0; ir; *d++ = s->g; *d++ = s->b; } else { *d++ = i; *d++ = i; *d++ = i; } } Out->Write(Buf, Colours * 3); } if (Transparent.CastInt32()) { // Graphic Control Extension uchar gce[] = {0x21, 0xF9, 4, 1, 0, 0, (uchar)Back, 0 }; Out->Write(gce, sizeof(gce)); } // Image descriptor c = 0x2c; Out->Write(&c, 1); // Image Separator s = 0; Write(Out, &s, sizeof(s)); // Image left position s = 0; Write(Out, &s, sizeof(s)); // Image top position s = pDC->X(); Write(Out, &s, sizeof(s)); // Image width s = pDC->Y(); Write(Out, &s, sizeof(s)); // Image height c = 0; Out->Write(&c, 1); // Flags // Image data c = 8; Out->Write(&c, 1); // Min code size LMemQueue Encode, Pixels; // Get input ready int Len = (pDC->X() * pDC->GetBits() + 7) / 8; uint8_t *buf = pDC->AlphaDC() ? new uint8_t[Len] : 0; for (int y=0; yY(); y++) { uint8_t *p = (*pDC)[y]; if (!p) continue; if (pDC->AlphaDC()) { // Preprocess pixels to make the alpha channel into the // transparent colour. uint8_t *a = (*pDC->AlphaDC())[y]; uint8_t *e = p + pDC->X(); uint8_t *o = buf; while (p < e) { if (*a++) *o++ = *p; else *o++ = Back; p++; } LAssert(o == buf + Len); p = buf; } Pixels.Write(p, Len); } DeleteArray(buf); // Compress Lzw Encoder; Encoder.Meter = Meter; if (Encoder.Compress(&Encode, &Pixels)) { uchar Buf[256]; // write data out while ((Len = (int)Encode.GetSize()) > 0) { int l = MIN(Len, 255); if (Encode.Read(Buf, l)) { c = l; Out->Write(&c, 1); // Sub block size Out->Write(Buf, l); } } c = 0; Out->Write(&c, 1); // Terminator sub block } // Trailer c = 0x3b; Out->Write(&c, 1); return LFilter::IoSuccess; } GdcGif::GdcGif() { ProcessedScanlines = 0; } diff --git a/src/common/Gdc2/Font/DisplayString.cpp b/src/common/Gdc2/Font/DisplayString.cpp --- a/src/common/Gdc2/Font/DisplayString.cpp +++ b/src/common/Gdc2/Font/DisplayString.cpp @@ -1,2275 +1,2275 @@ ////////////////////////////////////////////////////////////////////// // Includes #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" #include "lgi/common/FontSelect.h" #include "lgi/common/GdiLeak.h" #include "lgi/common/DisplayString.h" #include "lgi/common/PixelRops.h" #include "lgi/common/UnicodeString.h" #ifdef FontChange #undef FontChange #endif #ifdef LGI_SDL #include "ftsynth.h" #endif #if WINNATIVE static OsChar GDisplayStringDots[] = {'.', '.', '.', 0}; #endif //345678123456781234567812345678 // 2nd #define DEBUG_CHAR_AT 0 #define DEBUG_BOUNDS 0 #if defined(__GTK_H__) || (defined(MAC) && !defined(LGI_SDL)) #define DISPLAY_STRING_FRACTIONAL_NATIVE 1 #else #define DISPLAY_STRING_FRACTIONAL_NATIVE 0 #endif #if defined(__GTK_H__) struct Block : public LRect { /// This points to somewhere in Ds->Str OsChar *Str = NULL; /// Bytes in this block int Bytes = 0; /// Utf-8 characters in this block int Chars = 0; /// Alternative font to get characters from (NULL if using the display string's font) LFont *Fnt = NULL; /// Layout for this block. Shouldn't ever be NULL. But shouldn't crash otherwise. Gtk::PangoLayout *Hnd = NULL; ~Block() { if (Hnd) g_object_unref(Hnd); } }; struct GDisplayStringPriv { LDisplayString *Ds; LArray Blocks; bool Debug; int LastTabOffset; GDisplayStringPriv(LDisplayString *str) : Ds(str) { #if 0 Debug = Stristr(Ds->Str, "(Jumping).wma") != 0; #else Debug = false; #endif LastTabOffset = -1; } ~GDisplayStringPriv() { } void Create(Gtk::GtkPrintContext *PrintCtx) { auto *Fs = LFontSystem::Inst(); auto *Fnt = Ds->Font; auto Tbl = Fnt->GetGlyphMap(); int Chars = 0; LUtf8Ptr p(Ds->Str); auto *Start = p.GetPtr(); if (Tbl) { int32 w; Block *b = NULL; auto DisplayCtx = LFontSystem::Inst()->GetContext(); while ((w = (int32)p)) { LFont *f; if (w >= 0x80 && !_HasUnicodeGlyph(Tbl, w)) f = Fs->GetGlyph(w, Ds->Font); else f = Ds->Font; if (!b || (f != NULL && f != Fnt)) { // Finish old block if (b) { b->Bytes = p.GetPtr() - Start; b->Chars = Chars; Chars = 0; } Start = p.GetPtr(); if (f) { // Start new block... b = &Blocks.New(); b->Str = (char*)Start; b->Bytes = -1; // unknown at this point if (f != Ds->Font) // External font b->Fnt = f; // Create a pango layout if (PrintCtx) b->Hnd = Gtk::gtk_print_context_create_pango_layout(PrintCtx); else b->Hnd = Gtk::pango_layout_new(DisplayCtx); } // else no font supports glyph Fnt = f; } // else no change in font p++; Chars++; } if (b) { // Finish the last block b->Bytes = p.GetPtr() - Start; b->Chars = Chars; } if (Debug) { // Print the block array for (size_t i=0; iStrWords) { p++; Chars++; } auto &b = Blocks.New(); b.Str = (char*)Start; b.Bytes = p.GetPtr() - Start; b.Chars = Chars; if (PrintCtx) b.Hnd = Gtk::gtk_print_context_create_pango_layout(PrintCtx); else b.Hnd = Gtk::pango_layout_new(LFontSystem::Inst()->GetContext()); } /* This could get stuck in an infinite loop. Leaving out for the moment. for (auto &b: Blocks) { if (b.Hnd == NULL && b.Fnt == NULL) { Blocks.Length(0); goto Start; } } */ } void UpdateTabs(int Offset, int Size, bool Debug = false) { if (Ds->Font && Ds->Font->TabSize()) { int Len = 16; LastTabOffset = Offset; Gtk::PangoTabArray *t = Gtk::pango_tab_array_new(Len, true); if (t) { for (int i=0; i bool StringConvert(Out *&out, ssize_t &OutWords, const In *in, ssize_t InLen) { if (!in) { out = 0; OutWords = 0; return false; } auto InSz = sizeof(In); auto OutSz = sizeof(Out); // Work out input size ssize_t InWords; if (InLen >= 0) InWords = InLen; else for (InWords = 0; in[InWords]; InWords++) ; if (InSz == OutSz) { // No conversion optimization out = (Out*)Strdup(in, InWords); OutWords = out ? InWords : 0; return out != 0; } else { // Convert the string to new word size static const char *Cp[] = { NULL, "utf-8", "utf-16", NULL, "utf-32"}; LAssert(OutSz <= 4 && InSz <= 4 && Cp[OutSz] && Cp[InSz]); out = (Out*) LNewConvertCp(Cp[OutSz], in, Cp[InSz], InWords*sizeof(In)); OutWords = Strlen(out); return out != NULL; } return false; } ////////////////////////////////////////////////////////////////////// #define SubtractPtr(a, b) ( ((a)-(b)) / sizeof(*a) ) #define VisibleTabChar 0x2192 #define IsTabChar(c) (c == '\t') // || (c == VisibleTabChar && VisibleTab)) #if USE_CORETEXT #include void LDisplayString::CreateAttrStr() { if (!Wide) return; if (AttrStr) { CFRelease(AttrStr); AttrStr = NULL; } wchar_t *w = Wide; CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, (const uint8_t*)w, StrlenW(w) * sizeof(*w), kCFStringEncodingUTF32LE, false); if (string) { CFDictionaryRef attributes = Font->GetAttributes(); if (attributes) AttrStr = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); // else LAssert(0); CFRelease(string); } } #endif LDisplayString::LDisplayString(LFont *f, const char *s, ssize_t l, LSurface *pdc) { pDC = pdc; Font = f; #if LGI_DSP_STR_CACHE StringConvert(Wide, WideWords, s, l); #endif StringConvert(Str, StrWords, s, l); x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if defined __GTK_H__ d = new GDisplayStringPriv(this); if (Font && Str) { LAssert(StrWords >= 0); if (StrWords > 0) d->Create(pDC ? pDC->GetPrintContext() : NULL); } #elif defined MAC && !defined(LGI_SDL) Hnd = 0; #if USE_CORETEXT AttrStr = NULL; #endif if (Font && Str && StrWords > 0) { #if USE_CORETEXT CreateAttrStr(); #else ATSUCreateTextLayout(&Hnd); #endif } #endif } LDisplayString::LDisplayString(LFont *f, const char16 *s, ssize_t l, LSurface *pdc) { pDC = pdc; Font = f; #if LGI_DSP_STR_CACHE StringConvert(Wide, WideWords, s, l); #endif StringConvert(Str, StrWords, s, l); x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if defined __GTK_H__ d = new GDisplayStringPriv(this); if (Font && Str && StrWords > 0) d->Create(pDC ? pDC->GetPrintContext() : NULL); #elif defined MAC && !defined(LGI_SDL) Hnd = NULL; #if USE_CORETEXT AttrStr = NULL; #endif if (Font && Str && StrWords > 0) { #if USE_CORETEXT CreateAttrStr(); #else OSStatus e = ATSUCreateTextLayout(&Hnd); if (e) printf("%s:%i - ATSUCreateTextLayout failed with %i.\n", _FL, (int)e); #endif } #endif } #ifdef _MSC_VER LDisplayString::LDisplayString(LFont *f, const uint32_t *s, ssize_t l, LSurface *pdc) { pDC = pdc; Font = f; x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if LGI_DSP_STR_CACHE StringConvert(Wide, WideWords, s, l); #endif StringConvert(Str, StrWords, s, l); #if defined __GTK_H__ d = new GDisplayStringPriv(this); if (Font && Str && StrWords > 0) d->Create(pDC ? pDC->GetPrintContext() : NULL); #endif } #endif LDisplayString::~LDisplayString() { #if defined(LGI_SDL) Img.Reset(); #elif defined __GTK_H__ DeleteObj(d); #elif defined MAC #if USE_CORETEXT if (Hnd) { CFRelease(Hnd); Hnd = NULL; } if (AttrStr) { CFRelease(AttrStr); AttrStr = NULL; } #else if (Hnd) ATSUDisposeTextLayout(Hnd); #endif #endif DeleteArray(Str); #if LGI_DSP_STR_CACHE DeleteArray(Wide); #endif } void LDisplayString::DrawWhiteSpace(LSurface *pDC, char Ch, LRect &r) { if (Ch == '\t') { r.Inset(3, 3); if (r.Y()/2 == 0) r.y2++; int Cy = (r.Y() >> 1); pDC->Line(r.x1, r.y1+Cy, r.x2, r.y1+Cy); pDC->Line(r.x2, r.y1+Cy, r.x2-Cy, r.y1); pDC->Line(r.x2, r.y1+Cy, r.x2-Cy, r.y2); } else // Space { int x = r.x1 + (r.X()>>1) - 1; int Cy = r.y1 + (int)ceil(Font->Ascent()) - 2; pDC->Rectangle(x, Cy, x+1, Cy+1); } } void LDisplayString::Layout(bool Debug) { if (LaidOut || !Font) return; LaidOut = 1; #if defined(LGI_SDL) FT_Face Fnt = Font->Handle(); FT_Error error; if (!Fnt || !Str) return; // Create an array of glyph indexes LArray Glyphs; for (OsChar *s = Str; *s; s++) { FT_UInt index = FT_Get_Char_Index(Fnt, *s); if (index) Glyphs.Add(index); } // Measure the string... LPoint Sz; int FontHt = Font->GetHeight(); int AscentF = (int) (Font->Ascent() * FScale); int LoadMode = FT_LOAD_FORCE_AUTOHINT; for (unsigned i=0; iglyph->metrics.horiBearingY; Sz.x += Fnt->glyph->metrics.horiAdvance; Sz.y = MAX(Sz.y, PyF + Fnt->glyph->metrics.height); } } // Create the memory context to draw into xf = Sz.x; x = ((Sz.x + FScale - 1) >> FShift) + 1; yf = FontHt << FShift; y = FontHt; // ((Sz.y + FScale - 1) >> FShift) + 1; if (Img.Reset(new LMemDC(x, y, CsIndex8))) { // Clear the context to black Img->Colour(0); Img->Rectangle(); bool IsItalic = Font->Italic(); int CurX = 0; int FBaseline = Fnt->size->metrics.ascender; for (unsigned i=0; iglyph); error = FT_Render_Glyph(Fnt->glyph, FT_RENDER_MODE_NORMAL); if (error == 0) { FT_Bitmap &bmp = Fnt->glyph->bitmap; if (bmp.buffer) { // Copy rendered glyph into our image memory int Px = (CurX + (FScale >> 1)) >> FShift; int PyF = AscentF - Fnt->glyph->metrics.horiBearingY; int Py = PyF >> FShift; int Skip = 0; if (Fnt->glyph->format == FT_GLYPH_FORMAT_BITMAP) { Px += Fnt->glyph->bitmap_left; Skip = Fnt->glyph->bitmap_left < 0 ? -Fnt->glyph->bitmap_left : 0; Py = (AscentF >> FShift) - Fnt->glyph->bitmap_top; } LAssert(Px + bmp.width <= Img->X()); for (int y=0; y= 0); out += Px+Skip; memcpy(out, in+Skip, bmp.width-Skip); } /* else { LAssert(!"No scanline?"); break; } */ } } if (i < Glyphs.Length() - 1) { FT_Vector kerning; FT_Get_Kerning(Fnt, Glyphs[i], Glyphs[i+1], FT_KERNING_DEFAULT, &kerning); CurX += Fnt->glyph->metrics.horiAdvance + kerning.x; } else { CurX += Fnt->glyph->metrics.horiAdvance; } } } } } else LgiTrace("::Layout Create MemDC failed\n"); #elif defined(__GTK_H__) y = Font->GetHeight(); yf = y * PANGO_SCALE; if (!d->Blocks.Length() || !Font->Handle()) return; LUtf8Ptr Utf(Str); int32 Wide; while (*Utf.GetPtr()) { Wide = Utf; if (!Wide) { LgiTrace("%s:%i - Not utf8\n", _FL); return; } Utf++; } LFontSystem *FSys = LFontSystem::Inst(); Gtk::pango_context_set_font_description(FSys->GetContext(), Font->Handle()); int TabSizePx = Font->TabSize(); int TabSizeF = TabSizePx * FScale; int TabOffsetF = DrawOffsetF % TabSizeF; int OffsetF = TabOffsetF ? TabSizeF - TabOffsetF : 0; d->UpdateTabs(OffsetF / FScale, Font->TabSize()); if (Font->Underline()) { Gtk::PangoAttrList *attrs = Gtk::pango_attr_list_new(); Gtk::PangoAttribute *attr = Gtk::pango_attr_underline_new(Gtk::PANGO_UNDERLINE_SINGLE); Gtk::pango_attr_list_insert(attrs, attr); for (auto &b: d->Blocks) Gtk::pango_layout_set_attributes(b.Hnd, attrs); Gtk::pango_attr_list_unref(attrs); } int Fx = 0; for (auto &b: d->Blocks) { int bx = 0, by = 0; if (b.Hnd) { if (!LIsUtf8(b.Str, b.Bytes)) { LgiTrace("Invalid UTF8: '%.*S'\n", (int)b.Bytes, b.Str); } else { Gtk::pango_layout_set_text(b.Hnd, b.Str, b.Bytes); } Gtk::pango_layout_get_size(b.Hnd, &bx, &by); } else if (b.Fnt) { b.Fnt->_Measure(bx, by, b.Str, b.Bytes); bx <<= FShift; by <<= FShift; } b.ZOff(bx-1, by-1); b.Offset(Fx, 0); xf += bx; yf = MAX(yf, by); } x = (xf + PANGO_SCALE - 1) / PANGO_SCALE; #if 1 y = Font->GetHeight(); #else y = (yf + PANGO_SCALE - 1) / PANGO_SCALE; #endif if (y > Font->GetHeight()) { printf("%s:%i - Height error: %i > %i\n", _FL, y, Font->GetHeight()); } #elif defined MAC && !defined(LGI_SDL) #if USE_CORETEXT int height = Font->GetHeight(); y = height; if (AttrStr) { LAssert(!Hnd); Hnd = CTLineCreateWithAttributedString(AttrStr); if (Hnd) { CGFloat ascent = 0.0; CGFloat descent = 0.0; CGFloat leading = 0.0; double width = CTLineGetTypographicBounds(Hnd, &ascent, &descent, &leading); x = ceil(width); xf = width * FScale; yf = height * FScale; } } #else if (!Hnd || !Str) return; OSStatus e = ATSUSetTextPointerLocation(Hnd, Str, 0, len, len); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUSetTextPointerLocation failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } e = ATSUSetRunStyle(Hnd, Font->Handle(), 0, len); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUSetRunStyle failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } ATSUTextMeasurement fTextBefore; ATSUTextMeasurement fTextAfter; if (pDC) { OsPainter dc = pDC->Handle(); ATSUAttributeTag Tags[1] = {kATSUCGContextTag}; ByteCount Sizes[1] = {sizeof(CGContextRef)}; ATSUAttributeValuePtr Values[1] = {&dc}; e = ATSUSetLayoutControls(Hnd, 1, Tags, Sizes, Values); if (e) printf("%s:%i - ATSUSetLayoutControls failed (e=%i)\n", _FL, (int)e); } ATSUTab Tabs[32]; for (int i=0; iTabSize()) << 16; Tabs[i].tabType = kATSULeftTab; } e = ATSUSetTabArray(Hnd, Tabs, CountOf(Tabs)); if (e) printf("%s:%i - ATSUSetTabArray failed (e=%i)\n", _FL, (int)e); e = ATSUGetUnjustifiedBounds( Hnd, kATSUFromTextBeginning, kATSUToTextEnd, &fTextBefore, &fTextAfter, &fAscent, &fDescent); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUGetUnjustifiedBounds failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } xf = fTextAfter - fTextBefore; yf = fAscent + fDescent; x = (xf + 0xffff) >> 16; y = (yf + 0xffff) >> 16; ATSUSetTransientFontMatching(Hnd, true); #endif #elif defined(HAIKU) if (!Font) { LgiTrace("%s:%i - Missing pointer: %p\n", _FL, Font); return; } BFont *fnt = Font->Handle(); if (!fnt) { LgiTrace("%s:%i - Missing handle. %p/%p\n", _FL, fnt); return; } int tabSize = Font->TabSize() ? Font->TabSize() : 32; font_height height = {0}; fnt->GetHeight(&height); yf = y = height.ascent + height.descent + height.leading; if (!Str) return; LUtf8Ptr start(Str); int isTab = -1; for (LUtf8Ptr p(Str); true; p++) { int32_t ch = p; if (isTab < 0) { isTab = IsTabChar(ch); } else if (!ch || IsTabChar(ch) ^ isTab) { auto &l = Info.New(); l.Str = start.GetPtr(); l.Len = p.GetPtr() - start.GetPtr(); const char *strArr[] = { l.Str }; const int32 strLen[] = { l.Len }; float width[1] = { 0 }; fnt->GetStringWidths(strArr, strLen, 1, width); if (isTab) { // Handle tab(s) for (int t=0; tHandle()) Font->Create(); y = Font->GetHeight(); LFontSystem *Sys = LFontSystem::Inst(); if (Sys && Str) { LFont *f = Font; bool GlyphSub = Font->SubGlyphs(); Info[i].Str = Str; int TabSize = Font->TabSize() ? Font->TabSize() : 32; bool WasTab = IsTabChar(*Str); f = GlyphSub ? Sys->GetGlyph(*Str, Font) : Font; if (f && f != Font) { f->Size(Font->Size()); f->SetWeight(Font->GetWeight()); if (!f->Handle()) f->Create(); } bool Debug = WasTab; uint32_t u32; for (LUnicodeString u(Str, StrWords); true; u++) { u32 = *u; LFont *n = GlyphSub ? Sys->GetGlyph(u32, Font) : Font; bool Change = n != f || // The font changed (IsTabChar(u32) ^ WasTab) || // Entering/leaving a run of tabs !u32 || // Hit a NULL character (u.Get() - Info[i].Str) >= 1000; // This is to stop very long segments not rendering if (Change) { // End last segment if (n && n != Font) { n->Size(Font->Size()); n->SetWeight(Font->GetWeight()); if (!n->Handle()) n->Create(); } Info[i].Len = (int) (u.Get() - Info[i].Str); if (Info[i].Len) { if (WasTab) { // Handle tab(s) for (int t=0; tGetHeight() > Font->GetHeight())) { Info[i].SizeDelta = -1; f->PointSize(Font->PointSize() + Info[i].SizeDelta); f->Create(); } #endif if (!f) { // no font, so ? out the chars... as they aren't available anyway // printf("Font Cache Miss, Len=%i\n\t", Info[i].Len); m = Font; for (int n=0; n_Measure(sx, sy, Info[i].Str, Info[i].Len); x += Info[i].X = sx > 0xffff ? 0xffff : sx; } auto Ch = Info[i].First(); Info[i].FontId = !f || Font == f ? 0 : Sys->Lut[Ch]; i++; } f = n; // Start next segment WasTab = IsTabChar(u32); Info[i].Str = u.Get(); } if (!u32) break; } if (Info.Length() > 0 && Info.Last().Len == 0) { Info.Length(Info.Length()-1); } } xf = x; yf = y; #endif } int LDisplayString::GetDrawOffset() { return DrawOffsetF >> FShift; } void LDisplayString::SetDrawOffset(int Px) { if (LaidOut) LAssert(!"No point setting TabOrigin after string is laid out.\n"); DrawOffsetF = Px << FShift; } bool LDisplayString::ShowVisibleTab() { return VisibleTab; } void LDisplayString::ShowVisibleTab(bool i) { VisibleTab = i; } bool LDisplayString::IsTruncated() { return AppendDots; } void LDisplayString::TruncateWithDots(int Width) { Layout(); #if defined __GTK_H__ int Fx = 0; int Fwid = Width << FShift; for (auto &b: d->Blocks) { if (Fwid < Fx + b.X()) { if (b.Hnd) { Gtk::pango_layout_set_ellipsize(b.Hnd, Gtk::PANGO_ELLIPSIZE_END); Gtk::pango_layout_set_width(b.Hnd, Fwid - Fx); Gtk::pango_layout_set_single_paragraph_mode(b.Hnd, true); } else if (b.Fnt) { } break; } Fx += b.X(); } #elif WINNATIVE if (Width < X() + 8) { ssize_t c = CharAt(Width); if (c >= 0 && c < StrWords) { if (c > 0) c--; // fudge room for dots if (c > 0) c--; AppendDots = 1; if (Info.Length()) { int Width = 0; int Pos = 0; for (int i=0; i= Pos && c < Pos + Info[i].Len) { Info[i].Len = (int) (c - Pos); Info[i].Str[Info[i].Len] = 0; LFont *f = Font; if (Info[i].FontId) { LFontSystem *Sys = LFontSystem::Inst(); f = Sys->Font[Info[i].FontId]; f->PointSize(Font->PointSize() + Info[i].SizeDelta); if (!f->Handle()) { f->Create(); } } else { f = Font; } if (f) { int Sx, Sy; f->_Measure(Sx, Sy, Info[i].Str, Info[i].Len); Info[i].X = Sx; Width += Info[i].X; } Info.Length(i + 1); break; } Pos += Info[i].Len; Width += Info[i].X; } int DotsX, DotsY; Font->_Measure(DotsX, DotsY, GDisplayStringDots, 3); x = Width + DotsX; } } } #elif defined(LGI_SDL) #elif defined(MAC) #if USE_CORETEXT if (Hnd) { /* CFAttributedStringRef truncationString = CFAttributedStringCreate(NULL, CFSTR("\u2026"), Font->GetAttributes()); if (truncationString) { CTLineRef truncationToken = CTLineCreateWithAttributedString(truncationString); CFRelease(truncationString); if (truncationToken) { CTLineRef TruncatedLine = CTLineCreateTruncatedLine(Hnd, Width, kCTLineTruncationEnd, truncationToken); CFRelease(truncationToken); if (TruncatedLine) { CFRelease(Hnd); Hnd = TruncatedLine; } } } */ } #endif #endif } ssize_t LDisplayString::CharAt(int Px, LPxToIndexType Type) { Layout(); if (Px < 0) { return 0; } else if (Px >= (int)x) { #if defined __GTK_H__ if (Str) { LUtf8Str u(Str); return u.GetChars(); } return 0; #else #if LGI_DSP_STR_CACHE return WideWords; #else return StrWords; #endif #endif } int Status = -1; #if defined(__GTK_H__) int Fx = 0; int Fpos = Px << FShift; Status = 0; for (auto &b: d->Blocks) { int Index = 0, Trailing = 0; int Foffset = Fpos - Fx; if (b.Hnd && Gtk::pango_layout_xy_to_index(b.Hnd, Foffset, 0, &Index, &Trailing)) { if (d->Debug) printf("CharAt(%g) x=%g Status=%i Foffset=%g index=%i trailing=%i\n", (double)Fpos/FScale, (double)b.X()/FScale, Status, (double)Foffset/FScale, Index, Trailing); LUtf8Str u(Str); while ((OsChar*)u.GetPtr() < Str + Index + Trailing) { u++; Status++; } return Status; } else { if (d->Debug) printf("CharAt(%g) x=%g Status=%i Chars=%i\n", (double)Fpos/FScale, (double)b.X()/FScale, Status, b.Chars); Status += b.Chars; } Fx += b.X(); } #elif defined(LGI_SDL) LAssert(!"Impl me"); #elif defined(MAC) if (Hnd && Str) { #if USE_CORETEXT CGPoint pos = { (CGFloat)Px, 1.0f }; CFIndex utf16 = CTLineGetStringIndexForPosition(Hnd, pos); // 'utf16' is in UTF-16, and the API needs to return a UTF-32 index... // So convert between the 2 here... LAssert(Str != NULL); int utf32 = 0; for (int i=0; Str[i] && iTabSize() ? Font->TabSize() : 32; int Cx = 0; int Char = 0; #if DEBUG_CHAR_AT printf("CharAt(%i) Str='%s'\n", Px, Str); #endif for (int i=0; i= Cx && Px < Cx + Info[i].X) { // The position is in this block of characters if (IsTabChar(Info[i].Str[0])) { // Search through tab block for (int t=0; t= Cx && Px < Cx + TabX) { Status = Char; #if DEBUG_CHAR_AT printf("\tIn tab block %i\n", i); #endif break; } Cx += TabX; Char++; } } else { // Find the pos in this block LFont *f = Font; #if defined(WIN32) if (Info[i].FontId) { f = Sys->Font[Info[i].FontId]; f->Colour(Font->Fore(), Font->Back()); f->Size(Font->Size()); if (!f->Handle()) { f->Create(); } } #endif int Fit = f->_CharAt(Px - Cx, Info[i].Str, Info[i].Len, Type); #if DEBUG_CHAR_AT printf("\tNon tab block %i, Fit=%i, Px-Cx=%i-%i=%i, Str='%.5s'\n", i, Fit, Px, Cx, Px-Cx, Info[i].Str); #endif if (Fit >= 0) Status = Char + Fit; else Status = -1; break; } } else { // Not in this block, skip the whole lot Cx += Info[i].X; Char += Info[i].Len; } } if (Status < 0) { Status = Char; } } #endif return Status; } ssize_t LDisplayString::Length() { return StrWords; } int LDisplayString::X() { Layout(); return x; } int LDisplayString::Y() { Layout(); return y; } LPoint LDisplayString::Size() { Layout(); return LPoint(x, y); } #if defined LGI_SDL template bool CompositeText8Alpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip) { OutPx map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... // Create colour map of the foreground/background colours uint8_t *Div255 = Div255Lut; LColour fore = Font->Fore(); LRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = Div255[a * fore_px.r]; map[a].g = Div255[a * fore_px.g]; map[a].b = Div255[a * fore_px.b]; map[a].a = a; } } else { LColour back = Font->Back(); LRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = (oma * back_px.r) + (a * fore_px.r) / 255; map[a].g = (oma * back_px.g) + (a * fore_px.g) / 255; map[a].b = (oma * back_px.b) + (a * fore_px.b) / 255; map[a].a = 255; } } uint8_t *StartOfBuffer = (*Out)[0]; uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (unsigned y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *d = ((OutPx*) (*Out)[py + y]) + Clip.DstClip.x1; uint8_t *i = (*In)[y]; if (!i) return false; uint8_t *e = i + Clip.DstClip.X(); LAssert((uint8_t*)d >= StartOfBuffer); if (Font->Transparent()) { uint8_t a, o; OutPx *s; while (i < e) { // Alpha blend map and output pixel a = *i++; switch (a) { case 0: break; case 255: // Copy *d = map[a]; break; default: // Blend o = 255 - a; s = map + a; #define NonPreMulOver32NoAlpha(c) d->c = ((s->c * a) + (Div255[d->c * 255] * o)) / 255 NonPreMulOver32NoAlpha(r); NonPreMulOver32NoAlpha(g); NonPreMulOver32NoAlpha(b); break; } d++; } } else { while (i < e) { // Copy rop *d++ = map[*i++]; } } LAssert((uint8_t*)d <= EndOfBuffer); } return true; } template bool CompositeText8NoAlpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip) { LRgba32 map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... // Create colour map of the foreground/background colours uint8_t *DivLut = Div255Lut; LColour fore = Font->Fore(); LRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = DivLut[a * fore_px.r]; map[a].g = DivLut[a * fore_px.g]; map[a].b = DivLut[a * fore_px.b]; map[a].a = a; } } else { LColour back = Font->Back(); LRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = DivLut[(oma * back_px.r) + (a * fore_px.r)]; map[a].g = DivLut[(oma * back_px.g) + (a * fore_px.g)]; map[a].b = DivLut[(oma * back_px.b) + (a * fore_px.b)]; map[a].a = 255; } } uint8_t *StartOfBuffer = (*Out)[0]; uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (int y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *dst = (OutPx*) (*Out)[py + y]; if (!dst) continue; dst += Clip.DstClip.x1; if ((uint8_t*)dst < StartOfBuffer) continue; uint8_t *i = (*In)[y]; if (!i) return false; uint8_t *e = i + Clip.DstClip.X(); LRgba32 *src; LAssert((uint8_t*)dst >= StartOfBuffer); if (Font->Transparent()) { uint8_t a, oma; while (i < e) { // Alpha blend map and output pixel a = *i++; src = map + a; switch (a) { case 0: break; case 255: // Copy dst->r = src->r; dst->g = src->g; dst->b = src->b; break; default: // Blend OverPm32toPm24(src, dst); break; } dst++; } } else { while (i < e) { // Copy rop src = map + *i++; dst->r = src->r; dst->g = src->g; dst->b = src->b; dst++; } } LAssert((uint8_t*)dst <= EndOfBuffer); } return true; } template bool CompositeText5NoAlpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip) { OutPx map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... #define MASK_5BIT 0x1f #define MASK_6BIT 0x3f // Create colour map of the foreground/background colours uint8_t *Div255 = Div255Lut; LColour fore = Font->Fore(); LRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = (int)Div255[a * fore_px.r] >> 3; map[a].g = (int)Div255[a * fore_px.g] >> 2; map[a].b = (int)Div255[a * fore_px.b] >> 3; } } else { LColour back = Font->Back(); LRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = Div255[(oma * back_px.r) + (a * fore_px.r)] >> 3; map[a].g = Div255[(oma * back_px.g) + (a * fore_px.g)] >> 2; map[a].b = Div255[(oma * back_px.b) + (a * fore_px.b)] >> 3; } } uint8_t *StartOfBuffer = (*Out)[0]; uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (unsigned y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *dst = ((OutPx*) (*Out)[py + y]); if (!dst) continue; dst += Clip.DstClip.x1; uint8_t *i = (*In)[y]; if (!i) return false; uint8_t *e = i + Clip.DstClip.X(); LAssert((uint8_t*)dst >= StartOfBuffer); if (Font->Transparent()) { uint8_t a; OutPx *src; while (i < e) { // Alpha blend map and output pixel a = *i++; switch (a) { case 0: break; case 255: // Copy *dst = map[a]; break; default: { // Blend #if 0 uint8_t oma = 255 - a; src = map + a; LRgb24 d = { G5bitTo8bit(dst->r), G6bitTo8bit(dst->g), G5bitTo8bit(dst->b)}; LRgb24 s = { G5bitTo8bit(src->r), G6bitTo8bit(src->g), G5bitTo8bit(src->b)}; dst->r = Div255[(oma * d.r) + (a * s.r)] >> 3; dst->g = Div255[(oma * d.g) + (a * s.g)] >> 2; dst->b = Div255[(oma * d.b) + (a * s.b)] >> 3; #else uint8_t a5 = a >> 3; uint8_t a6 = a >> 2; uint8_t oma5 = MASK_5BIT - a5; uint8_t oma6 = MASK_6BIT - a6; src = map + a; dst->r = ((oma5 * (uint8_t)dst->r) + (a5 * (uint8_t)src->r)) / MASK_5BIT; dst->g = ((oma6 * (uint8_t)dst->g) + (a6 * (uint8_t)src->g)) / MASK_6BIT; dst->b = ((oma5 * (uint8_t)dst->b) + (a5 * (uint8_t)src->b)) / MASK_5BIT; #endif break; } } dst++; } } else { while (i < e) { // Copy rop *dst++ = map[*i++]; } } LAssert((uint8_t*)dst <= EndOfBuffer); } return true; } #endif void LDisplayString::Draw(LSurface *pDC, int px, int py, LRect *r, bool Debug) { Layout(); #if DISPLAY_STRING_FRACTIONAL_NATIVE // GTK / Mac use fractional pixels, so call the fractional version: LRect rc; if (r) { rc = *r; rc.x1 <<= FShift; rc.y1 <<= FShift; #if defined(MAC) && !defined(__GTK_H__) rc.x2 <<= FShift; rc.y2 <<= FShift; #else rc.x2 = (rc.x2 + 1) << FShift; rc.y2 = (rc.y2 + 1) << FShift; #endif } FDraw(pDC, px << FShift, py << FShift, r ? &rc : NULL, Debug); #elif defined(HAIKU) if (!Font || !pDC) { LgiTrace("%s:%i - No ptr: %p/%p.\n", _FL, Font, pDC); return; } BFont *fnt = Font->Handle(); BView *view = pDC->Handle(); if (!fnt || !view) { LgiTrace("%s:%i - No handle: %p/%p(%s).\n", _FL, fnt, view, pDC->GetClass()); return; } if (!Info.Length()) { - LgiTrace("%s:%i - No layout.\n", _FL); + LgiTrace("%s:%i - No layout for '%s'.\n", _FL, Str); return; } font_height height = {0}; fnt->GetHeight(&height); if (!Font->Transparent()) { view->SetHighColor(Font->Back()); if (r) view->FillRect(*r); else view->FillRect(BRect(px, py, px+x, py+y)); } view->SetFont(fnt); view->SetHighColor(Font->Fore()); int cx = px; for (auto &i: Info) { view->DrawString(i.Str, i.Len, BPoint(cx, py + height.ascent)); cx += i.X; } #elif defined(LGI_SDL) if (Img && pDC && pDC->Y() > 0 && (*pDC)[0]) { int Ox = 0, Oy = 0; pDC->GetOrigin(Ox, Oy); LBlitRegions Clip(pDC, px-Ox, py-Oy, Img, r); LColourSpace DstCs = pDC->GetColourSpace(); switch (DstCs) { #define DspStrCase(px_fmt, comp) \ case Cs##px_fmt: \ CompositeText##comp(pDC, Img, Font, px-Ox, py-Oy, Clip); \ break; DspStrCase(Rgb16, 5NoAlpha) DspStrCase(Bgr16, 5NoAlpha) DspStrCase(Rgb24, 8NoAlpha) DspStrCase(Bgr24, 8NoAlpha) DspStrCase(Rgbx32, 8NoAlpha) DspStrCase(Bgrx32, 8NoAlpha) DspStrCase(Xrgb32, 8NoAlpha) DspStrCase(Xbgr32, 8NoAlpha) DspStrCase(Rgba32, 8Alpha) DspStrCase(Bgra32, 8Alpha) DspStrCase(Argb32, 8Alpha) DspStrCase(Abgr32, 8Alpha) default: LgiTrace("%s:%i - LDisplayString::Draw Unsupported colour space.\n", _FL); // LAssert(!"Unsupported colour space."); break; #undef DspStrCase } } else LgiTrace("::Draw argument error.\n"); #elif defined WINNATIVE if (Info.Length() && pDC && Font) { LFontSystem *Sys = LFontSystem::Inst(); COLOUR Old = pDC->Colour(); int TabSize = Font->TabSize() ? Font->TabSize() : 32; int Ox = px; LColour cFore = Font->Fore(); LColour cBack = Font->Back(); LColour cWhitespace; if (VisibleTab) { cWhitespace = Font->WhitespaceColour(); LAssert(cWhitespace.IsValid()); } for (int i=0; iFont[Info[i].FontId]; f->Colour(cFore, cBack); auto Sz = Font->Size(); Sz.Value += Info[i].SizeDelta; f->Size(Sz); f->Transparent(Font->Transparent()); f->Underline(Font->Underline()); if (!f->Handle()) { f->Create(); } } else { f = Font; } if (f) { LRect b; if (r) { b.x1 = i ? px : r->x1; b.y1 = r->y1; b.x2 = i < Info.Length() - 1 ? px + Info[i].X - 1 : r->x2; b.y2 = r->y2; } else { b.x1 = px; b.y1 = py; b.x2 = px + Info[i].X - 1; b.y2 = py + Y() - 1; } if (b.Valid()) { if (IsTabChar(*Info[i].Str)) { // Invisible tab... draw blank space if (!Font->Transparent()) { pDC->Colour(cBack); pDC->Rectangle(&b); } if (VisibleTab) { int X = px; for (int n=0; nColour(cWhitespace); DrawWhiteSpace(pDC, '\t', r); X += Dx; } } } else { // Draw the character(s) LColour Fg = f->Fore(); LAssert(Fg.IsValid()); f->_Draw(pDC, px, py, Info[i].Str, Info[i].Len, &b, Fg); if (VisibleTab) { OsChar *start = Info[i].Str; OsChar *s = start; OsChar *e = s + Info[i].Len; int Sp = -1; while (s < e) { if (*s == ' ') { int Sx, Sy; if (Sp < 0) f->_Measure(Sp, Sy, s, 1); f->_Measure(Sx, Sy, start, (int)(s - start)); LRect r(0, 0, Sp-1, Sy-1); r.Offset(px + Sx, py); pDC->Colour(cWhitespace); DrawWhiteSpace(pDC, ' ', r); } s++; } } } } } // Inc my position px += Info[i].X; } if (AppendDots) { int Sx, Sy; Font->_Measure(Sx, Sy, GDisplayStringDots, 3); LRect b; if (r) { b.x1 = px; b.y1 = r->y1; b.x2 = min(px + Sx - 1, r->x2); b.y2 = r->y2; } else { b.x1 = px; b.y1 = py; b.x2 = px + Sx - 1; b.y2 = py + Y() - 1; } LColour Fg = Font->Fore(); Font->_Draw(pDC, px, py, GDisplayStringDots, 3, &b, Fg); } pDC->Colour(Old); } else if (r && Font && !Font->Transparent()) { pDC->Colour(Font->Back()); pDC->Rectangle(r); } #endif } int LDisplayString::GetDrawOffsetF() { return DrawOffsetF; } void LDisplayString::SetDrawOffsetF(int Fpx) { if (LaidOut) LAssert(!"No point setting TabOrigin after string is laid out.\n"); DrawOffsetF = Fpx; } int LDisplayString::FX() { Layout(); return xf; } int LDisplayString::FY() { Layout(); return yf; } LPoint LDisplayString::FSize() { Layout(); return LPoint(xf, yf); } void LDisplayString::FDraw(LSurface *pDC, int fx, int fy, LRect *frc, bool Debug) { Layout(Debug); #if !DISPLAY_STRING_FRACTIONAL_NATIVE // Windows doesn't use fractional pixels, so call the integer version: LRect rc; if (frc) { rc = *frc; rc.x1 >>= FShift; rc.y1 >>= FShift; rc.x2 >>= FShift; rc.y2 >>= FShift; } Draw(pDC, fx >> FShift, fy >> FShift, frc ? &rc : NULL, Debug); #elif defined __GTK_H__ Gtk::cairo_t *cr = pDC->Handle(); if (!cr) { LAssert(!"Can't get cairo."); return; } pango_context_set_font_description(LFontSystem::Inst()->GetContext(), Font->Handle()); cairo_save(cr); LColour b = Font->Back(); double Dx = ((double)fx / FScale); double Dy = ((double)fy / FScale); double rx, ry, rw, rh; if (!Font->Transparent()) { // Background fill cairo_set_source_rgb(cr, (double)b.r()/255.0, (double)b.g()/255.0, (double)b.b()/255.0); cairo_new_path(cr); if (frc) { rx = ((double)frc->x1 / FScale); ry = ((double)frc->y1 / FScale); rw = (double)frc->X() / FScale; rh = (double)frc->Y() / FScale; } else { rx = Dx; ry = Dy; rw = x; rh = y; } cairo_rectangle(cr, rx, ry, rw, rh); cairo_fill(cr); if (frc) { cairo_rectangle(cr, rx, ry, rw, rh); cairo_clip(cr); } } else if (frc) { rx = ((double)frc->x1 / FScale); ry = ((double)frc->y1 / FScale); rw = (double)frc->X() / FScale; rh = (double)frc->Y() / FScale; cairo_rectangle(cr, rx, ry, rw, rh); cairo_clip(cr); } cairo_translate(cr, Dx, Dy); LColour f = Font->Fore(); for (auto &b: d->Blocks) { double Bx = ((double)b.X()) / FScale; #if DEBUG_BOUNDS double By = Font->GetHeight(); cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); cairo_rectangle(cr, 0, 0, Bx, By); cairo_rectangle(cr, 1, 1, Bx - 2.0, By - 2.0); cairo_set_fill_rule(cr, Gtk::CAIRO_FILL_RULE_EVEN_ODD); cairo_fill(cr); #endif if (b.Hnd) { cairo_set_source_rgb( cr, (double)f.r()/255.0, (double)f.g()/255.0, (double)f.b()/255.0); pango_cairo_show_layout(cr, b.Hnd); if (VisibleTab && Str) { LUtf8Str Ptr(Str); auto Ws = Font->WhitespaceColour(); pDC->Colour(Ws); for (int32 u, Idx = 0 ; (u = Ptr); Idx++) { if (IsTabChar(u) || u == ' ') { Gtk::PangoRectangle pos; Gtk::pango_layout_index_to_pos(b.Hnd, Idx, &pos); LRect r(0, 0, pos.width / FScale, pos.height / FScale); r.Offset(pos.x / FScale, pos.y / FScale); DrawWhiteSpace(pDC, u, r); } Ptr++; } } } else if (b.Fnt) { b.Fnt->Transparent(Font->Transparent()); b.Fnt->Back(Font->Back()); b.Fnt->_Draw(pDC, 0, 0, b.Str, b.Bytes, NULL, f); } else LAssert(0); cairo_translate(cr, Bx, 0); } cairo_restore(cr); #elif defined MAC && !defined(LGI_SDL) int Ox = 0, Oy = 0; int px = fx >> FShift; int py = fy >> FShift; LRect rc; if (frc) rc.Set( frc->x1 >> FShift, frc->y1 >> FShift, frc->x2 >> FShift, frc->y2 >> FShift); if (pDC && !pDC->IsScreen()) pDC->GetOrigin(Ox, Oy); if (pDC && !Font->Transparent()) { LColour Old = pDC->Colour(Font->Back()); if (frc) { pDC->Rectangle(&rc); } else { LRect a(px, py, px + x - 1, py + y - 1); pDC->Rectangle(&a); } pDC->Colour(Old); } if (Hnd && pDC && StrWords > 0) { OsPainter dc = pDC->Handle(); #if USE_CORETEXT int y = (pDC->Y() - py + Oy); CGContextSaveGState(dc); pDC->Colour(Font->Fore()); if (pDC->IsScreen()) { if (frc) { CGRect rect = rc; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } CGContextTranslateCTM(dc, 0, pDC->Y()-1); CGContextScaleCTM(dc, 1.0, -1.0); } else { if (frc) { CGContextSaveGState(dc); CGRect rect = rc; rect.origin.x -= Ox; rect.origin.y = pDC->Y() - rect.origin.y + Oy - rect.size.height; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } } CGFloat Tx = (CGFloat)fx / FScale - Ox; CGFloat Ty = (CGFloat)y - Font->Ascent(); CGContextSetTextPosition(dc, Tx, Ty); CTLineDraw(Hnd, dc); CGContextRestoreGState(dc); #else ATSUAttributeTag Tags[1] = {kATSUCGContextTag}; ByteCount Sizes[1] = {sizeof(CGContextRef)}; ATSUAttributeValuePtr Values[1] = {&dc}; e = ATSUSetLayoutControls(Hnd, 1, Tags, Sizes, Values); if (e) { printf("%s:%i - ATSUSetLayoutControls failed (e=%i)\n", _FL, (int)e); } else { // Set style attr ATSURGBAlphaColor c; LColour Fore = Font->Fore(); c.red = (double) Fore.r() / 255.0; c.green = (double) Fore.g() / 255.0; c.blue = (double) Fore.b() / 255.0; c.alpha = 1.0; ATSUAttributeTag Tags[] = {kATSURGBAlphaColorTag}; ATSUAttributeValuePtr Values[] = {&c}; ByteCount Lengths[] = {sizeof(c)}; e = ATSUSetAttributes( Font->Handle(), CountOf(Tags), Tags, Lengths, Values); if (e) { printf("%s:%i - Error setting font attr (e=%i)\n", _FL, (int)e); } else { int y = (pDC->Y() - py + Oy); if (pDC->IsScreen()) { CGContextSaveGState(dc); if (frc) { CGRect rect = rc; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } CGContextTranslateCTM(dc, 0, pDC->Y()-1); CGContextScaleCTM(dc, 1.0, -1.0); e = ATSUDrawText(Hnd, kATSUFromTextBeginning, kATSUToTextEnd, fx - Long2Fix(Ox), Long2Fix(y) - fAscent); CGContextRestoreGState(dc); } else { if (frc) { CGContextSaveGState(dc); CGRect rect = rc; rect.origin.x -= Ox; rect.origin.y = pDC->Y() - rect.origin.y + Oy - rect.size.height; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } e = ATSUDrawText(Hnd, kATSUFromTextBeginning, kATSUToTextEnd, Long2Fix(px - Ox), Long2Fix(y) - fAscent); if (frc) CGContextRestoreGState(dc); } if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUDrawText failed with %i, len=%i, str=%.20s\n", _FL, (int)e, len, a); DeleteArray(a); } } } #endif } #endif } diff --git a/src/common/Gdc2/Surface.cpp b/src/common/Gdc2/Surface.cpp --- a/src/common/Gdc2/Surface.cpp +++ b/src/common/Gdc2/Surface.cpp @@ -1,2183 +1,2181 @@ /*hdr ** FILE: GdcPrim.cpp ** AUTHOR: Matthew Allen ** DATE: 1/3/97 ** DESCRIPTION: GDC v2.xx device independent primitives ** ** Copyright (C) 2001, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include #include "lgi/common/Gdc2.h" #include "lgi/common/GdiLeak.h" #include "lgi/common/Palette.h" #include "lgi/common/Variant.h" #define LINE_SOLID 0xFFFFFFFF #ifdef MAC #define REGISTER #else #define REGISTER register #endif void LSurface::Init() { OriginX = OriginY = 0; pMem = NULL; pAlphaDC = 0; Flags = 0; Clip.ZOff(-1, -1); pPalette = NULL; pApp = NULL; ColourSpace = CsNone; for (int i=0; iX(), pDC->Y(), pDC->GetColourSpace())) { Blt(0, 0, pDC); if (pDC->Palette()) { LPalette *Pal = new LPalette(pDC->Palette()); if (Pal) { Palette(Pal, true); } } } } LSurface::~LSurface() { #if defined(LINUX) && !defined(LGI_SDL) /* if (Cairo) { Gtk::cairo_destroy(Cairo); Cairo = 0; } */ #endif DrawOnAlpha(false); DeleteObj(pMem); DeleteObj(pAlphaDC); if (pPalette && (Flags & GDC_OWN_PALETTE)) { DeleteObj(pPalette); } if ( (Flags & GDC_OWN_APPLICATOR) && !(Flags & GDC_CACHED_APPLICATOR)) { DeleteObj(pApp); } for (int i=0; iSetSmoothingMode(AntiAlias() ? Gdiplus::SmoothingModeAntiAlias : Gdiplus::SmoothingModeNone); return GdiplusGfx; } Gdiplus::Color LSurface::GdiColour() { COLOUR col = Colour(); switch (GetBits()) { default: LAssert(!"Impl me."); // fall through case 32: return Gdiplus::Color(A32(col), R32(col), G32(col), B32(col)); case 24: return Gdiplus::Color(R24(col), G24(col), B24(col)); } } #endif LString LSurface::GetStr() { LString::Array s; s.SetFixedLength(false); s.New().Printf("Size(%ix%i)", X(), Y()); s.New().Printf("ColourSpace(%s)", LColourSpaceToString(ColourSpace)); s.New().Printf("IsScreen(%i)", IsScreen()); if (pAlphaDC) s.New().Printf("pAlphaDC(%ix%i,%s)", pAlphaDC->X(), pAlphaDC->Y(), LColourSpaceToString(pAlphaDC->GetColourSpace())); if (Clip.Valid()) s.New().Printf("Clip(%s)", Clip.GetStr()); s.New().Printf("Op(%i)", Op()); s.New().Printf("GetRowStep(" LPrintfSizeT ")", GetRowStep()); return LString(" ").Join(s); } LSurface *LSurface::SubImage(LRect r) { if (!pMem || !pMem->Base) return NULL; LRect clip = r; LRect bounds = Bounds(); clip.Intersection(&bounds); if (!clip.Valid()) return NULL; LAutoPtr s(new LSurface); if (!s) return NULL; int BytePx = pMem->GetBits() >> 3; s->pMem = new LBmpMem; s->pMem->Base = pMem->Base + (pMem->Line * clip.y1) + (BytePx * clip.x1); s->pMem->x = clip.X(); s->pMem->y = clip.Y(); s->pMem->Line = pMem->Line; s->pMem->Cs = pMem->Cs; s->pMem->Flags = 0; // Don't own memory. s->Clip = s->Bounds(); s->ColourSpace = pMem->Cs; s->Op(GDC_SET); return s.Release(); } template void SetAlphaPm(Px *src, int x, uint8_t a) { REG uint8_t *Lut = Div255Lut; REG Px *s = src; REG Px *e = s + x; while (s < e) { s->r = Lut[s->r * a]; s->g = Lut[s->g * a]; s->b = Lut[s->b * a]; s->a = Lut[s->a * a]; s++; } } template void SetAlphaNpm(Px *src, int x, uint8_t a) { REG uint8_t *Lut = Div255Lut; REG Px *s = src; REG Px *e = s + x; while (s < e) { s->a = Lut[s->a * a]; s++; } } bool LSurface::SetConstantAlpha(uint8_t Alpha) { bool HasAlpha = LColourSpaceHasAlpha(GetColourSpace()); if (!HasAlpha) return false; if (!pMem || !pMem->Base) return false; for (int y=0; yy; y++) { uint8_t *src = pMem->Base + (y * pMem->Line); if (pMem->PreMul()) { switch (pMem->Cs) { #define SetAlphaCase(px) \ case Cs##px: SetAlphaPm((L##px*)src, pMem->x, Alpha); break SetAlphaCase(Rgba32); SetAlphaCase(Bgra32); SetAlphaCase(Argb32); SetAlphaCase(Abgr32); #undef SetAlphaCase default: LAssert(!"Unknown CS."); break; } } else { switch (pMem->Cs) { #define SetAlphaCase(px) \ case Cs##px: SetAlphaNpm((L##px*)src, pMem->x, Alpha); break SetAlphaCase(Rgba32); SetAlphaCase(Bgra32); SetAlphaCase(Argb32); SetAlphaCase(Abgr32); #undef SetAlphaCase default: LAssert(!"Unknown CS."); break; } } } return true; } OsBitmap LSurface::GetBitmap() { #if WINNATIVE return hBmp; #else return NULL; #endif } OsPainter LSurface::Handle() { #if WINNATIVE return hDC; #elif defined(__GTK_H__) return NULL; #else return 0; #endif } uchar *LSurface::operator[](int y) { if (pMem && pMem->Base && y >= 0 && y < pMem->y) { return pMem->Base + (pMem->Line * y); } return 0; } void LSurface::Set(int x, int y) { OrgXy(x, y); if (x >= Clip.x1 && y >= Clip.y1 && x <= Clip.x2 && y <= Clip.y2) { pApp->SetPtr(x, y); pApp->Set(); Update(GDC_BITS_CHANGE); } } COLOUR LSurface::Get(int x, int y) { OrgXy(x, y); if (x >= Clip.x1 && y >= Clip.y1 && x <= Clip.x2 && y <= Clip.y2) { pApp->SetPtr(x, y); return pApp->Get(); } return (COLOUR)-1; } void LSurface::HLine(int x1, int x2, int y) { OrgXy(x1, y); OrgX(x2); if (x1 > x2) LSwap(x1, x2); if (x1 < Clip.x1) x1 = Clip.x1; if (x2 > Clip.x2) x2 = Clip.x2; if (x1 <= x2 && y >= Clip.y1 && y <= Clip.y2) { pApp->SetPtr(x1, y); if (LineBits == LINE_SOLID) { pApp->Rectangle(x2 - x1 + 1, 1); Update(GDC_BITS_CHANGE); } else { for (; x1 <= x2; x1++) { if (LineMask & LineBits) { pApp->Set(); } LineMask >>= 1; if (!LineMask) LineMask = LineReset; pApp->IncX(); } Update(GDC_BITS_CHANGE); } } } void LSurface::VLine(int x, int y1, int y2) { OrgXy(x, y1); OrgY(y2); if (y1 > y2) LSwap(y1, y2); if (y1 < Clip.y1) y1 = Clip.y1; if (y2 > Clip.y2) y2 = Clip.y2; if (y1 <= y2 && x >= Clip.x1 && x <= Clip.x2) { pApp->SetPtr(x, y1); if (LineBits == LINE_SOLID) { pApp->VLine(y2 - y1 + 1); Update(GDC_BITS_CHANGE); } else { for (; y1 <= y2; y1++) { if (LineMask & LineBits) { pApp->Set(); } LineMask >>= 1; if (!LineMask) LineMask = LineReset; pApp->IncY(); } Update(GDC_BITS_CHANGE); } } } void LSurface::Line(int x1, int y1, int x2, int y2) { if (x1 == x2) { VLine(x1, y1, y2); } else if (y1 == y2) { HLine(x1, x2, y1); } else if (Clip.Valid()) { OrgXy(x1, y1); OrgXy(x2, y2); // angled if (y1 > y2) { LSwap(y1, y2); LSwap(x1, x2); } LRect Bound(x1, y1, x2, y2); Bound.Normal(); if (!Bound.Overlap(&Clip)) return; double m = (double) (y2-y1) / (x2-x1); double b = (double) y1 - (m*x1); int xt = (int) (((double)Clip.y1-b)/m); int xb = (int) (((double)Clip.y2-b)/m); if (y1 < Clip.y1) { x1 = xt; y1 = Clip.y1; } if (y2 > Clip.y2) { x2 = xb; y2 = Clip.y2; } int X1, X2; if (x1 < x2) { X1 = x1; X2 = x2; } else { X1 = x2; X2 = x1; } if (X1 < Clip.x1) { if (X2 < Clip.x1) return; if (x1 < Clip.x1) { y1 = (int) (((double) Clip.x1 * m) + b); x1 = Clip.x1; } else { y2 = (int) (((double) Clip.x1 * m) + b); x2 = Clip.x1; } } if (X2 > Clip.x2) { if (X1 > Clip.x2) return; if (x1 > Clip.x2) { y1 = (int) (((double) Clip.x2 * m) + b); x1 = Clip.x2; } else { y2 = (int) (((double) Clip.x2 * m) + b); x2 = Clip.x2; } } int dx = abs(x2-x1); int dy = abs(y2-y1); int EInc, ENoInc, E, Inc = 1; if (dy < dx) { // flat if (x1 > x2) { LSwap(x1, x2); LSwap(y1, y2); } if (y1 > y2) { Inc = -1; } EInc = dy + dy; E = EInc - dx; ENoInc = E - dx; pApp->SetPtr(x1, y1); if (LineBits == LINE_SOLID) { for (; x1<=x2; x1++) { pApp->Set(); if (E < 0) { pApp->IncX(); E += EInc; } else { pApp->IncPtr(1, Inc); E += ENoInc; } } } else { for (; x1<=x2; x1++) { if (LineBits & LineMask) { pApp->Set(); } LineMask >>= 1; if (!LineMask) LineMask = LineReset; if (E < 0) { pApp->IncX(); E += EInc; } else { pApp->IncPtr(1, Inc); E += ENoInc; } } } } else { if (x1 > x2) { Inc = -1; } // steep EInc = dx + dx; E = EInc - dy; ENoInc = E - dy; pApp->SetPtr(x1, y1); if (LineBits == LINE_SOLID) { for (; y1<=y2; y1++) { pApp->Set(); if (E < 0) { pApp->IncY(); E += EInc; } else { pApp->IncPtr(Inc, 1); E += ENoInc; } } } else { for (; y1<=y2; y1++) { if (LineBits & LineMask) { pApp->Set(); } LineMask >>= 1; if (!LineMask) LineMask = LineReset; if (E < 0) { pApp->IncY(); E += EInc; } else { pApp->IncPtr(Inc, 1); E += ENoInc; } } } } Update(GDC_BITS_CHANGE); } } void LSurface::Circle(double Cx, double Cy, double radius) { #if defined WINNATIVE if (Op() == GDC_SET && GetBits() > 8) { auto g = GetGfx(); Gdiplus::Pen pen(GdiColour()); Gdiplus::RectF r((Gdiplus::REAL)(Cx-radius), (Gdiplus::REAL)(Cy-radius), (Gdiplus::REAL)(radius*2.0)-1, (Gdiplus::REAL)(radius*2.0)-1); auto status = g->DrawEllipse(&pen, r); Update(GDC_BITS_CHANGE); return; } #endif int cx = (int)Cx; int cy = (int)Cy; int d = (int) (3 - (2 * radius)); int x = 0; int y = (int) radius; Set(cx, cy + y); Set(cx, cy - y); Set(cx + y, cy); Set(cx - y, cy); if (d < 0) { d += (4 * x) + 6; } else { d += (4 * (x - y)) + 10; y--; } x++; while (x < y) { Set(cx + x, cy + y); Set(cx - x, cy + y); Set(cx + x, cy - y); Set(cx - x, cy - y); Set(cx + y, cy + x); Set(cx - y, cy + x); Set(cx + y, cy - x); Set(cx - y, cy - x); if (d < 0) { d += (4 * x) + 6; } else { d += (4 * (x - y)) + 10; y--; } x++; } if (x == y) { Set(cx + x, cy + x); Set(cx - x, cy + x); Set(cx + x, cy - x); Set(cx - x, cy - x); } Update(GDC_BITS_CHANGE); } void LSurface::FilledCircle(double Cx, double Cy, double radius) { #if defined WINNATIVE if (Op() == GDC_SET && GetBits() > 8) { auto g = GetGfx(); auto col = Colour(); Gdiplus::SolidBrush brush(GdiColour()); Gdiplus::RectF r((Gdiplus::REAL)(Cx-radius), (Gdiplus::REAL)(Cy-radius), (Gdiplus::REAL)(radius*2.0-1.0), (Gdiplus::REAL)(radius*2.0-1.0)); if (!AntiAlias()) r.Inflate(1.0f, 1.0f); auto status = g->FillEllipse(&brush, r); Update(GDC_BITS_CHANGE); return; } #endif int cx = (int)Cx; int cy = (int)Cy; int d = (int) (3 - 2 * radius); int x = 0; int y = (int) radius; // HLine(cx + y, cx - y, cy); if (d < 0) { d += 4 * x + 6; } else { HLine(cx, cx, cy + y); d += 4 * (x - y) + 10; y--; } while (x < y) { HLine(cx + y, cx - y, cy + x); if (x != 0) HLine(cx + y, cx - y, cy - x); if (d < 0) { d += 4 * x + 6; } else { HLine(cx + x, cx - x, cy + y); HLine(cx + x, cx - x, cy - y); d += 4 * (x - y) + 10; y--; } x++; } if (x == y) { HLine(cx + y, cx - y, cy + x); HLine(cx + y, cx - y, cy - x); } Update(GDC_BITS_CHANGE); } void LSurface::Box(LRect *a) { if (a) { LRect b = *a; b.Normal(); if (b.x1 != b.x2 && b.y1 != b.y2) { HLine(b.x1, b.x2 - 1, b.y1); VLine(b.x2, b.y1, b.y2 - 1); HLine(b.x1 + 1, b.x2, b.y2); VLine(b.x1, b.y1 + 1, b.y2); } else { Set(b.x1, b.y1); } } else { LRect b(0, 0, X()-1, Y()-1); HLine(b.x1, b.x2 - 1, b.y1); VLine(b.x2, b.y1, b.y2 - 1); HLine(b.x1 + 1, b.x2, b.y2); VLine(b.x1, b.y1 + 1, b.y2); } } void LSurface::Box(int x1, int y1, int x2, int y2) { LRect a(x1, y1, x2, y2); Box(&a); } void LSurface::Rectangle(LRect *a) { LRect b; if (a) b = a; else b.ZOff(pMem->x-1, pMem->y-1); OrgRgn(b); b.Normal(); b.Bound(&Clip); if (b.Valid()) { pApp->SetPtr(b.x1, b.y1); pApp->Rectangle(b.X(), b.Y()); Update(GDC_BITS_CHANGE); } } void LSurface::Rectangle(int x1, int y1, int x2, int y2) { LRect a(x1, y1, x2, y2); Rectangle(&a); } void LSurface::Ellipse(double Cx, double Cy, double radiusX, double radiusY) { #if defined WINNATIVE if (Op() == GDC_SET && GetBits() > 8) { auto g = GetGfx(); Gdiplus::Pen pen(GdiColour()); Gdiplus::RectF r((Gdiplus::REAL)(Cx-radiusX), (Gdiplus::REAL)(Cy-radiusY), (Gdiplus::REAL)(radiusX*2.0)-1, (Gdiplus::REAL)(radiusY*2.0)-1); auto status = g->DrawEllipse(&pen, r); Update(GDC_BITS_CHANGE); return; } #endif #define incx() x++, dxt += d2xt, t += dxt #define incy() y--, dyt += d2yt, t += dyt int x = 0, y = (int)radiusY; int a = (int)radiusX; if (a % 2 == 0) a--; int b = (int)radiusY; int xc = (int)Cx; int yc = (int)Cy; long a2 = (long)(a*a), b2 = (long)(b*b); long crit1 = -(a2/4 + (int)a%2 + b2); long crit2 = -(b2/4 + b%2 + a2); long crit3 = -(b2/4 + b%2); long t = -a2*y; /* e(x+1/2,y-1/2) - (a^2+b^2)/4 */ long dxt = 2*b2*x, dyt = -2*a2*y; long d2xt = 2*b2, d2yt = 2*a2; if (a % 2) { // Odd version while (y>=0 && x<=a) { Set(xc+x, yc+y); if (x!=0 || y!=0) Set(xc-x, yc-y); if (x!=0 && y!=0) { Set(xc+x, yc-y); Set(xc-x, yc+y); } if (t + b2*x <= crit1 || /* e(x+1,y-1/2) <= 0 */ t + a2*y <= crit3) /* e(x+1/2,y) <= 0 */ incx(); else if (t - a2*y > crit2) /* e(x+1/2,y-1) > 0 */ incy(); else { incx(); incy(); } } } else // even version { while (y>=0 && x<=a) { Set(xc+x+1, yc+y); Set(xc+x+1, yc-y); Set(xc-x, yc-y); Set(xc-x, yc+y); if (t + b2*x <= crit1 || /* e(x+1,y-1/2) <= 0 */ t + a2*y <= crit3) /* e(x+1/2,y) <= 0 */ incx(); else if (t - a2*y > crit2) /* e(x+1/2,y-1) > 0 */ incy(); else { incx(); incy(); } } } Update(GDC_BITS_CHANGE); } void LSurface::FilledEllipse(double Cx, double Cy, double radiusX, double radiusY) { #if defined WINNATIVE if (Op() == GDC_SET && GetBits() > 8) { auto g = GetGfx(); auto col = Colour(); Gdiplus::SolidBrush brush(GdiColour()); Gdiplus::RectF r((Gdiplus::REAL)(Cx-radiusX), (Gdiplus::REAL)(Cy-radiusY), (Gdiplus::REAL)(radiusX*2.0-1.0), (Gdiplus::REAL)(radiusY*2.0-1.0)); if (!AntiAlias()) r.Inflate(1.0f, 1.0f); auto status = g->FillEllipse(&brush, r); Update(GDC_BITS_CHANGE); return; } #endif // TODO: fix this primitive for odd widths and heights int cx = (int)Cx; int cy = (int)Cy; /* a = floor(a); b = floor(b); */ long aSq = (long) (radiusX * radiusX); long bSq = (long) (radiusY * radiusY); long two_aSq = aSq+aSq; long two_bSq = bSq+bSq; long x=0, y=(long)radiusY, two_xBsq = 0, two_yAsq = y * two_aSq, error = -y * aSq; if (aSq && bSq && error) { while (two_xBsq <= two_yAsq) if (Op() == GDC_SET) { x++; two_xBsq += two_bSq; error += two_xBsq - bSq; if (error >= 0) { Line((int) (cx+x-1), (int) (cy+y), (int) (cx-x+1), (int) (cy+y)); if (y != 0) Line((int) (cx+x-1), (int) (cy-y), (int) (cx-x+1), (int) (cy-y)); y--; two_yAsq -= two_aSq; error -= two_yAsq; } } x=(long)radiusX; y=0; two_xBsq = x * two_bSq; two_yAsq = 0; error = -x * bSq; while (two_xBsq > two_yAsq) { Line( (int) (cx+x), (int) (cy+y), (int) (cx-x), (int) (cy+y)); if (y != 0) Line((int) (cx+x), (int) (cy-y), (int) (cx-x), (int) (cy-y)); y++; two_yAsq += two_aSq; error += two_yAsq - aSq; if (error >= 0) { x--; two_xBsq -= two_bSq; error -= two_xBsq; } } } Update(GDC_BITS_CHANGE); } struct EDGE { int yMin, yMax, x, dWholeX, dX, dY, frac; }; int nActive, nNextEdge; void LSurface::Polygon(int nPoints, LPoint *aPoints) { LPoint p0, p1; int i, j, gap, x0, x1, y, nEdges; EDGE *ET, **GET, **AET; /******************************************************************** * Add entries to the global edge table. The global edge table has a * bucket for each scan line in the polygon. Each bucket contains all * the edges whose yMin == yScanline. Each bucket contains the yMax, * the x coordinate at yMax, and the denominator of the slope (dX) */ // allocate the tables ET = new EDGE[nPoints]; GET = new EDGE*[nPoints]; AET = new EDGE*[nPoints]; if (ET && GET && AET) { for ( i = 0, nEdges = 0; i < nPoints; i++) { p0 = aPoints[i]; p1 = aPoints[(i+1) % nPoints]; // ignore if this is a horizontal edge if (p0.y == p1.y) continue; // swap points if necessary to ensure p0 contains yMin if (p0.y > p1.y) { p0 = p1; p1 = aPoints[i]; } // create the new edge ET[nEdges].yMin = p0.y; ET[nEdges].yMax = p1.y; ET[nEdges].x = p0.x; ET[nEdges].dX = p1.x - p0.x; ET[nEdges].dY = p1.y - p0.y; ET[nEdges].frac = 0; GET[nEdges] = &ET[nEdges]; nEdges++; } // sort the GET on yMin for (gap = 1; gap < nEdges; gap = 3*gap + 1); for (gap /= 3; gap > 0; gap /= 3) for (i = gap; i < nEdges; i++) for (j = i-gap; j >= 0; j -= gap) { if (GET[j]->yMin <= GET[j+gap]->yMin) break; EDGE *t = GET[j]; GET[j] = GET[j+gap]; GET[j+gap] = t; } // initialize the active edge table, and set y to first entering edge nActive = 0; nNextEdge = 0; y = GET[nNextEdge]->yMin; /* Now process the edges using the scan line algorithm. Active edges will be added to the Active Edge Table (AET), and inactive edges will be deleted. X coordinates will be updated with incremental integer arithmetic using the slope (dY / dX) of the edges. */ while (nNextEdge < nEdges || nActive) { /* Move from the ET bucket y to the AET those edges whose yMin == y (entering edges) */ while (nNextEdge < nEdges && GET[nNextEdge]->yMin == y) AET[nActive++] = GET[nNextEdge++]; /* Remove from the AET those entries for which yMax == y (leaving edges) */ i = 0; while (i < nActive) { if (AET[i]->yMax == y) memmove(&AET[i], &AET[i+1], sizeof(AET[0]) * (--nActive - i)); else i++; } /* Now sort the AET on x. Since the list is usually quite small, the sort is implemented as a simple non-recursive shell sort */ for (gap = 1; gap < nActive; gap = 3*gap + 1); for (gap /= 3; gap > 0; gap /= 3) for (i = gap; i < nActive; i++) for (j = i-gap; j >= 0; j -= gap) { if (AET[j]->x <= AET[j+gap]->x) break; EDGE *t = AET[j]; AET[j] = AET[j+gap]; AET[j+gap] = t; } /* Fill in desired pixels values on scan line y by using pairs of x coordinates from the AET */ for (i = 0; i < nActive; i += 2) { x0 = AET[i]->x; x1 = AET[i+1]->x; /* Left edge adjustment for positive fraction. 0 is interior. */ if (AET[i]->frac > 0) x0++; // Right edge adjustment for negative fraction. 0 is exterior. */ if (AET[i+1]->frac <= 0) x1--; // Draw interior spans if (x1 >= x0) HLine(x0, x1, y); } /* Update all the x coordinates. Edges are scan converted using a modified midpoint algorithm (Bresenham's algorithm reduces to the midpoint algorithm for two dimensional lines) */ for (i = 0; i < nActive; i++) { EDGE *e = AET[i]; // update the fraction by dX e->frac += e->dX; if (e->dX < 0) while ( -(e->frac) >= e->dY) { e->frac += e->dY; e->x--; } else while (e->frac >= e->dY) { e->frac -= e->dY; e->x++; } } y++; } } // Release tables DeleteArray(ET); DeleteArray(GET); DeleteArray(AET); } void LSurface::Blt(int x, int y, LSurface *Src, LRect *a) { OrgXy(x, y); - if (Src && Src->pMem && Src->pMem->Base) - { - LRect S; - if (a) S = *a; - else S.ZOff(Src->X()-1, Src->Y()-1); - S.Offset(Src->OriginX, Src->OriginY); + if (!Src || !Src->pMem || !Src->pMem->Base) + return; - LRect SClip = S; - SClip.Bound(&Src->Clip); + LRect S; + if (a) S = *a; + else S.ZOff(Src->X()-1, Src->Y()-1); + S.Offset(Src->OriginX, Src->OriginY); + + LRect SClip = S; + SClip.Bound(&Src->Clip); + + if (!SClip.Valid()) + return; - if (SClip.Valid()) - { - LRect D = SClip; - D.Offset(x-S.x1, y-S.y1); + LRect D = SClip; + D.Offset(x-S.x1, y-S.y1); - LRect DClip = D; - DClip.Bound(&Clip); + LRect DClip = D; + DClip.Bound(&Clip); - LRect Re = DClip; - Re.Offset(S.x1-x, S.y1-y); - SClip.Bound(&Re); + LRect Re = DClip; + Re.Offset(S.x1-x, S.y1-y); + SClip.Bound(&Re); - if (DClip.Valid() && SClip.Valid()) - { - LBmpMem Bits, Alpha; + if (!DClip.Valid() || !SClip.Valid()) + return; + + LBmpMem Bits, Alpha; - int PixelBytes = LColourSpaceToBits(Src->pMem->Cs) >> 3; - - Bits.Base = Src->pMem->Base + - (SClip.y1 * Src->pMem->Line) + - (SClip.x1 * PixelBytes); - Bits.x = MIN(SClip.X(), DClip.X()); - Bits.y = MIN(SClip.Y(), DClip.Y()); - Bits.Line = Src->pMem->Line; - Bits.Cs = Src->GetColourSpace(); - Bits.PreMul(Src->pMem->PreMul()); + Bits.Base = Src->pMem->AddressOf(SClip.x1, SClip.y1); + Bits.x = MIN(SClip.X(), DClip.X()); + Bits.y = MIN(SClip.Y(), DClip.Y()); + Bits.Line = Src->pMem->Line; + Bits.Cs = Src->GetColourSpace(); + Bits.PreMul(Src->pMem->PreMul()); - if (Src->pAlphaDC && !Src->DrawOnAlpha()) - { - LBmpMem *ASurface = Src->pAlphaDC->pMem; - Alpha = Bits; - Alpha.Cs = CsIndex8; - Alpha.Line = ASurface->Line; - Alpha.Base = ASurface->Base + - (SClip.y1 * ASurface->Line) + - (SClip.x1); - } + if (Src->pAlphaDC && !Src->DrawOnAlpha()) + { + auto ASurface = Src->pAlphaDC->pMem; + Alpha = Bits; + Alpha.Cs = CsIndex8; + Alpha.Line = ASurface->Line; + Alpha.Base = ASurface->Base + + (SClip.y1 * ASurface->Line) + + (SClip.x1); + } - pApp->SetPtr(DClip.x1, DClip.y1); - LPalette *SrcPal = Src->DrawOnAlpha() ? NULL : Src->Palette(); - // printf("\t%p::Blt pApp=%p, %s\n", this, pApp, pApp->GetClass()); - pApp->Blt(&Bits, SrcPal, Alpha.Base ? &Alpha : NULL); - Update(GDC_BITS_CHANGE); + pApp->SetPtr(DClip.x1, DClip.y1); + LPalette *SrcPal = Src->DrawOnAlpha() ? NULL : Src->Palette(); + + // printf("\t%p::Blt pApp=%p, %s\n", this, pApp, pApp->GetClass()); + + pApp->Blt(&Bits, SrcPal, Alpha.Base ? &Alpha : NULL); + Update(GDC_BITS_CHANGE); - if (pApp->GetFlags() & GDC_UPDATED_PALETTE) - { - Palette(new LPalette(pApp->GetPal())); - } - } - } + if (pApp->GetFlags() & GDC_UPDATED_PALETTE) + { + Palette(new LPalette(pApp->GetPal())); } } #define sqr(a) ((a)*(a)) #define Distance(a, b) (sqrt(sqr(a.x-b.x)+sqr(a.y-b.y))) class FPt2 { public: double x, y; }; typedef FPt2 BPt; void LSurface::Bezier(int Threshold, LPoint *Pt) { if (Pt) { int OldPts = 3; int NewPts = 0; BPt *BufA = new BPt[1024]; BPt *BufB = new BPt[1024]; BPt *Old = BufA; BPt *New = BufB; Threshold = MAX(Threshold, 1); if (!Old || !New) return; for (int n=0; n Threshold); if (Threshold > 1) { for (int i=0; i= Size) { SetSize(Size+1024); } if (Stack) { Stack[Used].x = x; Stack[Used].y = y; Used++; } } void Pop(int &x, int &y) { if (Stack && Used > 0) { Used--; x = Stack[Used].x; y = Stack[Used].y; } } }; // This should return true if 'Pixel' is in the region being filled. typedef bool (*FillMatchProc)(COLOUR Seed, COLOUR Pixel, COLOUR Border, int Bits); bool FillMatch_Diff(COLOUR Seed, COLOUR Pixel, COLOUR Border, int Bits) { return Seed == Pixel; } bool FillMatch_Near(COLOUR Seed, COLOUR Pixel, COLOUR Border, int Bits) { COLOUR s24 = CBit(24, Seed, Bits); COLOUR p24 = CBit(24, Pixel, Bits); int Dr = R24(s24) - R24(p24); int Dg = G24(s24) - G24(p24); int Db = B24(s24) - B24(p24); return ((unsigned)abs(Dr) < Border) && ((unsigned)abs(Dg) < Border) && ((unsigned)abs(Db) < Border); } void LSurface::FloodFill(int StartX, int StartY, int Mode, COLOUR Border, LRect *FillBounds) { COLOUR Seed = Get(StartX, StartY); if (Seed == 0xffffffff) return; // Doesn't support get pixel PointStack Ps; LRect Bounds; FillMatchProc Proc = 0; int Bits = GetBits(); Bounds.x1 = X(); Bounds.y1 = Y(); Bounds.x2 = 0; Bounds.y2 = 0; Ps.Push(StartX, StartY); switch (Mode) { case GDC_FILL_TO_DIFFERENT: { Proc = FillMatch_Diff; break; } case GDC_FILL_TO_BORDER: { break; } case GDC_FILL_NEAR: { Proc = FillMatch_Near; break; } } if (Proc) { COLOUR Start = Colour(); if (!Proc(Seed, Start, Border, Bits)) { while (Ps.GetSize() > 0) { bool Above = true; bool Below = true; int Ox, Oy; Ps.Pop(Ox, Oy); int x = Ox, y = Oy; // move right loop COLOUR c = Get(x, y); while (x < X() && Proc(Seed, c, Border, Bits)) { Set(x, y); Bounds.Union(x, y); if (y > 0) { c = Get(x, y - 1); if (Above) { if (Proc(Seed, c, Border, Bits)) { Ps.Push(x, y - 1); Above = false; } } else if (!Proc(Seed, c, Border, Bits)) { Above = true; } } if (y < Y() - 1) { c = Get(x, y + 1); if (Below) { if (Proc(Seed, c, Border, Bits)) { Ps.Push(x, y + 1); Below = false; } } else if (!Proc(Seed, c, Border, Bits)) { Below = true; } } x++; c = Get(x, y); } // move left loop x = Ox; Above = !((y > 0) && (Get(x, y - 1) == Seed)); Below = !((y < Y() - 1) && (Get(x, y + 1) == Seed)); x--; c = Get(x, y); while (x >= 0 && Proc(Seed, c, Border, Bits)) { Set(x, y); Bounds.Union(x, y); if (y > 0) { c = Get(x, y - 1); if (Above) { if (Proc(Seed, c, Border, Bits)) { Ps.Push(x, y - 1); Above = false; } } else if (!Proc(Seed, c, Border, Bits)) { Above = true; } } if (y < Y() - 1) { c = Get(x, y + 1); if (Below) { if (Proc(Seed, c, Border, Bits)) { Ps.Push(x, y + 1); Below = false; } } else if (!Proc(Seed, c, Border, Bits)) { Below = true; } } x--; c = Get(x, y); } } } } if (FillBounds) { *FillBounds = Bounds; } Update(GDC_BITS_CHANGE); } void LSurface::Arc(double cx, double cy, double radius, double start, double end) {} void LSurface::FilledArc(double cx, double cy, double radius, double start, double end) {} void LSurface::StretchBlt(LRect *d, LSurface *Src, LRect *s) {} bool LSurface::AntiAlias() { return (Flags & GDC_ANTI_ALIAS) != 0; } bool LSurface::AntiAlias(bool antiAlias) { if (antiAlias) Flags |= GDC_ANTI_ALIAS; else Flags &= ~GDC_ANTI_ALIAS; return true; } bool LSurface::HasAlpha(bool b) { DrawOnAlpha(false); if (b) { if (!pAlphaDC) { pAlphaDC = new LMemDC; } if (pAlphaDC && pMem) { if (!pAlphaDC->Create(pMem->x, pMem->y, CsIndex8)) { DeleteObj(pAlphaDC); } else { ClearFlag(Flags, GDC_DRAW_ON_ALPHA); } } } else { DeleteObj(pAlphaDC); } return (b == HasAlpha()); } bool LSurface::DrawOnAlpha(bool Draw) { bool Prev = DrawOnAlpha(); bool Swap = false; if (Draw) { if (!Prev && pAlphaDC && pMem) { Swap = true; SetFlag(Flags, GDC_DRAW_ON_ALPHA); PrevOp = Op(GDC_SET); } } else { if (Prev && pAlphaDC && pMem) { Swap = true; ClearFlag(Flags, GDC_DRAW_ON_ALPHA); Op(PrevOp); } } if (Swap) { LBmpMem *Temp = pMem; pMem = pAlphaDC->pMem; pAlphaDC->pMem = Temp; #if WINNATIVE OsBitmap hTmp = hBmp; hBmp = pAlphaDC->hBmp; pAlphaDC->hBmp = hTmp; OsPainter hP = hDC; hDC = pAlphaDC->hDC; pAlphaDC->hDC = hP; #endif } return Prev; } LApplicator *LSurface::CreateApplicator(int Op, LColourSpace Cs) { LApplicator *pA = NULL; if (!Cs) { if (pMem) { if (DrawOnAlpha()) { Cs = CsIndex8; } else if (pMem->Cs) { Cs = pMem->Cs; } else { LAssert(!"Memory context has no colour space..."); } } else if (IsScreen()) { Cs = GdcD->GetColourSpace(); } else { LAssert(!"No memory context to read colour space from."); } } pA = LApplicatorFactory::NewApp(Cs, Op); if (pA) { if (DrawOnAlpha()) { pA->SetSurface(pMem); } else { pA->SetSurface(pMem, pPalette, (pAlphaDC) ? pAlphaDC->pMem : 0); } pA->SetOp(Op); } else { LApplicatorFactory::NewApp(Cs, Op); const char *CsStr = LColourSpaceToString(Cs); LgiTrace("Error: GDeviceContext::CreateApplicator(%i, %x, %s) failed.\n", Op, Cs, CsStr); LAssert(!"No applicator"); } return pA; } bool LSurface::Applicator(LApplicator *pApplicator) { bool Status = false; if (pApplicator) { if (Flags & GDC_OWN_APPLICATOR) { DeleteObj(pApp) Flags &= ~GDC_OWN_APPLICATOR; } Flags &= ~GDC_CACHED_APPLICATOR; pApp = pApplicator; if (DrawOnAlpha()) { pApp->SetSurface(pMem); } else { pApp->SetSurface(pMem, pPalette, pAlphaDC->pMem); } pApp->SetPtr(0, 0); Status = true; } return Status; } LApplicator *LSurface::Applicator() { return pApp; } LRect LSurface::ClipRgn(LRect *Rgn) { LRect Old = Clip; if (Rgn) { Clip.x1 = MAX(0, Rgn->x1 - OriginX); Clip.y1 = MAX(0, Rgn->y1 - OriginY); Clip.x2 = MIN(X()-1, Rgn->x2 - OriginX); Clip.y2 = MIN(Y()-1, Rgn->y2 - OriginY); } else { Clip.x1 = 0; Clip.y1 = 0; Clip.x2 = X()-1; Clip.y2 = Y()-1; } return Old; } LRect LSurface::ClipRgn() { return Clip; } LColour LSurface::Colour(LSystemColour SysCol) { return Colour(LColour(SysCol)); } LColour LSurface::Colour(LColour c) { LAssert(pApp != NULL); LColour cPrev; uint32_t c32 = c.c32(); LColourSpace Cs = pApp->GetColourSpace(); switch (Cs) { case CsIndex8: { cPrev.Set(pApp->c, 8); if (c.GetColourSpace() == CsIndex8) { pApp->c = c.c8(); } else { LPalette *p = Palette(); if (p) // Colour pApp->c = p->MatchRgb(Rgb32To24(c32)); else // Grey scale pApp->c = c.GetGray(); } break; } case CsRgb15: case CsBgr15: { cPrev.Set(pApp->c, 15); pApp->c = Rgb32To15(c32); break; } case CsRgb16: case CsBgr16: { cPrev.Set(pApp->c, 16); pApp->c = Rgb32To16(c32); break; } case CsRgba32: case CsBgra32: case CsArgb32: case CsAbgr32: case CsRgbx32: case CsBgrx32: case CsXrgb32: case CsXbgr32: case CsRgba64: case CsBgra64: case CsArgb64: case CsAbgr64: { cPrev.Rgb(pApp->p32.r, pApp->p32.g, pApp->p32.b, pApp->p32.a); pApp->p32.r = R32(c32); pApp->p32.g = G32(c32); pApp->p32.b = B32(c32); pApp->p32.a = A32(c32); break; } case CsRgb24: case CsBgr24: case CsRgb48: case CsBgr48: { cPrev.Rgb(pApp->p24.r, pApp->p24.g, pApp->p24.b); pApp->p24.r = R32(c32); pApp->p24.g = G32(c32); pApp->p24.b = B32(c32); break; } default: LAssert(0); break; } #if defined LGI_CARBON { // Update the current colour of the drawing context if present. OsPainter Hnd = Handle(); if (Hnd) { float r = (float)c.r()/255.0; float g = (float)c.g()/255.0; float b = (float)c.b()/255.0; float a = (float)c.a()/255.0; CGContextSetRGBFillColor(Hnd, r, g, b, a); CGContextSetRGBStrokeColor(Hnd, r, g, b, a); } } #endif return cPrev; } COLOUR LSurface::Colour(COLOUR c, int Bits) { LColour n(c, Bits ? Bits : GetBits()); LColour Prev = Colour(n); switch (GetBits()) { case 8: return Prev.c8(); case 24: return Prev.c24(); case 32: return Prev.c32(); } return Prev.c32(); } int LSurface::Op(int NewOp, NativeInt Param) { int PrevOp = (pApp) ? pApp->GetOp() : GDC_SET; if (!pApp || PrevOp != NewOp) { COLOUR cCurrent = (pApp) ? Colour() : 0; if (Flags & GDC_OWN_APPLICATOR) { DeleteObj(pApp); } if (NewOp < GDC_CACHE_SIZE && !DrawOnAlpha()) { pApp = (pAppCache[NewOp]) ? pAppCache[NewOp] : pAppCache[NewOp] = CreateApplicator(NewOp, GetColourSpace()); Flags &= ~GDC_OWN_APPLICATOR; Flags |= GDC_CACHED_APPLICATOR; } else { pApp = CreateApplicator(NewOp, GetColourSpace()); Flags &= ~GDC_CACHED_APPLICATOR; Flags |= GDC_OWN_APPLICATOR; } if (pApp) { Colour(cCurrent); if (Param >= 0) pApp->SetVar(GAPP_ALPHA_A, Param); } else { printf("Error: Couldn't create applicator, Op=%i\n", NewOp); LAssert(0); } } return PrevOp; } LPalette *LSurface::Palette() { if (!pPalette && pMem && (pMem->Flags & GDC_ON_SCREEN) && pMem->Cs == CsIndex8) { pPalette = GdcD->GetSystemPalette(); // printf("Setting sys palette: %p\n", pPalette); if (pPalette) { Flags &= ~GDC_OWN_PALETTE; } } return pPalette; } void LSurface::Palette(LPalette *pPal, bool bOwnIt) { if (pPal == pPalette) { LgiTrace("%s:%i - Palette setting itself.\n", _FL); return; } // printf("LSurface::Palette %p %i\n", pPal, bOwnIt); if (pPalette && (Flags & GDC_OWN_PALETTE) != 0) { // printf("\tdel=%p\n", pPalette); delete pPalette; } pPalette = pPal; if (pPal && bOwnIt) { Flags |= GDC_OWN_PALETTE; // printf("\tp=%p own\n", pPalette); } else { Flags &= ~GDC_OWN_PALETTE; // printf("\tp=%p not-own\n", pPalette); } if (pApp) { pApp->SetSurface(pMem, pPalette, (pAlphaDC) ? pAlphaDC->pMem : 0); } } bool LSurface::GetVariant(const char *Name, LVariant &Dst, const char *Array) { switch (LStringToDomProp(Name)) { case SurfaceX: // Type: Int32 { Dst = X(); break; } case SurfaceY: // Type: Int32 { Dst = Y(); break; } case SurfaceBits: // Type: Int32 { Dst = GetBits(); break; } case SurfaceColourSpace: // Type: Int32 { Dst = GetColourSpace(); break; } case SurfaceIncludeCursor: // Type: Int32 { Dst = TestFlag(Flags, GDC_CAPTURE_CURSOR); break; } default: { return false; } } return true; } bool LSurface::SetVariant(const char *Name, LVariant &Value, const char *Array) { switch (LStringToDomProp(Name)) { case SurfaceIncludeCursor: { if (Value.CastInt32()) SetFlag(Flags, GDC_CAPTURE_CURSOR); else ClearFlag(Flags, GDC_CAPTURE_CURSOR); break; } default: break; } return false; } bool LSurface::CallMethod(const char *Name, LVariant *ReturnValue, LArray &Args) { return false; } bool LSurface::IsPreMultipliedAlpha() { return pMem ? pMem->PreMul() : false; } template void ConvertToPreMul(Px *src, int x) { REGISTER uchar *DivLut = Div255Lut; REGISTER Px *s = src; REGISTER Px *e = s + x; while (s < e) { s->r = DivLut[s->r * s->a]; s->g = DivLut[s->g * s->a]; s->b = DivLut[s->b * s->a]; s++; } } template void ConvertFromPreMul(Px *src, int x) { // REGISTER uchar *DivLut = Div255Lut; REGISTER Px *s = src; REGISTER Px *e = s + x; while (s < e) { if (s->a > 0 && s->a < 255) { s->r = (int) s->r * 255 / s->a; s->g = (int) s->g * 255 / s->a; s->b = (int) s->b * 255 / s->a; } s++; } } bool LSurface::ConvertPreMulAlpha(bool ToPreMul) { if (!pMem || !pMem->Base) return false; for (int y=0; yy; y++) { uint8_t *src = pMem->Base + (y * pMem->Line); if (ToPreMul) { switch (pMem->Cs) { #define ToPreMulCase(px) \ case Cs##px: ConvertToPreMul((L##px*)src, pMem->x); break ToPreMulCase(Rgba32); ToPreMulCase(Bgra32); ToPreMulCase(Argb32); ToPreMulCase(Abgr32); #undef ToPreMulCase default: break; } } else { switch (pMem->Cs) { #define FromPreMulCase(px) \ case Cs##px: ConvertFromPreMul((L##px*)src, pMem->x); break FromPreMulCase(Rgba32); FromPreMulCase(Bgra32); FromPreMulCase(Argb32); FromPreMulCase(Abgr32); #undef FromPreMulCase default: break; } } } pMem->PreMul(ToPreMul); return true; } template void MakeOpaqueRop32(Px *in, int len) { REGISTER Px *s = in; REGISTER Px *e = s + len; while (s < e) { s->a = 0xff; s++; } } template void MakeOpaqueRop64(Px *in, int len) { REGISTER Px *s = in; REGISTER Px *e = s + len; while (s < e) { s->a = 0xffff; s++; } } bool LSurface::MakeOpaque() { if (!pMem || !pMem->Base) return false; for (int y=0; yy; y++) { uint8_t *src = pMem->Base + (y * pMem->Line); switch (pMem->Cs) { #define OpaqueCase(px, sz) \ case Cs##px: MakeOpaqueRop##sz((L##px*)src, pMem->x); break OpaqueCase(Rgba32, 32); OpaqueCase(Bgra32, 32); OpaqueCase(Argb32, 32); OpaqueCase(Abgr32, 32); OpaqueCase(Rgba64, 64); OpaqueCase(Bgra64, 64); OpaqueCase(Argb64, 64); OpaqueCase(Abgr64, 64); #undef OpaqueCase default: break; } } return true; } diff --git a/src/common/Widgets/ToolBar.cpp b/src/common/Widgets/ToolBar.cpp --- a/src/common/Widgets/ToolBar.cpp +++ b/src/common/Widgets/ToolBar.cpp @@ -1,1760 +1,1756 @@ /* ** FILE: GToolbar.cpp ** AUTHOR: Matthew Allen ** DATE: 18/10/2001 ** DESCRIPTION: Toolbar classes ** ** Copyright (C) 2001, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Palette.h" #include "lgi/common/Notifications.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" #include "lgi/common/ToolBar.h" #include "lgi/common/ToolTip.h" #include "lgi/common/Menu.h" #define ToolBarHilightColour LC_HIGH #ifdef WIN32 HPALETTE GetSystemPalette(); bool BltBmpToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop); bool BltBmpToDc(HDC DestDC, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop); bool BltDcToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HDC SrcDC, int xSrc, int ySrc, DWORD dwRop); #define AttachButton(b) AddView(b); #else #define AttachButton(b) b->Attach(this); #endif enum IconCacheType { IconNormal, IconHilight, IconDisabled }; COLOUR Map(LSurface *pDC, COLOUR c); //////////////////////////////////////////////////////////////////////// LImageList *LLoadImageList(const char *File, int x, int y) { if (!File) return NULL; if (x < 0 || y < 0) { // Detect dimensions in the filename. auto leaf = LString(File).Split(DIR_STR).Last(); auto parts = leaf.RSplit(".", 1); auto last = parts[0].Split("-").Last(); auto dim = last.Split("x"); if (dim.Length() == 1) { auto i = dim[0].Strip().Int(); if (i > 0) x = y = (int)i; } else if (dim.Length() == 2) { auto X = dim[0].Strip().Int(), Y = dim[1].Strip().Int(); if (X > 0 && Y > 0) { x = (int)X; y = (int)Y; } } } auto Path = LFileExists(File) ? LString(File) : LFindFile(File); if (!Path) { LgiTrace("%s:%i - Couldn't find '%s'\n", _FL, File); return NULL; } LAutoPtr pDC(GdcD->Load(Path)); if (!pDC) { LgiTrace("%s:%i - Couldn't load '%s'\n", _FL, Path.Get()); return NULL; } return new LImageList(x, y, pDC); } LToolBar *LgiLoadToolbar(LViewI *Parent, const char *File, int x, int y) { LToolBar *Toolbar = new LToolBar; if (!Toolbar) return NULL; LString FileName = LFindFile(File); if (FileName) { bool Success = FileName && Toolbar->SetBitmap(FileName, x, y); if (!Success) { LgiMsg(Parent, "Can't load '%s' for the toolbar.\n" "This is probably because libpng/libjpeg is missing.", "LgiLoadToolbar", MB_OK, File); } } else { LgiMsg(Parent, "Can't find the graphic '%s' for the toolbar.\n" "You can find it in this program's archive.", "LgiLoadToolbar", MB_OK, File); } return Toolbar; } ///////////////////////////////////////////////////////////////////////////////////////////////////////// #define ImgLst_Empty 0x40000000 #define IgmLst_Add 0x80000000 class LImageListPriv { public: LImageList *ImgLst; int Sx, Sy; uint8_t DisabledAlpha; struct CacheDC : public LMemDC { bool Disabled; LColour Back; }; LArray Cache; LArray Bounds; CacheDC *GetCache(LColour Back, bool Disabled) { if (Back.IsTransparent()) return NULL; for (int i=0; iBack == Back && dc->Disabled == Disabled) return dc; } CacheDC *dc = new CacheDC; if (dc) { dc->Disabled = Disabled; dc->Back = Back; bool Status = dc->Create(ImgLst->X(), ImgLst->Y(), GdcD->GetColourSpace()); if (Status) { dc->Colour(dc->Back); dc->Rectangle(); dc->Op(GDC_ALPHA); if (Disabled) { LMemDC tmp(ImgLst->X(), ImgLst->Y(), System32BitColourSpace, LSurface::SurfaceRequireExactCs); tmp.Colour(0, 32); tmp.Rectangle(); tmp.Op(GDC_ALPHA); tmp.Blt(0, 0, ImgLst); tmp.SetConstantAlpha(DisabledAlpha); dc->Blt(0, 0, &tmp); } else { dc->Blt(0, 0, ImgLst); } Cache.Add(dc); } else { delete dc; LAssert(!"Create memdc failed."); } } return dc; } LImageListPriv(LImageList *imglst, int x, int y) { ImgLst = imglst; Sx = x; Sy = y; DisabledAlpha = 40; } ~LImageListPriv() { Cache.DeleteObjects(); } }; static bool HasPad(LColourSpace cs) { if (cs == CsRgbx32 || cs == CsBgrx32 || cs == CsXrgb32 || cs == CsXbgr32) return true; return false; } LImageList::LImageList(int x, int y, LSurface *pDC) { d = new LImageListPriv(this, x, y); uint32_t Transparent = 0; if (pDC && Create(pDC->X(), pDC->Y(), System32BitColourSpace, LSurface::SurfaceRequireExactCs)) { Colour(Transparent, 32); Rectangle(); int Old = Op(GDC_ALPHA); Blt(0, 0, pDC); Op(Old); #if 0 printf("Toolbar input image is %s, has_alpha=%i, has_pad=%i\n", LColourSpaceToString(pDC->GetColourSpace()), pDC->HasAlpha(), HasPad(pDC->GetColourSpace())); #endif + #if 1 + static int Idx = 0; + char s[256]; + + sprintf_s(s, sizeof(s), "imglst_%i.bmp", Idx++); + GdcD->Save(s, this); + + // sprintf_s(s, sizeof(s), "src_%i.bmp", Idx++); + // GdcD->Save(s, pDC); + #endif + if (pDC->GetBits() < 32 || HasPad(pDC->GetColourSpace())) { - // auto InCs = pDC->GetColourSpace(); - if (!pDC->HasAlpha()) { // No source alpha, do colour keying to create the alpha channel REG uint32_t *p = (uint32_t*)(*this)[0]; if (p) { uint32_t key = *p; for (int y=0; ya == 0) { p->r = 0; p->g = 0; p->b = 0; } p++; } } } - #if 0 - static int Idx = 0; - char s[256]; - sprintf_s(s, sizeof(s), "imglst_%i.bmp", Idx++); - WriteDC(s, this); - #endif + } } LImageList::~LImageList() { DeleteObj(d); } void LImageList::Draw(LSurface *pDC, int Dx, int Dy, int Image, LColour Background, bool Disabled) { if (!pDC) return; LRect rSrc; rSrc.ZOff(d->Sx-1, d->Sy-1); rSrc.Offset(Image * d->Sx, 0); LImageListPriv::CacheDC *Cache = d->GetCache(Background, Disabled); if (!Cache && Background.IsValid()) { LRect rDst; rDst.ZOff(d->Sx-1, d->Sy-1); rDst.Offset(Dx, Dy); pDC->Colour(Background); pDC->Rectangle(&rDst); pDC->Colour(LColour(255, 0, 0)); pDC->Line(rDst.x1, rDst.y1, rDst.x2, rDst.y2); pDC->Line(rDst.x2, rDst.y1, rDst.x1, rDst.y2); return; } if (pDC->SupportsAlphaCompositing()) { int Old = pDC->Op(GDC_ALPHA, Disabled ? d->DisabledAlpha : -1); pDC->Blt(Dx, Dy, this, &rSrc); pDC->Op(Old); } else if (Cache) { pDC->Blt(Dx, Dy, Cache, &rSrc); } else LAssert(!"Impl me."); } int LImageList::TileX() { return d->Sx; } int LImageList::TileY() { return d->Sy; } int LImageList::GetItems() { return X() / d->Sx; } void LImageList::Update(int Flags) { } uint8_t LImageList::GetDisabledAlpha() { return d->DisabledAlpha; } void LImageList::SetDisabledAlpha(uint8_t alpha) { d->DisabledAlpha = alpha; } LRect LImageList::GetIconRect(int Idx) { LRect r(0, 0, -1, -1); if (Idx >= 0 && Idx < GetItems()) { r.ZOff(d->Sx-1, d->Sy-1); r.Offset(Idx * d->Sx, 0); } return r; } LRect *LImageList::GetBounds() { if (!d->Bounds.Length() && (*this)[0]) { int Items = GetItems(); if (d->Bounds.Length(Items)) { for (int i=0; iBounds[i].ZOff(d->Sx - 1, d->Sy - 1); d->Bounds[i].Offset(i * d->Sx, 0); LFindBounds(this, &d->Bounds[i]); d->Bounds[i].Offset(-i * d->Sx, 0); } } } return &d->Bounds[0]; } ///////////////////////////////////////////////////////////////////////////////////////////////////////// class LToolBarPrivate { public: int Bx, By; int Sx, Sy; bool Vertical; bool Text; int LastIndex; bool OwnImgList; LImageList *ImgList; LFont *Font; LToolTip *Tip; // Customization menu LDom *CustomDom; const char *CustomProp; // bitmap cache LAutoPtr IconCache; LToolBarPrivate() { Bx = By = 16; Sx = Sy = 10; Vertical = false; Text = false; Font = 0; Tip = 0; CustomProp = 0; CustomDom = 0; } bool ShowTextLabels() { return (Text || !ImgList) && Font; } void FixSeparators(LToolBar *Tb) { // Fix up separators so that no 2 separators are next to each other. I.e. // all the buttons between them are switched off. LToolButton *Last = 0; bool HasVis = false; for (LViewI *v: Tb->IterateViews()) { LToolButton *Btn = dynamic_cast(v); if (Btn) { if (Btn->Separator()) { Btn->Visible(HasVis); if (HasVis) { Last = Btn; } HasVis = false; } else { HasVis |= Btn->Visible(); } } } if (Last) { Last->Visible(HasVis); } } void Customizable(LToolBar *Tb) { LVariant v; if (CustomDom) { CustomDom->GetValue(CustomProp, v); } char *o; if ((o = v.Str())) { auto t = LString(o).SplitDelimit(","); if (t.Length() >= 1) { Text = stricmp(t[0], "text") == 0; // Make all controls not visible. for (auto v: Tb->IterateViews()) { LToolButton *Btn = dynamic_cast(v); if (Btn) v->Visible(false); } // Set sub-set of ctrls visible according to saved ID list for (int i=1; i 0) Tb->SetCtrlVisible(Id, true); } FixSeparators(Tb); } } } }; ///////////////////////////////////////////////////////////////////////////////////////////////////////// struct LToolButtonPriv { LArray Text; }; LToolButton::LToolButton(int Bx, int By) { d = new LToolButtonPriv; Type = TBT_PUSH; SetId(IDM_NONE); Down = false; Clicked = false; Over = false; ImgIndex = -1; NeedsRightClick = false; LRect r(0, 0, Bx+1, By+1); SetPos(r); SetParent(0); TipId = -1; _BorderSize = 0; LResources::StyleElement(this); } LToolButton::~LToolButton() { d->Text.DeleteObjects(); delete d; } bool LToolButton::Name(const char *n) { bool s = LView::Name(n); d->Text.DeleteObjects(); return s; } void LToolButton::Layout() { auto Parent = GetParent(); LToolBar *ToolBar = dynamic_cast(Parent); if (!ToolBar) return; // Text auto s = Name(); if (!ToolBar->d->ShowTextLabels() || !s) return; // Write each word centered on a different line char Buf[256]; strcpy_s(Buf, sizeof(Buf), s); auto t = LString(Buf).SplitDelimit(" "); if (t.Length() < 3) { if (t.Length() > 0) d->Text.Add(new LDisplayString(ToolBar->d->Font, t[0])); if (t.Length() > 1) d->Text.Add(new LDisplayString(ToolBar->d->Font, t[1])); } else if (t.Length() == 3) { sprintf_s(Buf, sizeof(Buf), "%s %s", t[0].Get(), t[1].Get()); LDisplayString *d1 = new LDisplayString(ToolBar->d->Font, Buf); sprintf_s(Buf, sizeof(Buf), "%s %s", t[1].Get(), t[2].Get()); LDisplayString *d2 = new LDisplayString(ToolBar->d->Font, Buf); if (d1 && d2) { if (d1->X() < d2->X()) { DeleteObj(d2); d->Text.Add(d1); d->Text.Add(new LDisplayString(ToolBar->d->Font, t[2])); } else { DeleteObj(d1); d->Text.Add(new LDisplayString(ToolBar->d->Font, t[0])); d->Text.Add(d2); } } } } void LToolButton::OnPaint(LSurface *pDC) { LToolBar *Par = dynamic_cast(GetParent()); bool e = Enabled(); if (Par) { LRect p = GetClient(); #if 0 // def _DEBUG pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif LCssTools Tools(this); LColour cBack = Tools.GetBack(); auto BackImg = Tools.GetBackImage(); bool Hilight = e && Over; if (Hilight) cBack = cBack.Mix(LColour::White); // Draw Background if (GetId() >= 0) { // Draw border LColour Background; if (Down) // Sunken if the button is pressed LThinBorder(pDC, p, DefaultSunkenEdge); if (BackImg) { LDoubleBuffer Buf(pDC); Tools.PaintContent(pDC, p); if (Hilight) { // Draw translucent white over image... pDC->Op(GDC_ALPHA); pDC->Colour(LColour(255, 255, 255, 128)); pDC->Rectangle(&p); } } else { Background = cBack; pDC->Colour(Background); pDC->Box(&p); p.Inset(1, 1); } LRect IconPos; if (Par->d->ImgList) IconPos.Set(0, 0, Par->d->ImgList->TileX()-1, Par->d->ImgList->TileY()-1); else IconPos.ZOff(Par->d->Bx-1, Par->d->By-1); LRegion Unpainted(p); // Center the icon if (IconPos.X() < p.X() - 1) IconPos.Offset((p.X() - IconPos.X()) >> 1, 0); // Offset it if the button is pressed if (Down) IconPos.Offset(2, 2); // Draw any icon. if (ImgIndex >= 0) { if (Par->d->ImgList) { // Draw cached if (BackImg) { Par->d->ImgList->SetDisabledAlpha(0x60); } else if (pDC->SupportsAlphaCompositing()) { pDC->Colour(Background); pDC->Rectangle(&IconPos); } Par->d->ImgList->Draw(pDC, IconPos.x1, IconPos.y1, ImgIndex, Background, !e); Unpainted.Subtract(&IconPos); if (!BackImg) { // Fill in the rest of the area pDC->Colour(Background); for (LRect *r = Unpainted.First(); r; r = Unpainted.Next()) pDC->Rectangle(r); } } else { // Draw a red cross indicating no icons. pDC->Colour(Background); pDC->Rectangle(&p); pDC->Colour(LColour::Red); pDC->Line(IconPos.x1, IconPos.y1, IconPos.x2, IconPos.y2); pDC->Line(IconPos.x2, IconPos.y1, IconPos.x1, IconPos.y2); } } else if (!BackImg) { Tools.PaintContent(pDC, p); } // Text if (Par->d->ShowTextLabels()) { if (Name() && !d->Text.Length()) { Layout(); } if (d->Text.Length()) { // Write each word centered on a different line int Ty = Down + Par->d->By + 2; LColour a = Tools.GetFore(); LColour b = Tools.GetBack(); if (!e) a = b.Mix(a); Par->d->Font->Colour(a, b); for (int i=0; iText.Length(); i++) { LDisplayString *Ds = d->Text[i]; Ds->Draw(pDC, Down + ((X()-Ds->X())/2), Ty); Ty += Ds->Y(); } } } } else { // Separator int Px = X()-1; int Py = Y()-1; if (BackImg) Tools.PaintContent(pDC, p); else { pDC->Colour(cBack); pDC->Rectangle(); } LColour cLow = cBack.Mix(LColour::Black); LColour cHigh = cBack.Mix(LColour::White, 0.8f); if (X() > Y()) { int c = Y()/2-1; pDC->Colour(cLow); pDC->Line(2, c, Px-2, c); pDC->Colour(cHigh); pDC->Line(2, c+1, Px-2, c+1); } else { int c = X()/2-1; pDC->Colour(cLow); pDC->Line(c, 2, c, Py-2); pDC->Colour(cHigh); pDC->Line(c+1, 2, c+1, Py-2); } } } #if 0 // def _DEBUG pDC->Colour(LColour(255, 0, 255)); pDC->Box(); #endif } void LToolButton::Image(int i) { if (ImgIndex != i) { ImgIndex = i; Invalidate(); } } void LToolButton::Value(int64 b) { switch (Type) { case TBT_PUSH: { // do nothing... can't set value break; } case TBT_TOGGLE: { if (Value() != b) { Down = b != 0; Invalidate(); SendNotify(LNotifyValueChanged); } break; } case TBT_RADIO: { if (GetParent() && b) { // Clear any other radio buttons that are down auto it = GetParent()->IterateViews(); ssize_t CurIdx = it.IndexOf(this); if (CurIdx >= 0) { for (ssize_t i=CurIdx-1; i>=0; i--) { LToolButton *But = dynamic_cast(it[i]); if (But->Separator()) break; if (But->Type == TBT_RADIO && But->Down) But->Value(false); } for (size_t i=CurIdx+1; i(it[i]); if (But->Separator()) break; if (But->Type == TBT_RADIO && But->Down) But->Value(false); } } } Down = b != 0; if (GetParent()) { GetParent()->Invalidate(); SendNotify(LNotifyValueChanged); } break; } } } void LToolButton::SendCommand() { LToolBar *t = dynamic_cast(GetParent()); if (t) t->OnButtonClick(this); else printf("%s:%i - Error: parent not toolbar.\n", _FL); } void LToolButton::OnMouseClick(LMouse &m) { LToolBar *ToolBar = dynamic_cast(GetParent()); #if 0 printf("tool button click %i,%i down=%i, left=%i right=%i middle=%i, ctrl=%i alt=%i shift=%i Double=%i\n", m.x, m.y, m.Down(), m.Left(), m.Right(), m.Middle(), m.Ctrl(), m.Alt(), m.Shift(), m.Double()); #endif if (m.IsContextMenu()) { if (!NeedsRightClick && ToolBar && ToolBar->IsCustomizable()) { m.ToScreen(); ToolBar->ContextMenu(m); } else { SendNotify(LNotification(m)); } } else if (m.Left()) { // left click action... if (GetId() >= 0 && Enabled()) { switch (Type) { case TBT_PUSH: { bool Old = Down; Clicked = m.Down(); Capture(m.Down()); if (Old && IsOver(m)) { SendCommand(); SendNotify(LNotifyActivate); } Down = m.Down(); if (Old != Down) { Invalidate(); } break; } case TBT_TOGGLE: { if (m.Down()) { if (m.Left()) { Value(!Down); SendCommand(); } SendNotify(LNotifyActivate); } break; } case TBT_RADIO: { if (m.Down()) { if (!Down && m.Left()) { Value(true); SendCommand(); } SendNotify(LNotifyActivate); } break; } } } } } void LToolButton::OnMouseEnter(LMouse &m) { if (!Separator() && Enabled()) { Over = true; Invalidate(); } if (Clicked) { Value(true); Invalidate(); } else { LToolBar *Bar = dynamic_cast(GetParent()); if (Bar) { Bar->OnMouseEnter(m); if (!Bar->TextLabels() && Bar->d->Tip && TipId < 0) { TipId = Bar->d->Tip->NewTip(Name(), GetPos()); } } if (GetParent()) { LToolBar *ToolBar = dynamic_cast(GetParent()); if (ToolBar) ToolBar->PostDescription(this, Name()); } } } void LToolButton::OnMouseMove(LMouse &m) { } void LToolButton::OnMouseExit(LMouse &m) { if (Over) { Over = false; Invalidate(); } if (Clicked) { Value(false); Invalidate(); } else if (GetParent()) { LToolBar *ToolBar = dynamic_cast(GetParent()); if (ToolBar) ToolBar->PostDescription(this, ""); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////// LToolBar::LToolBar() { d = new LToolBarPrivate; Name("LGI_Toolbar"); _BorderSize = 1; _IsToolBar = 1; // Setup tool button font LFontType SysFontType; if (SysFontType.GetSystemFont("Small")) { d->Font = SysFontType.Create(); if (d->Font) { d->Font->PointSize(MIN(d->Font->PointSize(), LSysFont->PointSize())); d->Font->Colour(L_TEXT); d->Font->Bold(false); d->Font->Transparent(true); } } d->LastIndex = 0; d->OwnImgList = false; d->ImgList = 0; GetCss(true)->BackgroundColor(LColour(L_MED).Mix(LColour::Black, 0.05f)); LResources::StyleElement(this); } LToolBar::~LToolBar() { DeleteObj(d->Tip); if (d->OwnImgList) DeleteObj(d->ImgList); DeleteObj(d->Font); DeleteObj(d); } void LToolBar::OnCreate() { #ifndef WIN32 AttachChildren(); #endif } int LToolBar::GetBx() { return d->Bx; } int LToolBar::GetBy() { return d->By; } void LToolBar::ContextMenu(LMouse &m) { if (IsCustomizable()) { LSubMenu *Sub = new LSubMenu; if (Sub) { int n = 1; for (auto it = Children.begin(); it != Children.end(); it++, n++) { LViewI *v = *it; LToolButton *Btn = dynamic_cast(v); if (Btn && Btn->Separator()) { Sub->AppendSeparator(); } else { auto Item = Sub->AppendItem(v->Name(), n, true); if (Item) { Item->Checked(v->Visible()); } } } Sub->AppendSeparator(); auto Txt = Sub->AppendItem(LLoadString(L_TOOLBAR_SHOW_TEXT, "Show Text Labels"), 1000, true); Txt->Checked(d->Text); bool Save = false; int Pick = Sub->Float(this, m); switch (Pick) { case 1000: { d->Text = !d->Text; Save = true; SendNotify(LNotifyTableLayoutRefresh); break; } default: { LViewI *Ctrl = Children[Pick - 1]; if (Ctrl) { Ctrl->Visible(!Ctrl->Visible()); Save = true; } break; } } DeleteObj(Sub); if (Save) { LStringPipe p(256); p.Push((char*) (d->Text ? "text" : "no")); for (auto v: Children) { if (v->Visible()) { p.Print(",%i", v->GetId()); } } char *o = p.NewStr(); if (o) { if (d->CustomDom) { LVariant v(o); d->CustomDom->SetValue(d->CustomProp, v); } DeleteArray(o); } d->FixSeparators(this); for (auto v: Children) { LToolButton *b = dynamic_cast(v); if (b && b->TipId >= 0) { d->Tip->DeleteTip(b->TipId); b->TipId = -1; } } GetWindow()->PourAll(); } } } } bool LToolBar::IsCustomizable() { return d->CustomDom != 0 && d->CustomProp; } void LToolBar::Customizable(LDom *Store, const char *Option) { d->CustomDom = Store; d->CustomProp = Option; d->Customizable(this); } bool LToolBar::IsVertical() { return d->Vertical; } void LToolBar::IsVertical(bool v) { d->Vertical = v; } bool LToolBar::TextLabels() { return d->Text; } void LToolBar::TextLabels(bool i) { d->Text = i; } LFont *LToolBar::GetFont() { return d->Font; } bool LToolBar::OnLayout(LViewLayoutInfo &Inf) { if (Inf.Width.Min == 0) { // Calc width LRegion r(0, 0, 10000, 10000); Pour(r); Inf.Width.Min = X(); Inf.Width.Max = X(); } else { // Calc height Inf.Height.Min = Y(); Inf.Height.Max = Y(); } return true; } #define GetBorderSpacing() GetCss() && GetCss()->BorderSpacing().IsValid() ? \ GetCss()->BorderSpacing().ToPx(X(), GetFont()) : \ 1 bool LToolBar::Pour(LRegion &r) { int BorderSpacing = GetBorderSpacing(); int EndX = 0; int EndY = 0; int MaxDim = 0; LCssTools Tools(this); LRect Border = Tools.GetBorder(r); LRect Padding = Tools.GetPadding(r); int PosX = BorderSpacing + Border.x1 + Padding.x1; int PosY = BorderSpacing + Border.y1 + Padding.y1; LRect ButPos; for (auto But: Children) { if (But->Visible()) { int Tx = 0, Ty = 0; LToolButton *Btn = dynamic_cast(But); if (d->ShowTextLabels()) { if (Btn) { if (Btn->d->Text.Length() == 0) { Btn->Layout(); } for (int i=0; id->Text.Length(); i++) { LDisplayString *Ds = Btn->d->Text[i]; Tx = MAX(Ds->X() + 4, Tx); Ty += Ds->Y(); } } } ButPos = But->GetPos(); if (Btn) { if (Btn->Separator()) { // This will be stretched out later by the code that makes // everything the same height. ButPos.ZOff(BORDER_SEPARATOR+1, BORDER_SEPARATOR+1); } else { if (Btn->Image() >= 0) { // Set initial size to the icon size ButPos.ZOff(d->Bx + 2, d->By + 2); } else { // Otherwise default to text size if (d->Vertical) ButPos.ZOff(0, 7); else ButPos.ZOff(7, 0); } Tx += 4; if (ButPos.X() < Tx) { // Make button wider for text label ButPos.x2 = Tx - 1; } ButPos.y2 += Ty; } } if (d->Vertical) MaxDim = MAX(MaxDim, ButPos.X()); else MaxDim = MAX(MaxDim, ButPos.Y()); ButPos.Offset(PosX - ButPos.x1, PosY - ButPos.y1); if (But->GetId() == IDM_BREAK) { ButPos.ZOff(0, 0); if (d->Vertical) { PosX = MaxDim; PosY = BORDER_SHADE + BorderSpacing; } else { PosX = BORDER_SHADE + BorderSpacing; PosY = MaxDim; } } else { if (d->Vertical) PosY = ButPos.y2 + BorderSpacing; else PosX = ButPos.x2 + BorderSpacing; } But->SetPos(ButPos); } else { LRect p(-100, -100, -90, -90); But->SetPos(p); } } for (auto w: Children) { LRect p = w->GetPos(); if (d->Vertical) { if (w->X() < MaxDim) { p.x2 = p.x1 + MaxDim - 1; w->SetPos(p); } } else { if (w->Y() < MaxDim) { p.y2 = p.y1 + MaxDim - 1; w->SetPos(p); } } EndX = MAX(EndX, p.x2); EndY = MAX(EndY, p.y2); } d->Sx = EndX + BorderSpacing; d->Sy = EndY + BorderSpacing; d->Sx += Border.x2 + Padding.x2; d->Sy += Border.y2 + Padding.y2; LRect n; n.ZOff(MAX(7, d->Sx), MAX(7, d->Sy)); LRect *Best = FindLargestEdge(r, GV_EDGE_TOP); if (Best) { n.Offset(Best->x1, Best->y1); n.Bound(Best); SetPos(n, true); // _Dump(); return true; } else LgiTrace("%s:%i - No best pos.\n", _FL); return false; } void LToolBar::OnButtonClick(LToolButton *Btn) { LViewI *v = GetNotify() ? GetNotify() : GetParent(); if (v && Btn) { int Id = Btn->GetId(); if (v->PostEvent(M_COMMAND, (LMessage::Param) Id #if LGI_VIEW_HANDLE , (LMessage::Param) Handle() #endif )) ; //printf("Send M_COMMAND(%i)\n", Id); else printf("%s:%i - Failed to send M_COMMAND.\n", _FL); } else printf("%s:%i - Ptr error: %p %p\n", _FL, v, Btn); } int LToolBar::PostDescription(LView *Ctrl, const char *Text) { if (GetParent()) { return GetParent()->PostEvent(M_DESCRIBE, (LMessage::Param) Ctrl, (LMessage::Param) Text); } return 0; } LMessage::Result LToolBar::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_CHANGE: { if (GetParent()) return GetParent()->OnEvent(Msg); LAutoPtr note((LNotification*)Msg->B()); break; } } return LView::OnEvent(Msg); } void LToolBar::OnPaint(LSurface *pDC) { LRect c = GetClient(); LCssTools Tools(this); Tools.PaintBorder(pDC, c); Tools.PaintPadding(pDC, c); Tools.PaintContent(pDC, c); } void LToolBar::OnMouseClick(LMouse &m) { } void LToolBar::OnMouseEnter(LMouse &m) { if (!d->Tip) { d->Tip = new LToolTip; if (d->Tip) { d->Tip->Attach(this); } } } void LToolBar::OnMouseExit(LMouse &m) { } void LToolBar::OnMouseMove(LMouse &m) { } bool LToolBar::SetBitmap(char *File, int bx, int by) { - bool Status = false; - - LSurface *pDC = GdcD->Load(File); - if (pDC) - { - Status = SetDC(pDC, bx, by); - DeleteObj(pDC); - } - - return Status; + LAutoPtr pDC(GdcD->Load(File)); + return pDC ? SetDC(pDC, bx, by) : false; } bool LToolBar::SetDC(LSurface *pNewDC, int bx, int by) { if (d->OwnImgList) { DeleteObj(d->ImgList); } d->Bx = bx; d->By = by; if (pNewDC) { d->ImgList = new LImageList(bx, by, pNewDC); if (d->ImgList) { d->OwnImgList = true; return true; } } return false; } LImageList *LToolBar::GetImageList() { return d->ImgList; } bool LToolBar::SetImageList(LImageList *l, int bx, int by, bool Own) { if (d->OwnImgList) DeleteObj(d->ImgList); d->OwnImgList = Own; d->Bx = bx; d->By = by; d->ImgList = l; return d->ImgList != 0; } LToolButton *LToolBar::AppendButton(const char *Tip, int Id, int Type, int Enabled, int IconId) { // bool HasIcon = IconId != TOOL_ICO_NONE; LToolButton *But = new LToolButton(d->Bx, d->By); if (But) { But->Name(Tip); But->SetId(Id); But->Type = Type; But->Enabled(Enabled != 0); if (IconId >= 0) { But->ImgIndex = IconId; } else if (IconId == TOOL_ICO_NEXT) { But->ImgIndex = d->LastIndex++; } else if (IconId == TOOL_ICO_NONE) { But->ImgIndex = -1; } AttachButton(But); } return But; } bool LToolBar::AppendSeparator() { LToolButton *But = new LToolButton(d->Bx, d->By); if (But) { But->SetId(IDM_SEPARATOR); AttachButton(But); return true; } return false; } bool LToolBar::AppendBreak() { LToolButton *But = new LToolButton(d->Bx, d->By); if (But) { But->SetId(IDM_BREAK); But->SetParent(this); AttachButton(But); return true; } return false; } bool LToolBar::AppendControl(LView *Ctrl) { bool Status = false; if (Ctrl) { Ctrl->SetParent(this); AttachButton(Ctrl); Status = true; } return Status; } void LToolBar::Empty() { for (auto But: Children) { DeleteObj(But); } } #ifdef MAC bool LToolBar::Attach(LViewI *parent) { return LLayout::Attach(parent); } #endif /////////////////////////////////////////////////////////////////////// COLOUR Map(LSurface *pDC, COLOUR c) { if (pDC && pDC->GetBits() <= 8) { if (pDC->IsScreen()) { c = CBit(24, c); } #ifdef WIN32 else { HPALETTE hPal = GetSystemPalette(); if (hPal) { c = GetNearestPaletteIndex(hPal, c); DeleteObject(hPal); } } #endif } return c; } #ifdef WIN32 HPALETTE GetSystemPalette() { HPALETTE hPal = 0; LOGPALETTE *Log = (LOGPALETTE*) new uchar[sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * 255)]; if (Log) { Log->palVersion = 0x300; Log->palNumEntries = 256; HDC hDC = CreateCompatibleDC(0); GetSystemPaletteEntries(hDC, 0, 256, Log->palPalEntry); DeleteDC(hDC); hPal = CreatePalette(Log); } return hPal; } bool BltBmpToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop) { bool Status = false; HDC DestDC = CreateCompatibleDC(0); HDC SrcDC = CreateCompatibleDC(0); if (DestDC && SrcDC) { hDest = (HBITMAP) SelectObject(DestDC, hDest); hSrc = (HBITMAP) SelectObject(SrcDC, hSrc); Status = BitBlt(DestDC, xDst, yDst, cx, cy, SrcDC, xSrc, ySrc, dwRop) != 0; hDest = (HBITMAP) SelectObject(DestDC, hDest); hSrc = (HBITMAP) SelectObject(SrcDC, hSrc); } if (DestDC) { DeleteDC(DestDC); } if (SrcDC) { DeleteDC(SrcDC); } return Status; } bool BltBmpToDc(HDC DestDC, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop) { bool Status = false; HDC SrcDC = CreateCompatibleDC(0); if (DestDC && SrcDC) { hSrc = (HBITMAP) SelectObject(SrcDC, hSrc); Status = BitBlt(DestDC, xDst, yDst, cx, cy, SrcDC, xSrc, ySrc, dwRop) != 0; hSrc = (HBITMAP) SelectObject(SrcDC, hSrc); } if (SrcDC) { DeleteDC(SrcDC); } return Status; } bool BltDcToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HDC SrcDC, int xSrc, int ySrc, DWORD dwRop) { bool Status = false; HDC DestDC = CreateCompatibleDC(0); if (DestDC && SrcDC) { hDest = (HBITMAP) SelectObject(DestDC, hDest); Status = BitBlt(DestDC, xDst, yDst, cx, cy, SrcDC, xSrc, ySrc, dwRop) != 0; hDest = (HBITMAP) SelectObject(DestDC, hDest); } if (DestDC) { DeleteDC(DestDC); } return Status; } #endif diff --git a/src/common/Widgets/Tree.cpp b/src/common/Widgets/Tree.cpp --- a/src/common/Widgets/Tree.cpp +++ b/src/common/Widgets/Tree.cpp @@ -1,2250 +1,2261 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Tree.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Palette.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" #define TREE_BLOCK 16 #define DRAG_THRESHOLD 4 #define DRAG_SCROLL_EDGE 20 #define DRAG_SCROLL_X 8 #define DRAG_SCROLL_Y 1 #define TreeUpdateNow false #define TREELOCK LMutex::Auto Lck(d, _FL); #define ForAll(Items) for (auto c : Items) ////////////////////////////////////////////////////////////////////////////// // Private class definitions for binary compatibility class LTreePrivate : public LMutex { public: // Private data int LineFlags[4]; bool LayoutDirty; LPoint Limit; LPoint LastClick; LPoint DragStart; int DragData; LMemDC *IconCache; bool InPour; int64 DropSelectTime; int8 IconTextGap; int LastLayoutPx; LMouse *CurrentClick; LTreeItem *ScrollTo; // Visual style LTree::ThumbStyle Btns; bool JoiningLines; // Pointers into items... be careful to clear when deleting items... LTreeItem *LastHit; List Selection; LTreeItem *DropTarget; LTreePrivate() : LMutex("LTreePrivate") { CurrentClick = NULL; LastLayoutPx = -1; DropSelectTime = 0; InPour = false; LastHit = 0; DropTarget = 0; IconCache = 0; LayoutDirty = true; IconTextGap = 0; ScrollTo = NULL; Btns = LTree::TreeTriangle; JoiningLines = false; } ~LTreePrivate() { DeleteObj(IconCache); } }; class LTreeItemPrivate { LArray Ds; LArray ColPx; public: LTreeItem *Item; LRect Pos; LRect Thumb; LRect Text; LRect Icon; bool Open; bool Selected; bool Visible; bool Last; int Depth; LTreeItemPrivate(LTreeItem *it) { Item = it; Ds = NULL; Pos.ZOff(-1, -1); Open = false; Selected = false; Visible = false; Last = false; Depth = 0; Text.ZOff(-1, -1); Icon.ZOff(-1, -1); } ~LTreeItemPrivate() { Ds.DeleteObjects(); } LDisplayString *GetDs(int Col, int FixPx) { if (!Ds[Col]) { - LFont *f = Item->GetTree() ? Item->GetTree()->GetFont() : LSysFont; - Ds[Col] = new LDisplayString(f, Item->GetText(Col)); - if (Ds[Col]) - { - ColPx[Col] = Ds[Col]->X(); - if (FixPx > 0) + LFont *f = Item->GetTree() ? Item->GetTree()->GetFont() : LSysFont; + auto txt = Item->GetText(Col); + if (txt) + { + Ds[Col] = new LDisplayString(f, Item->GetText(Col)); + if (Ds[Col]) { - Ds[Col]->TruncateWithDots(FixPx); + ColPx[Col] = Ds[Col]->X(); + if (FixPx > 0) + { + Ds[Col]->TruncateWithDots(FixPx); + } } } } return Ds[Col]; } void ClearDs(int Col = -1) { if (Col >= 0) { delete Ds[Col]; Ds[Col] = NULL; } else { Ds.DeleteObjects(); } } int GetColumnPx(int Col) { int BasePx = 0; GetDs(Col, 0); if (Col == 0) { BasePx = (Depth + 1) * TREE_BLOCK; } return ColPx[Col] + BasePx; } }; ////////////////////////////////////////////////////////////////////////////// LTreeNode::LTreeNode() { Parent = NULL; Tree = NULL; } LTreeNode::~LTreeNode() { } void LTreeNode::SetLayoutDirty() { Tree->d->LayoutDirty = true; } void LTreeNode::_Visible(bool v) { for (LTreeItem *i=GetChild(); i; i=i->GetNext()) { LAssert(i != this); i->OnVisible(v); i->_Visible(v); } } void LTreeNode::_ClearDs(int Col) { List::I it = Items.begin(); for (LTreeItem *c = *it; c; c = *++it) { c->_ClearDs(Col); } } LItemContainer *LTreeItem::GetContainer() { return Tree; } LTreeItem *LTreeNode::Insert(LTreeItem *Obj, ssize_t Idx) { LAssert(Obj != this); if (Obj && Obj->Tree) Obj->Remove(); LTreeItem *NewObj = Obj ? Obj : new LTreeItem; if (NewObj) { NewObj->Parent = Item(); NewObj->_SetTreePtr(Tree); Items.Delete(NewObj); Items.Insert(NewObj, Idx); if (Tree) { Tree->d->LayoutDirty = true; if (Pos() && Pos()->Y() > 0) Tree->_UpdateBelow(Pos()->y1); else Tree->Invalidate(); } } return NewObj; } void LTreeNode::Detach() { if (Parent) { LTreeItem *It = Item(); if (It) { LAssert(Parent->Items.HasItem(It)); Parent->Items.Delete(It); } Parent = 0; } if (Tree) { Tree->d->LayoutDirty = true; Tree->Invalidate(); } if (Item()) Item()->_SetTreePtr(0); } void LTreeNode::Remove() { int y = 0; if (Parent) { LTreeItem *i = Item(); if (i && i->IsRoot()) { LRect *p = Pos(); LTreeItem *Prev = GetPrev(); if (Prev) { y = Prev->d->Pos.y1; } else { y = p->y1; } } else { y = Parent->d->Pos.y1; } } LTree *t = Tree; if (Item()) Item()->_Remove(); if (t) { t->_UpdateBelow(y); } } bool LTreeNode::IsRoot() { return Parent == 0 || (LTreeNode*)Parent == (LTreeNode*)Tree; } size_t LTreeNode::Length() { return Items.Length(); } bool LTreeNode::HasItem(LTreeItem *obj, bool recurse) { if (!obj) return false; if (this == (LTreeNode*)obj) return true; for (auto i: Items) { if (i == obj) return true; if (recurse && i->HasItem(obj, recurse)) return true; } return false; } int LTreeNode::ForEach(std::function Fn) { int Count = 0; for (auto t : Items) { Fn(t); Count += t->ForEach(Fn); } return Count + 1; } ssize_t LTreeNode::IndexOf() { if (Parent) { return Parent->Items.IndexOf(Item()); } else if (Tree) { return Tree->Items.IndexOf(Item()); } return -1; } LTreeItem *LTreeNode::GetChild() { return Items.Length() ? Items[0] : NULL; } LTreeItem *LTreeNode::GetPrev() { List *l = (Parent) ? &Parent->Items : (Tree) ? &Tree->Items : 0; if (l) { ssize_t Index = l->IndexOf(Item()); if (Index >= 0) { return l->ItemAt(Index-1); } } return 0; } LTreeItem *LTreeNode::GetNext() { List *l = (Parent) ? &Parent->Items : (Tree) ? &Tree->Items : 0; if (l) { ssize_t Index = l->IndexOf(Item()); if (Index >= 0) { return l->ItemAt(Index+1); } } return 0; } ////////////////////////////////////////////////////////////////////////////// LTreeItem::LTreeItem() { d = new LTreeItemPrivate(this); } LTreeItem::~LTreeItem() { if (Tree) { if (Tree->d->DropTarget == this) Tree->d->DropTarget = 0; if (Tree->d->LastHit == this) Tree->d->LastHit = 0; if (Tree->IsCapturing()) Tree->Capture(false); } int y = 0; LTree *t = 0; if (Parent && (LTreeNode*)Parent != (LTreeNode*)Tree) { t = Tree; y = Parent->d->Pos.y1; } else if ((LTreeNode*)this != (LTreeNode*)Tree) { t = Tree; LTreeItem *p = GetPrev(); if (p) y = p->d->Pos.y1; else y = d->Pos.y1; } _Remove(); while (Items.Length()) { auto It = Items.begin(); delete *It; } DeleteObj(d); if (t) t->_UpdateBelow(y); } int LTreeItem::GetColumnSize(int Col) { int Px = d->GetColumnPx(Col); if (Expanded()) { ForAll(Items) { int ChildPx = c->GetColumnSize(Col); Px = MAX(ChildPx, Px); } } return Px; } LRect *LTreeItem::Pos() { return &d->Pos; } LPoint LTreeItem::_ScrollPos() { LPoint p; if (Tree) p = Tree->_ScrollPos(); return p; } LRect *LTreeItem::_GetRect(LTreeItemRect Which) { switch (Which) { case TreeItemPos: return &d->Pos; case TreeItemThumb: return &d->Thumb; case TreeItemText: return &d->Text; case TreeItemIcon: return &d->Icon; } return 0; } bool LTreeItem::IsDropTarget() { LTree *t = GetTree(); if (t && t->d && t->d->DropTarget == this) return true; return false; } LRect *LTreeItem::GetPos(int Col) { if (!d->Pos.Valid() && Tree) Tree->_Pour(); static LRect r; r = d->Pos; if (Col >= 0) { LItemColumn *Column = 0; int Cx = Tree->GetImageList() ? 16 : 0; for (int c=0; cColumnAt(c); if (Column) { Cx += Column->Width(); } } Column = Tree->ColumnAt(Col); if (Column) { r.x1 = Cx; r.x2 = Cx + Column->Width() - 1; } } return &r; } void LTreeItem::_RePour() { if (Tree) Tree->_Pour(); } void LTreeItem::ScrollTo() { if (!Tree) return; if (Tree->VScroll) { LRect c = Tree->GetClient(); LRect p = d->Pos; int y = d->Pos.Y() ? d->Pos.Y() : 16; p.Offset(0, (int) (-Tree->VScroll->Value() * y)); if (p.y1 < c.y1) { int Lines = (c.y1 - p.y1 + y - 1) / y; Tree->VScroll->Value(Tree->VScroll->Value() - Lines); } else if (p.y2 > c.y2) { int Lines = (p.y2 - c.y2 + y - 1) / y; Tree->VScroll->Value(Tree->VScroll->Value() + Lines); } } else { Tree->d->ScrollTo = this; if (Tree->IsAttached()) Tree->PostEvent(M_SCROLL_TO); } } void LTreeItem::_SetTreePtr(LTree *t) { if (Tree && !t) { // Clearing tree pointer, must remove all references to this item that // the tree might still have. if (d->Selected) { Tree->d->Selection.Delete(this); d->Selected = false; } if (Tree->d->LastHit == this) { Tree->d->LastHit = 0; } if (Tree->d->DropTarget == this) { Tree->d->DropTarget = 0; } } Tree = t; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) { i->_SetTreePtr(t); } } void LTreeItem::_Remove() { if ((LTreeNode*)this != (LTreeNode*)Tree) { if (Parent) { LAssert(Parent->Items.HasItem(this)); Parent->Items.Delete(this); } else if (Tree) { LAssert(Tree->Items.HasItem(this)); Tree->Items.Delete(this); } if (Tree) { LAssert(Tree->d != NULL); Tree->d->LayoutDirty = true; if (Tree->IsCapturing()) Tree->Capture(false); } } Parent = 0; _SetTreePtr(0); } void LTreeItem::_PourText(LPoint &Size) { LFont *f = Tree ? Tree->GetFont() : LSysFont; auto *Txt = GetText(); #if defined(_WIN64) && defined(_DEBUG) if ((void*)Txt == (void*)0xfeeefeeefeeefeee || (void*)Txt == (void*)0xcdcdcdcdcdcdcdcd) { LAssert(!"Yeah nah..."); } #endif LDisplayString ds(f, Txt); Size.x = ds.X() + 4; Size.y = 0; } void LTreeItem::_PaintText(LItem::ItemPaintCtx &Ctx) { const char *Text = GetText(); if (Text) { LDisplayString *Ds = d->GetDs(0, d->Text.X()); LFont *f = Tree ? Tree->GetFont() : LSysFont; int Tab = f->TabSize(); f->TabSize(0); f->Transparent(false); f->Colour(Ctx.Fore, Ctx.TxtBack); if (Ds) { Ds->Draw(Ctx.pDC, d->Text.x1 + 2, d->Text.y1 + 1, &d->Text); if (Ctx.x2 > d->Text.x2) { LRect r = Ctx; r.x1 = d->Text.x2 + 1; Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&r); } } f->TabSize(Tab); } else { Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&Ctx); } } void LTreeItem::_Pour(LPoint *Limit, int ColumnPx, int Depth, bool Visible) { auto css = GetCss(false); auto display = css ? css->Display() != LCss::DispNone : true; d->Visible = display && Visible; d->Depth = Depth; if (d->Visible) { LPoint TextSize; _PourText(TextSize); LImageList *ImgLst = Tree->GetImageList(); // int IconX = (ImgLst && GetImage() >= 0) ? ImgLst->TileX() + Tree->d->IconTextGap : 0; int IconY = (ImgLst && GetImage() >= 0) ? ImgLst->TileY() : 0; int Height = MAX(TextSize.y, IconY); if (!Height) Height = 16; LDisplayString *Ds = d->GetDs(0, 0); d->Pos.ZOff(ColumnPx - 1, (Ds ? MAX(Height, Ds->Y()) : Height) - 1); d->Pos.Offset(0, Limit->y); if (!d->Pos.Valid()) { printf("Invalid pos: %s, ColumnPx=%i\n", d->Pos.GetStr(), ColumnPx); } Limit->x = MAX(Limit->x, d->Pos.x2 + 1); Limit->y = MAX(Limit->y, d->Pos.y2 + 1); } else { d->Pos.ZOff(-1, -1); } LTreeItem *n; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=n) { n = *++it; i->d->Last = n == 0; i->_Pour(Limit, ColumnPx, Depth+1, d->Open && d->Visible); } } void LTreeItem::_ClearDs(int Col) { d->ClearDs(Col); LTreeNode::_ClearDs(Col); } const char *LTreeItem::GetText(int i) { return Str[i]; } bool LTreeItem::SetText(const char *s, int i) { LAutoPtr Lck; if (Tree) Lck.Reset(new LMutex::Auto(Tree->d, -1, _FL)); Str[i] = s; if (Tree) Update(); return true; } int LTreeItem::GetImage(int Flags) { return Sys_Image; } void LTreeItem::SetImage(int i) { Sys_Image = i; } void LTreeItem::Update() { if (Tree) { LRect p = d->Pos; p.x2 = 10000; d->ClearDs(); Tree->_Update(&p, TreeUpdateNow); } } bool LTreeItem::Select() { return d->Selected; } void LTreeItem::Select(bool b) { if (d->Selected != b) { d->Selected = b; if (b) { LTreeItem *p = this; while ((p = p->GetParent())) { p->Expanded(true); } } Update(); if (b && Tree) { Tree->_OnSelect(this); Tree->OnItemSelect(this); } } } bool LTreeItem::Expanded() { return d->Open; } void LTreeItem::Expanded(bool b) { if (d->Open != b) { d->Open = b; if (Items.Length() > 0) { if (Tree) { Tree->d->LayoutDirty = true; Tree->_UpdateBelow(d->Pos.y1); } OnExpand(b); } } } void LTreeItem::OnExpand(bool b) { _Visible(b); } LTreeItem *LTreeItem::_HitTest(int x, int y, bool Debug) { LTreeItem *Status = 0; if (d->Pos.Overlap(x, y) && x > (d->Depth*TREE_BLOCK)) { Status = this; } if (d->Open) { List::I it = Items.begin(); for (LTreeItem *i=*it; i && !Status; i=*++it) { Status = i->_HitTest(x, y, Debug); } } return Status; } void LTreeItem::_MouseClick(LMouse &m) { if (m.Down()) { if ((Items.Length() > 0 && d->Thumb.Overlap(m.x, m.y)) || m.Double()) { Expanded(!Expanded()); } LRect rText = d->Text; if (Tree && Tree->Columns.Length() > 0) rText.x2 = Tree->X(); if (rText.Overlap(m.x, m.y) || d->Icon.Overlap(m.x, m.y)) { Select(true); if (Tree) Tree->OnItemClick(this, m); } } } void LTreeItem::OnPaint(ItemPaintCtx &Ctx) { LAssert(Tree != NULL); if (!d->Visible) return; // background up to text LSurface *&pDC = Ctx.pDC; pDC->Colour(Ctx.Back); pDC->Rectangle(0, d->Pos.y1, (d->Depth*TREE_BLOCK)+TREE_BLOCK, d->Pos.y2); // draw trunk LRect Pos = d->Pos; Pos.x2 = Pos.x1 + Ctx.ColPx[0] - 1; int x = 0; LColour Ws(L_WORKSPACE); LColour Lines = Ws.Invert().Mix(Ws); pDC->Colour(Lines); if (Tree->d->JoiningLines) { for (int i=0; iDepth; i++) { if (Tree->d->LineFlags[0] & (1 << i)) pDC->Line(x + 8, Pos.y1, x + 8, Pos.y2); x += TREE_BLOCK; } } else { x += TREE_BLOCK * d->Depth; } // draw node int cy = Pos.y1 + (Pos.Y() >> 1); if (Items.Length() > 0) { d->Thumb.ZOff(8, 8); d->Thumb.Offset(x + 4, cy - 4); switch (Tree->d->Btns) { case LTree::TreePlus: { // plus/minus symbol pDC->Colour(L_LOW); pDC->Box(&d->Thumb); pDC->Colour(L_WHITE); pDC->Rectangle(d->Thumb.x1+1, d->Thumb.y1+1, d->Thumb.x2-1, d->Thumb.y2-1); pDC->Colour(L_SHADOW); pDC->Line( d->Thumb.x1+2, d->Thumb.y1+4, d->Thumb.x1+6, d->Thumb.y1+4); if (!d->Open) { // not open, so draw the cross bar making the '-' into a '+' pDC->Colour(L_SHADOW); pDC->Line( d->Thumb.x1+4, d->Thumb.y1+2, d->Thumb.x1+4, d->Thumb.y1+6); } break; } case LTree::TreeTriangle: { // Triangle style expander pDC->Colour(Lines); int Off = 2; if (d->Open) { for (int y=0; yThumb.Y(); y++) { int x1 = d->Thumb.x1 + y; int x2 = d->Thumb.x2 - y; if (x2 < x1) break; pDC->HLine(x1, x2, d->Thumb.y1 + y + Off); } } else { for (int x=0; xThumb.X(); x++) { int y1 = d->Thumb.y1 + x; int y2 = d->Thumb.y2 - x; if (y2 < y1) break; pDC->VLine(d->Thumb.x1 + x + Off, y1, y2); } } break; } } pDC->Colour(Lines); if (Tree->d->JoiningLines) { if (Parent || IndexOf() > 0) // draw line to item above pDC->Line(x + 8, Pos.y1, x + 8, d->Thumb.y1-1); // draw line to leaf beside pDC->Line(d->Thumb.x2+1, cy, x + (TREE_BLOCK-1), cy); if (!d->Last) // draw line to item below pDC->Line(x + 8, d->Thumb.y2+1, x + 8, Pos.y2); } } else if (Tree->d->JoiningLines) { // leaf node pDC->Colour(L_MED); if (d->Last) pDC->Rectangle(x + 8, Pos.y1, x + 8, cy); else pDC->Rectangle(x + 8, Pos.y1, x + 8, Pos.y2); pDC->Rectangle(x + 8, cy, x + (TREE_BLOCK-1), cy); } x += TREE_BLOCK; // draw icon int Image = GetImage(Select()); LImageList *Lst = Tree->GetImageList(); if (Image >= 0 && Lst) { d->Icon.ZOff(Lst->TileX() + Tree->d->IconTextGap - 1, Pos.Y() - 1); d->Icon.Offset(x, Pos.y1); pDC->Colour(Ctx.Back); if (Tree->d->IconCache) { // no flicker LRect From; From.ZOff(Lst->TileX()-1, Tree->d->IconCache->Y()-1); From.Offset(Lst->TileX()*Image, 0); pDC->Blt(d->Icon.x1, d->Icon.y1, Tree->d->IconCache, &From); pDC->Rectangle(d->Icon.x1 + Lst->TileX(), d->Icon.y1, d->Icon.x2, d->Icon.y2); } else { // flickers... int Px = d->Icon.y1 + ((Lst->TileY()-Pos.Y()) >> 1); pDC->Rectangle(&d->Icon); Tree->GetImageList()->Draw(pDC, d->Icon.x1, Px, Image, Ctx.Back); } x += d->Icon.X(); } LColour SelFore(Tree->Focus() ? L_FOCUS_SEL_FORE : L_NON_FOCUS_SEL_FORE); LColour SelBack(Tree->Focus() ? L_FOCUS_SEL_BACK : L_NON_FOCUS_SEL_BACK); bool IsSelected = (Tree->d->DropTarget == this) || (Tree->d->DropTarget == NULL && Select()); LColour Fore = Ctx.Fore; LColour TxtBack = Ctx.TxtBack; auto Css = GetCss(); LCss::ColorDef f, b; if (Css) { f = Css->Color(); b = Css->BackgroundColor(); } // text: first column Ctx.Fore = f.Type == LCss::ColorRgb ? (LColour)f : (IsSelected ? SelFore : Fore); Ctx.TxtBack = b.Type == LCss::ColorRgb ? (LColour)b : (IsSelected ? SelBack : Ctx.Back); auto ColourDiff = abs(Ctx.Fore.GetGray() - Ctx.TxtBack.GetGray()); if (ColourDiff < 32) // Check if the colours are too similar and then disambiguate... { // LgiTrace("%s %s are too similar %i\n", Ctx.Fore.GetStr(), Ctx.TxtBack.GetStr(), (int)ColourDiff); Ctx.TxtBack = Ctx.TxtBack.Mix(L_WORKSPACE); } LPoint TextSize; _PourText(TextSize); d->Text.ZOff(TextSize.x-1, Pos.Y()-1); d->Text.Offset(x, Pos.y1); (LRect&)Ctx = d->Text; Ctx.x2 = Ctx.ColPx[0] - 1; _PaintText(Ctx); x = Pos.x2 + 1; // text: other columns Ctx.Fore = f.Type == LCss::ColorRgb ? (LColour)f : Fore; Ctx.TxtBack = b.Type == LCss::ColorRgb ? (LColour)b : Ctx.Back; for (int i=1; iColumns[i]); x = Ctx.x2 + 1; } Ctx.Fore = Fore; Ctx.TxtBack = TxtBack; // background after text pDC->Colour(Ctx.Back); pDC->Rectangle(x, Pos.y1, MAX(Tree->X(), Tree->d->Limit.x), Pos.y2); // children if (d->Open) { if (!d->Last) Tree->d->LineFlags[0] |= 1 << d->Depth; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->OnPaint(Ctx); Tree->d->LineFlags[0] &= ~(1 << d->Depth); } } void LTreeItem::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { LDisplayString *ds = d->GetDs(i, Ctx.ColPx[i]); if (ds) { + // Draw the text in the context area: LFont *f = ds->GetFont(); f->Colour(Ctx.Fore, Ctx.TxtBack); ds->Draw(Ctx.pDC, Ctx.x1 + 2, Ctx.y1 + 1, &Ctx); } + else + { + // No string, fill the space with background + Ctx.pDC->Colour(Ctx.Back); + Ctx.pDC->Rectangle(&Ctx); + } } ////////////////////////////////////////////////////////////////////////////// LTree::LTree(int id, int x, int y, int cx, int cy, const char *name) : ResObject(Res_TreeView) { d = new LTreePrivate; SetId(id); LRect e(x, y, x+cx, y+cy); SetPos(e); if (name) Name(name); else Name("LGI.LTree"); Sunken(true); Tree = this; Lines = true; Buttons = true; LinesAtRoot = true; EditLabels = false; ColumnHeaders = false; rItems.ZOff(-1, -1); #if WINNATIVE SetStyle(GetStyle() | WS_CHILD | WS_VISIBLE | WS_TABSTOP); #endif SetTabStop(true); LResources::StyleElement(this); } LTree::~LTree() { Empty(); DeleteObj(d); } bool LTree::Lock(const char *file, int line, int TimeOut) { if (TimeOut > 0) return d->LockWithTimeout(TimeOut, file, line); return d->Lock(file, line); } void LTree::Unlock() { return d->Unlock(); } // Internal tree methods List *LTree::GetSelLst() { return &d->Selection; } void LTree::_Update(LRect *r, bool Now) { TREELOCK if (r) { LRect u = *r; LPoint s = _ScrollPos(); LRect c = GetClient(); u.Offset(c.x1-s.x, c.y1-s.y); Invalidate(&u, Now && !d->InPour); } else { Invalidate((LRect*)0, Now && !d->InPour); } } void LTree::_UpdateBelow(int y, bool Now) { TREELOCK LPoint s = _ScrollPos(); LRect c = GetClient(); LRect u(c.x1, y - s.y + c.y1, X()-1, Y()-1); Invalidate(&u, Now); } void LTree::ClearDs(int Col) { TREELOCK List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->_ClearDs(Col); } LPoint LTree::_ScrollPos() { TREELOCK LPoint Status; Status.x = (HScroll) ? (int)HScroll->Value() : 0; Status.y = (VScroll) ? (int)VScroll->Value() * TREE_BLOCK : 0; return Status; } void LTree::_UpdateScrollBars() { static bool Processing = false; if (!Processing) { Processing = true; { TREELOCK LPoint Old = _ScrollPos(); LRect Client = GetClient(); bool x = d->Limit.x > Client.X(); bool y = d->Limit.y > Client.Y(); SetScrollBars(x, y); Client = GetClient(); // x scroll... in pixels if (HScroll) { HScroll->SetRange(d->Limit.x); HScroll->SetPage(Client.X()); int Max = d->Limit.x - Client.X(); if (HScroll->Value() > Max) { HScroll->Value(Max+1); } } // y scroll... in items if (VScroll) { int All = (d->Limit.y + TREE_BLOCK - 1) / TREE_BLOCK; int Visible = Client.Y() / TREE_BLOCK; VScroll->SetRange(All); VScroll->SetPage(Visible); /* Why is this commented out? -fret Dec2018 int Max = All - Visible + 1; if (VScroll->Value() > Max) VScroll->Value(Max); */ } LPoint New = _ScrollPos(); if (Old.x != New.x || Old.y != New.y) { Invalidate(); } } Processing = false; } } void LTree::_OnSelect(LTreeItem *Item) { TREELOCK if ( !MultiSelect() || !d->CurrentClick || ( d->CurrentClick && !d->CurrentClick->Ctrl() ) ) { for (auto i: d->Selection) { if (i != Item) i->Select(false); } d->Selection.Empty(); } else { d->Selection.Delete(Item); } d->Selection.Insert(Item); } void LTree::_Pour() { TREELOCK d->InPour = true; d->Limit.x = rItems.x1; d->Limit.y = rItems.y1; int ColumnPx = 0; if (Columns.Length()) { for (int i=0; iWidth(); } } else { ColumnPx = d->LastLayoutPx = GetClient().X(); if (ColumnPx < 16) ColumnPx = 16; } LTreeItem *n; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=n) { n = *++it; i->d->Last = n == 0; i->_Pour(&d->Limit, ColumnPx, 0, true); } _UpdateScrollBars(); d->LayoutDirty = false; d->InPour = false; } // External methods and events void LTree::OnItemSelect(LTreeItem *Item) { if (!Item) return; TREELOCK Item->OnSelect(); SendNotify(LNotifyItemSelect); } void LTree::OnItemExpand(LTreeItem *Item, bool Expand) { TREELOCK if (Item) Item->OnExpand(Expand); } LTreeItem *LTree::GetAdjacent(LTreeItem *i, bool Down) { TREELOCK LTreeItem *Ret = NULL; if (i) { if (Down) { LTreeItem *n = i->GetChild(); if (!n || !n->d->Visible) { for (n = i; n; ) { LTreeItem *p = n->GetParent(); if (p) { ssize_t Index = n->IndexOf(); if (Index < (ssize_t)p->Items.Length()-1) { n = n->GetNext(); break; } else { n = p; } } else { n = n->GetNext(); break; } } } Ret = n; } else { LTreeItem *p = i->GetParent() ? i->GetParent() : 0; ssize_t Index = i->IndexOf(); if (p) { LTreeItem *n = p; if (Index > 0) { n = i->GetPrev(); while ( n->GetChild() && n->GetChild()->d->Visible) { n = n->Items.ItemAt(n->Items.Length()-1); } } Ret = n; } else if (Index > 0) { p = i->GetTree()->ItemAt(Index - 1); while (p->GetChild() && p->GetChild()->d->Visible) { if (p->Items.Length()) { p = p->Items.ItemAt(p->Items.Length()-1); } else break; } Ret = p; } } } return Ret; } bool LTree::OnKey(LKey &k) { if (!Lock(_FL)) return false; bool Status = false; LTreeItem *i = d->Selection[0]; if (!i) { i = Items[0]; if (i) i->Select(); } if (k.Down()) { switch (k.vkey) { case LK_PAGEUP: case LK_PAGEDOWN: { if (i && i->d->Pos.Y() > 0) { int Page = GetClient().Y() / i->d->Pos.Y(); for (int j=0; jSelect(true); i->ScrollTo(); } } Status = true; break; } case LK_HOME: { LTreeItem *i; if ((i = Items[0])) { i->Select(true); i->ScrollTo(); } Status = true; break; } case LK_END: { LTreeItem *n = i, *p = 0; while ((n = GetAdjacent(n, true))) { p = n; } if (p) { p->Select(true); p->ScrollTo(); } Status = true; break; } case LK_LEFT: { if (i) { if (i->Items.Length() && i->Expanded()) { i->Expanded(false); break; } else { LTreeItem *p = i->GetParent(); if (p) { p->Select(true); p->Expanded(false); _Pour(); break; } } } // fall thru } case LK_UP: { LTreeItem *n = GetAdjacent(i, false); if (n) { n->Select(true); n->ScrollTo(); } Status = true; break; } case LK_RIGHT: { if (i) { i->Expanded(true); if (d->LayoutDirty) { _Pour(); break; } } // fall thru } case LK_DOWN: { LTreeItem *n = GetAdjacent(i, true); if (n) { n->Select(true); n->ScrollTo(); } Status = true; break; } case LK_DELETE: { if (k.Down()) { Unlock(); // before potentially being deleted...? SendNotify(LNotification(k)); // This might delete the item... so just return here. return true; } break; } #ifdef VK_APPS case VK_APPS: { LTreeItem *s = Selection(); if (s) { LRect *r = &s->d->Text; if (r) { LMouse m; m.x = r->x1 + (r->X() >> 1); m.y = r->y1 + (r->Y() >> 1); m.Target = this; m.ViewCoords = true; m.Down(true); m.Right(true); s->OnMouseClick(m); } } break; } #endif default: { switch (k.c16) { case 'F': case 'f': { if (k.Ctrl()) SendNotify(LNotifyContainerFind); break; } } break; } } } if (i && i != (LTreeItem*)this) { i->OnKey(k); } Unlock(); return Status; } LTreeItem *LTree::ItemAtPoint(int x, int y, bool Debug) { TREELOCK LPoint s = _ScrollPos(); List::I it = Items.begin(); LTreeItem *Hit = NULL; for (LTreeItem *i = *it; i; i=*++it) { Hit = i->_HitTest(s.x + x, s.y + y, Debug); if (Hit) break; } return Hit; } bool LTree::OnMouseWheel(double Lines) { TREELOCK if (VScroll) VScroll->Value(VScroll->Value() + (int)Lines); return true; } void LTree::OnMouseClick(LMouse &m) { TREELOCK d->CurrentClick = &m; if (m.Down()) { DragMode = DRAG_NONE; if (ColumnHeaders && ColumnHeader.Overlap(m.x, m.y)) { d->DragStart.x = m.x; d->DragStart.y = m.y; // Clicked on a column heading LItemColumn *Resize; LItemColumn *Over = NULL; HitColumn(m.x, m.y, Resize, Over); if (Resize) { if (m.Double()) { Resize->Width(Resize->GetContentSize() + DEFAULT_COLUMN_SPACING); Invalidate(); } else { DragMode = RESIZE_COLUMN; d->DragData = (int)Columns.IndexOf(Resize); Capture(true); } } /* else { DragMode = CLICK_COLUMN; d->DragData = Columns.IndexOf(Over); if (Over) { Over->Value(true); LRect r = Over->GetPos(); Invalidate(&r); Capture(true); } } */ } else if (rItems.Overlap(m.x, m.y)) { Focus(true); Capture(true); d->LastClick.x = m.x; d->LastClick.y = m.y; d->LastHit = ItemAtPoint(m.x, m.y, true); if (d->LastHit) { LPoint c = _ScrollPos(); m.x += c.x; m.y += c.y; d->LastHit->_MouseClick(m); } else { SendNotify(LNotification(m, LNotifyContainerClick)); } } } else if (IsCapturing()) { Capture(false); if (rItems.Overlap(m.x, m.y)) { d->LastClick.x = m.x; d->LastClick.y = m.y; d->LastHit = ItemAtPoint(m.x, m.y); if (d->LastHit) { LPoint c = _ScrollPos(); m.x += c.x; m.y += c.y; d->LastHit->_MouseClick(m); } } } d->CurrentClick = NULL; } void LTree::OnMouseMove(LMouse &m) { if (!IsCapturing()) return; TREELOCK switch (DragMode) { /* case DRAG_COLUMN: { if (DragCol) { LPoint p; PointToScreen(p); LRect r = DragCol->GetPos(); r.Offset(-p.x, -p.y); // to view co-ord r.Offset(m.x - DragCol->GetOffset() - r.x1, 0); if (r.x1 < 0) r.Offset(-r.x1, 0); if (r.x2 > X()-1) r.Offset((X()-1)-r.x2, 0); r.Offset(p.x, p.y); // back to screen co-ord DragCol->SetPos(r, true); r = DragCol->GetPos(); } break; } */ case RESIZE_COLUMN: { LItemColumn *c = Columns[d->DragData]; if (c) { // int OldWidth = c->Width(); int NewWidth = m.x - c->GetPos().x1; c->Width(MAX(NewWidth, 4)); _ClearDs(d->DragData); Invalidate(); } break; } default: { if (rItems.Overlap(m.x, m.y)) { if (abs(d->LastClick.x - m.x) > DRAG_THRESHOLD || abs(d->LastClick.y - m.y) > DRAG_THRESHOLD) { OnItemBeginDrag(d->LastHit, m); Capture(false); } } break; } } } void LTree::OnPosChange() { TREELOCK if (Columns.Length() == 0 && d->LastLayoutPx != GetClient().X()) d->LayoutDirty = true; LLayout::OnPosChange(); _UpdateScrollBars(); } void LTree::OnPaint(LSurface *pDC) { TREELOCK LCssTools Tools(this); #if 0 // coverage testing... pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif rItems = GetClient(); LFont *f = GetFont(); if (ShowColumnHeader()) { ColumnHeader.ZOff(rItems.X()-1, f->GetHeight() + 4); PaintColumnHeadings(pDC); rItems.y1 = ColumnHeader.y2 + 1; } else { ColumnHeader.ZOff(-1, -1); } d->IconTextGap = GetFont()->GetHeight() / 6; auto cText = LColour(L_TEXT); auto cWs = LColour(L_WORKSPACE); LColour Fore = Tools.GetFore(&cText); LColour Background = Tools.GetBack(&cWs, 0); // icon cache if (GetImageList() && !d->IconCache) { int CacheHeight = MAX(LSysFont->GetHeight(), GetImageList()->Y()); d->IconCache = new LMemDC; if (d->IconCache && d->IconCache->Create(GetImageList()->X(), CacheHeight, GdcD->GetColourSpace())) { if (d->IconCache->GetColourSpace() == CsIndex8) { d->IconCache->Palette(new LPalette(GdcD->GetGlobalColour()->GetPalette())); } d->IconCache->Colour(Background); d->IconCache->Rectangle(); d->IconCache->Op(GDC_ALPHA); GetImageList()->Lock(); int DrawY = (CacheHeight - GetImageList()->TileY()) >> 1; LAssert(DrawY >= 0); for (int i=0; iGetItems(); i++) { GetImageList()->Draw(d->IconCache, i * GetImageList()->TileX(), DrawY, i, Background); } GetImageList()->Unlock(); d->IconCache->Unlock(); } } // scroll LPoint s = _ScrollPos(); int Ox, Oy; pDC->GetOrigin(Ox, Oy); pDC->SetOrigin(Ox + s.x, Oy + s.y); // selection colour LArray ColPx; LItem::ItemPaintCtx Ctx; Ctx.pDC = pDC; if (Columns.Length() > 0) { Ctx.Columns = (int)Columns.Length(); for (int i=0; iWidth(); } else { Ctx.Columns = 1; ColPx[0] = rItems.X(); } Ctx.ColPx = &ColPx[0]; Ctx.Fore = Fore; Ctx.Back = Background; Ctx.TxtBack = Background; LColour SelFore(Focus() ? L_FOCUS_SEL_FORE : L_NON_FOCUS_SEL_FORE); LColour SelBack(Focus() ? L_FOCUS_SEL_BACK : L_NON_FOCUS_SEL_BACK); // layout items if (d->LayoutDirty) { _Pour(); } // paint items ZeroObj(d->LineFlags); List::I it = Items.begin(); for (LTreeItem *i = *it; i; i=*++it) i->OnPaint(Ctx); pDC->SetOrigin(Ox, Oy); if (d->Limit.y-s.y < rItems.Y()) { // paint after items pDC->Colour(Background); pDC->Rectangle(rItems.x1, d->Limit.y - s.y, rItems.x2, rItems.y2); } } int LTree::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_HSCROLL: case IDC_VSCROLL: { TREELOCK if (n.Type == LNotifyScrollBarCreate) { _UpdateScrollBars(); if (VScroll) { if (HasItem(d->ScrollTo)) d->ScrollTo->ScrollTo(); d->ScrollTo = NULL; } } Invalidate(); break; } } return LLayout::OnNotify(Ctrl, n); } LMessage::Result LTree::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_SCROLL_TO: { LTreeItem *Item = (LTreeItem*)Msg->A(); if (!HasItem(Item)) break; if (VScroll) Item->ScrollTo(); break; } } return LItemContainer::OnEvent(Msg); } LTreeItem *LTree::Insert(LTreeItem *Obj, ssize_t Pos) { TREELOCK LTreeItem *NewObj = LTreeNode::Insert(Obj, Pos); if (NewObj) NewObj->_SetTreePtr(this); return NewObj; } bool LTree::HasItem(LTreeItem *Obj, bool Recurse) { TREELOCK if (!Obj) return false; return LTreeNode::HasItem(Obj, Recurse); } bool LTree::Remove(LTreeItem *Obj) { TREELOCK bool Status = false; if (Obj && Obj->Tree == this) { Obj->Remove(); Status = true; } return Status; } void LTree::RemoveAll() { TREELOCK List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->_Remove(); Invalidate(); } void LTree::Empty() { TREELOCK LTreeItem *i; while ((i = Items[0])) Delete(i); } bool LTree::Delete(LTreeItem *Obj) { bool Status = false; TREELOCK if (Obj) { LTreeItem *i; while ((i = Obj->Items[0])) { Delete(i); } Obj->Remove(); DeleteObj(Obj); Status = true; } return Status; } void LTree::OnPulse() { TREELOCK if (d->DropTarget) { int64 p = LCurrentTime() - d->DropSelectTime; if (p >= 1000) { SetPulse(); if (!d->DropTarget->Expanded() && d->DropTarget->GetChild()) { d->DropTarget->Expanded(true); } } } if (InsideDragOp()) { LMouse m; if (GetMouse(m)) { if (!m.Left() && !m.Right() && !m.Middle()) { // Be robust against missing drag exit events (Mac specific?) InsideDragOp(false); } else { LRect c = GetClient(); if (VScroll) { if (m.y < DRAG_SCROLL_EDGE) { // Scroll up... VScroll->Value(VScroll->Value() - DRAG_SCROLL_Y); } else if (m.y > c.Y() - DRAG_SCROLL_EDGE) { // Scroll down... VScroll->Value(VScroll->Value() + DRAG_SCROLL_Y); } } if (HScroll) { if (m.x < DRAG_SCROLL_EDGE) { // Scroll left... HScroll->Value(HScroll->Value() - DRAG_SCROLL_X); } else if (m.x > c.X() - DRAG_SCROLL_EDGE) { // Scroll right... HScroll->Value(HScroll->Value() + DRAG_SCROLL_X); } } } } } } int LTree::GetContentSize(int ColumnIdx) { TREELOCK int MaxPx = 0; List::I it = Items.begin(); for (LTreeItem *i = *it; i; i=*++it) { int ItemPx = i->GetColumnSize(ColumnIdx); MaxPx = MAX(ItemPx, MaxPx); } return MaxPx; } LCursor LTree::GetCursor(int x, int y) { TREELOCK LItemColumn *Resize = NULL, *Over = NULL; HitColumn(x, y, Resize, Over); return (Resize) ? LCUR_SizeHor : LCUR_Normal; } void LTree::OnDragEnter() { TREELOCK InsideDragOp(true); SetPulse(120); } void LTree::OnDragExit() { TREELOCK InsideDragOp(false); SetPulse(); SelectDropTarget(0); } void LTree::SelectDropTarget(LTreeItem *Item) { TREELOCK if (Item != d->DropTarget) { bool Update = (d->DropTarget != 0) ^ (Item != 0); LTreeItem *Old = d->DropTarget; d->DropTarget = Item; if (Old) { Old->Update(); } if (d->DropTarget) { d->DropTarget->Update(); d->DropSelectTime = LCurrentTime(); } if (Update) { OnFocus(true); } } } bool LTree::Select(LTreeItem *Obj) { TREELOCK bool Status = false; if (Obj && IsAttached()) { Obj->Select(true); Status = true; } else if (d->Selection.Length()) { d->Selection.Empty(); OnItemSelect(0); Status = true; } return Status; } LTreeItem *LTree::Selection() { TREELOCK return d->Selection[0]; } bool LTree::ForAllItems(std::function Callback) { TREELOCK return ForEach(Callback) > 0; } void LTree::OnItemClick(LTreeItem *Item, LMouse &m) { if (!Item) return; TREELOCK Item->OnMouseClick(m); if (!m.Ctrl() && !m.Shift()) SendNotify(LNotification(m)); } void LTree::OnItemBeginDrag(LTreeItem *Item, LMouse &m) { if (!Item) return; TREELOCK Item->OnBeginDrag(m); } void LTree::OnFocus(bool b) { TREELOCK // errors during deletion of the control can cause // this to be called after the destructor if (d) { List::I it = d->Selection.begin(); for (LTreeItem *i=*it; i; i=*++it) i->Update(); } } static void LTreeItemUpdateAll(LTreeNode *n) { for (LTreeItem *i=n->GetChild(); i; i=i->GetNext()) { i->Update(); LTreeItemUpdateAll(i); } } void LTree::UpdateAllItems() { TREELOCK d->LayoutDirty = true; LTreeItemUpdateAll(this); } void LTree::SetVisualStyle(ThumbStyle Btns, bool JoiningLines) { TREELOCK d->Btns = Btns; d->JoiningLines = JoiningLines; Invalidate(); } diff --git a/src/haiku/MemDC.cpp b/src/haiku/MemDC.cpp --- a/src/haiku/MemDC.cpp +++ b/src/haiku/MemDC.cpp @@ -1,330 +1,349 @@ /*hdr ** FILE: MemDC.cpp ** AUTHOR: Matthew Allen ** DATE: 14/10/2000 ** DESCRIPTION: GDC v2.xx header ** ** Copyright (C) 2021, Matthew Allen ** fret@memecode.com */ #include #include +#include "Screen.h" #include "lgi/common/Gdc2.h" #include "lgi/common/LgiString.h" #include ///////////////////////////////////////////////////////////////////////////////////////////////////// #define ROUND_UP(bits) (((bits) + 7) / 8) class LMemDCPrivate { public: ::LArray Client; LColourSpace CreateCs = CsNone; BBitmap *Bmp = NULL; BView *View = NULL; LMemDCPrivate() { } ~LMemDCPrivate() { Empty(); } void Empty() { } }; LMemDC::LMemDC(int x, int y, LColourSpace cs, int flags) { d = new LMemDCPrivate; if (cs != CsNone) Create(x, y, cs, flags); } LMemDC::LMemDC(LSurface *pDC) { d = new LMemDCPrivate; if (pDC && Create(pDC->X(), pDC->Y(), pDC->GetColourSpace())) { Blt(0, 0, pDC); } } LMemDC::~LMemDC() { Empty(); DeleteObj(d); } OsBitmap LMemDC::GetBitmap() { return d->Bmp; } OsPainter LMemDC::Handle() { if (!d->View && d->Bmp) { d->View = new BView(d->Bmp->Bounds(), "BBitmapView", B_FOLLOW_NONE, B_WILL_DRAW); if (d->View) d->Bmp->AddChild(d->View); } return d->View; } void LMemDC::SetClient(LRect *c) { if (c) { Handle(); LRect Doc; if (d->Client.Length()) Doc = d->Client.Last(); else Doc = Bounds(); LRect r = *c; r.Bound(&Doc); d->Client.Add(r); Clip = r; OriginX = -r.x1; OriginY = -r.y1; } else { if (d->Client.Length()) d->Client.PopLast(); if (d->Client.Length()) { auto &r = d->Client.Last(); OriginX = -r.x1; OriginY = -r.y1; Clip = r; } else { OriginX = 0; OriginY = 0; Clip.ZOff(pMem->x-1, pMem->y-1); } } } void LMemDC::Empty() { d->Empty(); DeleteObj(pMem); } bool LMemDC::Lock() { return false; } bool LMemDC::Unlock() { return false; } bool LMemDC::Create(int x, int y, LColourSpace Cs, int Flags) { BRect b(0, 0, x, y); d->Bmp = new BBitmap(b, B_RGB32, false, true); if (!d->Bmp || d->Bmp->InitCheck() != B_OK) { DeleteObj(d->Bmp); LgiTrace("%s:%i - Failed to create memDC(%i,%i)\n", _FL, x, y); return false; } pMem = new LBmpMem; if (!pMem) return false; pMem->x = d->Bmp->Bounds().Width(); pMem->y = d->Bmp->Bounds().Height(); ColourSpace = pMem->Cs = System32BitColourSpace; pMem->Line = d->Bmp->BytesPerRow(); pMem->Base = (uchar*)d->Bmp->Bits(); int NewOp = (pApp) ? Op() : GDC_SET; if ((Flags & GDC_OWN_APPLICATOR) && !(Flags & GDC_CACHED_APPLICATOR)) { DeleteObj(pApp); } for (int i=0; ix, pMem->y, LColourSpaceToString(pMem->Cs), pMem->Line, pMem->Base); return true; } void LMemDC::Blt(int x, int y, LSurface *Src, LRect *a) { if (!Src) return; bool Status = false; LBlitRegions br(this, x, y, Src, a); if (!br.Valid()) return; LScreenDC *Screen; if ((Screen = Src->IsScreen())) { - if (pMem->Base) + BScreen scr; + BBitmap *bitmap = NULL; + BRect src = br.SrcClip; + auto r = scr.GetBitmap(&bitmap, TestFlag(Flags, GDC_CAPTURE_CURSOR), &src); + if (r == B_OK) { - + bitmap->LockBits(); + + int dstPx = GetBits() / 8; + size_t srcPx = 4, row = 0, chunk = 0; + get_pixel_size_for(bitmap->ColorSpace(), &srcPx, &row, &chunk); + + for (int y=0; yBits()) + (bitmap->BytesPerRow() * (br.SrcClip.y1 + y)) + (br.SrcClip.x1 * srcPx); + LAssert(!"Impl pixel converter."); + } + + bitmap->UnlockBits(); } - - if (!Status) + else { Colour(Rgb24(255, 0, 255), 24); - Rectangle(); + Rectangle(a); } + + delete bitmap; } else if ((*Src)[0]) { // Memory -> Memory (Source alpha used) LSurface::Blt(x, y, Src, a); } } void LMemDC::StretchBlt(LRect *d, LSurface *Src, LRect *s) { LAssert(!"Not implemented"); } void LMemDC::SetOrigin(int x, int y) { } bool LMemDC::SupportsAlphaCompositing() { return true; } void LMemDC::GetOrigin(int &x, int &y) { LSurface::GetOrigin(x, y); } LRect LMemDC::ClipRgn(LRect *Rgn) { LRect Old = Clip; if (Rgn) { LRect Dc(0, 0, X()-1, Y()-1); Clip = *Rgn; Clip.Offset(-OriginX, -OriginY); Clip.Bound(&Dc); } else { Clip.ZOff(X()-1, Y()-1); } return Old; } void LMemDC::HorzLine(int x1, int x2, int y, COLOUR a, COLOUR b) { if (x1 > x2) LSwap(x1, x2); if (x1 < Clip.x1) x1 = Clip.x1; if (x2 > Clip.x2) x2 = Clip.x2; if (x1 <= x2 && y >= Clip.y1 && y <= Clip.y2 && pApp) { COLOUR Prev = pApp->c; pApp->SetPtr(x1, y); for (; x1 <= x2; x1++) { if (x1 & 1) { pApp->c = a; } else { pApp->c = b; } pApp->Set(); pApp->IncX(); } pApp->c = Prev; } } void LMemDC::VertLine(int x, int y1, int y2, COLOUR a, COLOUR b) { if (y1 > y2) LSwap(y1, y2); if (y1 < Clip.y1) y1 = Clip.y1; if (y2 > Clip.y2) y2 = Clip.y2; if (y1 <= y2 && x >= Clip.x1 && x <= Clip.x2 && pApp) { COLOUR Prev = pApp->c; pApp->SetPtr(x, y1); for (; y1 <= y2; y1++) { if (y1 & 1) { pApp->c = a; } else { pApp->c = b; } pApp->Set(); pApp->IncY(); } pApp->c = Prev; } }